Skip to content

Commit

Permalink
HLS,MSE: Implement GetLowestPresentationTimestamp
Browse files Browse the repository at this point in the history
To assist managing the HLS presentation timeline, the underlying MSE
buffering mechanism needs to allow retrieval of the starting, lowest,
presentation timestamp across all ranges resulting from the parsing and
buffering (using the MSE coded frame processing algorithm) of a
potentially muxed input stream. Future HLS CLs will use this information
to verify and adjust buffering of parsed streams. In a less immediate
scenario, MSE (if spec updated) might also need this to more
interoperably and more precisely support buffering of SAP-Type-2
sequences of coded frames. For this latter reason, and to more readily
detect regression, the new impl in this change is *not* conditioned on
gn arg "enable_hls_demuxer".

This change is informed by cassew@'s prototype HLS demuxer CL:
https://chromium-review.googlesource.com/c/chromium/src/+/3425659

BUG=1266991
TEST=2 unittests added to SourceBufferStreamTest suite

Change-Id: If5195bca6a18f0c6b7ba1ec0efc49ffcdca0b430
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4129995
Auto-Submit: Matthew Wolenetz <wolenetz@chromium.org>
Reviewed-by: Will Cassella <cassew@chromium.org>
Commit-Queue: Matthew Wolenetz <wolenetz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1087694}
  • Loading branch information
wolenetz authored and Chromium LUCI CQ committed Dec 30, 2022
1 parent fa72420 commit fc9d95e
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 0 deletions.
16 changes: 16 additions & 0 deletions media/filters/chunk_demuxer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ Ranges<base::TimeDelta> ChunkDemuxerStream::GetBufferedRanges(
return range.IntersectionWith(valid_time_range);
}

base::TimeDelta ChunkDemuxerStream::GetLowestPresentationTimestamp() const {
base::AutoLock auto_lock(lock_);
return stream_->GetLowestPresentationTimestamp();
}

base::TimeDelta ChunkDemuxerStream::GetHighestPresentationTimestamp() const {
base::AutoLock auto_lock(lock_);
return stream_->GetHighestPresentationTimestamp();
Expand Down Expand Up @@ -812,6 +817,17 @@ Ranges<base::TimeDelta> ChunkDemuxer::GetBufferedRanges(
return itr->second->GetBufferedRanges(duration_, state_ == ENDED);
}

base::TimeDelta ChunkDemuxer::GetLowestPresentationTimestamp(
const std::string& id) const {
base::AutoLock auto_lock(lock_);
DCHECK(!id.empty());

auto itr = source_state_map_.find(id);

DCHECK(itr != source_state_map_.end());
return itr->second->GetLowestPresentationTimestamp();
}

base::TimeDelta ChunkDemuxer::GetHighestPresentationTimestamp(
const std::string& id) const {
base::AutoLock auto_lock(lock_);
Expand Down
8 changes: 8 additions & 0 deletions media/filters/chunk_demuxer.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ class MEDIA_EXPORT ChunkDemuxerStream : public DemuxerStream {
// Returns the range of buffered data in this stream, capped at |duration|.
Ranges<base::TimeDelta> GetBufferedRanges(base::TimeDelta duration) const;

// Returns the lowest PTS of the buffered data.
// Returns base::TimeDelta() if the stream has no buffered data.
base::TimeDelta GetLowestPresentationTimestamp() const;

// Returns the highest PTS of the buffered data.
// Returns base::TimeDelta() if the stream has no buffered data.
base::TimeDelta GetHighestPresentationTimestamp() const;
Expand Down Expand Up @@ -283,6 +287,10 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
// Gets the currently buffered ranges for the specified ID.
Ranges<base::TimeDelta> GetBufferedRanges(const std::string& id) const;

// Gets the lowest buffered PTS for the specified |id|. If there is nothing
// buffered, returns base::TimeDelta().
base::TimeDelta GetLowestPresentationTimestamp(const std::string& id) const;

// Gets the highest buffered PTS for the specified |id|. If there is nothing
// buffered, returns base::TimeDelta().
base::TimeDelta GetHighestPresentationTimestamp(const std::string& id) const;
Expand Down
19 changes: 19 additions & 0 deletions media/filters/source_buffer_state.cc
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,25 @@ Ranges<base::TimeDelta> SourceBufferState::GetBufferedRanges(
return ComputeRangesIntersection(ranges_list, ended);
}

base::TimeDelta SourceBufferState::GetLowestPresentationTimestamp() const {
base::TimeDelta min_pts;

for (const auto& it : audio_streams_) {
min_pts = std::min(min_pts, it.second->GetLowestPresentationTimestamp());
}

for (const auto& it : video_streams_) {
min_pts = std::min(min_pts, it.second->GetLowestPresentationTimestamp());
}

for (const auto& it : text_streams_) {
min_pts = std::min(min_pts, it.second->GetLowestPresentationTimestamp());
}

DCHECK_LE(base::TimeDelta(), min_pts);
return min_pts;
}

base::TimeDelta SourceBufferState::GetHighestPresentationTimestamp() const {
base::TimeDelta max_pts;

Expand Down
4 changes: 4 additions & 0 deletions media/filters/source_buffer_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ class MEDIA_EXPORT SourceBufferState {
Ranges<base::TimeDelta> GetBufferedRanges(base::TimeDelta duration,
bool ended) const;

// Returns the lowest PTS of currently buffered frames in this source, or
// base::TimeDelta() if none of the streams contain buffered data.
base::TimeDelta GetLowestPresentationTimestamp() const;

// Returns the highest PTS of currently buffered frames in this source, or
// base::TimeDelta() if none of the streams contain buffered data.
base::TimeDelta GetHighestPresentationTimestamp() const;
Expand Down
8 changes: 8 additions & 0 deletions media/filters/source_buffer_stream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,14 @@ Ranges<base::TimeDelta> SourceBufferStream::GetBufferedTime() const {
return ranges;
}

base::TimeDelta SourceBufferStream::GetLowestPresentationTimestamp() const {
if (ranges_.empty()) {
return base::TimeDelta();
}

return ranges_.front()->GetStartTimestamp();
}

base::TimeDelta SourceBufferStream::GetHighestPresentationTimestamp() const {
if (ranges_.empty())
return base::TimeDelta();
Expand Down
4 changes: 4 additions & 0 deletions media/filters/source_buffer_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ class MEDIA_EXPORT SourceBufferStream {
// Returns a list of the buffered time ranges.
Ranges<base::TimeDelta> GetBufferedTime() const;

// Returns the lowest buffered PTS or base::TimeDelta() if nothing is
// buffered.
base::TimeDelta GetLowestPresentationTimestamp() const;

// Returns the highest buffered PTS or base::TimeDelta() if nothing is
// buffered.
base::TimeDelta GetHighestPresentationTimestamp() const;
Expand Down
56 changes: 56 additions & 0 deletions media/filters/source_buffer_stream_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5109,6 +5109,62 @@ TEST_F(SourceBufferStreamTest,
CheckNoNextBuffer();
}

TEST_F(SourceBufferStreamTest, GetLowestPresentationTimestamp_NonMuxed) {
EXPECT_EQ(base::TimeDelta(), stream_->GetLowestPresentationTimestamp());

NewCodedFrameGroupAppend("100K 110K");
EXPECT_EQ(base::Milliseconds(100), stream_->GetLowestPresentationTimestamp());

RemoveInMs(110, 120, 120);
EXPECT_EQ(base::Milliseconds(100), stream_->GetLowestPresentationTimestamp());

RemoveInMs(100, 110, 120);
EXPECT_EQ(base::TimeDelta(), stream_->GetLowestPresentationTimestamp());

NewCodedFrameGroupAppend("100K 110K");
EXPECT_EQ(base::Milliseconds(100), stream_->GetLowestPresentationTimestamp());

RemoveInMs(100, 110, 120);
EXPECT_EQ(base::Milliseconds(110), stream_->GetLowestPresentationTimestamp());

RemoveInMs(110, 120, 120);
EXPECT_EQ(base::TimeDelta(), stream_->GetLowestPresentationTimestamp());
}

TEST_F(SourceBufferStreamTest, GetLowestPresentationTimestamp_Muxed) {
// Simulate `stream_` being one of multiple resulting from parsing and
// buffering a muxed bytestream. In this case, it is common for range start
// times across the streams in the same muxed segment to not precisely align.
// The frame processing algorithm indicates the segment's "coded frame group
// start time" to the SourceBufferStream, and the underlying range remembers
// this even if the corresponding actual start time in the underlying range is
// later than that start time. However, if the start of that range is removed,
// then the underlying range no longer attempts to maintain the original
// "coded frame group start time" as the lowest timestamp. This impacts
// GetLowestPresentationTimestamp(), since the underlying range start time of
// the first range is involved and is conditional. See also
// SourceBufferRange::GetStartTimestamp().
EXPECT_EQ(base::TimeDelta(), stream_->GetLowestPresentationTimestamp());

NewCodedFrameGroupAppend(base::Milliseconds(50), "100K 110K");
EXPECT_EQ(base::Milliseconds(50), stream_->GetLowestPresentationTimestamp());

RemoveInMs(110, 120, 120);
EXPECT_EQ(base::Milliseconds(50), stream_->GetLowestPresentationTimestamp());

RemoveInMs(100, 110, 120);
EXPECT_EQ(base::TimeDelta(), stream_->GetLowestPresentationTimestamp());

NewCodedFrameGroupAppend(base::Milliseconds(50), "100K 110K");
EXPECT_EQ(base::Milliseconds(50), stream_->GetLowestPresentationTimestamp());

RemoveInMs(100, 110, 120);
EXPECT_EQ(base::Milliseconds(110), stream_->GetLowestPresentationTimestamp());

RemoveInMs(110, 120, 120);
EXPECT_EQ(base::TimeDelta(), stream_->GetLowestPresentationTimestamp());
}

TEST_F(SourceBufferStreamTest, GetHighestPresentationTimestamp) {
EXPECT_EQ(base::TimeDelta(), stream_->GetHighestPresentationTimestamp());

Expand Down

0 comments on commit fc9d95e

Please sign in to comment.