engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / sound / snd_streamplayer.cpp
blobccaced4c72051d7917d5cc0cce6babaa25cd1c72
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "sound.h"
28 #include "snd_local.h"
30 //#define VCCRUN_SOUND_THREAD_DEBUG
31 //#define VCCRUN_SOUND_THREAD_DEBUG_TICKER
33 #ifdef VCCRUN_SOUND_THREAD_DEBUG
34 # define SDLOG(...) GLog.WriteLine(__VA_ARGS__)
35 #else
36 # define SDLOG(...) do {} while (0)
37 #endif
40 extern VCvarF snd_master_volume;
43 //==========================================================================
45 // VStreamMusicPlayerWorker::doTick
47 // returns `true` if caller should perform stopping
49 //==========================================================================
50 bool VStreamMusicPlayerWorker::doTick (VStreamMusicPlayer *strm) {
51 if (!strm->StrmOpened) return false;
52 if (strm->Stopping && strm->FinishTime+0.5 < Sys_Time()) {
53 // finish playback
54 //Stop();
55 return true;
57 if (strm->Paused) {
58 // pause playback
59 return false;
61 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
62 GLog.Logf(NAME_Debug, "doTick: decoding... (%d)", strm->SoundDevice->GetStreamAvailable());
63 #endif
64 int loopsAllowed = 42;
65 const double stt = Sys_Time();
66 for (int Len = strm->SoundDevice->GetStreamAvailable(); Len; Len = strm->SoundDevice->GetStreamAvailable()) {
67 vint16 *Data = strm->SoundDevice->GetStreamBuffer();
68 int StartPos = 0;
69 int decodedFromLoop = 0, loopCount = 0;
70 while (!strm->Stopping && StartPos < Len) {
71 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
72 GLog.Logf(NAME_Debug, "doTick: trying to decode %d samples (len=%d, stpos=%d)", Len-StartPos, Len, StartPos);
73 #endif
74 int SamplesDecoded = strm->Codec->Decode(Data+StartPos*2, Len-StartPos);
75 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
76 GLog.Logf(NAME_Debug, "doTick: decoded %d samples", SamplesDecoded);
77 #endif
78 if (SamplesDecoded < 0) SamplesDecoded = 0;
79 StartPos += SamplesDecoded;
80 decodedFromLoop += SamplesDecoded;
81 if (strm->Codec->Finished() || SamplesDecoded <= 0) {
82 // stream ended
83 strm->IncLoopCounter();
84 bool bislooped;
85 { VStreamMusicPlayer::DataLocker dlock(strm); bislooped = strm->CurrLoop; }
86 if (bislooped) {
87 // restart stream
88 strm->Codec->Restart();
89 ++loopCount;
90 if (loopCount == 1) decodedFromLoop = 0;
91 if (loopCount == 3 && decodedFromLoop < 256) {
92 GLog.Logf(NAME_Warning, "Looped music stream is too short, aborting it");
93 VStreamMusicPlayer::DataLocker dlock(strm);
94 strm->CurrLoop = false;
95 strm->Stopping = true;
96 strm->FinishTime = Sys_Time();
98 } else {
99 // we'll wait for some time to finish playing
100 strm->Stopping = true;
101 strm->FinishTime = Sys_Time();
103 } else if (StartPos < Len) {
104 // should never happen
105 GLog.Logf(NAME_Warning, "Music stream decoded less but is not finished.");
106 strm->Stopping = true;
107 strm->FinishTime = Sys_Time();
110 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
111 GLog.Logf(NAME_Debug, "doTick: setting data; len=%d; stpos=%d", Len, StartPos);
112 #endif
113 if (strm->Stopping && StartPos < Len) memset(Data+StartPos*2, 0, (Len-StartPos)*4);
114 strm->SoundDevice->SetStreamData(Data, Len);
115 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
116 GLog.Logf(NAME_Debug, "doTick: decoded; len=%d; stpos=%d; time=%g", Len, StartPos, Sys_Time()-stt);
117 #endif
118 if (--loopsAllowed <= 0) break;
119 if (Sys_Time()-stt > 0.5) break;
121 #ifdef VCCRUN_SOUND_THREAD_DEBUG_TICKER
122 GLog.Log(NAME_Debug, "doTick: DONE decoing...");
123 #endif
124 return false;
128 //==========================================================================
130 // VStreamMusicPlayer::stpThreadWaitPing
132 // use this in streamer thread to check if there was a ping.
133 // returns `true` if ping was received.
135 //==========================================================================
136 bool VStreamMusicPlayer::stpThreadWaitPing (unsigned int msecs) {
137 if (msecs < 1) msecs = 1;
138 mythread_condtime ctime;
139 mythread_condtime_set(&ctime, &stpPingCond, msecs);
140 auto res = mythread_cond_timedwait(&stpPingCond, &stpPingLock, &ctime);
141 return (res == 0);
145 //==========================================================================
147 // VStreamMusicPlayer::stpThreadSendPong
149 // use this in streamer thread to notify main thead that it can go on
151 //==========================================================================
152 void VStreamMusicPlayer::stpThreadSendPong () {
153 SDLOG("STP: getting lock for pong sending");
154 // we'll aquire lock if another thread is in cond_wait
155 mythread_mutex_lock(&stpLockPong);
156 SDLOG("STP: releasing lock for pong sending");
157 // and immediately release it
158 mythread_mutex_unlock(&stpLockPong);
159 // send signal
160 mythread_cond_signal(&stpCondPong);
164 //==========================================================================
166 // VStreamMusicPlayer::stpThreadSendCommand
168 // send command to streamer thread, wait for it to be processed
170 //==========================================================================
171 void VStreamMusicPlayer::stpThreadSendCommand (STPCommand acmd) {
172 stpcmd = acmd;
173 SDLOG("MAIN: sending command %u", (unsigned)stpcmd);
174 SDLOG("MAIN: getting lock for ping sending");
175 // we'll aquire lock if another thread is in cond_wait
176 mythread_mutex_lock(&stpPingLock);
177 SDLOG("MAIN: releasing lock for ping sending");
178 // and immediately release it
179 mythread_mutex_unlock(&stpPingLock);
180 // send signal
181 mythread_cond_signal(&stpPingCond);
182 SDLOG("MAIN: ping sent.");
183 if (acmd == STP_Quit) {
184 SDLOG("MAIN: waiting for streamer thread to stop");
185 // wait for it to complete
186 mythread_join(stpThread);
187 SDLOG("MAIN: streamer thread stopped");
188 } else {
189 mythread_cond_wait(&stpCondPong, &stpLockPong);
190 SDLOG("MAIN: pong received.");
195 //==========================================================================
197 // VStreamMusicPlayerWorker::streamPlayerThread
199 //==========================================================================
200 MYTHREAD_RET_TYPE VStreamMusicPlayerWorker::streamPlayerThread (void *adevobj) {
201 Sys_PinOtherThread();
202 VStreamMusicPlayer *strm = (VStreamMusicPlayer *)adevobj;
203 mythread_mutex_lock(&strm->stpPingLock);
204 // set sound device context for this thread
205 strm->SoundDevice->AddCurrentThread();
206 strm->SoundDevice->SetStreamPitch(strm->stpNewPitch);
207 strm->SoundDevice->SetStreamVolume(strm->stpNewVolume);
208 // send "we are ready" signal
209 strm->stpThreadSendPong();
210 bool doLoop = false;
211 VStr newSongName;
212 VStr lastLoadedSongName;
213 bool lastSongWasLooped = false;
214 bool doLoadNewSong = false;
215 SDLOG("STP: streaming thread started.");
216 for (;;) {
217 //SDLOG("STP: streaming thread waiting");
218 if (strm->stpThreadWaitPing(100*5)) {
219 SDLOG("STP: streaming thread received the command: %u", (unsigned)strm->stpcmd);
220 // ping received
221 bool doQuit = false;
222 switch (strm->stpcmd) {
223 case VStreamMusicPlayer::STP_Quit: // stop playing, and quit immediately
224 case VStreamMusicPlayer::STP_Stop: // stop current stream
225 doQuit = (strm->stpcmd == VStreamMusicPlayer::STP_Quit);
226 SDLOG("STP: %s", (doQuit ? "quit" : "stop"));
227 if (strm->StrmOpened) {
228 // unpause it, just in case
229 if (strm->Paused) {
230 strm->SoundDevice->ResumeStream();
231 strm->Paused = false;
233 strm->SoundDevice->CloseStream();
234 strm->StrmOpened = false;
235 delete strm->Codec;
236 strm->Codec = nullptr;
238 lastLoadedSongName.clear();
239 break;
240 case VStreamMusicPlayer::STP_Start: // start playing current stream
241 SDLOG("STP: start");
242 strm->StrmOpened = true;
243 strm->SoundDevice->SetStreamPitch(1.0f);
244 strm->SoundDevice->SetStreamVolume(strm->stpNewVolume);
246 VStreamMusicPlayer::DataLocker dlock(strm);
247 lastLoadedSongName = strm->CurrSong.cloneUniqueMT();
249 break;
250 case VStreamMusicPlayer::STP_Restart: // start playing current stream
251 SDLOG("STP: restart");
252 if (!lastLoadedSongName.isEmpty()) {
253 doLoadNewSong = true;
254 doLoop = lastSongWasLooped;
255 newSongName = lastLoadedSongName;
257 break;
258 case VStreamMusicPlayer::STP_Pause: // pause current stream
259 SDLOG("STP: pause");
260 if (strm->StrmOpened) {
261 strm->SoundDevice->PauseStream();
262 strm->Paused = true;
264 break;
265 case VStreamMusicPlayer::STP_Resume: // resume current stream
266 SDLOG("STP: resume");
267 if (strm->StrmOpened) {
268 strm->SoundDevice->ResumeStream();
269 strm->Paused = false;
271 break;
272 case VStreamMusicPlayer::STP_IsPlaying: // check if current stream is playing
273 SDLOG("STP: playing state request");
274 strm->stpIsPlaying = strm->StrmOpened;
275 break;
276 case VStreamMusicPlayer::STP_SetPitch:
277 SDLOG("STP: pitch change");
278 if (strm->StrmOpened) {
279 strm->SoundDevice->SetStreamPitch(strm->stpNewPitch);
281 break;
282 case VStreamMusicPlayer::STP_SetVolume:
283 SDLOG("STP: volume change");
284 strm->SoundDevice->SetStreamVolume(strm->stpNewVolume);
285 break;
286 case VStreamMusicPlayer::STP_PlaySongLooped:
287 case VStreamMusicPlayer::STP_PlaySong:
288 SDLOG("STP: play song X");
289 doLoadNewSong = true;
290 doLoop = (strm->stpcmd != VStreamMusicPlayer::STP_PlaySong);
291 newSongName = VStr(strm->namebuf);
292 break;
294 // quit doesn't require pong
295 if (doQuit) break;
296 // send confirmation
297 SDLOG("STP: sending pong");
298 strm->stpThreadSendPong();
299 SDLOG("STP: pong sent.");
301 // load new song
302 if (doLoadNewSong) {
303 strm->ResetLoopCounter();
304 doLoadNewSong = false;
305 bool wasPlaying;
306 // stop current song
307 if (strm->StrmOpened) {
308 wasPlaying = true;
309 // unpause it, just in case
310 if (strm->Paused) {
311 strm->SoundDevice->ResumeStream();
312 strm->Paused = false;
314 strm->SoundDevice->CloseStream();
315 strm->StrmOpened = false;
316 delete strm->Codec;
317 strm->Codec = nullptr;
318 } else {
319 wasPlaying = false;
321 lastSongWasLooped = doLoop;
322 lastLoadedSongName = newSongName.cloneUniqueMT();
323 bool success = false;
324 // load a new song
325 if (!newSongName.isEmpty() && GAudio) {
326 // unlock thread, so other song commands won't stall
327 // k8: nope, it won't work anyway
328 //mythread_mutex_unlock(&stpPingLock);
329 //mythread_mutex_lock(&stpPingLock);
330 //GLog.Logf("STRM: loading song '%s'", *newSongName);
331 VAudioCodec *codec = GAudio->LoadSongInternal(*newSongName, wasPlaying, true); // called from streaming thread
332 if (codec) {
333 bool xopened = strm->SoundDevice->OpenStream(codec->SampleRate, codec->SampleBits, codec->NumChannels);
334 if (!xopened) {
335 GLog.WriteLine(NAME_Warning, "cannot' start song '%s'", *newSongName);
336 } else {
337 success = true;
338 //GLog.Logf("STRM: starting song '%s'", *newSongName);
339 strm->Codec = codec;
341 VStreamMusicPlayer::DataLocker dlock(strm);
342 strm->CurrSong = newSongName.cloneUniqueMT();
343 strm->CurrLoop = doLoop;
345 strm->Stopping = false;
346 strm->stpNewVolume = strm->lastVolume;
347 strm->stpNewPitch = 1.0f;
348 //stpThreadSendCommand(STP_Start);
349 strm->StrmOpened = true;
350 strm->SoundDevice->SetStreamPitch(1.0f);
351 strm->SoundDevice->SetStreamVolume(strm->stpNewVolume);
355 newSongName.clear();
356 if (!success) lastLoadedSongName.clear();
358 // tick
359 if (strm->StrmOpened) {
360 //SDLOG("STP: streaming thread ticking");
361 // advance playing stream
362 #ifndef VCCRUN_SOUND_THREAD_DUMMY
363 if (doTick(strm)) {
364 // unpause it, just in case
365 if (strm->Paused) {
366 strm->SoundDevice->ResumeStream();
367 strm->Paused = false;
369 strm->SoundDevice->CloseStream();
370 strm->StrmOpened = false;
371 delete strm->Codec;
372 strm->Codec = nullptr;
374 #endif
377 strm->SoundDevice->RemoveCurrentThread();
378 mythread_mutex_unlock(&strm->stpPingLock);
379 SDLOG("STP: streaming thread complete.");
380 Z_ThreadDone();
381 return MYTHREAD_RET_VALUE;
385 //==========================================================================
387 // VStreamMusicPlayer::Init
389 //==========================================================================
390 void VStreamMusicPlayer::Init () {
391 mythread_mutex_init(&stpPingLock);
392 mythread_cond_init(&stpPingCond);
394 mythread_mutex_init(&stpLockPong);
395 mythread_cond_init(&stpCondPong);
397 // init for the first pong
398 mythread_mutex_lock(&stpLockPong);
400 // create stream player thread
401 if (mythread_create(&stpThread, &VStreamMusicPlayerWorker::streamPlayerThread, this)) Sys_Error("OpenAL driver cannot create streaming thread");
402 // wait for the first pong
403 mythread_cond_wait(&stpCondPong, &stpLockPong);
404 SDLOG("MAIN: first pong received.");
405 threadInited = true;
409 //==========================================================================
411 // VStreamMusicPlayer::Shutdown
413 //==========================================================================
414 void VStreamMusicPlayer::Shutdown () {
415 if (SoundDevice) {
416 if (developer) GLog.Log(NAME_Dev, "VStreamMusicPlayer::Shutdown(): sending quit command");
417 stpThreadSendCommand(STP_Quit); // this joins player thread
418 mythread_mutex_unlock(&stpLockPong);
420 // threading cleanup
421 mythread_mutex_destroy(&stpPingLock);
422 mythread_cond_destroy(&stpPingCond);
424 mythread_mutex_destroy(&stpLockPong);
425 mythread_cond_destroy(&stpCondPong);
427 if (developer) GLog.Log(NAME_Dev, "VStreamMusicPlayer::Shutdown(): shutdown complete!");
428 SDLOG("MAIN: destroyed mutexes and conds");
429 SoundDevice = nullptr;
431 threadInited = false;
435 //==========================================================================
437 // VStreamMusicPlayer::Play
439 //==========================================================================
440 void VStreamMusicPlayer::Play (VAudioCodec *InCodec, const char *InName, bool InLoop) {
441 if (!threadInited) {
442 GLog.Logf(NAME_Error, "*** cannot load song to uninited stream player!");
443 return;
445 stpThreadSendCommand(STP_Stop);
446 if (InName && InName[0]) {
447 bool xopened = SoundDevice->OpenStream(InCodec->SampleRate, InCodec->SampleBits, InCodec->NumChannels);
448 if (!xopened) {
449 GLog.WriteLine("WARNING: cannot' start song '%s'", InName);
450 return;
452 // no need to lock anything here, because no song is playing, but just in case
454 DataLocker dlock(this);
455 Codec = InCodec;
456 CurrSong = InName;
457 CurrLoop = InLoop;
458 Stopping = false;
459 stpNewVolume = lastVolume;
460 stpNewPitch = 1.0f;
462 stpThreadSendCommand(STP_Start);
467 //==========================================================================
469 // VStreamMusicPlayer::LoadAndPlay
471 //==========================================================================
472 void VStreamMusicPlayer::LoadAndPlay (const char *InName, bool InLoop) {
473 if (!threadInited) {
474 GLog.Logf(NAME_Error, "*** cannot load song to uninited stream player!");
475 return;
477 if (!InName) InName = "";
478 memset(namebuf, 0, sizeof(namebuf));
479 strncpy(namebuf, InName, sizeof(namebuf)-1);
480 stpThreadSendCommand(InLoop ? STP_PlaySongLooped : STP_PlaySong);
484 //==========================================================================
486 // VStreamMusicPlayer::Pause
488 //==========================================================================
489 void VStreamMusicPlayer::Pause () {
490 if (threadInited) stpThreadSendCommand(STP_Pause);
494 //==========================================================================
496 // VStreamMusicPlayer::Resume
498 //==========================================================================
499 void VStreamMusicPlayer::Resume () {
500 if (threadInited) stpThreadSendCommand(STP_Resume);
504 //==========================================================================
506 // VStreamMusicPlayer::Stop
508 //==========================================================================
509 void VStreamMusicPlayer::Stop () {
510 if (threadInited) stpThreadSendCommand(STP_Stop);
514 //==========================================================================
516 // VStreamMusicPlayer::Restart
518 //==========================================================================
519 void VStreamMusicPlayer::Restart () {
520 if (threadInited) stpThreadSendCommand(STP_Restart);
524 //==========================================================================
526 // VStreamMusicPlayer::IsPlaying
528 //==========================================================================
529 bool VStreamMusicPlayer::IsPlaying () {
530 if (!threadInited) return false;
531 stpThreadSendCommand(STP_IsPlaying);
532 return stpIsPlaying;
536 //==========================================================================
538 // VStreamMusicPlayer::SetPitch
540 //==========================================================================
541 void VStreamMusicPlayer::SetPitch (float pitch) {
542 stpNewPitch = pitch;
543 if (threadInited) stpThreadSendCommand(STP_SetPitch);
547 //==========================================================================
549 // VStreamMusicPlayer::SetVolume
551 //==========================================================================
552 void VStreamMusicPlayer::SetVolume (float volume, bool fromStreamThread) {
553 volume = clampval(volume*snd_master_volume.asFloat(), 0.0f, 1.0f);
554 if (volume == lastVolume) return;
555 lastVolume = volume;
556 stpNewVolume = volume;
557 if (!fromStreamThread) {
558 if (threadInited) stpThreadSendCommand(STP_SetVolume);
563 //==========================================================================
565 // VStreamMusicPlayer::GetCurrentSong
567 //==========================================================================
568 VStr VStreamMusicPlayer::GetCurrentSong () {
569 DataLocker dlock(this);
570 return CurrSong;
574 //==========================================================================
576 // VStreamMusicPlayer::IsCurrentSongLooped
578 //==========================================================================
579 bool VStreamMusicPlayer::IsCurrentSongLooped () {
580 DataLocker dlock(this);
581 return CurrLoop;
585 //==========================================================================
587 // VStreamMusicPlayer::GetNewVolume
589 //==========================================================================
590 float VStreamMusicPlayer::GetNewVolume () {
591 return stpNewVolume;