//-------------------------------------- // vorbisStreamSource.cc // implementation of streaming audio source // // Kurtis Seebaldt //-------------------------------------- #include "audio/vorbisStreamSource.h" #include "vorbis/codec.h" #define BUFFERSIZE 32768 #define CHUNKSIZE 4096 #if defined(TORQUE_BIG_ENDIAN) #define ENDIAN 1 #else #define ENDIAN 0 #endif extern const char * MusicPlayerStreamingHook(const AUDIOHANDLE & handle); VorbisStreamSource::VorbisStreamSource(const char *filename) { stream = NULL; bIsValid = false; bBuffersAllocated = false; bVorbisFileInitialized = false; mBufferList[0] = 0; clear(); mFilename = filename; mPosition = Point3F(0.f,0.f,0.f); } VorbisStreamSource::~VorbisStreamSource() { if(bReady && bIsValid) freeStream(); } void VorbisStreamSource::clear() { if(stream) freeStream(); mHandle = NULL_AUDIOHANDLE; mSource = NULL; if(mBufferList[0] != 0) alDeleteBuffers(NUMBUFFERS, mBufferList); for(int i = 0; i < NUMBUFFERS; i++) mBufferList[i] = 0; dMemset(&mDescription, 0, sizeof(Audio::Description)); mEnvironment = 0; mPosition.set(0.f,0.f,0.f); mDirection.set(0.f,1.f,0.f); mPitch = 1.f; mScore = 0.f; mCullTime = 0; bReady = false; bFinishedPlaying = false; bIsValid = false; bBuffersAllocated = false; bVorbisFileInitialized = false; } bool VorbisStreamSource::initStream() { vorbis_info *vi; ALint error; bFinished = false; // JMQ: changed buffer to static and doubled size. workaround for // https://206.163.64.242/mantis/view_bug_page.php?f_id=0000242 static char data[BUFFERSIZE*2]; alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); stream = ResourceManager->openStream(mFilename); if(stream != NULL) { if(vf.ov_open(stream, NULL, 0) < 0) { return false; } bVorbisFileInitialized = true; //Read Vorbis File Info vi = vf.ov_info(-1); freq = vi->rate; long samples = (long)vf.ov_pcm_total(-1); if(vi->channels == 1) { format = AL_FORMAT_MONO16; DataSize = 2 * samples; } else { format = AL_FORMAT_STEREO16; DataSize = 4 * samples; } DataLeft = DataSize; // Clear Error Code alGetError(); alGenBuffers(NUMBUFFERS, mBufferList); if ((error = alGetError()) != AL_NO_ERROR) return false; bBuffersAllocated = true; int numBuffers = 0; for(int loop = 0; loop < NUMBUFFERS; loop++) { ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft; if (DataToRead == DataLeft) bFinished = AL_TRUE; long ret = oggRead(data, BUFFERSIZE, ENDIAN, ¤t_section); if(ret <= 0) { bFinished = AL_TRUE; break; } DataLeft -= ret; alBufferData(mBufferList[loop], format, data, ret, freq); ++numBuffers; if ((error = alGetError()) != AL_NO_ERROR) return false; if(bFinished) break; } // Queue the buffers on the source alSourceQueueBuffers(mSource, NUMBUFFERS, mBufferList); if ((error = alGetError()) != AL_NO_ERROR) return false; alSourcei(mSource, AL_LOOPING, AL_FALSE); bReady = true; } else { return false; } bIsValid = true; return true; } bool VorbisStreamSource::updateBuffers() { ALint processed; ALuint BufferID; ALint error; // JMQ: changed buffer to static and doubled size. workaround for // https://206.163.64.242/mantis/view_bug_page.php?f_id=0000242 static char data[BUFFERSIZE*2]; // don't do anything if stream not loaded properly if(!bIsValid) return false; if(bFinished && mDescription.mIsLooping) resetStream(); // reset AL error code alGetError(); #if 1 //def TORQUE_OS_LINUX // JMQ: this doesn't really help on linux. it may make things worse. // if it doesn't help on mac/win either, could disable it. ALint state; alGetSourcei(mSource, AL_SOURCE_STATE, &state); if (state == AL_STOPPED) { // um, yeah. you should be playing // restart alSourcePlay(mSource); //#ifdef TORQUE_DEBUG //Con::errorf(">><<>><< THE MUSIC STOPPED >><<>><<"); //#endif return true; } #endif #ifdef TORQUE_OS_LINUX checkPosition(); #endif // Get status alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); // If some buffers have been played, unqueue them and load new audio into them, then add them to the queue if (processed > 0) { while (processed) { alSourceUnqueueBuffers(mSource, 1, &BufferID); if ((error = alGetError()) != AL_NO_ERROR) return false; if (!bFinished) { ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft; if (DataToRead == DataLeft) bFinished = AL_TRUE; long ret = oggRead(data, BUFFERSIZE, ENDIAN, ¤t_section); if(ret > 0) { DataLeft -= ret; alBufferData(BufferID, format, data, ret, freq); if ((error = alGetError()) != AL_NO_ERROR) return false; // Queue buffer alSourceQueueBuffers(mSource, 1, &BufferID); if ((error = alGetError()) != AL_NO_ERROR) return false; } processed--; if(bFinished && mDescription.mIsLooping) { resetStream(); } } else { buffersinqueue--; processed--; if (buffersinqueue == 0) { bFinishedPlaying = AL_TRUE; return AL_FALSE; } } } } return true; } void VorbisStreamSource::freeStream() { bReady = false; if(stream != NULL) ResourceManager->closeStream(stream); stream = NULL; if(bBuffersAllocated) { if(mBufferList[0] != 0) alDeleteBuffers(NUMBUFFERS, mBufferList); for(int i = 0; i < NUMBUFFERS; i++) mBufferList[i] = 0; bBuffersAllocated = false; } if(bVorbisFileInitialized) { vf.ov_clear(); bVorbisFileInitialized = false; } } void VorbisStreamSource::resetStream() { // MusicPlayerStreamingHook allow you to create a handler // where you can rotate through streaming files // Comment in and provide hook if desired. // //const char * newFile = MusicPlayerStreamingHook(mHandle); //if (newFile) //{ // setNewFile(newFile); // return; //} //else { vf.ov_pcm_seek(0); DataLeft = DataSize; bFinished = AL_FALSE; } } void VorbisStreamSource::setNewFile(const char * file) { //--------------------- // close down old file... //--------------------- if(stream != NULL) { ResourceManager->closeStream(stream); stream = NULL; } if(bVorbisFileInitialized) { vf.ov_clear(); bVorbisFileInitialized = false; } //--------------------- // open up new file... //--------------------- mFilename = file; stream = ResourceManager->openStream(mFilename); if(stream != NULL) { if(vf.ov_open(stream, NULL, 0) < 0) { bFinished = AL_TRUE; bVorbisFileInitialized = false; bIsValid = false; return; } //Read Vorbis File Info vorbis_info * vi = vf.ov_info(-1); freq = vi->rate; long samples = (long)vf.ov_pcm_total(-1); if(vi->channels == 1) { format = AL_FORMAT_MONO16; DataSize = 2 * samples; } else { format = AL_FORMAT_STEREO16; DataSize = 4 * samples; } DataLeft = DataSize; // Clear Error Code alGetError(); bFinished = AL_FALSE; bVorbisFileInitialized = true; bIsValid = true; } } // ov_read() only returns a maximum of one page worth of data // this helper function will repeat the read until buffer is full long VorbisStreamSource::oggRead(char *buffer,int length, int bigendianp,int *bitstream) { long bytesRead = 0; long totalBytes = 0; long offset = 0; long bytesToRead = 0; while((offset) < length) { if((length - offset) < CHUNKSIZE) bytesToRead = length - offset; else bytesToRead = CHUNKSIZE; bytesRead = vf.ov_read(buffer, bytesToRead, bigendianp, bitstream); //#ifdef TORQUE_OS_LINUX #if 1 // Might fix mac audio issue and possibly others...based on references, this looks like correct action // linux ver will hang on exit after a stream loop if we don't // do this if (bytesRead == OV_HOLE) // retry, see: // http://www.xiph.org/archives/vorbis-dev/200102/0163.html // http://www.mit.edu/afs/sipb/user/xiphmont/ogg-sandbox/vorbis/doc/vorbis-errors.txt continue; #endif if(bytesRead <= 0) break; offset += bytesRead; buffer += bytesRead; } return offset; } #ifdef TORQUE_OS_LINUX // JMQ: OpenAL sometimes replaces the stream source's position with its own // nifty value, causing the music to pan around the listener. how nice. // this function checks to see if the current source position in openal // is near the initial position, and slams it to the correct value if it // is wrong. // This is a bad place to put this, but I don't feel like adding a new // .cc file. And since this is an incredibly lame hack to // workaround a stupid OpenAL bug, I see no point in overengineering it. void AudioStreamSource::checkPosition() { ALfloat pos[3]; alGetSourcefv(mSource, AL_POSITION, pos); // just compute the difference between the openal pos and the // correct pos. it better be pretty friggin small. Point3F openalPos(pos[0], pos[1], pos[2]); F32 slopDist = 0.0001f; F32 dist = mFabs((openalPos - mPosition).len()); if (dist > slopDist) // slam! alSource3f(mSource, AL_POSITION, mPosition.x, mPosition.y, mPosition.z); } #endif F32 VorbisStreamSource::getElapsedTime() { return vf.ov_time_tell(); } F32 VorbisStreamSource::getTotalTime() { return vf.ov_time_total(-1); }