tge/engine/audio/vorbisStreamSource.cc
2025-02-17 23:17:30 -06:00

443 lines
11 KiB
C++
Executable File

//--------------------------------------
// 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, &current_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, &current_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);
}