/**
* \ file stream_file_source . cpp
* \ brief CStreamFileSource
* \ date 2012 - 04 - 11 09 : 57 GMT
* \ author Jan Boon ( Kaetemi )
* CStreamFileSource
*/
// NeL - MMORPG Framework <https://wiki.ryzom.dev/>
// Copyright (C) 2012-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
# include "stdsound.h"
# include <nel/sound/stream_file_source.h>
// STL includes
// NeL includes
// #include <nel/misc/debug.h>
// Project includes
# include <nel/sound/audio_mixer_user.h>
# include <nel/sound/audio_decoder.h>
using namespace std ;
// using namespace NLMISC;
// #define NLSOUND_STREAM_FILE_DEBUG
namespace NLSOUND {
CStreamFileSource : : CStreamFileSource ( CStreamFileSound * streamFileSound , bool spawn , TSpawnEndCallback cb , void * cbUserParam , NL3D : : CCluster * cluster , CGroupController * groupController )
: CStreamSource ( streamFileSound , spawn , cb , cbUserParam , cluster , groupController ) , m_AudioDecoder ( NULL ) , m_Paused ( false ) , m_DecodingEnded ( false )
{
m_Thread = NLMISC : : IThread : : create ( this ) ;
}
CStreamFileSource : : ~ CStreamFileSource ( )
{
stop ( ) ;
m_Thread - > wait ( ) ; // thread must have stopped for delete!
delete m_Thread ;
m_Thread = NULL ;
delete m_AudioDecoder ;
m_AudioDecoder = NULL ;
}
void CStreamFileSource : : play ( )
{
// note: CStreamSource will assert crash if already physically playing!
if ( m_WaitingForPlay )
{
if ( m_Thread - > isRunning ( ) )
{
if ( m_NextBuffer | | ! m_FreeBuffers )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " play waiting, play stream %s " , getStreamFileSound ( ) - > getFilePath ( ) . c_str ( ) ) ;
# endif
CStreamSource : : play ( ) ;
if ( ! _Playing & & ! m_WaitingForPlay )
{
nldebug ( " Stream file source playback not possible or necessary for some reason " ) ;
}
}
else
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " play waiting, hop onto waiting list %s " , getStreamFileSound ( ) - > getFilePath ( ) . c_str ( ) ) ;
# endif
m_WaitingForPlay = true ;
CAudioMixerUser * mixer = CAudioMixerUser : : instance ( ) ;
mixer - > addSourceWaitingForPlay ( this ) ;
}
}
else
{
// thread went kaboom while not started playing yet, probably the audiodecoder cannot be started
// don't play
m_WaitingForPlay = false ;
}
}
else if ( ! _Playing )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " play go %s " , getStreamFileSound ( ) - > getFilePath ( ) . c_str ( ) ) ;
# endif
//if (!m_WaitingForPlay)
//{
// thread may be stopping from stop call
m_Thread - > wait ( ) ;
//}
//else
//{
// nlwarning("Already waiting for play");
//}
std : : string filepath = getStreamFileSound ( ) - > getFilePath ( ) ;
m_LookupPath = NLMISC : : CPath : : lookup ( filepath , false , false ) ;
if ( m_LookupPath . empty ( ) )
{
nlwarning ( " Music file %s does not exist! " , filepath . c_str ( ) ) ;
return ;
}
if ( ! getStreamFileSound ( ) - > getAsync ( ) )
{
if ( ! prepareDecoder ( ) )
{
return ;
}
}
// else load audiodecoder in thread
m_WaitingForPlay = true ;
m_Thread - > start ( ) ;
m_Thread - > setPriority ( NLMISC : : ThreadPriorityHighest ) ;
if ( ! getStreamFileSound ( ) - > getAsync ( ) )
{
// wait until at least one buffer is ready
while ( ! ( m_NextBuffer | | ! m_FreeBuffers ) & & m_WaitingForPlay & & m_Thread - > isRunning ( ) )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " wait buffer " ) ;
# endif
NLMISC : : nlSleep ( 100 ) ;
}
if ( m_WaitingForPlay & & m_Thread - > isRunning ( ) )
{
CStreamSource : : play ( ) ;
if ( ! _Playing )
{
nlwarning ( " Failed to synchronously start playing a file stream source. This happens when all physical tracks are in use. Use a Highest Priority sound " ) ;
}
}
}
else
{
CAudioMixerUser * mixer = CAudioMixerUser : : instance ( ) ;
mixer - > addSourceWaitingForPlay ( this ) ;
}
}
else
{
nlwarning ( " Already playing " ) ;
}
/*if (!m_WaitingForPlay)
{
m_WaitingForPlay = true ;
m_Thread - > wait ( ) ; // thread must have stopped to restart it!
m_Thread - > start ( ) ;
m_Thread - > setPriority ( NLMISC : : ThreadPriorityHighest ) ;
}
CStreamSource : : play ( ) ; */
}
void CStreamFileSource : : stop ( )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " stop %s " , getStreamFileSound ( ) - > getFilePath ( ) . c_str ( ) ) ;
# endif
CStreamSource : : stopInt ( ) ;
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " stopInt ok " ) ;
# endif
if ( _Spawn )
{
if ( _SpawnEndCb ! = NULL )
_SpawnEndCb ( this , _CbUserParam ) ;
m_Thread - > wait ( ) ;
delete this ;
}
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " stop ok " ) ;
# endif
// thread will check _Playing to stop
}
bool CStreamFileSource : : isPlaying ( )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " isPlaying " ) ;
# endif
return m_Thread - > isRunning ( ) ;
}
void CStreamFileSource : : pause ( )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " pause " ) ;
# endif
if ( ! m_Paused )
{
// thread checks for this to not delete the audio decoder
m_Paused = true ;
// stop the underlying system
CStreamSource : : stop ( ) ;
// thread will check _Playing to stop
}
else
{
nlwarning ( " Already paused " ) ;
}
}
void CStreamFileSource : : resume ( )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " resume " ) ;
# endif
if ( m_Paused )
{
m_Thread - > wait ( ) ; // thread must have stopped to restart it!
play ( ) ;
}
else
{
nlwarning ( " Not paused " ) ;
}
}
bool CStreamFileSource : : isEnded ( )
{
return m_DecodingEnded | | ( ! m_Thread - > isRunning ( ) & & ! _Playing & & ! m_WaitingForPlay & & ! m_Paused ) ;
}
float CStreamFileSource : : getLength ( )
{
return m_AudioDecoder - > getLength ( ) ;
}
bool CStreamFileSource : : isLoadingAsync ( )
{
return m_WaitingForPlay ;
}
bool CStreamFileSource : : prepareDecoder ( )
{
// creates a new decoder or keeps going with the current decoder if the stream was paused
if ( m_Paused )
{
// handle paused!
m_Paused = false ;
}
else if ( m_AudioDecoder ) // audio decoder should normally not exist when not paused and starting the thread
{
nlwarning ( " CAudioDecoder already exists, possible thread race bug with pause " ) ;
delete m_AudioDecoder ;
m_AudioDecoder = NULL ;
}
if ( ! m_AudioDecoder )
{
// load the file
nlassert ( ! m_LookupPath . empty ( ) ) ;
m_AudioDecoder = IAudioDecoder : : createAudioDecoder ( m_LookupPath , getStreamFileSound ( ) - > getAsync ( ) , getStreamFileSound ( ) - > getLooping ( ) ) ;
if ( ! m_AudioDecoder )
{
nlwarning ( " Failed to create IAudioDecoder, likely invalid format " ) ;
return false ;
}
this - > setFormat ( m_AudioDecoder - > getChannels ( ) , m_AudioDecoder - > getBitsPerSample ( ) , ( uint32 ) m_AudioDecoder - > getSamplesPerSec ( ) ) ;
}
uint samples , bytes ;
this - > getRecommendedBufferSize ( samples , bytes ) ;
this - > preAllocate ( bytes * 2 ) ;
return true ;
}
inline bool CStreamFileSource : : bufferMore ( uint bytes ) // buffer from bytes (minimum) to bytes * 2 (maximum)
{
uint8 * buffer = this - > lock ( bytes * 2 ) ;
if ( buffer )
{
uint32 result = m_AudioDecoder - > getNextBytes ( buffer , bytes , bytes * 2 ) ;
this - > unlock ( result ) ;
return true ;
}
return false ;
}
void CStreamFileSource : : run ( )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " run %s " , getStreamFileSound ( ) - > getFilePath ( ) . c_str ( ) ) ;
uint dumpI = 0 ;
# endif
bool looping = _Looping ;
if ( getStreamFileSound ( ) - > getAsync ( ) )
{
if ( ! prepareDecoder ( ) )
return ;
}
uint samples , bytes ;
this - > getRecommendedBufferSize ( samples , bytes ) ;
uint32 recSleep = 40 ;
uint32 doSleep = 10 ;
m_DecodingEnded = false ;
while ( _Playing | | m_WaitingForPlay )
{
if ( ! m_AudioDecoder - > isMusicEnded ( ) )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
+ + dumpI ;
if ( ! ( dumpI % 100 ) )
{
nldebug ( " buffer %s %s %s " , _Playing ? " PLAYING " : " NP " , m_WaitingForPlay ? " WAITING " : " NW " , getStreamFileSound ( ) - > getFilePath ( ) . c_str ( ) ) ;
nldebug ( " gain %f " , hasPhysicalSource ( ) ? getPhysicalSource ( ) - > getGain ( ) : - 1.0f ) ;
}
# endif
bool newLooping = _Looping ;
if ( looping ! = newLooping )
{
m_AudioDecoder - > setLooping ( looping ) ;
looping = newLooping ;
}
// reduce sleeping time if nothing was buffered
if ( bufferMore ( bytes ) ) recSleep = doSleep = this - > getRecommendedSleepTime ( ) ;
else doSleep = recSleep > > 2 ; // /4
NLMISC : : nlSleep ( doSleep ) ;
}
else
{
// wait until done playing buffers
while ( this - > hasFilledBuffersAvailable ( ) & & ( _Playing | | m_WaitingForPlay ) )
{
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " music ended, wait until done %s " , getStreamFileSound ( ) - > getFilePath ( ) . c_str ( ) ) ;
# endif
NLMISC : : nlSleep ( 40 ) ;
}
// stop the physical source
// if (hasPhysicalSource())
// getPhysicalSource()->stop();
// the audio mixer will call stop on the logical source
break ;
}
}
if ( m_Paused )
{
// don't delete anything
}
else
{
delete m_AudioDecoder ;
m_AudioDecoder = NULL ;
// _Playing cannot be used to detect play state because its required in cleanup
// Using m_AudioDecoder in isEnded() may result race condition (decoder is only created after thread is started)
m_DecodingEnded = ! m_WaitingForPlay ;
}
// drop buffers
m_FreeBuffers = 3 ;
m_NextBuffer = 0 ;
# ifdef NLSOUND_STREAM_FILE_DEBUG
nldebug ( " run end %s " , getStreamFileSound ( ) - > getFilePath ( ) . c_str ( ) ) ;
# endif
}
} /* namespace NLSOUND */
/* end of file */