SDL, Qt: Configurable audio sample rate

This commit is contained in:
Jeffrey Pfau 2015-08-09 21:36:43 -07:00
parent 805e0b17eb
commit 9c5852e89e
16 changed files with 91 additions and 22 deletions

View File

@ -31,6 +31,7 @@ Features:
- Implement BIOS call Stop, for sleep mode - Implement BIOS call Stop, for sleep mode
- Automatically load patches, if found - Automatically load patches, if found
- Improved video synchronization - Improved video synchronization
- Configurable audio output sample rate
Bugfixes: Bugfixes:
- ARM7: Fix SWI and IRQ timings - ARM7: Fix SWI and IRQ timings
- GBA Audio: Force audio FIFOs to 32-bit - GBA Audio: Force audio FIFOs to 32-bit

View File

@ -247,6 +247,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) { if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) {
opts->audioBuffers = audioBuffers; opts->audioBuffers = audioBuffers;
} }
_lookupUIntValue(config, "sampleRate", &opts->sampleRate);
int fakeBool; int fakeBool;
if (_lookupIntValue(config, "useBios", &fakeBool)) { if (_lookupIntValue(config, "useBios", &fakeBool)) {
@ -305,6 +306,7 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval);
ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget); ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);
ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers); ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers);
ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate);
ConfigurationSetIntValue(&config->defaultsTable, 0, "audioSync", opts->audioSync); ConfigurationSetIntValue(&config->defaultsTable, 0, "audioSync", opts->audioSync);
ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync); ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync);
ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen); ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen);

View File

@ -29,6 +29,7 @@ struct GBAOptions {
int rewindBufferInterval; int rewindBufferInterval;
float fpsTarget; float fpsTarget;
size_t audioBuffers; size_t audioBuffers;
unsigned sampleRate;
int fullscreen; int fullscreen;
int width; int width;

View File

@ -31,6 +31,7 @@ public:
virtual void setInput(GBAThread* input); virtual void setInput(GBAThread* input);
int getBufferSamples() const { return m_samples; } int getBufferSamples() const { return m_samples; }
virtual unsigned sampleRate() const = 0;
public slots: public slots:
virtual void start() = 0; virtual void start() = 0;
@ -39,7 +40,7 @@ public slots:
virtual void setBufferSamples(int samples) = 0; virtual void setBufferSamples(int samples) = 0;
virtual void inputParametersChanged() = 0; virtual void inputParametersChanged() = 0;
virtual unsigned sampleRate() const = 0; virtual void requestSampleRate(unsigned) = 0;
protected: protected:
GBAThread* input() { return m_context; } GBAThread* input() { return m_context; }

View File

@ -20,6 +20,7 @@ AudioProcessorQt::AudioProcessorQt(QObject* parent)
: AudioProcessor(parent) : AudioProcessor(parent)
, m_audioOutput(nullptr) , m_audioOutput(nullptr)
, m_device(nullptr) , m_device(nullptr)
, m_sampleRate(44100)
{ {
} }
@ -45,7 +46,7 @@ void AudioProcessorQt::start() {
if (!m_audioOutput) { if (!m_audioOutput) {
QAudioFormat format; QAudioFormat format;
format.setSampleRate(44100); format.setSampleRate(m_sampleRate);
format.setChannelCount(2); format.setChannelCount(2);
format.setSampleSize(16); format.setSampleSize(16);
format.setCodec("audio/pcm"); format.setCodec("audio/pcm");
@ -84,6 +85,15 @@ void AudioProcessorQt::inputParametersChanged() {
} }
} }
void AudioProcessorQt::requestSampleRate(unsigned rate) {
m_sampleRate = rate;
if (m_device) {
QAudioFormat format(m_audioOutput->format());
format.setSampleRate(rate);
m_device->setFormat(format);
}
}
unsigned AudioProcessorQt::sampleRate() const { unsigned AudioProcessorQt::sampleRate() const {
if (!m_audioOutput) { if (!m_audioOutput) {
return 0; return 0;

View File

@ -20,6 +20,7 @@ public:
AudioProcessorQt(QObject* parent = nullptr); AudioProcessorQt(QObject* parent = nullptr);
virtual void setInput(GBAThread* input); virtual void setInput(GBAThread* input);
virtual unsigned sampleRate() const override;
public slots: public slots:
virtual void start(); virtual void start();
@ -28,11 +29,12 @@ public slots:
virtual void setBufferSamples(int samples); virtual void setBufferSamples(int samples);
virtual void inputParametersChanged(); virtual void inputParametersChanged();
virtual unsigned sampleRate() const override; virtual void requestSampleRate(unsigned) override;
private: private:
QAudioOutput* m_audioOutput; QAudioOutput* m_audioOutput;
AudioDevice* m_device; AudioDevice* m_device;
unsigned m_sampleRate;
}; };
} }

View File

@ -15,7 +15,7 @@ using namespace QGBA;
AudioProcessorSDL::AudioProcessorSDL(QObject* parent) AudioProcessorSDL::AudioProcessorSDL(QObject* parent)
: AudioProcessor(parent) : AudioProcessor(parent)
, m_audio() , m_audio{ 2048, 44100 }
{ {
} }
@ -55,6 +55,18 @@ void AudioProcessorSDL::setBufferSamples(int samples) {
void AudioProcessorSDL::inputParametersChanged() { void AudioProcessorSDL::inputParametersChanged() {
} }
unsigned AudioProcessorSDL::sampleRate() const { void AudioProcessorSDL::requestSampleRate(unsigned rate) {
return m_audio.obtainedSpec.freq; m_audio.sampleRate = rate;
if (m_audio.thread) {
GBASDLDeinitAudio(&m_audio);
GBASDLInitAudio(&m_audio, input());
}
}
unsigned AudioProcessorSDL::sampleRate() const {
if (m_audio.thread) {
return m_audio.obtainedSpec.freq;
} else {
return 0;
}
} }

View File

@ -22,6 +22,8 @@ public:
AudioProcessorSDL(QObject* parent = nullptr); AudioProcessorSDL(QObject* parent = nullptr);
~AudioProcessorSDL(); ~AudioProcessorSDL();
virtual unsigned sampleRate() const override;
public slots: public slots:
virtual void start(); virtual void start();
virtual void pause(); virtual void pause();
@ -29,7 +31,7 @@ public slots:
virtual void setBufferSamples(int samples); virtual void setBufferSamples(int samples);
virtual void inputParametersChanged(); virtual void inputParametersChanged();
virtual unsigned sampleRate() const override; virtual void requestSampleRate(unsigned) override;
private: private:
GBASDLAudio m_audio; GBASDLAudio m_audio;

View File

@ -53,6 +53,7 @@ GBAApp::GBAApp(int& argc, char* argv[])
return; return;
} }
AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController.getQtOption("audioDriver").toInt()));
Window* w = new Window(&m_configController); Window* w = new Window(&m_configController);
connect(w, &Window::destroyed, [this]() { connect(w, &Window::destroyed, [this]() {
m_windows[0] = nullptr; m_windows[0] = nullptr;
@ -67,9 +68,6 @@ GBAApp::GBAApp(int& argc, char* argv[])
freeArguments(&args); freeArguments(&args);
w->show(); w->show();
AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController.getQtOption("audioDriver").toInt()));
w->controller()->reloadAudioDriver();
w->controller()->setMultiplayerController(&m_multiplayer); w->controller()->setMultiplayerController(&m_multiplayer);
} }

View File

@ -87,7 +87,9 @@ GameController::GameController(QObject* parent)
m_threadContext.startCallback = [](GBAThread* context) { m_threadContext.startCallback = [](GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData); GameController* controller = static_cast<GameController*>(context->userData);
controller->m_audioProcessor->setInput(context); if (controller->m_audioProcessor) {
controller->m_audioProcessor->setInput(context);
}
context->gba->luminanceSource = &controller->m_lux; context->gba->luminanceSource = &controller->m_lux;
GBARTCGenericSourceInit(&controller->m_rtc, context->gba); GBARTCGenericSourceInit(&controller->m_rtc, context->gba);
context->gba->rtcSource = &controller->m_rtc.d; context->gba->rtcSource = &controller->m_rtc.d;
@ -588,10 +590,21 @@ void GameController::clearKeys() {
} }
void GameController::setAudioBufferSamples(int samples) { void GameController::setAudioBufferSamples(int samples) {
threadInterrupt(); if (m_audioProcessor) {
redoSamples(samples); threadInterrupt();
threadContinue(); redoSamples(samples);
QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples)); threadContinue();
QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples));
}
}
void GameController::setAudioSampleRate(unsigned rate) {
if (m_audioProcessor) {
threadInterrupt();
redoSamples(m_audioProcessor->getBufferSamples());
threadContinue();
QMetaObject::invokeMethod(m_audioProcessor, "requestSampleRate", Q_ARG(unsigned, rate));
}
} }
void GameController::setAudioChannelEnabled(int channel, bool enable) { void GameController::setAudioChannelEnabled(int channel, bool enable) {
@ -644,7 +657,9 @@ void GameController::setFPSTarget(float fps) {
if (m_turbo && m_turboSpeed > 0) { if (m_turbo && m_turboSpeed > 0) {
m_threadContext.fpsTarget *= m_turboSpeed; m_threadContext.fpsTarget *= m_turboSpeed;
} }
redoSamples(m_audioProcessor->getBufferSamples()); if (m_audioProcessor) {
redoSamples(m_audioProcessor->getBufferSamples());
}
threadContinue(); threadContinue();
} }
@ -801,7 +816,9 @@ void GameController::enableTurbo() {
m_threadContext.sync.audioWait = true; m_threadContext.sync.audioWait = true;
m_threadContext.sync.videoFrameWait = false; m_threadContext.sync.videoFrameWait = false;
} }
redoSamples(m_audioProcessor->getBufferSamples()); if (m_audioProcessor) {
redoSamples(m_audioProcessor->getBufferSamples());
}
threadContinue(); threadContinue();
} }
@ -830,11 +847,21 @@ void GameController::screenshot() {
#endif #endif
void GameController::reloadAudioDriver() { void GameController::reloadAudioDriver() {
QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); int samples = 0;
int samples = m_audioProcessor->getBufferSamples(); unsigned sampleRate = 0;
delete m_audioProcessor; if (m_audioProcessor) {
QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection);
samples = m_audioProcessor->getBufferSamples();
sampleRate = m_audioProcessor->sampleRate();
delete m_audioProcessor;
}
m_audioProcessor = AudioProcessor::create(); m_audioProcessor = AudioProcessor::create();
m_audioProcessor->setBufferSamples(samples); if (samples) {
m_audioProcessor->setBufferSamples(samples);
}
if (sampleRate) {
m_audioProcessor->requestSampleRate(sampleRate);
}
m_audioProcessor->moveToThread(m_audioThread); m_audioProcessor->moveToThread(m_audioThread);
connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start())); connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start()));
connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause())); connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause()));

View File

@ -119,6 +119,7 @@ public slots:
void keyReleased(int key); void keyReleased(int key);
void clearKeys(); void clearKeys();
void setAudioBufferSamples(int samples); void setAudioBufferSamples(int samples);
void setAudioSampleRate(unsigned rate);
void setAudioChannelEnabled(int channel, bool enable = true); void setAudioChannelEnabled(int channel, bool enable = true);
void setVideoLayerEnabled(int layer, bool enable = true); void setVideoLayerEnabled(int layer, bool enable = true);
void setFPSTarget(float fps); void setFPSTarget(float fps);

View File

@ -127,6 +127,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame())); connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame()));
connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide())); connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide()));
connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int))); connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int)));
connect(this, SIGNAL(sampleRateChanged(unsigned)), m_controller, SLOT(setAudioSampleRate(unsigned)));
connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float)));
connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS()));
connect(m_display, &Display::hideCursor, [this]() { connect(m_display, &Display::hideCursor, [this]() {
@ -195,6 +196,7 @@ void Window::loadConfig() {
m_controller->loadBIOS(opts->bios); m_controller->loadBIOS(opts->bios);
} }
// TODO: Move these to ConfigController
if (opts->fpsTarget) { if (opts->fpsTarget) {
emit fpsTargetChanged(opts->fpsTarget); emit fpsTargetChanged(opts->fpsTarget);
} }
@ -203,6 +205,10 @@ void Window::loadConfig() {
emit audioBufferSamplesChanged(opts->audioBuffers); emit audioBufferSamplesChanged(opts->audioBuffers);
} }
if (opts->sampleRate) {
emit sampleRateChanged(opts->sampleRate);
}
if (opts->width && opts->height) { if (opts->width && opts->height) {
resizeFrame(opts->width, opts->height); resizeFrame(opts->width, opts->height);
} }

View File

@ -54,6 +54,7 @@ signals:
void startDrawing(GBAThread*); void startDrawing(GBAThread*);
void shutdown(); void shutdown();
void audioBufferSamplesChanged(int samples); void audioBufferSamplesChanged(int samples);
void sampleRateChanged(unsigned samples);
void fpsTargetChanged(float target); void fpsTargetChanged(float target);
public slots: public slots:

View File

@ -112,6 +112,10 @@ int main(int argc, char** argv) {
bool didFail = false; bool didFail = false;
renderer.audio.samples = context.audioBuffers; renderer.audio.samples = context.audioBuffers;
renderer.audio.sampleRate = 44100;
if (opts.sampleRate) {
renderer.audio.sampleRate = opts.sampleRate;
}
if (!GBASDLInitAudio(&renderer.audio, &context)) { if (!GBASDLInitAudio(&renderer.audio, &context)) {
didFail = true; didFail = true;
} }

View File

@ -22,7 +22,7 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex
return false; return false;
} }
context->desiredSpec.freq = 44100; context->desiredSpec.freq = context->sampleRate;
context->desiredSpec.format = AUDIO_S16SYS; context->desiredSpec.format = AUDIO_S16SYS;
context->desiredSpec.channels = 2; context->desiredSpec.channels = 2;
context->desiredSpec.samples = context->samples; context->desiredSpec.samples = context->samples;

View File

@ -15,6 +15,7 @@
struct GBASDLAudio { struct GBASDLAudio {
// Input // Input
size_t samples; size_t samples;
unsigned sampleRate;
// State // State
SDL_AudioSpec desiredSpec; SDL_AudioSpec desiredSpec;