all repos — mgba @ ce0ad004e4acb88b3f1a0716caccf5eb1ac743db

mGBA Game Boy Advance Emulator

GBA Audio: Better audio resampling via FFmpeg
Jeffrey Pfau jeffrey@endrift.com
Sun, 21 Dec 2014 01:31:31 -0800
commit

ce0ad004e4acb88b3f1a0716caccf5eb1ac743db

parent

660ac6a6beb4ada2ba25b3a7870d7d640fb24ce7

M CHANGESCHANGES

@@ -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
M CMakeLists.txtCMakeLists.txt

@@ -156,6 +156,7 @@ endif()

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}")
A 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 <libavresample/avresample.h> +#include <libavutil/opt.h> + +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; +}
A 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
M src/platform/sdl/sdl-audio.csrc/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 <libavresample/avresample.h> +#endif + #define BUFFER_SIZE (GBA_AUDIO_SAMPLES >> 2) static void _GBASDLAudioCallback(void* context, Uint8* data, int len);

@@ -24,7 +29,9 @@ context->desiredSpec.channels = 2;

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 @@ if (context->samples > threadContext->audioBuffers) {

threadContext->audioBuffers = context->samples * 2; } +#ifdef USE_FFMPEG + context->avr = 0; +#endif + SDL_PauseAudio(0); return true; }

@@ -44,6 +55,9 @@ UNUSED(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 @@ if (!context || !audioContext->thread || !audioContext->thread->gba) {

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 @@ len /= 2 * audioContext->obtainedSpec.channels;

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 }
M src/platform/sdl/sdl-audio.hsrc/platform/sdl/sdl-audio.h

@@ -17,8 +17,12 @@

// State SDL_AudioSpec desiredSpec; SDL_AudioSpec obtainedSpec; +#ifndef USE_FFMPEG float drift; float ratio; +#else + struct AVAudioResampleContext* avr; +#endif struct GBAThread* thread; };