/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include #include "HeadSpin.h" #include "hsTimer.h" #include "hsGeometry3.h" #include "plgDispatch.h" #include "plProfile.h" #include "plWin32Sound.h" #include "plWin32StreamingSound.h" #include "plDSoundBuffer.h" #include "plAudioSystem.h" #include "plAudioCore/plAudioFileReader.h" #include "plAudioCore/plSoundBuffer.h" #include "plAudioCore/plSoundDeswizzler.h" #include "pnMessage/plSoundMsg.h" #include "pnMessage/plEventCallbackMsg.h" #include "plStatusLog/plStatusLog.h" #define STREAMING_UPDATE_MS 200 plProfile_Extern(MemSounds); plProfile_CreateAsynchTimer( "Stream Shove Time", "Sound", StreamSndShoveTime ); plProfile_CreateAsynchTimer( "Stream Swizzle Time", "Sound", StreamSwizzleTime ); plProfile_Extern( SoundLoadTime ); plWin32StreamingSound::plWin32StreamingSound() : fDataStream(nil), fBlankBufferFillCounter(0), fDeswizzler(nil), fStreamType(kNoStream), fLastStreamingUpdate(0), fStopping(false), fPlayWhenStopped(false), fStartPos(0) { fBufferLengthInSecs = plgAudioSys::GetStreamingBufferSize(); } plWin32StreamingSound::~plWin32StreamingSound() { /// Call before we delete our dataStream DeActivate(); IUnloadDataBuffer(); delete fDataStream; fDataStream = nil; fSrcFilename[ 0 ] = 0; delete fDeswizzler; } void plWin32StreamingSound::DeActivate() { plWin32Sound::DeActivate(); } // Change the filename used by this streaming sound, so we can play a different file void plWin32StreamingSound::SetFilename(const char *filename, bool isCompressed) { fNewFilename = filename; fIsCompressed = isCompressed; fFailed = false; // just in case the last sound failed to load turn this off so it can try to load the new sound } ////////////////////////////////////////////////////////////// // Override, 'cause we don't want to actually LOAD the sound for streaming, // just make sure it's decompressed and such and ready to stream. plSoundBuffer::ELoadReturnVal plWin32StreamingSound::IPreLoadBuffer( hsBool playWhenLoaded, hsBool isIncidental /* = false */ ) { if(fPlayWhenStopped) return plSoundBuffer::kPending; hsBool sfxPath = fNewFilename.size() ? false : true; if( fDataStream != nil && fNewFilename.size() == 0) return plSoundBuffer::kSuccess; // Already loaded if(!ILoadDataBuffer()) { return plSoundBuffer::kError; } plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded(); if(!buffer) return plSoundBuffer::kError; // The databuffer also needs to know if the source is compressed or not if(fNewFilename.length()) { buffer->SetFileName(fNewFilename.c_str()); buffer->SetFlag(plSoundBuffer::kStreamCompressed, fIsCompressed); fNewFilename.clear(); if(fReallyPlaying) { fPlayWhenStopped = true; return plSoundBuffer::kPending; } } if( buffer->IsValid() ) { plAudioFileReader::StreamType type = plAudioFileReader::kStreamNative; fStreamType = kStreamCompressed; if(!buffer->HasFlag(plSoundBuffer::kStreamCompressed)) { if(buffer->GetDataLengthInSecs() > plgAudioSys::GetStreamFromRAMCutoff( )) { fStreamType = kStreamFromDisk; type = plAudioFileReader::kStreamWAV; } else { fStreamType = kStreamFromRAM; type = plAudioFileReader::kStreamRAM; } } if(!fStartPos) { if(buffer->AsyncLoad(type, isIncidental ? 0 : STREAMING_BUFFERS * STREAM_BUFFER_SIZE ) == plSoundBuffer::kPending) { fPlayWhenLoaded = playWhenLoaded; fLoading = true; return plSoundBuffer::kPending; } } char str[ 256 ]; strncpy( fSrcFilename, buffer->GetFileName(), sizeof( fSrcFilename ) ); bool streamCompressed = (buffer->HasFlag(plSoundBuffer::kStreamCompressed) != 0); delete fDataStream; fDataStream = buffer->GetAudioReader(); if(!fDataStream) { plAudioCore::ChannelSelect select = buffer->GetReaderSelect(); bool streamCompressed = (buffer->HasFlag(plSoundBuffer::kStreamCompressed) != 0); /// Open da file CHAR strPath[ MAX_PATH ]; _getcwd(strPath, MAX_PATH); if(sfxPath) strcat( strPath, "\\sfx\\" ); else { // if we've changing the filename don't append 'sfx', just append a '\' since a path to the folder is expected strcat( strPath, "\\"); } strcat( strPath, fSrcFilename ); fDataStream = plAudioFileReader::CreateReader(strPath, select,type); } if( fDataStream == nil || !fDataStream->IsValid() ) { delete fDataStream; fDataStream = nil; return plSoundBuffer::kError; } sprintf( str, " Readied file %s for streaming", fSrcFilename ); IPrintDbgMessage( str ); // dont free sound data until we have a chance to use it in load sound return fDataStream ? plSoundBuffer::kSuccess : plSoundBuffer::kError; } plStatusLog::AddLineS("audio.log", "EnsureLoadable failed for streaming sound %d", fDataBufferKey->GetName()); return plSoundBuffer::kError; } void plWin32StreamingSound::IFreeBuffers( void ) { plWin32Sound::IFreeBuffers(); if( fLoadFromDiskOnDemand && !IsPropertySet( kPropLoadOnlyOnCall ) ) { // if the audio system has just restarted, dont delete the datastream. This way we can pick up where we left off instead of restarting the sound. if(!plgAudioSys::IsRestarting()) { // we are deleting the stream, we must release the sound data. FreeSoundData(); delete fDataStream; fDataStream = nil; } fSrcFilename[ 0 ] = 0; } } /////////////////////////////////////////////////////////////////// // Overload from plSound. Basically sets up the streaming file and fills the // first half of our buffer. We'll fill the rest of the buffer as we get // notifications for such. hsBool plWin32StreamingSound::LoadSound( hsBool is3D ) { if( fFailed ) return false; if( !plgAudioSys::Active() || fDSoundBuffer ) return false; if( fPriority > plgAudioSys::GetPriorityCutoff() ) return false; // Don't set the failed flag, just return // Debug flag #1 if( is3D && fChannelSelect > 0 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableRightSelect ) ) { // Force a fail fFailed = true; return false; } plSoundBuffer::ELoadReturnVal retVal = IPreLoadBuffer(true); if(retVal == plSoundBuffer::kPending) return true; if( retVal == plSoundBuffer::kError ) { char str[ 256 ]; sprintf( str, "Unable to open streaming source %s", fDataBufferKey->GetName() ); IPrintDbgMessage( str, true ); fFailed = true; return false; } SetProperty( kPropIs3DSound, is3D ); plWAVHeader header = fDataStream->GetHeader(); UInt32 bufferSize = (UInt32)(fBufferLengthInSecs * header.fAvgBytesPerSec); // Debug flag #2 if( is3D && fChannelSelect == 0 && header.fNumChannels > 1 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableLeftSelect ) ) { // Force a fail fFailed = true; return false; } if( header.fNumChannels > 1 && is3D ) { // We can only do a single channel of 3D sound. So copy over one (later) bufferSize /= header.fNumChannels; header.fBlockAlign /= header.fNumChannels; header.fAvgBytesPerSec /= header.fNumChannels; header.fNumChannels = 1; } // Actually create the buffer now (always looping) fDSoundBuffer = TRACKED_NEW plDSoundBuffer( bufferSize, header, is3D, IsPropertySet(kPropLooping), false, true ); if( !fDSoundBuffer->IsValid() ) { fDataStream->Close(); delete fDataStream; fDataStream = nil; delete fDSoundBuffer; fDSoundBuffer = nil; char str[256]; sprintf(str, "Can't create sound buffer for %s.wav. This could happen if the wav file is a stereo file. Stereo files are not supported on 3D sounds. If the file is not stereo then please report this error.", GetFileName()); IPrintDbgMessage( str, true ); fFailed = true; return false; } fTotalBytes = (UInt32)bufferSize; plProfile_NewMem(MemSounds, fTotalBytes); plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded(); if(!buffer) return false; bool setupSource = true; if(!buffer->GetData() || fStartPos) { if(fStartPos && fStartPos <= fDataStream->NumBytesLeft()) { fDataStream->SetPosition(fStartPos); plStatusLog::AddLineS("syncaudio.log", "startpos %d", fStartPos); } // if we get here we are not starting from the beginning of the sound. We still have an audio loaded and need to pick up where we left off if(!fDSoundBuffer->SetupStreamingSource(fDataStream)) { setupSource = false; } } else { // this sound is starting from the beginning. Get the data and start it. if(!fDSoundBuffer->SetupStreamingSource(buffer->GetData(), buffer->GetAsyncLoadLength())) { setupSource = false; } } if(!setupSource) { fDataStream->Close(); delete fDataStream; fDataStream = nil; delete fDSoundBuffer; fDSoundBuffer = nil; plStatusLog::AddLineS("audio.log", "Could not play streaming sound, no voices left %s", GetKeyName()); return false; } FreeSoundData(); IRefreshEAXSettings( true ); // Debug info char str[ 256 ]; sprintf( str, " Streaming %s.", fSrcFilename); IPrintDbgMessage( str ); plStatusLog::AddLineS( "audioTimes.log", 0xffffffff, "Streaming %4.2f secs of %s", fDataStream->GetLengthInSecs(), GetKey()->GetUoid().GetObjectName() ); // Get pertinent info SetLength( (hsScalar)fDataStream->GetLengthInSecs() ); // Set up our deswizzler, if necessary delete fDeswizzler; if( fDataStream->GetHeader().fNumChannels != header.fNumChannels ) fDeswizzler = TRACKED_NEW plSoundDeswizzler( (UInt32)(fBufferLengthInSecs * fDataStream->GetHeader().fAvgBytesPerSec), (UInt8)(fDataStream->GetHeader().fNumChannels), header.fBitsPerSample / 8 ); else fDeswizzler = nil; // LEAVE THE WAV FILE OPEN! (We *are* streaming, after all :) return true; } void plWin32StreamingSound::IStreamUpdate() { if(!fReallyPlaying) return; if(hsTimer::GetMilliSeconds() - fLastStreamingUpdate < STREAMING_UPDATE_MS) // filter out update requests so we aren't doing this more that we need to return; plProfile_BeginTiming( StreamSndShoveTime ); if(fDSoundBuffer) { if(fDSoundBuffer->BuffersQueued() == 0 && fDataStream->NumBytesLeft() == 0 && !fDSoundBuffer->IsLooping()) { // If we are fading out it's possible that we will hit this multiple times causing this sound to try and fade out multiple times, never allowing it to actually stop if(!fStopping) { fStopping = true; Stop(); plProfile_EndTiming( StreamSndShoveTime ); } return; } if(!fDSoundBuffer->StreamingFillBuffer(fDataStream)) { plStatusLog::AddLineS("audio.log", "%s Streaming buffer fill failed", GetKeyName()); } } plProfile_EndTiming( StreamSndShoveTime ); } void plWin32StreamingSound::Update() { plWin32Sound::Update(); IStreamUpdate(); } void plWin32StreamingSound::IDerivedActuallyPlay( void ) { fStopping = false; if( !fReallyPlaying ) { for(;;) { if(fSynchedStartTimeSec) { // if we are synching to another sound this is our latency time fDSoundBuffer->SetTimeOffsetSec((float)(hsTimer::GetSeconds() - fSynchedStartTimeSec)); fSynchedStartTimeSec = 0; } if(IsPropertySet(kPropIncidental)) { if(fIncidentalsPlaying >= MAX_INCIDENTALS) break; ++fIncidentalsPlaying; } fDSoundBuffer->Play(); fReallyPlaying = true; break; } } /// Send start callbacks plSoundEvent *event = IFindEvent( plSoundEvent::kStart ); if( event != nil ) event->SendCallbacks(); } void plWin32StreamingSound::IActuallyStop() { fStopping = false; plWin32Sound::IActuallyStop(); if(fPlayWhenStopped) { fPlayWhenStopped = false; Play(); } } unsigned plWin32StreamingSound::GetByteOffset() { if(fDataStream && fDSoundBuffer) { unsigned bytesQueued = fDSoundBuffer->BuffersQueued() * STREAM_BUFFER_SIZE; unsigned offset = fDSoundBuffer->GetByteOffset(); long byteoffset = ((fDataStream->GetDataSize() - fDataStream->NumBytesLeft()) - bytesQueued) + offset; return byteoffset < 0 ? fDataStream->GetDataSize() - abs(byteoffset) : byteoffset; } return 0; } float plWin32StreamingSound::GetActualTimeSec() { if(fDataStream && fDSoundBuffer) return fDSoundBuffer->BytePosToMSecs(fDataStream->NumBytesLeft()) / 1000.0f; return 0.0f; } void plWin32StreamingSound::SetStartPos(unsigned bytes) { fStartPos = bytes; } void plWin32StreamingSound::ISetActualTime( double t ) { //fStartTimeSec = 0; if(fDataStream && fDSoundBuffer) fDataStream->SetPosition(fDSoundBuffer->GetBufferBytePos((float)t)); //else // fStartTimeSec = t; } hsBool plWin32StreamingSound::MsgReceive( plMessage* pMsg ) { return plWin32Sound::MsgReceive( pMsg ); } void plWin32StreamingSound::IRemoveCallback( plEventCallbackMsg *pCBMsg ) { plWin32Sound::IRemoveCallback( pCBMsg ); } void plWin32StreamingSound::IAddCallback( plEventCallbackMsg *pCBMsg ) { if( plSoundEvent::GetTypeFromCallbackMsg( pCBMsg ) != plSoundEvent::kStop && plSoundEvent::GetTypeFromCallbackMsg( pCBMsg ) != plSoundEvent::kStart ) { hsAssert( false, "Streaming sounds only support start and stop callbacks at this time." ); return; } plWin32Sound::IAddCallback( pCBMsg ); }