mirror of https://github.com/mgba-emu/mgba.git
GBA Audio: Better audio resampling via FFmpeg
This commit is contained in:
parent
660ac6a6be
commit
ce0ad004e4
1
CHANGES
1
CHANGES
|
@ -3,6 +3,7 @@ Features:
|
||||||
- Support for gamepad axes, e.g. analog sticks or triggers
|
- Support for gamepad axes, e.g. analog sticks or triggers
|
||||||
- Add scale presets for up to 6x
|
- Add scale presets for up to 6x
|
||||||
- Debugger: Add CLI "frame", frame advance command
|
- Debugger: Add CLI "frame", frame advance command
|
||||||
|
- Better audio resampling via FFmpeg
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
- Qt: Fix issue with set frame sizes being the wrong height
|
- Qt: Fix issue with set frame sizes being the wrong height
|
||||||
- Qt: Fix emulator crashing when full screen if a game is not running
|
- Qt: Fix emulator crashing when full screen if a game is not running
|
||||||
|
|
|
@ -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})
|
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})
|
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-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]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION})
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_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 " GIF recording: ${USE_MAGICK}")
|
||||||
message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}")
|
message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}")
|
||||||
message(STATUS " ZIP support: ${USE_LIBZIP}")
|
message(STATUS " ZIP support: ${USE_LIBZIP}")
|
||||||
|
message(STATUS " Better audio resampling: ${USE_FFMPEG}")
|
||||||
message(STATUS "Frontend summary:")
|
message(STATUS "Frontend summary:")
|
||||||
message(STATUS " Qt: ${BUILD_QT}")
|
message(STATUS " Qt: ${BUILD_QT}")
|
||||||
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -8,6 +8,11 @@
|
||||||
#include "gba.h"
|
#include "gba.h"
|
||||||
#include "gba-thread.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)
|
#define BUFFER_SIZE (GBA_AUDIO_SAMPLES >> 2)
|
||||||
|
|
||||||
static void _GBASDLAudioCallback(void* context, Uint8* data, int len);
|
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.samples = context->samples;
|
||||||
context->desiredSpec.callback = _GBASDLAudioCallback;
|
context->desiredSpec.callback = _GBASDLAudioCallback;
|
||||||
context->desiredSpec.userdata = context;
|
context->desiredSpec.userdata = context;
|
||||||
|
#ifndef USE_FFMPEG
|
||||||
context->drift = 0.f;
|
context->drift = 0.f;
|
||||||
|
#endif
|
||||||
if (SDL_OpenAudio(&context->desiredSpec, &context->obtainedSpec) < 0) {
|
if (SDL_OpenAudio(&context->desiredSpec, &context->obtainedSpec) < 0) {
|
||||||
GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system");
|
GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system");
|
||||||
return false;
|
return false;
|
||||||
|
@ -35,6 +42,10 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex
|
||||||
threadContext->audioBuffers = context->samples * 2;
|
threadContext->audioBuffers = context->samples * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
context->avr = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
SDL_PauseAudio(0);
|
SDL_PauseAudio(0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -44,6 +55,9 @@ void GBASDLDeinitAudio(struct GBASDLAudio* context) {
|
||||||
SDL_PauseAudio(1);
|
SDL_PauseAudio(1);
|
||||||
SDL_CloseAudio();
|
SDL_CloseAudio();
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
avresample_free(&context->avr);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBASDLPauseAudio(struct GBASDLAudio* context) {
|
void GBASDLPauseAudio(struct GBASDLAudio* context) {
|
||||||
|
@ -62,6 +76,7 @@ static void _GBASDLAudioCallback(void* context, Uint8* data, int len) {
|
||||||
memset(data, 0, len);
|
memset(data, 0, len);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#ifndef USE_FFMPEG
|
||||||
audioContext->ratio = GBAAudioCalculateRatio(&audioContext->thread->gba->audio, audioContext->thread->fpsTarget, audioContext->obtainedSpec.freq);
|
audioContext->ratio = GBAAudioCalculateRatio(&audioContext->thread->gba->audio, audioContext->thread->fpsTarget, audioContext->obtainedSpec.freq);
|
||||||
if (audioContext->ratio == INFINITY) {
|
if (audioContext->ratio == INFINITY) {
|
||||||
memset(data, 0, len);
|
memset(data, 0, len);
|
||||||
|
@ -72,4 +87,16 @@ static void _GBASDLAudioCallback(void* context, Uint8* data, int len) {
|
||||||
if (audioContext->obtainedSpec.channels == 2) {
|
if (audioContext->obtainedSpec.channels == 2) {
|
||||||
GBAAudioResampleNN(&audioContext->thread->gba->audio, audioContext->ratio, &audioContext->drift, ssamples, len);
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,12 @@ struct GBASDLAudio {
|
||||||
// State
|
// State
|
||||||
SDL_AudioSpec desiredSpec;
|
SDL_AudioSpec desiredSpec;
|
||||||
SDL_AudioSpec obtainedSpec;
|
SDL_AudioSpec obtainedSpec;
|
||||||
|
#ifndef USE_FFMPEG
|
||||||
float drift;
|
float drift;
|
||||||
float ratio;
|
float ratio;
|
||||||
|
#else
|
||||||
|
struct AVAudioResampleContext* avr;
|
||||||
|
#endif
|
||||||
|
|
||||||
struct GBAThread* thread;
|
struct GBAThread* thread;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue