2 * \file stream_file_source.cpp
3 * \brief CStreamFileSource
4 * \date 2012-04-11 09:57GMT
5 * \author Jan Boon (Kaetemi)
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/>.
29 #include <nel/sound/stream_file_source.h>
34 // #include <nel/misc/debug.h>
37 #include <nel/sound/audio_mixer_user.h>
38 #include <nel/sound/audio_decoder.h>
41 // using namespace NLMISC;
43 // #define NLSOUND_STREAM_FILE_DEBUG
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()
56 m_Thread
->wait(); // thread must have stopped for delete!
59 delete m_AudioDecoder
;
60 m_AudioDecoder
= NULL
;
63 void CStreamFileSource::play()
65 // note: CStreamSource will assert crash if already physically playing!
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());
77 CStreamSource::play();
78 if (!_Playing
&& !m_WaitingForPlay
)
80 nldebug("Stream file source playback not possible or necessary for some reason");
85 #ifdef NLSOUND_STREAM_FILE_DEBUG
86 nldebug("play waiting, hop onto waiting list %s", getStreamFileSound()->getFilePath().c_str());
88 m_WaitingForPlay
= true;
89 CAudioMixerUser
*mixer
= CAudioMixerUser::instance();
90 mixer
->addSourceWaitingForPlay(this);
95 // thread went kaboom while not started playing yet, probably the audiodecoder cannot be started
97 m_WaitingForPlay
= false;
102 #ifdef NLSOUND_STREAM_FILE_DEBUG
103 nldebug("play go %s", getStreamFileSound()->getFilePath().c_str());
105 //if (!m_WaitingForPlay)
107 // thread may be stopping from stop call
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());
121 if (!getStreamFileSound()->getAsync())
123 if (!prepareDecoder())
128 // else load audiodecoder in thread
129 m_WaitingForPlay
= true;
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");
140 NLMISC::nlSleep(100);
142 if (m_WaitingForPlay
&& m_Thread
->isRunning())
144 CStreamSource::play();
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");
153 CAudioMixerUser
*mixer
= CAudioMixerUser::instance();
154 mixer
->addSourceWaitingForPlay(this);
159 nlwarning("Already playing");
163 /*if (!m_WaitingForPlay)
165 m_WaitingForPlay = true;
167 m_Thread->wait(); // thread must have stopped to restart it!
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());
182 CStreamSource::stopInt();
184 #ifdef NLSOUND_STREAM_FILE_DEBUG
185 nldebug("stopInt ok");
190 if (_SpawnEndCb
!= NULL
)
191 _SpawnEndCb(this, _CbUserParam
);
196 #ifdef NLSOUND_STREAM_FILE_DEBUG
200 // thread will check _Playing to stop
203 bool CStreamFileSource::isPlaying()
205 #ifdef NLSOUND_STREAM_FILE_DEBUG
206 nldebug("isPlaying");
209 return m_Thread
->isRunning();
212 void CStreamFileSource::pause()
214 #ifdef NLSOUND_STREAM_FILE_DEBUG
220 // thread checks for this to not delete the audio decoder
223 // stop the underlying system
224 CStreamSource::stop();
226 // thread will check _Playing to stop
230 nlwarning("Already paused");
234 void CStreamFileSource::resume()
236 #ifdef NLSOUND_STREAM_FILE_DEBUG
242 m_Thread
->wait(); // thread must have stopped to restart it!
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
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
;
285 nlassert(!m_LookupPath
.empty());
286 m_AudioDecoder
= IAudioDecoder::createAudioDecoder(m_LookupPath
, getStreamFileSound()->getAsync(), getStreamFileSound()->getLooping());
289 nlwarning("Failed to create IAudioDecoder, likely invalid format");
292 this->setFormat(m_AudioDecoder
->getChannels(), m_AudioDecoder
->getBitsPerSample(), (uint32
)m_AudioDecoder
->getSamplesPerSec());
295 this->getRecommendedBufferSize(samples
, bytes
);
296 this->preAllocate(bytes
* 2);
301 inline bool CStreamFileSource::bufferMore(uint bytes
) // buffer from bytes (minimum) to bytes * 2 (maximum)
303 uint8
*buffer
= this->lock(bytes
* 2);
306 uint32 result
= m_AudioDecoder
->getNextBytes(buffer
, bytes
, bytes
* 2);
307 this->unlock(result
);
313 void CStreamFileSource::run()
315 #ifdef NLSOUND_STREAM_FILE_DEBUG
316 nldebug("run %s", getStreamFileSound()->getFilePath().c_str());
320 bool looping
= _Looping
;
321 if (getStreamFileSound()->getAsync())
323 if (!prepareDecoder())
327 this->getRecommendedBufferSize(samples
, bytes
);
328 uint32 recSleep
= 40;
330 m_DecodingEnded
= false;
331 while (_Playing
|| m_WaitingForPlay
)
333 if (!m_AudioDecoder
->isMusicEnded())
335 #ifdef NLSOUND_STREAM_FILE_DEBUG
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
);
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
);
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());
366 // stop the physical source
367 // if (hasPhysicalSource())
368 // getPhysicalSource()->stop();
369 // the audio mixer will call stop on the logical source
375 // don't delete anything
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
;
389 #ifdef NLSOUND_STREAM_FILE_DEBUG
390 nldebug("run end %s", getStreamFileSound()->getFilePath().c_str());
394 } /* namespace NLSOUND */