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) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-25 22:27:53 +01:00
1 parent ce27034379
commit e031502d27
3 files changed
+72 -61

No files matched your search

+1 -1
View File
@@ -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})
-1
View File
@@ -756,7 +756,6 @@ if(FFmpeg_FOUND)
FFmpeg::avcodec
FFmpeg::avformat
FFmpeg::avutil
FFmpeg::swresample
FFmpeg::swscale
${ZLIB_LIBRARY}
)
+71 -59
View File
@@ -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.