mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-10-11 22:49:55 +00:00
media_utils: Flush the audio decoder and resampler at the end of a track
The resampler buffers part of its input whenever the file's sample rate differs from the 48 kHz output, because the output buffer was only sized for the frame's own samples. These samples piled up and were never flushed, so a 44.1 kHz track came out 8.125% short (5-20 s cut from the end of every song). The decoder itself was never drained either. - Size the output for the pending resampler samples too and only append what swr_convert actually wrote. - Flush the decoder and the resampler once the last packet has been read. - Reserve the whole track up front, reuse the resampler output buffer and byteswap in place before appending, instead of growing the track buffer and allocating a buffer for every frame. Fixes #18930 (custom music cut in Gran Turismo 5). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
committed by
Megamouse
co-authored by
Claude Opus 5.5
parent
55a3aff337
commit
925fbb2607
+136
-43
@@ -18,6 +18,7 @@ extern "C" {
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libswresample/swresample.h"
|
||||
}
|
||||
@@ -667,13 +668,116 @@ namespace utils
|
||||
|
||||
std::unique_ptr<AVPacket, decltype(free_packet)> packet_(packet);
|
||||
bool is_first_error = true;
|
||||
s64 last_timestamp_ms = 0;
|
||||
|
||||
// Iterate through frames
|
||||
while (thread_ctrl::state() != thread_state::aborting && av_read_frame(av.format_context, packet) >= 0)
|
||||
const int in_rate = stream->codecpar->sample_rate;
|
||||
const AVRational time_base = stream->time_base;
|
||||
const int bytes_per_sample = dst_channels * static_cast<int>(sizeof(f32));
|
||||
|
||||
// A whole track ends up in our data, so reserve it in one go instead of letting it grow piecewise.
|
||||
if (const s64 duration = av.format_context->duration; duration > 0)
|
||||
{
|
||||
if (int err = avcodec_send_packet(av.audio.context, packet); err < 0)
|
||||
constexpr u64 max_reserve = 256 * 1024 * 1024; // Don't trust the header blindly
|
||||
const u64 expected_size = static_cast<u64>(av_rescale(duration, sample_rate * bytes_per_sample, AV_TIME_BASE));
|
||||
|
||||
std::scoped_lock lock(m_mtx);
|
||||
data.reserve(std::min<u64>(expected_size, max_reserve));
|
||||
}
|
||||
|
||||
// The output buffer is reused by every frame. Only its size may grow.
|
||||
const auto free_av_buffer = [](u8* buf) { av_free(buf); };
|
||||
std::unique_ptr<u8, decltype(free_av_buffer)> buffer(nullptr, free_av_buffer);
|
||||
int buffer_samples = 0;
|
||||
|
||||
// Resamples the given frame and appends the result to our data.
|
||||
// Pass a null frame in order to flush the samples that are still pending in the resampler.
|
||||
const auto resample_and_append = [&](const AVFrame* frame) -> bool
|
||||
{
|
||||
const int in_samples = frame ? frame->nb_samples : 0;
|
||||
|
||||
// The resampler buffers samples internally, so we also have to make room for the ones that are still pending.
|
||||
// Otherwise they pile up and we lose the tail of the track whenever the file's sample rate differs from ours.
|
||||
const int out_samples = in_rate > 0
|
||||
? static_cast<int>(av_rescale_rnd(swr_get_delay(av.swr, in_rate) + in_samples, sample_rate, in_rate, AV_ROUND_UP))
|
||||
: in_samples;
|
||||
|
||||
if (out_samples <= 0)
|
||||
{
|
||||
if (is_first_error)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (out_samples > buffer_samples)
|
||||
{
|
||||
u8* new_buffer = nullptr;
|
||||
const int align = 0; // Let FFmpeg align the buffer, so the resampler can take its SIMD paths
|
||||
const int buffer_size = av_samples_alloc(&new_buffer, nullptr, dst_channels, out_samples, dst_format, align);
|
||||
if (buffer_size < 0)
|
||||
{
|
||||
media_log.error("audio_decoder: Error allocating buffer: %d='%s'", buffer_size, av_error_to_string(buffer_size));
|
||||
has_error = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer.reset(new_buffer);
|
||||
buffer_samples = out_samples;
|
||||
}
|
||||
|
||||
u8* out_buffer = buffer.get();
|
||||
|
||||
const int frame_count = swr_convert(av.swr, &out_buffer, out_samples, frame ? const_cast<const uint8_t**>(frame->data) : nullptr, in_samples);
|
||||
if (frame_count < 0)
|
||||
{
|
||||
media_log.error("audio_decoder: Error converting frame: %d='%s'", frame_count, av_error_to_string(frame_count));
|
||||
has_error = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frame_count == 0)
|
||||
{
|
||||
// Nothing came out of the resampler yet.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only the samples that were actually written are valid. The rest of the buffer is left over from previous frames.
|
||||
const u64 size = static_cast<u64>(frame_count) * bytes_per_sample;
|
||||
|
||||
if (frame)
|
||||
{
|
||||
last_timestamp_ms = time_base.den ? (1000 * frame->best_effort_timestamp * time_base.num) / time_base.den : 0;
|
||||
}
|
||||
|
||||
// The format is float 32bit per channel. Swap it in place, so we can append the buffer as-is.
|
||||
if (m_swap_endianness)
|
||||
{
|
||||
copy_samples<f32>(out_buffer, out_buffer, size / sizeof(f32), true);
|
||||
}
|
||||
|
||||
// Append resampled frames to data
|
||||
{
|
||||
std::scoped_lock lock(m_mtx);
|
||||
data.insert(data.cend(), out_buffer, out_buffer + size);
|
||||
|
||||
timestamps_ms.push_back({m_size, last_timestamp_ms});
|
||||
m_size += size;
|
||||
}
|
||||
|
||||
media_log.trace("audio_decoder: decoded frame_count=%d size=%d timestamp_ms=%d", frame_count, size, last_timestamp_ms);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Sends the given packet to the decoder and appends every frame it produces.
|
||||
// Pass a null packet in order to flush the frames that are still pending in the decoder.
|
||||
const auto decode_packet = [&](AVPacket* pkt) -> bool
|
||||
{
|
||||
if (int err = avcodec_send_packet(av.audio.context, pkt); err < 0)
|
||||
{
|
||||
if (!pkt && err == averror_eof)
|
||||
{
|
||||
// The decoder was already flushed.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_first_error && pkt)
|
||||
{
|
||||
is_first_error = false;
|
||||
|
||||
@@ -682,12 +786,13 @@ namespace utils
|
||||
// Some mp3s contain some invalid data at the beginning of the stream. They work fine if we just ignore them.
|
||||
// So let's skip the first invalid data error. Maybe there is a better way, but let's just roll with it for now.
|
||||
media_log.warning("audio_decoder: Ignoring first error: %d='%s'", err, av_error_to_string(err));
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
media_log.error("audio_decoder: Queuing error: %d='%s'", err, av_error_to_string(err));
|
||||
has_error = true;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
@@ -699,48 +804,36 @@ namespace utils
|
||||
|
||||
media_log.error("audio_decoder: Decoding error: %d='%s'", err, av_error_to_string(err));
|
||||
has_error = true;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resample frames
|
||||
u8* buffer = nullptr;
|
||||
const int align = 1;
|
||||
const int buffer_size = av_samples_alloc(&buffer, nullptr, dst_channels, av.audio.frame->nb_samples, dst_format, align);
|
||||
if (buffer_size < 0)
|
||||
if (!resample_and_append(av.audio.frame))
|
||||
{
|
||||
media_log.error("audio_decoder: Error allocating buffer: %d='%s'", buffer_size, av_error_to_string(buffer_size));
|
||||
has_error = true;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int frame_count = swr_convert(av.swr, &buffer, av.audio.frame->nb_samples, const_cast<const uint8_t**>(av.audio.frame->data), av.audio.frame->nb_samples);
|
||||
if (frame_count < 0)
|
||||
{
|
||||
media_log.error("audio_decoder: Error converting frame: %d='%s'", frame_count, av_error_to_string(frame_count));
|
||||
has_error = true;
|
||||
if (buffer)
|
||||
av_freep(&buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Append resampled frames to data
|
||||
{
|
||||
std::scoped_lock lock(m_mtx);
|
||||
data.resize(m_size + buffer_size);
|
||||
|
||||
// The format is float 32bit per channel.
|
||||
copy_samples<f32>(buffer, &data[m_size], buffer_size / sizeof(f32), m_swap_endianness);
|
||||
|
||||
const s64 timestamp_ms = stream->time_base.den ? (1000 * av.audio.frame->best_effort_timestamp * stream->time_base.num) / stream->time_base.den : 0;
|
||||
timestamps_ms.push_back({m_size, timestamp_ms});
|
||||
m_size += buffer_size;
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
av_freep(&buffer);
|
||||
|
||||
media_log.trace("audio_decoder: decoded frame_count=%d buffer_size=%d timestamp_us=%d", frame_count, buffer_size, av.audio.frame->best_effort_timestamp);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Iterate through frames
|
||||
while (thread_ctrl::state() != thread_state::aborting && av_read_frame(av.format_context, packet) >= 0)
|
||||
{
|
||||
if (!decode_packet(packet))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (thread_ctrl::state() == thread_state::aborting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Flush the decoder and the resampler, otherwise we lose the tail of the track.
|
||||
if (!decode_packet(nullptr) || !resample_and_append(nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user