1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 //    * Redistributions of source code must retain the above copyright notice,
11 //      this list of conditions and the following disclaimer.
12 //
13 //    * Redistributions in binary form must reproduce the above copyright
14 //      notice, this list of conditions and the following disclaimer in the
15 //      documentation and/or other materials provided with the distribution.
16 //
17 //    * Neither the name of the author nor the names of other contributors may
18 //      be used to endorse or promote products derived from this software
19 //      without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34 
35 #include "pch.hpp"
36 
37 #include <kiss_fftr.h>
38 #include <musikcore/debug.h>
39 #include <musikcore/audio/Stream.h>
40 #include <musikcore/audio/Player.h>
41 #include <musikcore/audio/Visualizer.h>
42 #include <musikcore/plugin/PluginFactory.h>
43 #include <musikcore/sdk/constants.h>
44 
45 #include <algorithm>
46 #include <math.h>
47 #include <future>
48 
49 #define MAX_PREBUFFER_QUEUE_COUNT 8
50 #define FFT_N 512
51 #define PI 3.14159265358979323846
52 
53 using namespace musik::core::audio;
54 using namespace musik::core::sdk;
55 
56 using std::min;
57 using std::max;
58 
59 static std::string TAG = "Player";
60 static float* hammingWindow = nullptr;
61 
62 using Listener = Player::EventListener;
63 using ListenerList = std::list<Listener*>;
64 
65 namespace musik {
66     namespace core {
67         namespace audio {
68             void playerThreadLoop(Player* player);
69 
70             struct FftContext {
FftContextmusik::core::audio::FftContext71                 FftContext() {
72                     samples = 0;
73                     cfg = nullptr;
74                     deinterleaved = nullptr;
75                     scratch = nullptr;
76                 }
77 
~FftContextmusik::core::audio::FftContext78                 ~FftContext() {
79                     Reset();
80                 }
81 
Resetmusik::core::audio::FftContext82                 void Reset() {
83                     kiss_fftr_free(cfg);
84                     delete[] deinterleaved;
85                     delete[] scratch;
86                     cfg = nullptr;
87                     deinterleaved = nullptr;
88                     scratch = nullptr;
89                 }
90 
Initmusik::core::audio::FftContext91                 void Init(int samples) {
92                     if (!cfg || samples != this->samples) {
93                         Reset();
94                         cfg = kiss_fftr_alloc(FFT_N, false, 0, 0);
95                         deinterleaved = new float[samples];
96                         scratch = new kiss_fft_cpx[(FFT_N / 2) + 1];
97                         this->samples = samples;
98                     }
99                 }
100 
101                 int samples;
102                 kiss_fftr_cfg cfg;
103                 float* deinterleaved;
104                 kiss_fft_cpx* scratch;
105             };
106         }
107     }
108 }
109 
Create(const std::string & url,std::shared_ptr<IOutput> output,DestroyMode destroyMode,EventListener * listener,Gain gain)110 Player* Player::Create(
111     const std::string &url,
112     std::shared_ptr<IOutput> output,
113     DestroyMode destroyMode,
114     EventListener *listener,
115     Gain gain)
116 {
117     return new Player(url, output, destroyMode, listener, gain);
118 }
119 
Player(const std::string & url,std::shared_ptr<IOutput> output,DestroyMode destroyMode,EventListener * listener,Gain gain)120 Player::Player(
121     const std::string &url,
122     std::shared_ptr<IOutput> output,
123     DestroyMode destroyMode,
124     EventListener *listener,
125     Gain gain)
126 : internalState(Player::Idle)
127 , streamState(StreamState::Buffering)
128 , stream(Stream::Create())
129 , url(url)
130 , currentPosition(0)
131 , output(output)
132 , notifiedStarted(false)
133 , seekToPosition(-1)
134 , nextMixPoint(-1.0)
135 , pendingBufferCount(0)
136 , destroyMode(destroyMode)
137 , fftContext(nullptr)
138 , gain(gain) {
139     musik::debug::info(TAG, "new instance created");
140 
141     this->spectrum = new float[FFT_N / 2];
142 
143     if (!this->output) {
144         throw std::runtime_error("output cannot be null!");
145     }
146 
147     if (listener) {
148         listeners.push_back(listener);
149     }
150 
151     /* each player instance is driven by a background thread. start it. */
152     this->thread = new std::thread(std::bind(&musik::core::audio::playerThreadLoop, this));
153 }
154 
~Player()155 Player::~Player() {
156     delete[] this->spectrum;
157     delete fftContext;
158 }
159 
Play()160 void Player::Play() {
161     std::unique_lock<std::mutex> lock(this->queueMutex);
162 
163     if (this->internalState != Player::Quit) {
164         this->internalState = Player::Playing;
165         this->writeToOutputCondition.notify_all();
166     }
167 }
168 
Destroy()169 void Player::Destroy() {
170     {
171         if (this->stream) {
172             this->stream->Interrupt();
173         }
174 
175         std::unique_lock<std::mutex> lock(this->queueMutex);
176 
177         if (this->internalState == Player::Quit && !this->thread) {
178             return; /* already terminated (or terminating) */
179         }
180 
181         this->internalState = Player::Quit;
182         this->writeToOutputCondition.notify_all();
183         this->thread->detach();
184         delete this->thread;
185         this->thread = nullptr;
186     }
187 }
188 
Destroy(DestroyMode mode)189 void Player::Destroy(DestroyMode mode) {
190     this->destroyMode = mode;
191     this->Destroy();
192 }
193 
Detach(EventListener * listener)194 void Player::Detach(EventListener* listener) {
195     if (listener) {
196         std::unique_lock<std::mutex> lock(this->listenerMutex);
197         this->listeners.remove_if([listener](EventListener *compare) {
198             return (listener == compare);
199         });
200     }
201 }
202 
Attach(EventListener * listener)203 void Player::Attach(EventListener* listener) {
204     this->Detach(listener);
205 
206     if (listener) {
207         std::unique_lock<std::mutex> lock(this->listenerMutex);
208         this->listeners.push_back(listener);
209     }
210 }
211 
Listeners()212 ListenerList Player::Listeners() {
213     std::unique_lock<std::mutex> lock(this->listenerMutex);
214     return ListenerList(this->listeners);
215 }
216 
GetPosition()217 double Player::GetPosition() {
218     double seek = this->seekToPosition.load();
219     double current = this->currentPosition.load();
220     const double latency = this->output ? this->output->Latency() : 0.0;
221     return std::max(0.0, round((seek >= 0 ? seek : current) - latency));
222 }
223 
GetPositionInternal()224 double Player::GetPositionInternal() {
225     const double latency = this->output ? this->output->Latency() : 0.0;
226     return std::max(0.0, round(this->currentPosition.load() - latency));
227 }
228 
GetDuration()229 double Player::GetDuration() {
230     return this->stream ? this->stream->GetDuration() : -1.0f;
231 }
232 
SetPosition(double seconds)233 void Player::SetPosition(double seconds) {
234     std::unique_lock<std::mutex> queueLock(this->queueMutex);
235 
236     if (this->stream) {
237         auto duration = this->stream->GetDuration();
238         if (duration > 0.0f) {
239             seconds = std::min(duration, seconds);
240         }
241     }
242 
243     this->seekToPosition.store(std::max(0.0, seconds));
244 
245     /* reset our mix points on seek! that way we'll notify again if necessary */
246 
247     this->pendingMixPoints.splice(
248         this->pendingMixPoints.begin(),
249         this->processedMixPoints);
250 
251     this->UpdateNextMixPointTime();
252 }
253 
AddMixPoint(int id,double time)254 void Player::AddMixPoint(int id, double time) {
255     std::unique_lock<std::mutex> queueLock(this->queueMutex);
256     this->pendingMixPoints.push_back(std::make_shared<MixPoint>(id, time));
257     this->UpdateNextMixPointTime();
258 }
259 
State()260 int Player::State() {
261     std::unique_lock<std::mutex> lock(this->queueMutex);
262     return this->internalState;
263 }
264 
HasCapability(Capability c)265 bool Player::HasCapability(Capability c) {
266     if (this->stream) {
267         return (this->stream->GetCapabilities() & (int) c) != 0;
268     }
269     return false;
270 }
271 
UpdateNextMixPointTime()272 void Player::UpdateNextMixPointTime() {
273     const double position = this->GetPositionInternal();
274 
275     double next = -1.0;
276     for (MixPointPtr mp : this->pendingMixPoints) {
277         if (mp->time >= position) {
278             if (mp->time < next || next == -1.0) {
279                 next = mp->time;
280             }
281         }
282     }
283 
284     this->nextMixPoint = next;
285 }
286 
playerThreadLoop(Player * player)287 void musik::core::audio::playerThreadLoop(Player* player) {
288 #ifdef WIN32
289     SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
290 #endif
291 
292     IBuffer* buffer = nullptr;
293 
294     float gain = player->gain.preamp * player->gain.gain;
295     if (gain > 1.0f && player->gain.peakValid) {
296         gain = player->gain.peak;
297     }
298 
299     if (player->stream->OpenStream(player->url)) {
300         for (Listener* l : player->Listeners()) {
301             player->streamState = StreamState::Buffered;
302             l->OnPlayerBuffered(player);
303         }
304 
305         /* wait until we enter the Playing or Quit state */
306         {
307             std::unique_lock<std::mutex> lock(player->queueMutex);
308             while (player->internalState == Player::Idle) {
309                 player->writeToOutputCondition.wait(lock);
310             }
311         }
312 
313         /* we're ready to go.... */
314         bool finished = false;
315 
316         while (!finished && !player->Exited()) {
317             /* see if we've been asked to seek since the last sample was
318             played. if we have, clear our output buffer and seek the
319             stream. */
320             double seek = player->seekToPosition.load();
321 
322             if (seek != -1.0) {
323                 player->output->Stop(); /* flush all buffers */
324                 player->output->Resume(); /* start it back up */
325 
326                 /* if we've allocated a buffer, but it hasn't been written
327                 to the output yet, unlock it. this is an important step, and if
328                 not performed, will result in a deadlock just below while
329                 waiting for all buffers to complete. */
330                 if (buffer) {
331                     player->OnBufferProcessed(buffer);
332                     buffer = nullptr;
333                 }
334 
335                 player->currentPosition.store(seek);
336 
337                 {
338                     std::unique_lock<std::mutex> lock(player->queueMutex);
339                     while (player->pendingBufferCount > 0) {
340                         player->writeToOutputCondition.wait(lock);
341                     }
342                 }
343 
344                 player->stream->SetPosition(seek);
345                 player->seekToPosition.exchange(-1.0);
346             }
347 
348             /* let's see if we can find some samples to play */
349             if (!buffer) {
350                 std::unique_lock<std::mutex> lock(player->queueMutex);
351 
352                 buffer = player->stream->GetNextProcessedOutputBuffer();
353 
354                 if (buffer) {
355                     /* apply replay gain, if specified */
356                     if (gain != 1.0f) {
357                         float* samples = buffer->BufferPointer();
358                         for (long i = 0; i < buffer->Samples(); i++) {
359                             samples[i] *= gain;
360                         }
361                     }
362 
363                     ++player->pendingBufferCount;
364                 }
365             }
366 
367             /* if we have a decoded, processed buffer available. let's try to send it to
368             the output device. */
369             if (buffer) {
370                 /* if this result is negative it's an error code defined by the sdk's
371                 OutputPlay enum. if it's a positive number it's the number of milliseconds
372                 we should wait until automatically trying to play the buffer again. */
373                 OutputState playResult = player->output->Play(buffer, player);
374 
375                 if (playResult == OutputState::BufferWritten) {
376                     buffer = nullptr; /* reset so we pick up a new one next iteration */
377                 }
378                 else {
379                     /* if the buffer was unable to be processed, we'll try again after
380                     sleepMs milliseconds */
381                     int sleepMs = 1000; /* default */
382 
383                     /* if the playResult value >= 0, that means the output requested a
384                     specific callback time because its internal buffer is full. */
385                     if ((int) playResult >= 0) {
386                         /* if there is no visualizer active, we can introduce an
387                         artifical delay of 25% of the buffer size to ease CPU load */
388                         auto visualizer = vis::SelectedVisualizer();
389                         if (!visualizer || !visualizer->Visible()) {
390                             sleepMs = std::max(
391                                 (int)(player->output->Latency() * 250.0),
392                                 (int) playResult);
393                         }
394                         else {
395                             sleepMs = (int) playResult;
396                         }
397                     }
398 
399                     std::unique_lock<std::mutex> lock(player->queueMutex);
400 
401                     player->writeToOutputCondition.wait_for(
402                         lock, std::chrono::milliseconds(sleepMs));
403                 }
404             }
405             else {
406                 if (player->stream->Eof()) {
407                     finished = true;
408                 }
409                 else {
410                     /* we should never get here, but if for some reason we can't get the
411                     next buffer, but we're not EOF, that means we need to wait for a short
412                     while and try again... */
413                     std::unique_lock<std::mutex> lock(player->queueMutex);
414                     player->writeToOutputCondition.wait_for(
415                         lock, std::chrono::milliseconds(10));
416                 }
417             }
418         }
419 
420         /* if the Quit flag isn't set, that means the stream has ended "naturally", i.e.
421         it wasn't stopped by the user. raise the "almost ended" flag. */
422         if (!player->Exited()) {
423             for (Listener* l : player->Listeners()) {
424                 player->streamState = StreamState::AlmostDone;
425                 l->OnPlayerAlmostEnded(player);
426             }
427         }
428     }
429 
430     /* if the stream failed to open... */
431     else {
432         if (!player->Exited()) {
433             for (Listener* l : player->Listeners()) {
434                 player->streamState = StreamState::OpenFailed;
435                 l->OnPlayerOpenFailed(player);
436             }
437         }
438     }
439 
440     /* if non-null, it was never accepted by the output. release it now. */
441     if (buffer) {
442         player->OnBufferProcessed(buffer);
443         buffer = nullptr;
444     }
445 
446     /* wait until all remaining buffers have been written, set final state... */
447     {
448         std::unique_lock<std::mutex> lock(player->queueMutex);
449         while (player->pendingBufferCount > 0) {
450             player->writeToOutputCondition.wait(lock);
451         }
452     }
453 
454     /* buffers have been written, wait for the output to play them all */
455     if (player->destroyMode == Player::DestroyMode::Drain) {
456         player->output->Drain();
457     }
458 
459     if (!player->Exited()) {
460         for (Listener* l : player->Listeners()) {
461             player->streamState = StreamState::Finished;
462             l->OnPlayerFinished(player);
463         }
464     }
465 
466     player->internalState = Player::Quit;
467 
468     for (Listener* l : player->Listeners()) {
469         player->streamState = StreamState::Stopped;
470         l->OnPlayerDestroying(player);
471     }
472 
473     player->Destroy();
474 
475     delete player;
476 }
477 
Exited()478 bool Player::Exited() {
479     std::unique_lock<std::mutex> lock(this->queueMutex);
480     return (this->internalState == Player::Quit);
481 }
482 
initHammingWindow()483 static inline void initHammingWindow() {
484     delete hammingWindow;
485     hammingWindow = new float[FFT_N];
486 
487     for (int i = 0; i < FFT_N; i++) {
488         hammingWindow[i] = 0.54f - 0.46f * (float) cos((2 * PI * i) / (FFT_N - 1));
489     }
490 }
491 
performFft(IBuffer * buffer,FftContext * fft,float * output,int outputSize)492 static inline bool performFft(IBuffer* buffer, FftContext* fft, float* output, int outputSize) {
493     long samples = buffer->Samples();
494     int channels = buffer->Channels();
495     long samplesPerChannel = samples / channels;
496 
497     if (samplesPerChannel < FFT_N || outputSize != FFT_N / 2) {
498         return false;
499     }
500 
501     if (!hammingWindow) {
502         initHammingWindow();
503     }
504 
505     memset(output, 0, outputSize * sizeof(float));
506 
507     float* input = buffer->BufferPointer();
508 
509     fft->Init(samples);
510 
511     for (int i = 0; i < samples; i++) {
512         const int to = ((i % channels) * samplesPerChannel) + (i / channels);
513         fft->deinterleaved[to] = input[i] * hammingWindow[to % FFT_N];
514     }
515 
516     int offset = 0;
517     int iterations = samples / FFT_N;
518     for (int i = 0; i < iterations; i++) {
519         kiss_fftr(fft->cfg, fft->deinterleaved + offset, fft->scratch);
520 
521         for (int z = 0; z < outputSize; z++) {
522             /* convert to decibels */
523             double db = (fft->scratch[z].r * fft->scratch[z].r + fft->scratch[z].i + fft->scratch[z].i);
524             output[z] += (db < 1 ? 0 : 20 * (float) log10(db)) / iterations; /* frequencies over all channels */
525         }
526 
527         offset += FFT_N;
528     }
529 
530     return true;
531 }
532 
OnBufferProcessed(IBuffer * buffer)533 void Player::OnBufferProcessed(IBuffer *buffer) {
534     bool started = false;
535     bool found = false;
536 
537     /* process visualizer data, and write to the selected plugin, if applicable */
538 
539     ISpectrumVisualizer* specVis = vis::SpectrumVisualizer();
540     IPcmVisualizer* pcmVis = vis::PcmVisualizer();
541 
542     if (specVis && specVis->Visible()) {
543         if (!fftContext) {
544             fftContext = new FftContext();
545         }
546 
547         if (performFft(buffer, fftContext, spectrum, FFT_N / 2)) {
548             vis::SpectrumVisualizer()->Write(spectrum, FFT_N / 2);
549         }
550     }
551     else if (pcmVis && pcmVis->Visible()) {
552         vis::PcmVisualizer()->Write(buffer);
553     }
554 
555     /* release the buffer back to the stream, find mixpoints */
556 
557     {
558         std::unique_lock<std::mutex> lock(this->queueMutex);
559 
560         /* removes the specified buffer from the list of locked buffers, and also
561         lets the stream know it can be recycled. */
562         --pendingBufferCount;
563         this->stream->OnBufferProcessedByPlayer((Buffer*)buffer);
564 
565         /* if we're seeking this value will be non-negative, so we shouldn't touch
566         the current time. */
567         if (this->seekToPosition.load() == -1) {
568             this->currentPosition.store(((Buffer*)buffer)->Position());
569         }
570 
571         /* did we hit any pending mixpoints? if so add them to our set and
572         move them to the processed set. we'll notify once out of the
573         critical section. */
574         double adjustedPosition = this->GetPositionInternal();
575         if (adjustedPosition >= this->nextMixPoint) {
576             auto it = this->pendingMixPoints.begin();
577             while (it != this->pendingMixPoints.end()) {
578                 if (adjustedPosition >= (*it)->time) {
579                     this->mixPointsHitTemp.push_back(*it);
580                     this->processedMixPoints.push_back(*it);
581                     it = this->pendingMixPoints.erase(it);
582                 }
583                 else {
584                     ++it;
585                 }
586             }
587 
588             /* kind of awkward to recalc the next mixpoint here, but we
589             already have the queue mutex acquired. */
590             if (this->mixPointsHitTemp.size()) {
591                 this->UpdateNextMixPointTime();
592             }
593         }
594 
595         if (!this->notifiedStarted) {
596             this->streamState = StreamState::Playing;
597             this->notifiedStarted = true;
598             started = true;
599         }
600     }
601 
602     /* check up front so we don't have to acquire the mutex if
603     we don't need to. */
604     if (started || this->mixPointsHitTemp.size()) {
605         ListenerList listeners = this->Listeners();
606         if (!this->Exited() && listeners.size()) {
607             for (Listener* l : ListenerList(listeners)) {
608                 /* we notify our listeners that we've started playing only after the first
609                 buffer has been consumed. this is because sometimes we precache buffers
610                 and send them to the output before they are actually processed by the
611                 output device */
612                 if (started) {
613                     l->OnPlayerStarted(this);
614                 }
615 
616                 for (MixPointPtr mp : this->mixPointsHitTemp) {
617                     l->OnPlayerMixPoint(this, mp->id, mp->time);
618                 }
619             }
620         }
621 
622         this->mixPointsHitTemp.clear();
623     }
624 
625     /* if the output device's internal buffers are full, it will stop
626     accepting new samples. now that a buffer has been processed, we can
627     try to enqueue another sample. the thread loop blocks on this condition */
628     this->writeToOutputCondition.notify_all();
629 }
630