- Add CoreAudio engine with rate estimation and buffer status support. Default engine on Mac OS X.

- 44100 Hz default sample rate on OS X, since OS X tends to resample everything to 44100 Hz.
- Get rid of buffer status averaging in OpenAlEngine, since it makes assumptions on usage pattern that shouldn't be made.
- Extend ringbuffer.h to support resetting size, and move it to common dir since gambatte_qt/coreaudioengine uses it too now.


git-svn-id: https://gambatte.svn.sourceforge.net/svnroot/gambatte@185 9dfb2916-2d38-0410-aef4-c5fe6c9ffc24
This commit is contained in:
sinamas 2008-10-24 02:51:13 +00:00
parent 2638002fd3
commit 8a321f16c5
9 changed files with 381 additions and 29 deletions

View File

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aam<EFBFBD>s *
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
@ -19,20 +19,20 @@
#ifndef RINGBUFFER_H
#define RINGBUFFER_H
#include "array.h"
#include <cstddef>
#include <algorithm>
#include <cstring>
template<typename T>
class RingBuffer {
T *const buf;
const std::size_t sz;
Array<T> buf;
std::size_t sz;
std::size_t rpos;
std::size_t wpos;
public:
RingBuffer(const std::size_t sz_in) : buf(new T[sz_in + 1]), sz(sz_in + 1), rpos(0), wpos(0) {}
~RingBuffer() { delete []buf; }
RingBuffer(const std::size_t sz_in = 0) : sz(0), rpos(0), wpos(0) { reset(sz_in); }
std::size_t avail() const {
return (wpos < rpos ? 0 : sz) + rpos - wpos - 1;
@ -46,6 +46,8 @@ public:
void read(T *out, std::size_t num);
void reset(std::size_t sz_in);
std::size_t size() const {
return sz - 1;
}
@ -59,7 +61,7 @@ public:
template<typename T>
void RingBuffer<T>::fill(const T value) {
std::fill(buf, buf + sz, value);
std::fill(buf + 0, buf + sz, value);
rpos = 0;
wpos = sz - 1;
}
@ -82,6 +84,13 @@ void RingBuffer<T>::read(T *out, std::size_t num) {
rpos = 0;
}
template<typename T>
void RingBuffer<T>::reset(const std::size_t sz_in) {
sz = sz_in + 1;
rpos = wpos = 0;
buf.reset(sz_in ? sz : 0);
}
template<typename T>
void RingBuffer<T>::write(const T *in, std::size_t num) {
if (wpos + num > sz) {

View File

@ -1,5 +1,5 @@
/***************************************************************************
* Copyright (C) 2007 by Sindre Aamås *
* Copyright (C) 2007 by Sindre AamÂs *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
@ -19,7 +19,9 @@
#include "addaudioengines.h"
#include "audioengines/openalengine.h"
#include "audioengines/coreaudioengine.h"
void addAudioEngines(std::vector<AudioEngine*> &audioEngines, WId /*winId*/) {
audioEngines.push_back(new CoreAudioEngine);
audioEngines.push_back(new OpenAlEngine);
}

View File

@ -0,0 +1,285 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* 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 version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "coreaudioengine.h"
#include <cstdio>
#include <cmath>
static int createMutex(pthread_mutex_t* &mutptr) {
mutptr = new pthread_mutex_t;
const int ret = pthread_mutex_init(mutptr, NULL);
if (ret) {
delete mutptr;
mutptr = NULL;
}
return ret;
}
static void destroyMutex(pthread_mutex_t* &mutptr) {
if (mutptr) {
pthread_mutex_lock(mutptr);
pthread_mutex_destroy(mutptr);
delete mutptr;
mutptr = NULL;
}
}
static int createCond(pthread_cond_t* &condptr) {
condptr = new pthread_cond_t;
const int ret = pthread_cond_init(condptr, NULL);
if (ret) {
delete condptr;
condptr = NULL;
}
return ret;
}
static void destroyCond(pthread_cond_t* &condptr) {
if (condptr) {
pthread_cond_destroy(condptr);
delete condptr;
condptr = NULL;
}
}
class MutexLocker {
pthread_mutex_t *const mut;
public:
const int err;
MutexLocker(pthread_mutex_t *mut) : mut(mut), err(pthread_mutex_lock(mut)) {
}
~MutexLocker() {
if (!err)
pthread_mutex_unlock(mut);
}
};
CoreAudioEngine::CoreAudioEngine() : AudioEngine("CoreAudio"), outUnit(0), outUnitState(UNIT_CLOSED), mutex(NULL), availCond(NULL), rateEst(0), rateVar(0), running(false) {
}
CoreAudioEngine::~CoreAudioEngine() {
uninit();
}
unsigned CoreAudioEngine::read(void *const stream, unsigned frames, const Float64 rateScalar) {
MutexLocker mutlock(mutex);
if (!mutlock.err) {
{
const Float64 rateEst = rate() / rateScalar;
rateVar = (rateVar * 15 + std::fabs(rateEst - this->rateEst)) * (1.0 / 16.0);
this->rateEst = rateEst;
}
if (frames > rbuf.used() / 2)
frames = rbuf.used() / 2;
rbuf.read(reinterpret_cast<SInt16*>(stream), frames * 2);
pthread_cond_signal(availCond);
}
return frames;
}
ComponentResult CoreAudioEngine::renderProc(void *inRefCon, AudioUnitRenderActionFlags */*inActionFlags*/, const AudioTimeStamp *inTimeStamp,
UInt32 /*inBusNumber*/, UInt32 inNumFrames, AudioBufferList *ioData)
{
ioData->mBuffers[0].mDataByteSize = reinterpret_cast<CoreAudioEngine*>(inRefCon)->read(ioData->mBuffers[0].mData, inNumFrames, inTimeStamp->mRateScalar) * 4;
return 0;
}
int CoreAudioEngine::doInit(const int rate, const unsigned latency) {
{
Component comp;
{
ComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
if ((comp = FindNextComponent(NULL, &desc)) == NULL) {
std::fprintf(stderr, "Failed to find output unit component\n");
goto fail;
}
}
if (ComponentResult err = OpenAComponent(comp, &outUnit)) {
std::fprintf(stderr, "Failed to open output unit component: %d\n", static_cast<int>(err));
goto fail;
}
outUnitState = UNIT_OPENED;
}
if (ComponentResult err = AudioUnitInitialize(outUnit)) {
std::fprintf(stderr, "Failed to initialize output unit component: %d\n", static_cast<int>(err));
goto fail;
}
outUnitState = UNIT_INITED;
{
AudioStreamBasicDescription desc;
desc.mSampleRate=rate;
desc.mFormatID=kAudioFormatLinearPCM;
desc.mChannelsPerFrame=2;
desc.mBitsPerChannel=16;
desc.mFramesPerPacket = 1;
desc.mBytesPerPacket = desc.mBytesPerFrame = 4;
desc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked;
#ifdef WORDS_BIGENDIAN
desc.mFormatFlags |= kAudioFormatFlagIsBigEndian;
#endif
if (ComponentResult err = AudioUnitSetProperty(outUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desc, sizeof(AudioStreamBasicDescription))) {
std::fprintf(stderr, "Failed to set the input format: %d\n", static_cast<int>(err));
goto fail;
}
}
{
AURenderCallbackStruct renderCallback;
renderCallback.inputProc = renderProc;
renderCallback.inputProcRefCon = this;
if (ComponentResult err = AudioUnitSetProperty(outUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct))) {
std::fprintf(stderr, "Failed to set render callback: %d\n", static_cast<int>(err));
goto fail;
}
}
if (int err = createMutex(mutex)) {
std::fprintf(stderr, "Failed to create mutex: %d\n", err);
goto fail;
}
if (int err = createCond(availCond)) {
std::fprintf(stderr, "Failed to create condition variable: %d\n", err);
goto fail;
}
rbuf.reset(((rate * latency + 500) / 1000) * 2);
rbuf.fill(0);
rateEst = rate;
rateVar = rate >> 12;
return rate;
fail:
uninit();
return -1;
}
void CoreAudioEngine::uninit() {
if (outUnitState >= UNIT_INITED)
AudioUnitUninitialize(outUnit);
if (outUnitState >= UNIT_OPENED)
CloseComponent(outUnit);
destroyMutex(mutex);
destroyCond(availCond);
outUnitState = UNIT_CLOSED;
running = false;
rbuf.reset(0);
}
void CoreAudioEngine::pause() {
if (running) {
AudioOutputUnitStop(outUnit);
running = false;
}
}
int CoreAudioEngine::doWrite(void *const buffer, unsigned samples) {
if (!running) {
if (ComponentResult err = AudioOutputUnitStart(outUnit)) {
std::fprintf(stderr, "Failed to start output unit: %d\n", static_cast<int>(err));
return -1;
}
running = true;
}
SInt16 *inBuf = reinterpret_cast<SInt16*>(buffer);
{
std::size_t avail;
while ((avail = rbuf.avail() / 2) < samples) {
rbuf.write(inBuf, avail * 2);
inBuf += avail * 2;
samples -= avail;
pthread_cond_wait(availCond, mutex);
}
}
rbuf.write(inBuf, samples * 2);
return 0;
}
int CoreAudioEngine::write(void *const buffer, const unsigned samples, BufferState &preBufState_out, RateEst::Result &rate_out) {
MutexLocker mutlock(mutex);
if (mutlock.err)
return -1;
preBufState_out.fromUnderrun = rbuf.used();
preBufState_out.fromOverflow = rbuf.avail();
rate_out.est = rateEst + 0.5;
rate_out.var = rateVar + 0.5;
return doWrite(buffer, samples);
}
int CoreAudioEngine::write(void *const buffer, const unsigned samples) {
MutexLocker mutlock(mutex);
if (mutlock.err)
return -1;
return doWrite(buffer, samples);
}
const AudioEngine::BufferState CoreAudioEngine::bufferState() const {
MutexLocker mutlock(mutex);
const BufferState bstate = { fromUnderrun: rbuf.used(), fromOverflow: rbuf.avail() };
return bstate;
}
const RateEst::Result CoreAudioEngine::rateEstimate() const {
MutexLocker mutlock(mutex);
const RateEst::Result r = { est: rateEst + 0.5, var: rateVar + 0.5 };
return r;
}

View File

@ -0,0 +1,56 @@
/***************************************************************************
* Copyright (C) 2008 by Sindre Aamås *
* aamas@stud.ntnu.no *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License version 2 as *
* published by the Free Software Foundation. *
* *
* 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 version 2 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* version 2 along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef CORE_AUDIO_ENGINE_H
#define CORE_AUDIO_ENGINE_H
#include "../audioengine.h"
#include <ringbuffer.h>
#include <AudioUnit/AudioUnit.h>
#include <pthread.h>
class CoreAudioEngine : public AudioEngine {
enum { UNIT_CLOSED = 0, UNIT_OPENED, UNIT_INITED };
RingBuffer<SInt16> rbuf;
AudioUnit outUnit;
int outUnitState;
pthread_mutex_t *mutex;
pthread_cond_t *availCond;
Float64 rateEst;
Float64 rateVar;
bool running;
static ComponentResult renderProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData);
unsigned read(void *stream, unsigned frames, Float64 rateScalar);
int doInit(int rate, unsigned latency);
int doWrite(void *buffer, unsigned frames);
public:
CoreAudioEngine();
~CoreAudioEngine();
void uninit();
int write(void *buffer, unsigned frames);
int write(void *buffer, unsigned samples, BufferState &preBufState_out, RateEst::Result &rate_out);
const BufferState bufferState() const;
const RateEst::Result rateEstimate() const;
void pause();
};
#endif

View File

@ -116,9 +116,7 @@ device(NULL),
context(NULL),
source(0),
buffers(0),
bufPos(0),
fromUnderrun(0),
rate(0) {
bufPos(0) {
}
OpenAlEngine::~OpenAlEngine() {
@ -179,11 +177,9 @@ int OpenAlEngine::doInit(const int rate, unsigned latency) {
}
failureCleaner.failed = false;
this->rate = rate;
buffers = rate * latency / (BUF_SZ * 1000);
++buffers;
// std::printf("buffers: %u\n", buffers);
fromUnderrun = 0;
buf = new qint16[BUF_SZ * 2];
bufPos = 0;
@ -251,7 +247,7 @@ int OpenAlEngine::write(void *const data, const unsigned samples) {
{
ALuint bufid = 0;
alGenBuffers(1, &bufid);
alBufferData(bufid, AL_FORMAT_STEREO16, buf, BUF_SZ * sizeof(qint16) * 2, rate);
alBufferData(bufid, AL_FORMAT_STEREO16, buf, BUF_SZ * sizeof(qint16) * 2, rate());
alSourceQueueBuffers(source, 1, &bufid);
}
}
@ -275,16 +271,16 @@ int OpenAlEngine::write(void *const data, const unsigned samples) {
const AudioEngine::BufferState OpenAlEngine::bufferState() const {
deleteProcessedBufs();
ALint queued = getSourcei(source, AL_BUFFERS_QUEUED);
// queued -= 1;
queued *= BUF_SZ;
queued += bufPos;
queued -= BUF_SZ >> 1;
unsigned fromUnderrun = 0;
if (queued < 0)
queued = 0;
{
const ALint queued = getSourcei(source, AL_BUFFERS_QUEUED);
fromUnderrun = fromUnderrun + queued >> 1;
if (queued > 0)
fromUnderrun = queued * BUF_SZ - (BUF_SZ >> 1);
}
fromUnderrun += bufPos;
const BufferState s = { fromUnderrun: fromUnderrun, fromOverflow: fromUnderrun > (buffers + 1) * BUF_SZ ? 0 : (buffers + 1) * BUF_SZ - fromUnderrun };

View File

@ -40,8 +40,6 @@ class OpenAlEngine : public AudioEngine {
ALuint source;
unsigned buffers;
unsigned bufPos;
mutable unsigned fromUnderrun;
int rate;
void deleteProcessedBufs() const;
int doInit(int rate, unsigned latency);

View File

@ -68,21 +68,23 @@ QT += opengl
INCLUDEPATH += framework/SDL_Joystick/include
INCLUDEPATH += $$COMMONPATH
DEFINES += HAVE_STDINT_H
QMAKE_CXXFLAGS += -fno-exceptions -fno-rtti
# QMAKE_CXXFLAGS = -g
macx {
HEADERS += $$COMMONPATH/ringbuffer.h
SOURCES += framework/addaudioengines_macx.cpp \
framework/addblitterwidgets.cpp \
framework/getfullmodetoggler_macx.cpp
SOURCES += framework/SDL_Joystick/src/darwin/SDL_sysjoystick.c \
framework/audioengines/openalengine.cpp \
framework/audioengines/coreaudioengine.cpp \
framework/fullmodetogglers/quartztoggler.cpp
HEADERS += framework/audioengines/openalengine.h \
framework/audioengines/coreaudioengine.h \
framework/fullmodetogglers/quartztoggler.h
LIBS += -framework \
IOKit \
-framework \
OpenAL
LIBS += -framework IOKit \
-framework AudioUnit \
-framework OpenAL
}
else:unix {
DEFINES += PLATFORM_UNIX

View File

@ -55,6 +55,10 @@ static SampleRateInfo generateSampleRateInfo() {
srinfo.minCustomRate = 8000;
srinfo.maxCustomRate = 192000;
#ifdef Q_WS_MAC
srinfo.defaultRateIndex = 1;
#endif
return srinfo;
}

View File

@ -19,7 +19,7 @@
#ifndef AUDIODATA_H
#define AUDIODATA_H
#include "ringbuffer.h"
#include <ringbuffer.h>
#include <rateest.h>
#include <SDL.h>