mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-18 18:00:31 +00:00
The segments are parsed for the SourceBufferProcessor by the WebMByteStreamParser. It parses the initialization segment to update its internal set of tracks, then SourceBufferProcessor/SourceBuffer set them up for playback. When a media segment is received, it also parses as much of it as is available, returning all the coded frames found so far. SourceBufferProcessor then tells TrackBufferDemuxer to remove any overlapping frames and insert the new ones. TrackBufferDemuxer implements the Demuxer interface in terms of the coded frame store maintained by the SourceBufferProcessor. It returns the frames in decode order when requested by a data provider. When a is needed, it finds the keyframe prior to the target timestamp, and checks that there are no gaps in data up to the target timestamp. If there are any gaps, it blocks until the gaps are gone.
274 lines
9.8 KiB
C++
274 lines
9.8 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
|
|
{
|
|
Threading::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)
|
|
{
|
|
Threading::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_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)
|
|
{
|
|
Threading::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_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();
|
|
}
|
|
}
|
|
|
|
void TrackBufferDemuxer::set_reached_end_of_stream()
|
|
{
|
|
Threading::MutexLocker locker { m_mutex };
|
|
m_reached_end_of_stream = true;
|
|
m_data_changed.broadcast();
|
|
}
|
|
|
|
void TrackBufferDemuxer::clear_reached_end_of_stream()
|
|
{
|
|
Threading::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&)
|
|
{
|
|
Threading::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();
|
|
}
|
|
|
|
Media::DecoderErrorOr<Media::DemuxerSeekResult> TrackBufferDemuxer::seek_to_most_recent_keyframe(Media::Track const&, AK::Duration timestamp, Media::DemuxerSeekOptions)
|
|
{
|
|
Threading::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);
|
|
Threading::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&)
|
|
{
|
|
Threading::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();
|
|
}
|
|
|
|
}
|