Linux multi-monitor fullscreen support
[ryzomcore.git] / nel / src / sound / stream_file_source.cpp
blobbd547eda46e864a6aca7fc46ded10b769a73b57d
1 /**
2 * \file stream_file_source.cpp
3 * \brief CStreamFileSource
4 * \date 2012-04-11 09:57GMT
5 * \author Jan Boon (Kaetemi)
6 * CStreamFileSource
7 */
9 // NeL - MMORPG Framework <https://wiki.ryzom.dev/>
10 // Copyright (C) 2012-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
12 // This source file has been modified by the following contributors:
13 // Copyright (C) 2018 Winch Gate Property Limited
15 // This program is free software: you can redistribute it and/or modify
16 // it under the terms of the GNU Affero General Public License as
17 // published by the Free Software Foundation, either version 3 of the
18 // License, or (at your option) any later version.
20 // This program is distributed in the hope that it will be useful,
21 // but WITHOUT ANY WARRANTY; without even the implied warranty of
22 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 // GNU Affero General Public License for more details.
25 // You should have received a copy of the GNU Affero General Public License
26 // along with this program. If not, see <http://www.gnu.org/licenses/>.
28 #include "stdsound.h"
29 #include <nel/sound/stream_file_source.h>
31 // STL includes
33 // NeL includes
34 // #include <nel/misc/debug.h>
36 // Project includes
37 #include <nel/sound/audio_mixer_user.h>
38 #include <nel/sound/audio_decoder.h>
40 using namespace std;
41 // using namespace NLMISC;
43 // #define NLSOUND_STREAM_FILE_DEBUG
45 namespace NLSOUND {
47 CStreamFileSource::CStreamFileSource(CStreamFileSound *streamFileSound, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController)
48 : CStreamSource(streamFileSound, spawn, cb, cbUserParam, cluster, groupController), m_AudioDecoder(NULL), m_Paused(false), m_DecodingEnded(false)
50 m_Thread = NLMISC::IThread::create(this);
53 CStreamFileSource::~CStreamFileSource()
55 stop();
56 m_Thread->wait(); // thread must have stopped for delete!
57 delete m_Thread;
58 m_Thread = NULL;
59 delete m_AudioDecoder;
60 m_AudioDecoder = NULL;
63 void CStreamFileSource::play()
65 // note: CStreamSource will assert crash if already physically playing!
68 if (m_WaitingForPlay)
70 if (m_Thread->isRunning())
72 if (m_NextBuffer || !m_FreeBuffers)
74 #ifdef NLSOUND_STREAM_FILE_DEBUG
75 nldebug("play waiting, play stream %s", getStreamFileSound()->getFilePath().c_str());
76 #endif
77 CStreamSource::play();
78 if (!_Playing && !m_WaitingForPlay)
80 nldebug("Stream file source playback not possible or necessary for some reason");
83 else
85 #ifdef NLSOUND_STREAM_FILE_DEBUG
86 nldebug("play waiting, hop onto waiting list %s", getStreamFileSound()->getFilePath().c_str());
87 #endif
88 m_WaitingForPlay = true;
89 CAudioMixerUser *mixer = CAudioMixerUser::instance();
90 mixer->addSourceWaitingForPlay(this);
93 else
95 // thread went kaboom while not started playing yet, probably the audiodecoder cannot be started
96 // don't play
97 m_WaitingForPlay = false;
100 else if (!_Playing)
102 #ifdef NLSOUND_STREAM_FILE_DEBUG
103 nldebug("play go %s", getStreamFileSound()->getFilePath().c_str());
104 #endif
105 //if (!m_WaitingForPlay)
107 // thread may be stopping from stop call
108 m_Thread->wait();
110 //else
112 // nlwarning("Already waiting for play");
114 std::string filepath = getStreamFileSound()->getFilePath();
115 m_LookupPath = NLMISC::CPath::lookup(filepath, false, false);
116 if (m_LookupPath.empty())
118 nlwarning("Music file %s does not exist!", filepath.c_str());
119 return;
121 if (!getStreamFileSound()->getAsync())
123 if (!prepareDecoder())
125 return;
128 // else load audiodecoder in thread
129 m_WaitingForPlay = true;
130 m_Thread->start();
131 m_Thread->setPriority(NLMISC::ThreadPriorityHighest);
132 if (!getStreamFileSound()->getAsync())
134 // wait until at least one buffer is ready
135 while (!(m_NextBuffer || !m_FreeBuffers) && m_WaitingForPlay && m_Thread->isRunning())
137 #ifdef NLSOUND_STREAM_FILE_DEBUG
138 nldebug("wait buffer");
139 #endif
140 NLMISC::nlSleep(100);
142 if (m_WaitingForPlay && m_Thread->isRunning())
144 CStreamSource::play();
145 if (!_Playing)
147 nlwarning("Failed to synchronously start playing a file stream source. This happens when all physical tracks are in use. Use a Highest Priority sound");
151 else
153 CAudioMixerUser *mixer = CAudioMixerUser::instance();
154 mixer->addSourceWaitingForPlay(this);
157 else
159 nlwarning("Already playing");
163 /*if (!m_WaitingForPlay)
165 m_WaitingForPlay = true;
167 m_Thread->wait(); // thread must have stopped to restart it!
169 m_Thread->start();
170 m_Thread->setPriority(NLMISC::ThreadPriorityHighest);
173 CStreamSource::play();*/
176 void CStreamFileSource::stop()
178 #ifdef NLSOUND_STREAM_FILE_DEBUG
179 nldebug("stop %s", getStreamFileSound()->getFilePath().c_str());
180 #endif
182 CStreamSource::stopInt();
184 #ifdef NLSOUND_STREAM_FILE_DEBUG
185 nldebug("stopInt ok");
186 #endif
188 if (_Spawn)
190 if (_SpawnEndCb != NULL)
191 _SpawnEndCb(this, _CbUserParam);
192 m_Thread->wait();
193 delete this;
196 #ifdef NLSOUND_STREAM_FILE_DEBUG
197 nldebug("stop ok");
198 #endif
200 // thread will check _Playing to stop
203 bool CStreamFileSource::isPlaying()
205 #ifdef NLSOUND_STREAM_FILE_DEBUG
206 nldebug("isPlaying");
207 #endif
209 return m_Thread->isRunning();
212 void CStreamFileSource::pause()
214 #ifdef NLSOUND_STREAM_FILE_DEBUG
215 nldebug("pause");
216 #endif
218 if (!m_Paused)
220 // thread checks for this to not delete the audio decoder
221 m_Paused = true;
223 // stop the underlying system
224 CStreamSource::stop();
226 // thread will check _Playing to stop
228 else
230 nlwarning("Already paused");
234 void CStreamFileSource::resume()
236 #ifdef NLSOUND_STREAM_FILE_DEBUG
237 nldebug("resume");
238 #endif
240 if (m_Paused)
242 m_Thread->wait(); // thread must have stopped to restart it!
244 play();
246 else
248 nlwarning("Not paused");
252 bool CStreamFileSource::isEnded()
254 return m_DecodingEnded || (!m_Thread->isRunning() && !_Playing && !m_WaitingForPlay && !m_Paused);
257 float CStreamFileSource::getLength()
259 return m_AudioDecoder->getLength();
262 bool CStreamFileSource::isLoadingAsync()
264 return m_WaitingForPlay;
267 bool CStreamFileSource::prepareDecoder()
269 // creates a new decoder or keeps going with the current decoder if the stream was paused
271 if (m_Paused)
273 // handle paused!
274 m_Paused = false;
276 else if (m_AudioDecoder) // audio decoder should normally not exist when not paused and starting the thread
278 nlwarning("CAudioDecoder already exists, possible thread race bug with pause");
279 delete m_AudioDecoder;
280 m_AudioDecoder = NULL;
282 if (!m_AudioDecoder)
284 // load the file
285 nlassert(!m_LookupPath.empty());
286 m_AudioDecoder = IAudioDecoder::createAudioDecoder(m_LookupPath, getStreamFileSound()->getAsync(), getStreamFileSound()->getLooping());
287 if (!m_AudioDecoder)
289 nlwarning("Failed to create IAudioDecoder, likely invalid format");
290 return false;
292 this->setFormat(m_AudioDecoder->getChannels(), m_AudioDecoder->getBitsPerSample(), (uint32)m_AudioDecoder->getSamplesPerSec());
294 uint samples, bytes;
295 this->getRecommendedBufferSize(samples, bytes);
296 this->preAllocate(bytes * 2);
298 return true;
301 inline bool CStreamFileSource::bufferMore(uint bytes) // buffer from bytes (minimum) to bytes * 2 (maximum)
303 uint8 *buffer = this->lock(bytes * 2);
304 if (buffer)
306 uint32 result = m_AudioDecoder->getNextBytes(buffer, bytes, bytes * 2);
307 this->unlock(result);
308 return true;
310 return false;
313 void CStreamFileSource::run()
315 #ifdef NLSOUND_STREAM_FILE_DEBUG
316 nldebug("run %s", getStreamFileSound()->getFilePath().c_str());
317 uint dumpI = 0;
318 #endif
320 bool looping = _Looping;
321 if (getStreamFileSound()->getAsync())
323 if (!prepareDecoder())
324 return;
326 uint samples, bytes;
327 this->getRecommendedBufferSize(samples, bytes);
328 uint32 recSleep = 40;
329 uint32 doSleep = 10;
330 m_DecodingEnded = false;
331 while (_Playing || m_WaitingForPlay)
333 if (!m_AudioDecoder->isMusicEnded())
335 #ifdef NLSOUND_STREAM_FILE_DEBUG
336 ++dumpI;
337 if (!(dumpI % 100))
339 nldebug("buffer %s %s %s", _Playing ? "PLAYING" : "NP", m_WaitingForPlay ? "WAITING" : "NW", getStreamFileSound()->getFilePath().c_str());
340 nldebug("gain %f", hasPhysicalSource() ? getPhysicalSource()->getGain() : -1.0f);
342 #endif
344 bool newLooping = _Looping;
345 if (looping != newLooping)
347 m_AudioDecoder->setLooping(looping);
348 looping = newLooping;
351 // reduce sleeping time if nothing was buffered
352 if (bufferMore(bytes)) recSleep = doSleep = this->getRecommendedSleepTime();
353 else doSleep = recSleep >> 2; // /4
354 NLMISC::nlSleep(doSleep);
356 else
358 // wait until done playing buffers
359 while (this->hasFilledBuffersAvailable() && (_Playing || m_WaitingForPlay))
361 #ifdef NLSOUND_STREAM_FILE_DEBUG
362 nldebug("music ended, wait until done %s", getStreamFileSound()->getFilePath().c_str());
363 #endif
364 NLMISC::nlSleep(40);
366 // stop the physical source
367 // if (hasPhysicalSource())
368 // getPhysicalSource()->stop();
369 // the audio mixer will call stop on the logical source
370 break;
373 if (m_Paused)
375 // don't delete anything
377 else
379 delete m_AudioDecoder;
380 m_AudioDecoder = NULL;
381 // _Playing cannot be used to detect play state because its required in cleanup
382 // Using m_AudioDecoder in isEnded() may result race condition (decoder is only created after thread is started)
383 m_DecodingEnded = !m_WaitingForPlay;
385 // drop buffers
386 m_FreeBuffers = 3;
387 m_NextBuffer = 0;
389 #ifdef NLSOUND_STREAM_FILE_DEBUG
390 nldebug("run end %s", getStreamFileSound()->getFilePath().c_str());
391 #endif
394 } /* namespace NLSOUND */
396 /* end of file */