From ce0ad004e4acb88b3f1a0716caccf5eb1ac743db Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 21 Dec 2014 01:31:31 -0800 Subject: [PATCH] GBA Audio: Better audio resampling via FFmpeg --- CHANGES | 1 + CMakeLists.txt | 2 + src/platform/ffmpeg/ffmpeg-resample.c | 53 +++++++++++++++++++++++++++ src/platform/ffmpeg/ffmpeg-resample.h | 16 ++++++++ src/platform/sdl/sdl-audio.c | 27 ++++++++++++++ src/platform/sdl/sdl-audio.h | 4 ++ 6 files changed, 103 insertions(+) create mode 100644 src/platform/ffmpeg/ffmpeg-resample.c create mode 100644 src/platform/ffmpeg/ffmpeg-resample.h diff --git a/CHANGES b/CHANGES index b7c9a8bbb..c38e55081 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Features: - Support for gamepad axes, e.g. analog sticks or triggers - Add scale presets for up to 6x - Debugger: Add CLI "frame", frame advance command + - Better audio resampling via FFmpeg Bugfixes: - Qt: Fix issue with set frame sizes being the wrong height - Qt: Fix emulator crashing when full screen if a game is not running diff --git a/CMakeLists.txt b/CMakeLists.txt index bd2745fe4..2be284d9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,7 @@ if(USE_FFMPEG) include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS}) link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS}) list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c") + list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-resample.c") string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION}) string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION}) string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) @@ -255,6 +256,7 @@ message(STATUS " Video recording: ${USE_FFMPEG}") message(STATUS " GIF recording: ${USE_MAGICK}") message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}") message(STATUS " ZIP support: ${USE_LIBZIP}") +message(STATUS " Better audio resampling: ${USE_FFMPEG}") message(STATUS "Frontend summary:") message(STATUS " Qt: ${BUILD_QT}") message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}") diff --git a/src/platform/ffmpeg/ffmpeg-resample.c b/src/platform/ffmpeg/ffmpeg-resample.c new file mode 100644 index 000000000..14293e301 --- /dev/null +++ b/src/platform/ffmpeg/ffmpeg-resample.c @@ -0,0 +1,53 @@ +/* Copyright (c) 2013-2014 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ffmpeg-resample.h" + +#include "gba-audio.h" + +#include +#include + +struct AVAudioResampleContext* GBAAudioOpenLAVR(struct GBAAudio* audio, unsigned outputRate) { + AVAudioResampleContext *avr = avresample_alloc_context(); + av_opt_set_int(avr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0); + av_opt_set_int(avr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); + av_opt_set_int(avr, "in_sample_rate", audio->sampleRate, 0); + av_opt_set_int(avr, "out_sample_rate", outputRate, 0); + av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16P, 0); + av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + if (avresample_open(avr)) { + avresample_free(&avr); + return 0; + } + return avr; +} + +unsigned GBAAudioResampleLAVR(struct GBAAudio* audio, struct AVAudioResampleContext* avr, struct GBAStereoSample* output, unsigned nSamples) { + int16_t left[GBA_AUDIO_SAMPLES]; + int16_t right[GBA_AUDIO_SAMPLES]; + int16_t* samples[2] = { left, right }; + + size_t totalRead = 0; + size_t available = avresample_available(avr); + if (available) { + totalRead = avresample_read(avr, (uint8_t**) &output, nSamples); + nSamples -= totalRead; + output += totalRead; + } + while (nSamples) { + unsigned read = GBAAudioCopy(audio, left, right, GBA_AUDIO_SAMPLES); + + size_t currentRead = avresample_convert(avr, (uint8_t**) &output, nSamples * sizeof(struct GBAStereoSample), nSamples, (uint8_t**) samples, sizeof(left), read); + nSamples -= currentRead; + output += currentRead; + totalRead += currentRead; + if (read < GBA_AUDIO_SAMPLES && nSamples) { + memset(output, 0, nSamples * sizeof(struct GBAStereoSample)); + break; + } + } + return totalRead; +} diff --git a/src/platform/ffmpeg/ffmpeg-resample.h b/src/platform/ffmpeg/ffmpeg-resample.h new file mode 100644 index 000000000..ee8970bf8 --- /dev/null +++ b/src/platform/ffmpeg/ffmpeg-resample.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2013-2014 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef FFMPEG_RESAMPLE +#define FFMPEG_RESAMPLE + +struct AVAudioResampleContext; +struct GBAAudio; +struct GBAStereoSample; + +struct AVAudioResampleContext* GBAAudioOpenLAVR(struct GBAAudio* audio, unsigned outputRate); +unsigned GBAAudioResampleLAVR(struct GBAAudio* audio, struct AVAudioResampleContext* avr, struct GBAStereoSample* output, unsigned nSamples); + +#endif diff --git a/src/platform/sdl/sdl-audio.c b/src/platform/sdl/sdl-audio.c index 3fe65d0f4..96daff575 100644 --- a/src/platform/sdl/sdl-audio.c +++ b/src/platform/sdl/sdl-audio.c @@ -8,6 +8,11 @@ #include "gba.h" #include "gba-thread.h" +#ifdef USE_FFMPEG +#include "platform/ffmpeg/ffmpeg-resample.h" +#include +#endif + #define BUFFER_SIZE (GBA_AUDIO_SAMPLES >> 2) static void _GBASDLAudioCallback(void* context, Uint8* data, int len); @@ -24,7 +29,9 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex context->desiredSpec.samples = context->samples; context->desiredSpec.callback = _GBASDLAudioCallback; context->desiredSpec.userdata = context; +#ifndef USE_FFMPEG context->drift = 0.f; +#endif if (SDL_OpenAudio(&context->desiredSpec, &context->obtainedSpec) < 0) { GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system"); return false; @@ -35,6 +42,10 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex threadContext->audioBuffers = context->samples * 2; } +#ifdef USE_FFMPEG + context->avr = 0; +#endif + SDL_PauseAudio(0); return true; } @@ -44,6 +55,9 @@ void GBASDLDeinitAudio(struct GBASDLAudio* context) { SDL_PauseAudio(1); SDL_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); +#ifdef USE_FFMPEG + avresample_free(&context->avr); +#endif } void GBASDLPauseAudio(struct GBASDLAudio* context) { @@ -62,6 +76,7 @@ static void _GBASDLAudioCallback(void* context, Uint8* data, int len) { memset(data, 0, len); return; } +#ifndef USE_FFMPEG audioContext->ratio = GBAAudioCalculateRatio(&audioContext->thread->gba->audio, audioContext->thread->fpsTarget, audioContext->obtainedSpec.freq); if (audioContext->ratio == INFINITY) { memset(data, 0, len); @@ -72,4 +87,16 @@ static void _GBASDLAudioCallback(void* context, Uint8* data, int len) { if (audioContext->obtainedSpec.channels == 2) { GBAAudioResampleNN(&audioContext->thread->gba->audio, audioContext->ratio, &audioContext->drift, ssamples, len); } +#else + if (!audioContext->avr) { + if (!audioContext->thread->gba->audio.sampleRate) { + memset(data, 0, len); + return; + } + audioContext->avr = GBAAudioOpenLAVR(&audioContext->thread->gba->audio, audioContext->obtainedSpec.freq); + } + struct GBAStereoSample* ssamples = (struct GBAStereoSample*) data; + len /= 2 * audioContext->obtainedSpec.channels; + GBAAudioResampleLAVR(&audioContext->thread->gba->audio, audioContext->avr, ssamples, len); +#endif } diff --git a/src/platform/sdl/sdl-audio.h b/src/platform/sdl/sdl-audio.h index 00804da7f..f5be051b9 100644 --- a/src/platform/sdl/sdl-audio.h +++ b/src/platform/sdl/sdl-audio.h @@ -17,8 +17,12 @@ struct GBASDLAudio { // State SDL_AudioSpec desiredSpec; SDL_AudioSpec obtainedSpec; +#ifndef USE_FFMPEG float drift; float ratio; +#else + struct AVAudioResampleContext* avr; +#endif struct GBAThread* thread; };