From e031502d27312ea32e2128ffb64cb2ec29efd392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Oct 2026 22:09:10 +0100 Subject: [PATCH] Audio: Convert FFmpeg's output to s16 ourselves, drop libswresample FFmpegAudioDecoder only used swresample to turn the decoders' output into interleaved stereo s16, at the same sample rate. A small conversion does that now, with swresample's rounding: floats go through lrintf and are clamped, and mono goes to both channels at -3 dB, the coefficient swresample's mixing matrix uses. Its output is sample-identical to swresample's for the MP3, AAC and ATRAC3+ decoders, mono and stereo. With "Use FFMPEG for all compressed audio", ATRAC3 got closed on its first frame: its decoder sets no channel layout, and swresample refused to set up without one. The conversion goes by the channel count, so that no longer fails. The system-FFmpeg build no longer looks for libswresample. Co-Authored-By: Claude Opus 5.5 (1M context) --- CMakeLists.txt | 2 +- Core/CMakeLists.txt | 1 - Core/HW/SimpleAudioDec.cpp | 130 ++++++++++++++++++++----------------- 3 files changed, 72 insertions(+), 61 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cbcec6489..f52568bf5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -610,7 +610,7 @@ if(USING_GLES2 OR (USING_EGL AND NOT USING_FBDEV)) endif() if(USE_FFMPEG AND NOT BUILD_BUNDLED_FFMPEG) - find_package(FFmpeg REQUIRED avcodec avformat avutil swresample swscale) + find_package(FFmpeg REQUIRED avcodec avformat avutil swscale) # Check if we need to use avcodec_(alloc|free)_frame instead of av_frame_(alloc|free) # Check if we need to use const AVCodec set(CMAKE_REQUIRED_INCLUDES ${FFmpeg_INCLUDE_avcodec};${FFmpeg_INCLUDE_avformat}) diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 56423ab636..cba79edcf6 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -756,7 +756,6 @@ if(FFmpeg_FOUND) FFmpeg::avcodec FFmpeg::avformat FFmpeg::avutil - FFmpeg::swresample FFmpeg::swscale ${ZLIB_LIBRARY} ) diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index 95022b2a55..1be14214ac 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -30,7 +30,6 @@ extern "C" { #include "libavformat/avformat.h" -#include "libswresample/swresample.h" #include "libavutil/samplefmt.h" #include "libavcodec/avcodec.h" #include "libavutil/version.h" @@ -44,7 +43,6 @@ extern "C" { extern "C" { struct AVCodec; struct AVCodecContext; - struct SwrContext; struct AVFrame; } @@ -88,7 +86,6 @@ private: AVFrame *frame_ = nullptr; AVCodec *codec_ = nullptr; AVCodecContext *codecCtx_ = nullptr; - SwrContext *swrCtx_ = nullptr; bool codecOpen_ = false; }; @@ -229,7 +226,6 @@ void FFmpegAudioDecoder::SetChannels(int channels) { FFmpegAudioDecoder::~FFmpegAudioDecoder() { #ifdef USE_FFMPEG - swr_free(&swrCtx_); av_frame_free(&frame_); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 52, 0) avcodec_free_context(&codecCtx_); @@ -245,6 +241,67 @@ FFmpegAudioDecoder::~FFmpegAudioDecoder() { } // Decodes a single input frame. +#ifdef USE_FFMPEG +static inline int16_t FloatToS16(float f) { + return av_clip_int16(lrintf(f * 32768.0f)); +} + +// Converts a decoded frame to interleaved stereo s16, rounding and clamping like swresample, which +// this replaces. Mono goes to both channels at -3 dB, as swresample's mixing matrix has it. Beyond +// two channels only the first two are kept (PSP audio never has more). Returns the number of +// samples, or -1 for a sample format not handled here. +static int ConvertToStereoS16(const AVFrame *frame, int channels, int16_t *out) { + const int n = frame->nb_samples; + if (channels < 1) { + return -1; + } + const float monoGain = (float)M_SQRT1_2; + const int monoGainS16 = 23170; // M_SQRT1_2 in 1.15 fixed point. + switch (frame->format) { + case AV_SAMPLE_FMT_FLTP: + case AV_SAMPLE_FMT_FLT: + { + const bool planar = frame->format == AV_SAMPLE_FMT_FLTP; + const float *left = (const float *)frame->extended_data[0]; + const float *right = planar ? (const float *)frame->extended_data[std::min(channels, 2) - 1] : left + 1; + const int step = planar ? 1 : channels; + for (int i = 0; i < n; i++) { + if (channels == 1) { + const int16_t s = FloatToS16(left[i] * monoGain); + out[i * 2] = s; + out[i * 2 + 1] = s; + } else { + out[i * 2] = FloatToS16(left[i * step]); + out[i * 2 + 1] = FloatToS16(right[i * step]); + } + } + return n; + } + case AV_SAMPLE_FMT_S16P: + case AV_SAMPLE_FMT_S16: + { + const bool planar = frame->format == AV_SAMPLE_FMT_S16P; + const int16_t *left = (const int16_t *)frame->extended_data[0]; + const int16_t *right = planar ? (const int16_t *)frame->extended_data[std::min(channels, 2) - 1] : left + 1; + const int step = planar ? 1 : channels; + for (int i = 0; i < n; i++) { + if (channels == 1) { + const int16_t s = (int16_t)((left[i] * monoGainS16 + 16384) >> 15); + out[i * 2] = s; + out[i * 2 + 1] = s; + } else { + out[i * 2] = left[i * step]; + out[i * 2 + 1] = right[i * step]; + } + } + return n; + } + default: + return -1; + } +} +#endif // USE_FFMPEG + bool FFmpegAudioDecoder::Decode(const uint8_t *inbuf, int inbytes, int *inbytesConsumed, int outputChannels, int16_t *outbuf, int *outSamples) { #ifdef USE_FFMPEG if (!codecOpen_) { @@ -305,68 +362,23 @@ bool FFmpegAudioDecoder::Decode(const uint8_t *inbuf, int inbytes, int *inbytesC } if (got_frame) { - // Initializing the sample rate convert. We will use it to convert float output into int. _dbg_assert_(outputChannels == 2); -#if LIBAVUTIL_VERSION_MAJOR >= 59 - AVChannelLayout wanted_channel_layout = AV_CHANNEL_LAYOUT_STEREO; // we want stereo output layout - const AVChannelLayout& dec_channel_layout = frame_->ch_layout; // decoded channel layout -#else - int64_t wanted_channel_layout = AV_CH_LAYOUT_STEREO; // we want stereo output layout - int64_t dec_channel_layout = frame_->channel_layout; // decoded channel layout -#endif - - if (!swrCtx_) { - // TODO: Allow these to differ. - const int inputSampleRate = codecCtx_->sample_rate; - const int outputSampleRate = codecCtx_->sample_rate; -#if LIBAVUTIL_VERSION_MAJOR >= 59 - swr_alloc_set_opts2( - &swrCtx_, - &wanted_channel_layout, - AV_SAMPLE_FMT_S16, - outputSampleRate, - &dec_channel_layout, - codecCtx_->sample_fmt, - inputSampleRate, - 0, - NULL); -#else - swrCtx_ = swr_alloc_set_opts( - swrCtx_, - wanted_channel_layout, - AV_SAMPLE_FMT_S16, - outputSampleRate, - dec_channel_layout, - codecCtx_->sample_fmt, - inputSampleRate, - 0, - NULL); -#endif - - if (!swrCtx_ || swr_init(swrCtx_) < 0) { - ERROR_LOG(Log::ME, "swr_init: Failed to initialize the resampling context"); -#if LIBAVCODEC_VERSION_MAJOR >= 62 - avcodec_free_context(&codecCtx_); -#else - avcodec_close(codecCtx_); -#endif - codec_ = 0; - return false; - } - } - - // convert audio to AV_SAMPLE_FMT_S16 - int swrRet = 0; + int samples = 0; if (outbuf != nullptr) { - swrRet = swr_convert(swrCtx_, (uint8_t **)&outbuf, frame_->nb_samples, (const u8 **)frame_->extended_data, frame_->nb_samples); +#if LIBAVUTIL_VERSION_MAJOR >= 59 + const int channels = frame_->ch_layout.nb_channels; +#else + const int channels = frame_->channels; +#endif + samples = ConvertToStereoS16(frame_, channels, outbuf); } - if (swrRet < 0) { - ERROR_LOG(Log::ME, "swr_convert: Error while converting: %d", swrRet); + if (samples < 0) { + ERROR_LOG(Log::ME, "Can't convert decoded audio (format %d) to s16", frame_->format); return false; } // output stereo samples per frame if (outSamples) { - *outSamples = swrRet; + *outSamples = samples; } // Save outbuf into pcm audio, you can uncomment this line to save and check the decoded audio into pcm file.