ladybird/Libraries/LibWeb/MediaSourceExtensions/TrackBufferDemuxer.cpp
Zaggy1024 289908968d LibWeb: Implement SourceBuffer capacity and pre-append eviction
Set a limit on SourceBuffer size to 150MiB for video and 12MiB for
audio, matching Chromium's limits.
2026-05-27 19:47:22 -05:00

369 lines
13 KiB
C++

/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/BinarySearch.h>
#include <LibWeb/MediaSourceExtensions/TrackBufferDemuxer.h>
namespace Web::MediaSourceExtensions {
TrackBufferDemuxer::TrackBufferDemuxer(Media::Track const& track, Media::CodecID codec_id, ByteBuffer codec_initialization_data)
: m_track(track)
, m_codec_id(codec_id)
, m_codec_initialization_data(move(codec_initialization_data))
{
}
TrackBufferDemuxer::~TrackBufferDemuxer() = default;
Media::TimeRanges TrackBufferDemuxer::track_buffer_ranges() const
{
Sync::MutexLocker locker { m_mutex };
// https://w3c.github.io/media-source/#track-buffer-ranges
// NOTE: Implementations MAY coalesce adjacent ranges separated by a gap smaller than 2 times the
// maximum frame duration buffered so far in this track buffer.
auto max_gap = maximum_time_range_gap();
if (max_gap.is_zero())
return m_track_buffer_ranges;
return m_track_buffer_ranges.coalesced(max_gap);
}
void TrackBufferDemuxer::add_coded_frame(Media::CodedFrame frame)
{
Sync::MutexLocker locker { m_mutex };
auto start = frame.timestamp();
auto end = frame.timestamp() + frame.duration();
m_last_frame_duration = frame.duration();
m_track_buffer_ranges.add_range(start, end);
// Insert in sorted order by presentation timestamp using binary search.
// Overlapping frames should have been removed by remove_coded_frames_and_dependants_in_range()
// before this call.
auto timestamp = frame.timestamp();
size_t insert_index = 0;
auto* existing_coded_frame = binary_search(m_coded_frames, timestamp, &insert_index, [](AK::Duration needle, Media::CodedFrame const& frame) {
return needle <=> frame.timestamp();
});
VERIFY(!existing_coded_frame);
if (insert_index < m_coded_frames.size() && m_coded_frames[insert_index].timestamp() < timestamp)
insert_index++;
m_total_bytes += frame.data().size();
m_coded_frames.insert(insert_index, move(frame));
if (insert_index <= m_read_position && (!m_last_returned_timestamp.has_value() || m_last_returned_timestamp.value() > timestamp))
m_read_position++;
m_data_changed.broadcast();
}
void TrackBufferDemuxer::remove_coded_frames_and_dependants_in_range(AK::Duration start, AK::Duration end)
{
Sync::MutexLocker locker { m_mutex };
// https://w3c.github.io/media-source/#sourcebuffer-coded-frame-processing
// 1.13. Remove all coded frames from track buffer that have a presentation timestamp greater than
// or equal to presentation timestamp and less than frame end timestamp.
// 1.14. Remove all possible decoding dependencies on the coded frames removed in the previous step
// by removing all coded frames from track buffer between those frames removed in the previous
// step and the next random access point after those removed frames.
// Find the first frame at or after the start of the range.
size_t remove_start = 0;
while (remove_start < m_coded_frames.size() && m_coded_frames[remove_start].timestamp() < start)
remove_start++;
// Find all overlapping frames plus subsequent frames up to the next keyframe.
size_t remove_end = remove_start;
while (remove_end < m_coded_frames.size() && m_coded_frames[remove_end].timestamp() < end)
remove_end++;
if (remove_end <= remove_start)
return;
while (remove_end < m_coded_frames.size() && !m_coded_frames[remove_end].is_keyframe())
remove_end++;
for (size_t i = remove_start; i < remove_end; i++) {
auto const& removed_frame = m_coded_frames[i];
auto removed_start = removed_frame.timestamp();
auto removed_end = (removed_frame.timestamp() + removed_frame.duration());
m_track_buffer_ranges.remove_range(removed_start, removed_end);
m_total_bytes -= removed_frame.data().size();
}
m_coded_frames.remove(remove_start, remove_end - remove_start);
// Adjust read position if it was in or past the removed range.
if (m_read_position >= remove_end) {
m_read_position -= (remove_end - remove_start);
} else if (m_read_position > remove_start) {
m_read_position = remove_start > 0 ? remove_start - 1 : 0;
while (m_read_position > 0 && !m_coded_frames[m_read_position].is_keyframe())
m_read_position--;
m_last_returned_timestamp.clear();
}
}
size_t TrackBufferDemuxer::total_bytes() const
{
Sync::MutexLocker locker { m_mutex };
return m_total_bytes;
}
bool TrackBufferDemuxer::is_frame_evictable_while_locked(Media::CodedFrame const& frame, AK::Duration current_time) const
{
auto time_range_start = current_time;
auto time_range_end = current_time;
if (m_last_returned_timestamp.has_value()) {
time_range_start = min(time_range_start, m_last_returned_timestamp.value());
time_range_end = max(time_range_end, m_last_returned_timestamp.value());
}
return frame.timestamp() < time_range_start || frame.timestamp() > time_range_end;
}
Optional<AK::Duration> TrackBufferDemuxer::earliest_evictable_frame_timestamp(AK::Duration current_time) const
{
Sync::MutexLocker locker { m_mutex };
if (m_coded_frames.is_empty())
return {};
auto const& frame = m_coded_frames[0];
if (!is_frame_evictable_while_locked(frame, current_time))
return {};
return frame.timestamp();
}
size_t TrackBufferDemuxer::take_earliest_frame()
{
Sync::MutexLocker locker { m_mutex };
auto frame = m_coded_frames.take_first();
m_track_buffer_ranges.remove_range(frame.timestamp(), frame.timestamp() + frame.duration());
auto bytes = frame.data().size();
m_total_bytes -= bytes;
if (m_read_position > 0)
m_read_position--;
return bytes;
}
Optional<AK::Duration> TrackBufferDemuxer::latest_evictable_frame_timestamp(AK::Duration current_time) const
{
Sync::MutexLocker locker { m_mutex };
if (m_coded_frames.is_empty())
return {};
auto const& frame = m_coded_frames.last();
if (!is_frame_evictable_while_locked(frame, current_time))
return {};
return frame.timestamp();
}
size_t TrackBufferDemuxer::take_latest_frame()
{
Sync::MutexLocker locker { m_mutex };
auto frame = m_coded_frames.take_last();
m_track_buffer_ranges.remove_range(frame.timestamp(), frame.timestamp() + frame.duration());
auto bytes = frame.data().size();
m_total_bytes -= bytes;
return bytes;
}
void TrackBufferDemuxer::set_reached_end_of_stream()
{
Sync::MutexLocker locker { m_mutex };
m_reached_end_of_stream = true;
m_data_changed.broadcast();
}
void TrackBufferDemuxer::clear_reached_end_of_stream()
{
Sync::MutexLocker locker { m_mutex };
m_reached_end_of_stream = false;
}
Media::DecoderErrorOr<void> TrackBufferDemuxer::create_context_for_track(Media::Track const&)
{
return {};
}
Media::DecoderErrorOr<Vector<Media::Track>> TrackBufferDemuxer::get_tracks_for_type(Media::TrackType type)
{
if (m_track.type() == type)
return Vector { m_track };
return Vector<Media::Track> {};
}
Media::DecoderErrorOr<Optional<Media::Track>> TrackBufferDemuxer::get_preferred_track_for_type(Media::TrackType type)
{
if (m_track.type() == type)
return Optional<Media::Track> { m_track };
return Optional<Media::Track> {};
}
AK::Duration TrackBufferDemuxer::maximum_time_range_gap() const
{
return m_last_frame_duration + m_last_frame_duration;
}
bool TrackBufferDemuxer::next_frame_is_in_gap_while_locked() const
{
auto max_gap = maximum_time_range_gap();
if (!m_last_returned_timestamp.has_value() || max_gap.is_zero())
return false;
if (m_read_position >= m_coded_frames.size())
return false;
auto delta = m_coded_frames[m_read_position].timestamp() - m_last_returned_timestamp.value();
return delta > max_gap;
}
Media::DecoderErrorOr<Media::CodedFrame> TrackBufferDemuxer::get_next_sample_for_track(Media::Track const&)
{
Sync::MutexLocker locker { m_mutex };
while (m_read_position >= m_coded_frames.size() || next_frame_is_in_gap_while_locked()) {
if (m_aborted.load())
return Media::DecoderError::with_description(Media::DecoderErrorCategory::Aborted, "Read aborted"sv);
if (m_read_position >= m_coded_frames.size() && m_reached_end_of_stream)
return Media::DecoderError::with_description(Media::DecoderErrorCategory::EndOfStream, "End of stream"sv);
m_data_changed.wait();
}
m_last_returned_timestamp = m_coded_frames[m_read_position].timestamp();
return m_coded_frames[m_read_position++];
}
Media::DecoderErrorOr<Media::CodecID> TrackBufferDemuxer::get_codec_id_for_track(Media::Track const&)
{
return m_codec_id;
}
Media::DecoderErrorOr<ReadonlyBytes> TrackBufferDemuxer::get_codec_initialization_data_for_track(Media::Track const&)
{
return m_codec_initialization_data.bytes();
}
AK::Duration TrackBufferDemuxer::select_fast_seek_target_for_track(Media::Track const&, AK::Duration target, Media::SeekMode mode)
{
Sync::MutexLocker locker { m_mutex };
if (m_coded_frames.is_empty())
return target;
size_t nearby_index = 0;
binary_search(m_coded_frames, target, &nearby_index, [](AK::Duration needle, Media::CodedFrame const& frame) {
return needle <=> frame.timestamp();
});
if (mode == Media::SeekMode::FastBefore) {
if (m_coded_frames[nearby_index].timestamp() > target)
return target;
for (auto i = nearby_index; i-- > 0;) {
if (m_coded_frames[i].is_keyframe())
return m_coded_frames[i].timestamp();
}
return target;
}
VERIFY(mode == Media::SeekMode::FastAfter);
auto start = nearby_index;
if (m_coded_frames[start].timestamp() < target)
start++;
for (auto i = start; i < m_coded_frames.size(); i++) {
if (m_coded_frames[i].is_keyframe())
return m_coded_frames[i].timestamp();
}
return target;
}
Media::DecoderErrorOr<Media::DemuxerSeekResult> TrackBufferDemuxer::seek_to_most_recent_keyframe(Media::Track const&, AK::Duration timestamp, Media::DemuxerSeekOptions)
{
Sync::MutexLocker locker { m_mutex };
size_t best_position = 0;
AK::Duration best_timestamp;
while (true) {
if (m_aborted.load())
return Media::DecoderError::with_description(Media::DecoderErrorCategory::Aborted, "Seek aborted"sv);
best_timestamp = AK::Duration::max();
for (size_t i = 0; i < m_coded_frames.size(); i++) {
auto const& frame = m_coded_frames[i];
if (frame.timestamp() >= timestamp)
break;
if (frame.is_keyframe()) {
best_position = i;
best_timestamp = frame.timestamp();
}
}
auto max_gap = maximum_time_range_gap();
if (timestamp >= best_timestamp) {
auto has_gap = [&] {
auto prior_timestamp = m_coded_frames[best_position].timestamp();
for (size_t i = best_position + 1; i < m_coded_frames.size(); i++) {
auto const& frame = m_coded_frames[i];
auto delta = frame.timestamp() - prior_timestamp;
if (delta > max_gap)
return true;
if (frame.timestamp() >= timestamp)
break;
prior_timestamp = frame.timestamp();
}
return timestamp - prior_timestamp > max_gap;
}();
if (!has_gap)
break;
} else if (best_position < m_coded_frames.size()) {
auto& future_frame = m_coded_frames[best_position];
if (future_frame.is_keyframe() && future_frame.timestamp() - timestamp <= max_gap) {
best_timestamp = timestamp;
break;
}
}
m_data_changed.wait();
}
m_read_position = best_position;
m_last_returned_timestamp = best_timestamp;
m_data_changed.broadcast();
return Media::DemuxerSeekResult::MovedPosition;
}
Media::DecoderErrorOr<AK::Duration> TrackBufferDemuxer::duration_of_track(Media::Track const&)
{
return AK::Duration::zero();
}
Media::DecoderErrorOr<AK::Duration> TrackBufferDemuxer::total_duration()
{
return AK::Duration::zero();
}
Media::TimeRanges TrackBufferDemuxer::buffered_time_ranges() const
{
return track_buffer_ranges();
}
void TrackBufferDemuxer::set_blocking_reads_aborted_for_track(Media::Track const&)
{
m_aborted.store(true);
Sync::MutexLocker locker { m_mutex };
m_data_changed.broadcast();
}
void TrackBufferDemuxer::reset_blocking_reads_aborted_for_track(Media::Track const&)
{
m_aborted.store(false);
}
bool TrackBufferDemuxer::is_read_blocked_for_track(Media::Track const&)
{
Sync::MutexLocker locker { m_mutex };
if (m_aborted.load())
return false;
return m_read_position >= m_coded_frames.size() || next_frame_is_in_gap_while_locked();
}
}