/** * \file stream_file_source.cpp * \brief CStreamFileSource * \date 2012-04-11 09:57GMT * \author Jan Boon (Kaetemi) * CStreamFileSource */ // NeL - MMORPG Framework // Copyright (C) 2012-2020 Jan BOON (Kaetemi) // // 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 . #include "stdsound.h" #include // STL includes // NeL includes // #include // Project includes #include #include 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 */