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