Working CoreAudio driver for OS X. (Does this make 8 attempts??)

This commit is contained in:
Steve Checkoway
2004-03-24 17:02:02 +00:00
parent 576ac720c6
commit 675706eb82
4 changed files with 253 additions and 880 deletions
+113
View File
@@ -0,0 +1,113 @@
#include "global.h"
#include "RageLog.h"
#include "RageSoundDriver_CA.h"
#include "CAHelpers.h"
#include "CAAudioHardwareDevice.h"
#include "CAAudioHardwareStream.h"
#include "CAException.h"
AudioConverter::AudioConverter(CAAudioHardwareDevice *dev, RageSound_CA *driver)
: mBuffer(NULL), mBufferSize(0)
{
AudioStreamID sID = dev->GetStreamByIndex(kAudioDeviceSectionOutput, 0);
CAAudioHardwareStream stream(sID);
vector<Desc> procFormats;
vector<Desc> physicalFormats;
UInt32 numFormats = stream.GetNumberAvailableIOProcFormats();
LOG->Trace("%u IOProc formats.", unsigned(numFormats));
for (UInt32 i=0; i<numFormats; ++i)
{
Desc desc;
stream.GetAvailableIOProcFormatByIndex(i, desc);
procFormats.push_back(desc);
}
const Desc& procFormat = FindClosestFormat(procFormats);
stream.SetCurrentIOProcFormat(procFormat);
numFormats = stream.GetNumberAvailablePhysicalFormats();
LOG->Trace("%u physical formats.", unsigned(numFormats));
for (UInt32 i=0; i<numFormats; ++i)
{
Desc desc;
stream.GetAvailablePhysicalFormatByIndex(i, desc);
physicalFormats.push_back(desc);
}
const Desc& physicalFormat = FindClosestFormat(physicalFormats);
stream.SetCurrentPhysicalFormat(physicalFormat);
const Desc SMFormat(44100.0, kAudioFormatLinearPCM, kBytesPerPacket,
kFramesPerPacket, kBytesPerFrame, kChannelsPerFrame,
kBitsPerChannel, kFormatFlags);
LOG->Trace("Initializing converter.");
SMFormat.Print();
procFormat.Print();
if (this->Initialize(SMFormat, procFormat))
RageException::ThrowNonfatal("Couldn't create the converter.");
mDriver = driver;
}
OSStatus AudioConverter::FormatConverterInputProc(UInt32& ioNumberDataPackets,
AudioBufferList& ioData,
AudioStreamPacketDescription **outDesc)
{
AudioBuffer& buf = ioData.mBuffers[0];
// This really shouldn't happen more than once, but better be sure.
if (mBufferSize != buf.mDataByteSize)
{
delete mBuffer; // deleting NULL does not crash, unlike free(NULL)
mBufferSize = buf.mDataByteSize;
mBuffer = new UInt8[mBufferSize];
}
mDriver->FillConverter(mBuffer, mBufferSize);
buf.mData = mBuffer;
return noErr;
}
Desc AudioConverter::FindClosestFormat(const vector<Desc>& formats)
{
LOG->Trace("FindClosestFormat");
vector<Desc> v;
vector<Desc>::const_iterator i;
for (i = formats.begin(); i != formats.end(); ++i)
{
const Desc& format = *i;
if (!format.IsPCM() || format.mSampleRate != 44100.0)
continue;
if (format.SampleWordSize() == 2 &&
(format.mFormatFlags & kAudioFormatFlagIsSignedInteger) ==
kAudioFormatFlagIsSignedInteger)
{ // exact match
LOG->Trace("Exact match.");
return format;
}
v.push_back(format);
}
LOG->Trace("Failed exact match.");
for (i = v.begin(); i != v.end(); ++i)
{
const Desc& format = *i;
if (format.SampleWordSize() == 2)
{
LOG->Trace("Close match.");
return format; // close
}
}
LOG->Trace("Failed close match.");
if (v.empty())
RageException::ThrowNonfatal("Couldn't find a close format.");
return v[0]; // something is better than nothing.
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef CA_HELPERS_H
#define CA_HELPERS_H
#include <vector>
#include "FormatConverterClient.h"
#include "CAStreamBasicDescription.h"
const UInt32 kFramesPerPacket = 1;
const UInt32 kChannelsPerFrame = 2;
const UInt32 kBitsPerChannel = 16;
const UInt32 kBytesPerPacket = kChannelsPerFrame * kBitsPerChannel / 8;
const UInt32 kBytesPerFrame = kBytesPerPacket;
const UInt32 kFormatFlags = kAudioFormatFlagsNativeEndian |
kAudioFormatFlagIsSignedInteger;
typedef CAStreamBasicDescription Desc;
class AudioConverter : public FormatConverterClient
{
public:
AudioConverter(CAAudioHardwareDevice *dev, RageSound_CA *driver);
protected:
OSStatus FormatConverterInputProc(UInt32& ioNumberDataPackets,
AudioBufferList& ioData,
AudioStreamPacketDescription **outDesc);
private:
static Desc FindClosestFormat(const vector<Desc>& formats);
RageSound_CA *mDriver;
UInt8 *mBuffer;
UInt32 mBufferSize;
};
#endif
+79 -777
View File
@@ -1,803 +1,105 @@
/*
* RageSoundDriver_CA.cpp
* stepmania
*
* Copyright (c) 2003 by the person(s) listed below. All rights reserved.
* Steve Checkoway
* Charlie Reading
*
*/
#include "global.h"
#include "RageSoundDriver_CA.h"
#include "RageSoundManager.h"
#include "RageSound.h"
#include "RageLog.h"
#include "RageThreads.h"
#include "RageSoundManager.h"
#include "RageSoundDriver_CA.h"
#include "CAHelpers.h"
#include <vector>
#include <AudioToolbox/AudioToolbox.h>
#include <CoreAudio/CoreAudio.h>
#include "CAAudioHardwareSystem.h"
#include "CAAudioHardwareDevice.h"
#include "CAStreamBasicDescription.h"
#include "CAException.h"
#include "archutils/Unix/CrashHandler.h"
#if (FEEDER == FEEDER_THREAD)
#include "VirtualRingBuffer.h"
#endif
//
// +-------------------------------------------+-------------------------------------------+
// | Packet N | Packet N+1 |
// | +-----------------+-----------------+ | +-----------------+-----------------+ |
// | | Frame M | Frame M+1... | | | Frame M | Frame M+1... | |
// | | +------+------+ | +------+------+ | ... | | +------+------+ | +------+------+ | ... | ...
// | | | Ch-A | Ch-B | | | Ch-A | Ch-B | | | | | Ch-A | Ch-B | | | Ch-A | Ch-B | | |
// | | +------+------+ | +------+------+ | | | +------+------+ | +------+------+ | |
// | +-----------------+-----------------+ | +-----------------+-----------------+ |
// +-------------------------------------------+-------------------------------------------+
//
// StepMania Sound Constants
const Float64 kSMSampleRate = 44100.0; // Number of samples per second
const UInt32 kSMChannelsPerFrame = 2; // Number of channels (e.g. left,right) per frame
const UInt32 kSMBitsPerChannel = 16; // Number of bits of sound data for each channel
const UInt32 kSMBytesPerFrame = (kSMChannelsPerFrame * kSMBitsPerChannel) / 8; // Size (in bytes) of each frame
const UInt32 kSMFramesPerPacket = 1; // Number of frames / packet (Uncompressed=1)
const UInt32 kSMBytesPerPacket = kSMBytesPerFrame * kSMFramesPerPacket; // Size (in bytes) of each packet
const int kOffPacketLimit = (int) (kSMSampleRate / 10); // Correct when off at least 0.1 second
const int kOffPacketReset = (int) (kOffPacketLimit / 100); // Reset when within 1% of the limit
#if (FEEDER == FEEDER_THREAD)
//
// Ring Buffer/Feeder Thread Support
//
// Number of buffers in the ring
const int kBuffersInRing = 3;
// VRB Chunk Header
typedef struct VRBChunkHeader
{
int sample; // Sample-number for chunk
size_t offset; // Offset into the chunk for live data (normally 0)
size_t length; // Length of live data within the chunk
} VBRChunkHeader;
#define FEEDER_THREAD_IMPORTANCE 6
// Additional priority to use for the feeder thread, on top of this task's ordinary priority.
// This should be the lowest possible value that gives good results (no dropouts even when the machine is under load).
// The value here (6) is good on my machine (G4/450, 1 processor, OS X 10.2.1) but don't take my word for it;
// you may want to test and adjust for other machines.
// It looks like we would use 6 to get the equivalent of what iTunes uses.
#endif
namespace
{
inline void TEST_ERR(OSStatus result)
AudioConverter *gConverter;
}
RageSound_CA::RageSound_CA()
{
try
{
#if defined(DEBUG)
char *errorMessage;
char fourcc[5];
CHECKPOINT;
memcpy(fourcc, &result, 4);
fourcc[4] = '\000';
asprintf(&errorMessage, "err = %d, %s", result, fourcc);
free(errorMessage);
#else
# pragma unused(result)
#endif
AudioDeviceID dID = CAAudioHardwareSystem::GetDefaultDevice(false, false);
mOutputDevice = new CAAudioHardwareDevice(dID);
}
catch (const CAException& e)
{
RageException::ThrowNonfatal("Couldn't create default output device.");
}
try
{
UInt32 frames = mOutputDevice->GetLatency(kAudioDeviceSectionOutput);
mLatency = frames / 44100.0; // maybe? I'm tired, I don't know.
}
catch (const CAException& e)
{
RageException::ThrowNonfatal("Couldn't get Latency.");
}
gConverter = new AudioConverter(mOutputDevice, this);
try
{
mOutputDevice->AddIOProc(GetData, this);
mOutputDevice->StartIOProc(GetData);
}
catch(const CAException& e)
{
RageException::Throw("Couldn't start the IOProc.");
}
}
#define INTERNAL_DEBUG 0
#if (INTERNAL_DEBUG)
#define LOG_MARKER "### "
#define DEBUG_LOG LOG->Trace
#else
#define LOG_MARKER ""
#define DEBUG_LOG
#endif
// ----------------------------------------------------------------------
#if (FEEDER == FEEDER_THREAD)
int RageSound_CA::FeederThread_start(void *p)
{
((RageSound_CA *) p)->FeederThread();
return 0;
}
void RageSound_CA::FeederThread()
{
/* SOUNDMAN will be set once RageSoundManager's ctor returns and
* assigns it; we might get here before that happens, though. */
while (!SOUNDMAN && !shutdown)
{
SDL_Delay(10);
}
while (!shutdown)
{
/* Sleep */
SDL_Delay( 10 ); // int(1000 * sleep_secs));
// Grab data (if we have some & enough room to drop a chunk in)
char *writePointer;
UInt32 bytesAvailableToWrite = vrb->lengthAvailableToWriteReturningPointer((void**) &writePointer);
if ((SOUNDMAN) && (nextSampleToWrite != 0) && (bytesAvailableToWrite >= vrbChunkSize))
{
if (streamsInUse == 0)
{
nextSampleToWrite = 0;
}
else
{
// Fill our local sound buffer
int sampleToWrite = nextSampleToWrite;
char buffer[buffersize];
size_t bufferUsed = this->FillSoundBuffer(buffer, buffersize, sampleToWrite);
//DEBUG_LOG(LOG_MARKER "RageSound_CA::FeederThread -- Buffer[fill] (sampleToWrite:%d, buffersize:%ld) = bufferUsed:%ld", sampleToWrite, buffersize, bufferUsed);
// Write it into the VRB
if (bufferUsed > 0)
{
VRBChunkHeader *ch = (VRBChunkHeader*) writePointer;
ch->sample = sampleToWrite;
ch->offset = 0;
ch->length = 0;
writePointer += sizeof(VRBChunkHeader);
if (bufferUsed < buffersize)
{
// We're out of data for the streams, so reset nextSampleToWrite
nextSampleToWrite = 0;
}
else
{
// Full house, so move ahead
int sample = sampleToWrite + smBytesToSamples(bufferUsed);
nextSampleToWrite = sample;
}
UInt32 bytesWritten = vrbChunkSize-sizeof(VRBChunkHeader);
OSStatus oss = AudioConverterConvertBuffer(converter, bufferUsed, buffer, &bytesWritten, writePointer);
if (bytesWritten > 0)
{
ch->length = bytesWritten;
vrb->didWriteLength(bytesWritten+sizeof(VRBChunkHeader));
}
}
}
}
}
}
#endif
// ----------------------------------------------------------------------
RageSound_CA::RageSound_CA() : idealFormat(NULL), actualFormat(NULL), converter(NULL)
{
#if true
RageException::ThrowNonfatal("Broken driver on G5--disabled.");
#endif
shutdown = false;
UInt32 thePropertySize;
OSStatus err;
// Create 16 streams
for (size_t i=0; i<16; i++)
{
stream *s = new stream();
stream_pool.push_back(s);
}
streamsInUse = 0;
// Grab the output-device
thePropertySize = sizeof(AudioDeviceID);
err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &thePropertySize,
&outputDevice);
TEST_ERR(err);
// Negotiate ideal vs. actual output-stream-format
idealFormat = new AudioStreamBasicDescription;
idealFormat->mSampleRate = kSMSampleRate;
idealFormat->mFormatID = kAudioFormatLinearPCM;
idealFormat->mFormatFlags = kLinearPCMFormatFlagIsBigEndian
| kLinearPCMFormatFlagIsPacked
| kLinearPCMFormatFlagIsSignedInteger;
idealFormat->mBytesPerPacket = kSMBytesPerPacket;
idealFormat->mFramesPerPacket = kSMFramesPerPacket;
idealFormat->mBytesPerFrame = kSMBytesPerFrame;
idealFormat->mChannelsPerFrame = kSMChannelsPerFrame;
idealFormat->mBitsPerChannel = kSMBitsPerChannel;
idealFormat->mReserved = 0;
actualFormat = new AudioStreamBasicDescription;
memcpy(actualFormat, idealFormat, sizeof(AudioStreamBasicDescription));
thePropertySize = sizeof(AudioStreamBasicDescription);
err = AudioDeviceGetProperty(outputDevice, 0, 0, kAudioDevicePropertyStreamFormatMatch, &thePropertySize, actualFormat);
TEST_ERR(err);
// Create a converter to get from ideal -> actual
err = AudioConverterNew(idealFormat, actualFormat, &converter);
TEST_ERR(err);
// Calculate the input buffersize by maxing @ output-device's buffer and then what the audio-converter needs to get there
thePropertySize = sizeof(UInt32);
err = AudioDeviceGetProperty(outputDevice, 0, 0, kAudioDevicePropertyBufferSize, &thePropertySize, &buffersize);
TEST_ERR(err);
err = AudioConverterGetProperty(converter, kAudioConverterPropertyCalculateInputBufferSize, &thePropertySize, &buffersize);
TEST_ERR(err);
samplesPerBuffer = smBytesToSamples(buffersize);
LOG->Info(LOG_MARKER "RageSound_CA::RageSound_CA -- buffersize:%ld, samplesPerBuffer:%d", buffersize, samplesPerBuffer);
char fourcc[5];
memcpy(fourcc, &actualFormat->mFormatID, 4);
fourcc[4] = '\000';
LOG->Info("\nIdeal Format:\n"
"Rate: %f\n"
"Format ID: %s\n"
"Format Flags: 0x%lX\n"
"Bytes per packet: %lu\n"
"Frames per packet: %lu\n"
"Bytes per frame: %lu\n"
"Channels per frame: %lu\n"
"Bits per channel: %lu\n",
idealFormat->mSampleRate,
fourcc,
idealFormat->mFormatFlags,
idealFormat->mBytesPerPacket,
idealFormat->mFramesPerPacket,
idealFormat->mBytesPerFrame,
idealFormat->mChannelsPerFrame,
idealFormat->mBitsPerChannel);
LOG->Info("\nActual Format:\n"
"Rate: %f\n"
"Format ID: %s\n"
"Format Flags: 0x%lX\n"
"Bytes per packet: %lu\n"
"Frames per packet: %lu\n"
"Bytes per frame: %lu\n"
"Channels per frame: %lu\n"
"Bits per channel: %lu\n",
actualFormat->mSampleRate,
fourcc,
actualFormat->mFormatFlags,
actualFormat->mBytesPerPacket,
actualFormat->mFramesPerPacket,
actualFormat->mBytesPerFrame,
actualFormat->mChannelsPerFrame,
actualFormat->mBitsPerChannel);
#if (FEEDER == FEEDER_THREAD)
// Set up the virtual-ring-buffer (if using a feeder-thread)
vrbChunkSize = sizeof(VRBChunkHeader) + (samplesPerBuffer * actualFormat->mBytesPerPacket); // (for converter output)
vrb = new VirtualRingBuffer(vrbChunkSize * kBuffersInRing);
#endif
// Set the output-device actual stream-format & start it up
thePropertySize = sizeof(AudioStreamBasicDescription);
err = AudioDeviceSetProperty(outputDevice, NULL, 0, 0, kAudioDevicePropertyStreamFormat,
thePropertySize, &actualFormat);
TEST_ERR(err);
err = AudioDeviceAddIOProc(outputDevice, GetData, this);
TEST_ERR(err);
err = AudioDeviceAddPropertyListener(outputDevice, 0, false, kAudioDeviceProcessorOverload,
OverloadListener, this);
TEST_ERR(err);
err = AudioDeviceAddPropertyListener(outputDevice, 1, false, kAudioDeviceProcessorOverload,
OverloadListener, this);
TEST_ERR(err);
err = AudioDeviceStart(outputDevice, GetData);
TEST_ERR(err);
latency = 0; //for now
startSampleTime = 0;
expected = 0;
#if (RESYNC == RESYNC_ACCUMULATE)
packetsOff = 0;
packetsOffSamples = 0;
#endif
#if (FEEDER == FEEDER_THREAD)
// Set up the feeder-thread if we're using it
nextSampleToWrite = 0;
feederThread.SetName("RageSound_CA");
feederThread.Create(FeederThread_start, this);
#endif
}
RageSound_CA::~RageSound_CA()
{
shutdown = true;
#if (FEEDER == FEEDER_THREAD)
/* Signal the feeder thread to quit. */
LOG->Trace("Shutting down feeder thread ...");
feederThread.Wait();
LOG->Trace("Feeder thread shut down.");
#endif
OSStatus err;
// Shutdown the audio device
err = AudioDeviceStop(outputDevice, GetData);
TEST_ERR(err);
err = AudioDeviceRemoveIOProc(outputDevice, GetData);
TEST_ERR(err);
// Nuke the converter
err = AudioConverterDispose(converter);
TEST_ERR(err);
delete idealFormat;
delete actualFormat;
// Nuke the stream pool
for (size_t i=0; i<stream_pool.size(); i++)
{
delete stream_pool[i];
}
#if (FEEDER == FEEDER_THREAD)
// Nuke the virtual-ring-buffer (if using a feeder-thread)
delete vrb;
#endif
mOutputDevice->StopIOProc(GetData);
delete gConverter;
delete mOutputDevice;
}
void RageSound_CA::StartMixing(RageSoundBase *snd)
{
LockMutex L(SOUNDMAN->lock);
// Find an unused buffer
size_t i;
for (i=0; i<stream_pool.size(); i++)
{
if (stream_pool[i]->state == stream::INACTIVE)
break;
}
// No free buffer? Fake it. (and log it)
if (i == stream_pool.size())
{
LOG->Warn(LOG_MARKER "RageSound_CA::StartMixing -- exceeded free buffers, faking it");
//SOUNDMAN->AddFakeSound(snd);
return;
}
// Reset the internals & stash it
stream_pool[i]->clear();
stream_pool[i]->snd = snd;
streamsInUse++;
LOG->Trace(LOG_MARKER "RageSound_CA::StartMixing -- [%ld] %s", i, (const char *) snd->GetLoadedFilePath());
/* Pre-buffer the stream. */
//stream_pool[i]->GetData(true);
//stream_pool[i]->pcm->Play();
/*
* Normally, at this point we should still be INACTIVE, in which case,
* tell the mixer thread to start mixing this channel. However, if it's
* been changed to STOPPING, then we actually finished the whole file
* in the prebuffering GetData calls above, so leave it alone and let it
* finish on its own.
*/
if (stream_pool[i]->state == stream::INACTIVE)
{
stream_pool[i]->state = stream::PLAYING;
}
}
void RageSound_CA::Update(float delta)
{
#pragma unused(delta)
//DEBUG_LOG(LOG_MARKER "RageSound_CA::Update (delta:%f)", delta);
/*
* SoundStopped() might erase sounds out from under us, so make a copy
* of the sound list.
*/
vector<stream *>snds = stream_pool;
ASSERT(SOUNDMAN);
LockMutex L(SOUNDMAN->lock);
for (size_t i=0; i<snds.size(); i++)
{
// If not stopping, we don't care
if (snds[i]->state != stream::STOPPING)
continue;
// Stopping, but still flushing
int ps = this->GetPosition(snds[i]->snd);
if (ps < snds[i]->flush_pos)
continue;
// Sound has stopped and flushed all its buffers
if (snds[i]->snd != NULL)
{
snds[i]->snd->StopPlaying();
}
streamsInUse--;
snds[i]->snd = NULL;
snds[i]->state = stream::INACTIVE;
}
}
void RageSound_CA::StopMixing(RageSoundBase *snd)
{
ASSERT(snd != NULL);
LockMutex L(SOUNDMAN->lock);
size_t i;
for (i=0; i<stream_pool.size(); i++)
{
if (stream_pool[i]->snd == snd)
break;
}
if (i == stream_pool.size())
{
LOG->Trace("not stopping a sound because it's not playing");
return;
}
LOG->Trace(LOG_MARKER "RageSound_CA::StopMixing -- [%ld] %s", i, (const char *) snd->GetLoadedFilePath());
/* STOPPING tells the mixer thread to release the stream once str->flush_bufs buffers have been flushed. */
stream_pool[i]->state = stream_pool[i]->STOPPING;
/* Flush two buffers worth of data. */
stream_pool[i]->flush_pos = this->GetPosition(stream_pool[i]->snd);
/*
* This function is called externally (by RageSound) to stop immediately.
* We need to prevent SoundStopped from being called; it should only be
* called when we stop implicitly at the end of a sound. Set snd to NULL.
*/
stream_pool[i]->snd = NULL;
}
int64_t RageSound_CA::GetPosition(const RageSoundBase *snd) const
int64_t RageSound_CA::GetPosition(const RageSoundBase *sound) const
{
AudioTimeStamp time;
OSStatus err = AudioDeviceGetCurrentTime(outputDevice, &time);
TEST_ERR(err);
return ConvertAudioTimeStampToPosition(&time);
mOutputDevice->GetCurrentTime(time);
return int64_t(time.mSampleTime);
}
float RageSound_CA::GetPlayLatency() const
void RageSound_CA::FillConverter(void *data, UInt32 dataByteSize)
{
OSStatus err;
UInt32 nowLatency = 0;
UInt32 propertySize = sizeof(nowLatency);
err = AudioDeviceGetProperty(outputDevice, 0, 0, kAudioDevicePropertyLatency, &propertySize, &nowLatency);
TEST_ERR(err);
return nowLatency;
LOG->Trace("FillConverter called for %u bytes.", unsigned(dataByteSize));
int frames = dataByteSize / gConverter->GetInputFormat().mBytesPerPacket;
LOG->Trace("Mix being called for %d frames.", frames);
LOG->Flush();
this->Mix((int16_t *)data, frames, mDecodePos, GetPosition(NULL));
LOG->Trace("Mixed the data.");
LOG->Flush();
}
int RageSound_CA::GetSampleRate() const
OSStatus RageSound_CA::GetData(AudioDeviceID inDevice,
const AudioTimeStamp *inNow,
const AudioBufferList *inInputData,
const AudioTimeStamp *inInputTime,
AudioBufferList *outOutputData,
const AudioTimeStamp *inOutputTime,
void *inClientData)
{
AudioTimeStamp time;
OSStatus err = AudioDeviceGetCurrentTime(outputDevice, &time);
TEST_ERR(err);
//return (int) (kSampleRate / time.mRateScalar);
return (int) kSMSampleRate;
}
size_t RageSound_CA::smSamplesToBytes(int samples)
{
return (samples * kSMBytesPerPacket);
}
int RageSound_CA::smBytesToSamples(size_t bytes)
{
return (bytes / kSMBytesPerPacket);
}
size_t RageSound_CA::caSamplesToBytes(int samples)
{
return (samples * actualFormat->mBytesPerPacket);
}
int RageSound_CA::caBytesToSamples(size_t bytes)
{
return (bytes / actualFormat->mBytesPerPacket);
}
size_t RageSound_CA::FillSoundBuffer(char* buffer, size_t maxBufferSize, int position)
{
bzero(buffer, maxBufferSize);
static SoundMixBuffer mix;
size_t bufferUsed = 0;
LockMutex L(SOUNDMAN->lock);
for (size_t i=0; i<stream_pool.size(); i++)
{
if (stream_pool[i]->state != stream::PLAYING)
continue;
bufferUsed = stream_pool[i]->snd->GetPCM(buffer, maxBufferSize, position);
//DEBUG_LOG(LOG_MARKER "RageSound_CA::FillSoundBuffer [%lu] GetPCM(buffer, size:%lu, sampleno:%d) = %lu", i, maxBufferSize, position, bufferUsed);
int samplesRead = smBytesToSamples(bufferUsed);
if (bufferUsed < maxBufferSize)
{
stream_pool[i]->state = stream::STOPPING;
stream_pool[i]->flush_pos = position + samplesRead;
}
if (streamsInUse > 1)
{
mix.write((SInt16 *) buffer, bufferUsed / sizeof(SInt16));
}
}
L.Unlock();
if (streamsInUse > 1)
{
bufferUsed = mix.size() * sizeof(SInt16);
mix.read((SInt16 *) buffer);
}
return bufferUsed;
}
OSStatus RageSound_CA::GetData(AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData)
{
size_t outputSize = outOutputData->mBuffers[0].mDataByteSize;
if (!SOUNDMAN)
static bool handlerInstalled = false;
if (!handlerInstalled)
{
LOG->Warn(LOG_MARKER "RageSound_CA::GetData() -- SOUNDMAN doesnt yet exist!");
bzero(outOutputData->mBuffers[0].mData, outputSize);
return noErr;
InstallExceptionHandler(CrashExceptionHandler);
handlerInstalled = true;
}
RageSound_CA *THIS = (RageSound_CA *) inClientData;
OSStatus oss = noErr;
// If uninitialized, set the startSampleTime
if (THIS->startSampleTime == 0)
{
THIS->startSampleTime = inOutputTime->mSampleTime;
LOG->Trace(LOG_MARKER "RageSound_CA::GetData -- startSampleTime set to %f", THIS->startSampleTime);
LOG->Trace(LOG_MARKER "RageSound_CA::GetData -- outputSize:%ld, output-samples:%d", outputSize, THIS->caBytesToSamples(outputSize));
}
// Where are we now (well, actually a little into the future)
int nowSample = THIS->ConvertAudioTimeStampToPosition(inOutputTime); // expected position for stream
// Need to resync?
if (THIS->expected == 0)
{
THIS->expected = nowSample;
#if (FEEDER == FEEDER_THREAD)
DEBUG_LOG(LOG_MARKER "RageSound_CA::GetData -- expected set to %d", THIS->expected);
#endif
}
int posExpected = THIS->expected;
#if (RESYNC == RESYNC_NEVER)
int posPlaying = posExpected;
#else
int posPlaying = nowSample;
#if (RESYNC == RESYNC_ACCUMULATE)
// if (abs(posPlaying-posExpected) <= kOffPacketReset) // If we happen to be close enough, reset how far we're off
// {
// THIS->packetsOff = 0;
// THIS->packetsOffSamples = 0;
// }
// else
// {
THIS->packetsOff += (posExpected - posPlaying); // >0=ahead of actual, <0=behind actual
THIS->packetsOffSamples++;
// }
#else
THIS->packetsOff = posExpected - posPlaying; // >0=ahead of actual, <0=behind actual
THIS->packetsOffSamples = 1;
#endif
if (abs(THIS->packetsOff) < kOffPacketLimit)
{
posPlaying = posExpected; // Since we're not correcting, do what's expected
}
else
{
LOG->Trace(LOG_MARKER "RageSound_CA::GetData -- %s (expected:%d, is:%d, dist:%d, packets-off:%d, samples:%d)", ((THIS->packetsOff<0)?"skipping ahead":"skipping back"), posExpected, posPlaying, posExpected-posPlaying, THIS->packetsOff, THIS->packetsOffSamples);
THIS->packetsOff = 0; // Reset number of packets off since we're resetting
THIS->packetsOffSamples = 0;
}
#endif
//
// Write into our output buffer until it's full
//
char *outputDataPtr = (char*) outOutputData->mBuffers[0].mData;
size_t outputDataLeft = outputSize;
#if (FEEDER == FEEDER_DIRECT)
// Load the sound data if we have active streams
if (THIS->streamsInUse > 0)
{
// Fill the sound buffer
char buffer[THIS->buffersize];
size_t bufferUsed = THIS->FillSoundBuffer(buffer, THIS->buffersize, posPlaying);
// Clean-up "expected"
THIS->expected = posPlaying + THIS->smBytesToSamples(THIS->buffersize);
// Convert if we got anything
if (bufferUsed != 0)
{
UInt32 size = outputSize;
oss = AudioConverterConvertBuffer(THIS->converter, bufferUsed, buffer, &size, outOutputData->mBuffers[0].mData);
outputDataLeft -= size;
outputDataPtr += size;
}
}
else
{
// Since we have no active streams, reset "expected" so it's fresh when we get something
THIS->expected = 0;
}
// Fill any remaining data in buffer with silence
if (outputDataLeft > 0)
{
bzero(outputDataPtr, outputDataLeft);
}
#elif (FEEDER == FEEDER_THREAD)
//
// 1. If we have some in the ring-buffer, use that (one chunk at a time)
//
int samples;
size_t bytes;
char *vrbReadPtr = NULL; // so-s we can do pointer arithmetic if necessary
UInt32 bytesAvailable = THIS->vrb->lengthAvailableToReadReturningPointer((void**) &vrbReadPtr);
int nextSampleToPlay = posPlaying;
while ((bytesAvailable > 0) && (outputDataLeft > 0))
{
// Grab the chunk and skip ahead to fresh data (since we know where the header is, we don't care
VRBChunkHeader *ch = (VRBChunkHeader*) vrbReadPtr;
vrbReadPtr += (sizeof(VRBChunkHeader) + ch->offset);
int vrbSample = ch->sample + THIS->caBytesToSamples(ch->offset);
if (vrbSample > nextSampleToPlay) // sample is _after_ what we're currently loading
{
// Fill gap before with silence
samples = vrbSample - nextSampleToPlay;
bytes = min(THIS->caSamplesToBytes(samples), outputDataLeft);
DEBUG_LOG(LOG_MARKER "RageSound_CA::GetData -- VRB(%d)[fill-gap] (nextSampleToPlay:%d, vrbSample:%d, samples:%d, bytes:%ld, outputDataLeft:%ld)", nowSample, nextSampleToPlay, vrbSample, samples, bytes, outputDataLeft);
bzero(outputDataPtr, bytes);
outputDataPtr += bytes;
outputDataLeft -= bytes;
nextSampleToPlay += samples;
}
else if (vrbSample < nextSampleToPlay) // sample is _behind_ what we're currently loading
{
// Skip forward in the chunk
samples = nextSampleToPlay - vrbSample;
bytes = min(THIS->caSamplesToBytes(samples), ch->length);
DEBUG_LOG(LOG_MARKER "RageSound_CA::GetData -- VRB(%d)[skip-forward] (nextSampleToPlay:%d, vrbSample:%d, samples:%d, bytes:%ld, outputDataLeft:%ld)", nowSample, nextSampleToPlay, vrbSample, samples, bytes, outputDataLeft);
ch->offset += bytes;
ch->length -= bytes;
vrbReadPtr += bytes;
// nextSampleToPlay is already in the right place
}
// Fill with what's left in the chunk
bytes = min(ch->length, outputDataLeft);
if (bytes > 0)
{
DEBUG_LOG(LOG_MARKER "RageSound_CA::GetData -- VRB(%d)[fill-from-chunk] (nextSampleToPlay:%d, bytes:%ld, outputDataLeft:%ld)", nowSample, nextSampleToPlay, bytes, outputDataLeft);
memcpy(outputDataPtr, vrbReadPtr, bytes);
ch->offset += bytes;
ch->length -= bytes;
vrbReadPtr += bytes;
outputDataPtr += bytes;
outputDataLeft -= bytes;
nextSampleToPlay += THIS->caBytesToSamples(bytes);
}
// If our chunk is empty, free it, refetch available & go at it again
if (ch->length == 0)
{
THIS->vrb->didReadLength(ch->offset+sizeof(VRBChunkHeader));
bytesAvailable = THIS->vrb->lengthAvailableToReadReturningPointer((void**) &vrbReadPtr);
}
}
// If we still have room left to fill, no more in VRB, but VRB not reading, try reading directly
// (this will also be the initial condition)
if ((outputDataLeft > 0) && (THIS->nextSampleToWrite == 0) && (THIS->streamsInUse > 0))
{
// Fill a sound-buffer
char buffer[THIS->buffersize];
samples = THIS->caBytesToSamples(outputDataLeft);
bytes = min(THIS->smSamplesToBytes(samples), THIS->buffersize);
size_t bufferUsed = THIS->FillSoundBuffer(buffer, bytes, nextSampleToPlay);
DEBUG_LOG(LOG_MARKER "RageSound_CA::GetData -- Buffer(%d)[fill] (nextSampleToPlay:%d, samples:%d, bytes:%ld) = bufferUsed:%ld", nowSample, nextSampleToPlay, samples, bytes, bufferUsed);
// Convert
if (bufferUsed > 0)
{
UInt32 size = outputDataLeft;
oss = AudioConverterConvertBuffer(THIS->converter, bufferUsed, buffer, &size, outputDataPtr);
DEBUG_LOG(LOG_MARKER "RageSound_CA::GetData -- Buffer(%d)[convert] (bufferUsed:%ld, outputDataLeft:%ld) = sizeConverted:%ld", nowSample, bufferUsed, outputDataLeft, size);
// Clean-up
outputDataPtr += size;
outputDataLeft -= size;
nextSampleToPlay += THIS->caBytesToSamples(size);
}
// Set up next-sample-to-write so VRB can take off (we _will_ be filling the rest of the buffer)
int nextSample = nextSampleToPlay + THIS->caBytesToSamples(outputSize - outputDataLeft);
THIS->nextSampleToWrite = nextSample;
}
// If we still have room left to fill, but have run out of data, we've gotten behind
if (outputDataLeft > 0)
{
// The ring buffer has run dry. This happens normally when we get to the end of the file's data, and
// also often happens after the audio has been started but before the feeder thread has run yet.
// If neither condition is the case, then the filler thread did not keep up for some reason, and we are
// forced to play a dropout.
// Fill it with dead air
bytes = outputDataLeft;
if (bytes != outputSize)
{
DEBUG_LOG(LOG_MARKER "RageSound_CA::GetData -- Ran-Dry(%d)[zero] (bytes:%ld, outputDataLeft:%ld)", nowSample, bytes, outputDataLeft);
}
bzero(outputDataPtr, bytes);
outputDataPtr += bytes;
outputDataLeft -= bytes;
nextSampleToPlay += THIS->caBytesToSamples(bytes);
}
// Bump what's expected
THIS->expected = nextSampleToPlay;
#else
#error "FEEDER must be set to either FEEDER_DIRECT or FEEDER_THREAD"
#endif
return oss;
}
OSStatus RageSound_CA::OverloadListener(AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void* inClientData)
{
LOG->Trace(LOG_MARKER "RageSound_CA::OverloadListener() -- hardware overloaded (channel=%lu)", inChannel);
return 0;
}
int64_t RageSound_CA::ConvertAudioTimeStampToPosition(const AudioTimeStamp *time) const
{
return int64_t(time->mSampleTime - startSampleTime);
RageSound_CA *This = (RageSound_CA *)inClientData;
UInt32 dataPackets = outOutputData->mBuffers[0].mDataByteSize;
dataPackets /= gConverter->GetOutputFormat().mBytesPerPacket;
LOG->Trace("Calling FillComplexBuffer for %i packets.", int(dataPackets));
This->mDecodePos = int64_t(inOutputTime->mSampleTime);
gConverter->FillComplexBuffer(dataPackets, *outOutputData, NULL);
return noErr;
}
+26 -103
View File
@@ -1,117 +1,40 @@
#ifndef RAGE_SOUND_CA
#define RAGE_SOUND_CA
/*
* RageSoundDriver_CA.h
* stepmania
*
* Sound stream pool code based on RageSoundDriver_ALSA9.h/.cpp by Glenn Maynard.
*
* Copyright (c) 2003 by the person(s) listed below. All rights reserved.
* Steve Checkoway
* Charlie Reading
*
*/
#include "RageSoundDriver.h"
#include "RageThreads.h"
#define FEEDER_DIRECT 0
#define FEEDER_THREAD 1
#define FEEDER FEEDER_DIRECT
#define RESYNC_NEVER 0
#define RESYNC_INSTANTANEOUS 1
#define RESYNC_ACCUMULATE 2
#define RESYNC RESYNC_ACCUMULATE
#ifndef RAGE_SOUND_DRIVER_CA_H
#define RAGE_SOUND_DRIVER_CA_H
#include "RageSoundDriver_Generic_Software.h"
struct AudioTimeStamp;
struct AudioBufferList;
typedef unsigned char Boolean;
typedef long OSStatus;
typedef unsigned long UInt32;
typedef UInt32 AudioDeviceID;
typedef UInt32 AudioDevicePropertyID;
typedef UInt32 AudioUnitRenderActionFlags;
typedef double Float64;
struct OpaqueAudioConverter;
typedef struct OpaqueAudioConverter *AudioConverterRef;
struct AudioStreamBasicDescription;
#if (FEEDER == FEEDER_THREAD)
class VirtualRingBuffer;
#endif
typedef long OSStatus;
class CAAudioHardwareDevice;
class RageSoundBase;
class RageSound_CA: public RageSoundDriver
class RageSound_CA : public RageSound_Generic_Software
{
private:
/* Sound stream */
struct stream
{
enum { INACTIVE, PLAYING, STOPPING } state;
RageSoundBase *snd;
int flush_pos;
void clear() { snd=NULL; state=INACTIVE; flush_pos=0; }
stream() { clear(); }
};
friend struct stream;
/* Pool of available streams. */
vector<stream *> stream_pool;
int streamsInUse;
AudioDeviceID outputDevice;
AudioStreamBasicDescription *idealFormat;
AudioStreamBasicDescription *actualFormat;
UInt32 buffersize;
int samplesPerBuffer;
AudioConverterRef converter;
bool shutdown;
float latency;
Float64 startSampleTime;
int expected;
#if (RESYNC != RESYNC_NEVER)
int packetsOff;
int packetsOffSamples;
#endif
#if (FEEDER == FEEDER_THREAD)
size_t vrbChunkSize;
VirtualRingBuffer *vrb;
RageThread feederThread;
int nextSampleToWrite;
static int FeederThread_start(void *p);
void FeederThread();
#endif
int64_t ConvertAudioTimeStampToPosition(const AudioTimeStamp *time) const;
void StartMixing(RageSoundBase *snd);
void Update (float delta);
void StopMixing(RageSoundBase *snd);
int64_t GetPosition(const RageSoundBase *snd) const;
float GetPlayLatency() const;
int GetSampleRate() const;
size_t smSamplesToBytes(int samples);
int smBytesToSamples(size_t bytes);
size_t caSamplesToBytes(int samples);
int caBytesToSamples(size_t bytes);
int64_t mDecodePos;
float mLatency;
CAAudioHardwareDevice *mOutputDevice;
static OSStatus GetData(AudioDeviceID inDevice,
const AudioTimeStamp *inNow,
const AudioBufferList *inInputData,
const AudioTimeStamp *inInputTime,
AudioBufferList *outOutputData,
const AudioTimeStamp *inOutputTime,
void *inClientData);
public:
void FillConverter(void *data, UInt32 dataByteSize);
RageSound_CA();
virtual ~RageSound_CA();
virtual size_t FillSoundBuffer(char* buffer, size_t maxBufferSize, int position);
static OSStatus GetData(AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData);
static OSStatus OverloadListener(AudioDeviceID inDevice, UInt32 inChannel, Boolean isInput, AudioDevicePropertyID inPropertyID, void* inClientData);
~RageSound_CA();
float GetPlayLatency() const { return mLatency; }
int64_t GetPosition(const RageSoundBase *sound) const;
//void VolumeChanged();
};
#endif RAGE_SOUND_CA
#define RAGE_SOUND_CA
#endif