From 8a321f16c5cb95d681caf684599418f96460b988 Mon Sep 17 00:00:00 2001 From: sinamas Date: Fri, 24 Oct 2008 02:51:13 +0000 Subject: [PATCH] - 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 --- {gambatte_sdl/src => common}/ringbuffer.h | 21 +- .../src/framework/addaudioengines_macx.cpp | 4 +- .../audioengines/coreaudioengine.cpp | 285 ++++++++++++++++++ .../framework/audioengines/coreaudioengine.h | 56 ++++ .../framework/audioengines/openalengine.cpp | 24 +- .../src/framework/audioengines/openalengine.h | 2 - gambatte_qt/src/framework/framework.pro | 12 +- gambatte_qt/src/framework/sounddialog.cpp | 4 + gambatte_sdl/src/audiodata.h | 2 +- 9 files changed, 381 insertions(+), 29 deletions(-) rename {gambatte_sdl/src => common}/ringbuffer.h (85%) create mode 100644 gambatte_qt/src/framework/audioengines/coreaudioengine.cpp create mode 100644 gambatte_qt/src/framework/audioengines/coreaudioengine.h diff --git a/gambatte_sdl/src/ringbuffer.h b/common/ringbuffer.h similarity index 85% rename from gambatte_sdl/src/ringbuffer.h rename to common/ringbuffer.h index 31a1862..34f22bf 100644 --- a/gambatte_sdl/src/ringbuffer.h +++ b/common/ringbuffer.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2008 by Sindre Aam�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 #include #include template class RingBuffer { - T *const buf; - const std::size_t sz; + Array 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 void RingBuffer::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::read(T *out, std::size_t num) { rpos = 0; } +template +void RingBuffer::reset(const std::size_t sz_in) { + sz = sz_in + 1; + rpos = wpos = 0; + buf.reset(sz_in ? sz : 0); +} + template void RingBuffer::write(const T *in, std::size_t num) { if (wpos + num > sz) { diff --git a/gambatte_qt/src/framework/addaudioengines_macx.cpp b/gambatte_qt/src/framework/addaudioengines_macx.cpp index 950a243..dd36dc2 100644 --- a/gambatte_qt/src/framework/addaudioengines_macx.cpp +++ b/gambatte_qt/src/framework/addaudioengines_macx.cpp @@ -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 &audioEngines, WId /*winId*/) { + audioEngines.push_back(new CoreAudioEngine); audioEngines.push_back(new OpenAlEngine); } diff --git a/gambatte_qt/src/framework/audioengines/coreaudioengine.cpp b/gambatte_qt/src/framework/audioengines/coreaudioengine.cpp new file mode 100644 index 0000000..a96c856 --- /dev/null +++ b/gambatte_qt/src/framework/audioengines/coreaudioengine.cpp @@ -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 +#include + +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(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(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(err)); + goto fail; + } + + outUnitState = UNIT_OPENED; + } + + if (ComponentResult err = AudioUnitInitialize(outUnit)) { + std::fprintf(stderr, "Failed to initialize output unit component: %d\n", static_cast(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(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(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(err)); + return -1; + } + + running = true; + } + + SInt16 *inBuf = reinterpret_cast(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; +} diff --git a/gambatte_qt/src/framework/audioengines/coreaudioengine.h b/gambatte_qt/src/framework/audioengines/coreaudioengine.h new file mode 100644 index 0000000..e6fd054 --- /dev/null +++ b/gambatte_qt/src/framework/audioengines/coreaudioengine.h @@ -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 +#include +#include + +class CoreAudioEngine : public AudioEngine { + enum { UNIT_CLOSED = 0, UNIT_OPENED, UNIT_INITED }; + + RingBuffer 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 diff --git a/gambatte_qt/src/framework/audioengines/openalengine.cpp b/gambatte_qt/src/framework/audioengines/openalengine.cpp index 5816887..b97c449 100644 --- a/gambatte_qt/src/framework/audioengines/openalengine.cpp +++ b/gambatte_qt/src/framework/audioengines/openalengine.cpp @@ -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 }; diff --git a/gambatte_qt/src/framework/audioengines/openalengine.h b/gambatte_qt/src/framework/audioengines/openalengine.h index edb7005..8c2294d 100644 --- a/gambatte_qt/src/framework/audioengines/openalengine.h +++ b/gambatte_qt/src/framework/audioengines/openalengine.h @@ -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); diff --git a/gambatte_qt/src/framework/framework.pro b/gambatte_qt/src/framework/framework.pro index 917adb6..a46e617 100644 --- a/gambatte_qt/src/framework/framework.pro +++ b/gambatte_qt/src/framework/framework.pro @@ -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 diff --git a/gambatte_qt/src/framework/sounddialog.cpp b/gambatte_qt/src/framework/sounddialog.cpp index 8939deb..fee7c6a 100644 --- a/gambatte_qt/src/framework/sounddialog.cpp +++ b/gambatte_qt/src/framework/sounddialog.cpp @@ -55,6 +55,10 @@ static SampleRateInfo generateSampleRateInfo() { srinfo.minCustomRate = 8000; srinfo.maxCustomRate = 192000; +#ifdef Q_WS_MAC + srinfo.defaultRateIndex = 1; +#endif + return srinfo; } diff --git a/gambatte_sdl/src/audiodata.h b/gambatte_sdl/src/audiodata.h index 9cef195..11eff55 100644 --- a/gambatte_sdl/src/audiodata.h +++ b/gambatte_sdl/src/audiodata.h @@ -19,7 +19,7 @@ #ifndef AUDIODATA_H #define AUDIODATA_H -#include "ringbuffer.h" +#include #include #include