LibMedia: Move overlapping audio block correction to the data provider

This prevents PlaybackManager's seek while enabling an audio track from
causing the AudioMixingSink to push audio blocks forward unnecessarily.
Previously, the seek would cause the initial block or blocks to repeat
from the perspective of AudioMixingSink, so it would think that it
needs to shift the first block after the seek forward by a few samples.
By moving this to the AudioDataProvider, we can clear the last sample
index every time the decoder is flushed, ensuring that the block
shifting always makes sense.

By doing this in AudioMixingSink instead of the Decoder
implementations, we avoid having to duplicate this shifting logic
across multiple implementations.

This also fixes an issue where multiple audio blocks occupying the same
timestamp would be skipped while seeking, causing a significant break
in audio.
This commit is contained in:
Zaggy1024 2025-11-10 16:40:25 -06:00 committed by Jelle Raaijmakers
parent 7e325d64f5
commit ae5e200dfc
Notes: github-actions[bot] 2025-11-17 15:53:04 +00:00
5 changed files with 38 additions and 21 deletions

View file

@ -17,7 +17,8 @@ public:
u32 sample_rate() const { return m_sample_rate; }
u8 channel_count() const { return m_channel_count; }
AK::Duration start_timestamp() const { return m_start_timestamp; }
AK::Duration timestamp() const { return m_timestamp; }
i64 timestamp_in_samples() const { return m_timestamp_in_samples; }
Data& data() { return m_data; }
Data const& data() const { return m_data; }
@ -25,20 +26,27 @@ public:
{
m_sample_rate = 0;
m_channel_count = 0;
m_start_timestamp = AK::Duration::zero();
m_timestamp_in_samples = 0;
m_data = Data();
}
template<typename Callback>
void emplace(u32 sample_rate, u8 channel_count, AK::Duration start_timestamp, Callback data_callback)
void emplace(u32 sample_rate, u8 channel_count, AK::Duration timestamp, Callback data_callback)
{
VERIFY(sample_rate != 0);
VERIFY(channel_count != 0);
VERIFY(m_data.is_empty());
m_sample_rate = sample_rate;
m_channel_count = channel_count;
m_start_timestamp = start_timestamp;
m_timestamp = timestamp;
m_timestamp_in_samples = timestamp.to_time_units(1, sample_rate);
data_callback(m_data);
}
void set_timestamp_in_samples(i64 timestamp_in_samples)
{
VERIFY(!is_empty());
m_timestamp_in_samples = timestamp_in_samples;
m_timestamp = AK::Duration::from_time_units(timestamp_in_samples, 1, m_sample_rate);
}
bool is_empty() const
{
return m_sample_rate == 0;
@ -55,7 +63,8 @@ public:
private:
u32 m_sample_rate { 0 };
u8 m_channel_count { 0 };
AK::Duration m_start_timestamp;
AK::Duration m_timestamp;
i64 m_timestamp_in_samples { 0 };
Data m_data;
};

View file

@ -105,6 +105,21 @@ bool AudioDataProvider::ThreadData::should_thread_exit() const
return m_exit;
}
void AudioDataProvider::ThreadData::flush_decoder()
{
m_decoder->flush();
m_last_sample = NumericLimits<i64>::min();
}
DecoderErrorOr<void> AudioDataProvider::ThreadData::retrieve_next_block(AudioBlock& block)
{
TRY(m_decoder->write_next_block(block));
if (block.timestamp_in_samples() < m_last_sample)
block.set_timestamp_in_samples(m_last_sample);
m_last_sample = block.timestamp_in_samples() + static_cast<i64>(block.sample_count());
return {};
}
template<typename T>
void AudioDataProvider::ThreadData::process_seek_on_main_thread(u32 seek_id, T&& function)
{
@ -166,7 +181,7 @@ bool AudioDataProvider::ThreadData::handle_seek()
auto demuxer_seek_result = demuxer_seek_result_or_error.value_or(DemuxerSeekResult::MovedPosition);
if (demuxer_seek_result == DemuxerSeekResult::MovedPosition)
m_decoder->flush();
flush_decoder();
auto new_seek_id = seek_id;
AudioBlock last_block;
@ -191,7 +206,7 @@ bool AudioDataProvider::ThreadData::handle_seek()
while (new_seek_id == seek_id) {
AudioBlock current_block;
auto block_result = m_decoder->write_next_block(current_block);
auto block_result = retrieve_next_block(current_block);
if (block_result.is_error()) {
if (block_result.error().category() == DecoderErrorCategory::EndOfStream) {
resolve_seek(seek_id);
@ -205,7 +220,7 @@ bool AudioDataProvider::ThreadData::handle_seek()
return true;
}
if (current_block.start_timestamp() > timestamp) {
if (current_block.timestamp() > timestamp) {
auto locker = take_lock();
m_queue.clear();
@ -291,7 +306,7 @@ void AudioDataProvider::ThreadData::push_data_and_decode_a_block()
}
auto block = AudioBlock();
auto timestamp_result = m_decoder->write_next_block(block);
auto timestamp_result = retrieve_next_block(block);
if (timestamp_result.is_error()) {
if (timestamp_result.error().category() == DecoderErrorCategory::NeedsMoreInput)
break;

View file

@ -53,6 +53,8 @@ private:
void set_error_handler(ErrorHandler&&);
bool should_thread_exit() const;
void flush_decoder();
DecoderErrorOr<void> retrieve_next_block(AudioBlock&);
bool handle_seek();
template<typename T>
void process_seek_on_main_thread(u32 seek_id, T&&);
@ -80,6 +82,7 @@ private:
NonnullRefPtr<MutexedDemuxer> m_demuxer;
Track m_track;
NonnullOwnPtr<AudioDecoder> m_decoder;
i64 m_last_sample { NumericLimits<i64>::min() };
size_t m_queue_max_size { 8 };
AudioQueue m_queue;

View file

@ -111,14 +111,7 @@ void AudioMixingSink::create_playback_stream(u32 sample_rate, u32 channel_count)
if (new_block.is_empty())
return false;
auto new_block_first_sample_offset = new_block.start_timestamp().to_time_units(1, sample_rate);
if (!track_data.current_block.is_empty() && track_data.current_block.sample_rate() == sample_rate && track_data.current_block.channel_count() == channel_count) {
auto current_block_end = track_data.current_block_first_sample_offset + static_cast<i64>(track_data.current_block.sample_count());
new_block_first_sample_offset = max(new_block_first_sample_offset, current_block_end);
}
track_data.current_block = move(new_block);
track_data.current_block_first_sample_offset = new_block_first_sample_offset;
return true;
};
@ -137,7 +130,7 @@ void AudioMixingSink::create_playback_stream(u32 sample_rate, u32 channel_count)
continue;
}
auto first_sample_offset = track_data.current_block_first_sample_offset;
auto first_sample_offset = current_block.timestamp_in_samples();
if (first_sample_offset >= samples_end)
break;
@ -279,10 +272,8 @@ void AudioMixingSink::set_time(AK::Duration time)
self->m_next_sample_to_write = time.to_time_units(1, self->m_playback_stream_sample_rate);
}
for (auto& [track, track_data] : self->m_track_mixing_datas) {
for (auto& [track, track_data] : self->m_track_mixing_datas)
track_data.current_block.clear();
track_data.current_block_first_sample_offset = 0;
}
}
if (self->m_playing)

View file

@ -72,7 +72,6 @@ private:
NonnullRefPtr<AudioDataProvider> provider;
AudioBlock current_block;
i64 current_block_first_sample_offset { NumericLimits<i64>::min() };
};
void deferred_create_playback_stream(Track const& track);