443 lines
11 KiB
C++
Executable File
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, ¤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);
|
|
}
|