Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(639)

Unified Diff: media/filters/chunk_demuxer.cc

Issue 20123002: Add Chromium-side support for SourceBuffer.appendWindowStart and SourceBuffer.appendWindowEnd. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/filters/chunk_demuxer.cc
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 29bfc6fb5577860040173e8d7fff5362ded679b7..edd9dd469e5f093a0c5a5b6e7c905c20de428c09 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -37,15 +37,15 @@ class SourceState {
SourceState(scoped_ptr<StreamParser> stream_parser, const LogCB& log_cb,
const CreateDemuxerStreamCB& create_demuxer_stream_cb,
- const IncreaseDurationCB& increase_duration_cb);
+ const IncreaseDurationCB& increase_duration_cb,
+ const StreamParser::NewMediaSegmentCB& new_segment_cb);
void Init(const StreamParser::InitCB& init_cb,
bool allow_audio,
bool allow_video,
const StreamParser::NewTextBuffersCB& text_cb,
const StreamParser::NeedKeyCB& need_key_cb,
- const AddTextTrackCB& add_text_track_cb,
- const StreamParser::NewMediaSegmentCB& new_segment_cb);
+ const AddTextTrackCB& add_text_track_cb);
// Appends new data to the StreamParser.
// Returns true if the data was successfully appended. Returns false if an
@@ -62,6 +62,9 @@ class SourceState {
TimeDelta timestamp_offset() const { return timestamp_offset_; }
+ void SetAppendWindowStart(TimeDelta start) { append_window_start_ = start; }
scherkus (not reviewing) 2013/07/24 22:47:41 these should be unix_hacker style as they're inlin
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 Done.
+ void SetAppendWindowEnd(TimeDelta end) { append_window_end_ = end; }
+
private:
// Called by the |stream_parser_| when a new initialization segment is
// encountered.
@@ -73,22 +76,19 @@ class SourceState {
// Called by the |stream_parser_| at the beginning of a new media segment.
// |timestamp| is the timestamp on the first buffer in the segment.
- // It modifies the state of this object and then calls |new_segment_cb| with
- // modified version of |timestamp|.
- void OnNewMediaSegment(const StreamParser::NewMediaSegmentCB& new_segment_cb,
- TimeDelta timestamp);
+ void OnNewMediaSegment(TimeDelta timestamp);
// Called by the |stream_parser_| at the end of a media segment.
void OnEndOfMediaSegment();
// Called by the |stream_parser_| when new buffers have been parsed. It
- // applies |timestamp_offset_| to all buffers in |buffers| and then calls
- // Append() on either |audio_| or |video_| with the modified buffers based on
- // the value of |type|.
+ // applies |timestamp_offset_| to all buffers in |audio_buffers| and
+ // |video_buffers| and then calls Append() on |audio_| and/or
+ // |video_| with the modified buffers.
// Returns true on a successful call. Returns false if an error occured while
// processing the buffers.
- bool OnBuffers(DemuxerStream::Type type,
- const StreamParser::BufferQueue& buffers);
+ bool OnNewBuffers(const StreamParser::BufferQueue& audio_buffers,
+ const StreamParser::BufferQueue& video_buffers);
// Called by the |stream_parser_| when new text buffers have been parsed. It
// applies |timestamp_offset_| to all buffers in |buffers| and then calls
@@ -102,12 +102,33 @@ class SourceState {
// Helper function that adds |timestamp_offset_| to each buffer in |buffers|.
void AdjustBufferTimestamps(const StreamParser::BufferQueue& buffers);
+ // Filters out buffers that are outside of the append window
+ // [|append_window_start_|, |append_window_end_|).
+ // |needs_keyframe| is a pointer to the |xxx_need_keyframe_| flag
+ // associated with the |buffers|. Its state is read an updated as
+ // this method filters |buffers|.
+ // Returns a filtered queue of buffers that are inside the append window.
+ StreamParser::BufferQueue FilterWithAppendWindow(
+ const StreamParser::BufferQueue& buffers, bool* needs_keyframe);
+
CreateDemuxerStreamCB create_demuxer_stream_cb_;
IncreaseDurationCB increase_duration_cb_;
+ StreamParser::NewMediaSegmentCB new_segment_cb_;
// The offset to apply to media segment timestamps.
TimeDelta timestamp_offset_;
+ TimeDelta append_window_start_;
+ TimeDelta append_window_end_;
+
+ // Set to true if the next buffers appended within the append window
+ // represent the start of a new media segment. This flag being set
+ // triggers a call to |new_segment_cb_| when the new buffers are
+ // appended. The flag is set on actual media segment boundaries and
+ // when the "append window" filtering causes discontinuities in the
+ // appended data.
+ bool new_media_segment_;
+
// Keeps track of whether |timestamp_offset_| can be modified.
bool can_update_offset_;
@@ -115,7 +136,10 @@ class SourceState {
scoped_ptr<StreamParser> stream_parser_;
ChunkDemuxerStream* audio_;
+ bool audio_needs_keyframe_;
+
ChunkDemuxerStream* video_;
+ bool video_needs_keyframe_;
LogCB log_cb_;
@@ -217,16 +241,24 @@ class ChunkDemuxerStream : public DemuxerStream {
SourceState::SourceState(scoped_ptr<StreamParser> stream_parser,
const LogCB& log_cb,
const CreateDemuxerStreamCB& create_demuxer_stream_cb,
- const IncreaseDurationCB& increase_duration_cb)
+ const IncreaseDurationCB& increase_duration_cb,
+ const StreamParser::NewMediaSegmentCB& new_segment_cb)
: create_demuxer_stream_cb_(create_demuxer_stream_cb),
increase_duration_cb_(increase_duration_cb),
+ new_segment_cb_(new_segment_cb),
+ append_window_start_(TimeDelta()),
scherkus (not reviewing) 2013/07/24 22:47:41 do you have this here to make it more explicit? i
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 Yeah that was the original idea, but it's a pretty
+ append_window_end_(kInfiniteDuration()),
+ new_media_segment_(false),
can_update_offset_(true),
stream_parser_(stream_parser.release()),
audio_(NULL),
+ audio_needs_keyframe_(true),
video_(NULL),
+ video_needs_keyframe_(true),
log_cb_(log_cb) {
DCHECK(!create_demuxer_stream_cb_.is_null());
DCHECK(!increase_duration_cb_.is_null());
+ DCHECK(!new_segment_cb_.is_null());
}
void SourceState::Init(const StreamParser::InitCB& init_cb,
@@ -234,34 +266,22 @@ void SourceState::Init(const StreamParser::InitCB& init_cb,
bool allow_video,
const StreamParser::NewTextBuffersCB& text_cb,
const StreamParser::NeedKeyCB& need_key_cb,
- const AddTextTrackCB& add_text_track_cb,
- const StreamParser::NewMediaSegmentCB& new_segment_cb) {
+ const AddTextTrackCB& add_text_track_cb) {
StreamParser::NewBuffersCB audio_cb;
- StreamParser::NewBuffersCB video_cb;
-
- if (allow_audio) {
- audio_cb = base::Bind(&SourceState::OnBuffers,
- base::Unretained(this), DemuxerStream::AUDIO);
- }
-
- if (allow_video) {
- video_cb = base::Bind(&SourceState::OnBuffers,
- base::Unretained(this), DemuxerStream::VIDEO);
- }
stream_parser_->Init(init_cb,
base::Bind(&SourceState::OnNewConfigs,
base::Unretained(this),
allow_audio,
allow_video),
- audio_cb,
- video_cb,
+ base::Bind(&SourceState::OnNewBuffers,
+ base::Unretained(this)),
base::Bind(&SourceState::OnTextBuffers,
base::Unretained(this), text_cb),
need_key_cb,
add_text_track_cb,
base::Bind(&SourceState::OnNewMediaSegment,
- base::Unretained(this), new_segment_cb),
+ base::Unretained(this)),
base::Bind(&SourceState::OnEndOfMediaSegment,
base::Unretained(this)),
log_cb_);
@@ -281,6 +301,8 @@ bool SourceState::Append(const uint8* data, size_t length) {
void SourceState::Abort() {
stream_parser_->Flush();
+ audio_needs_keyframe_ = true;
+ video_needs_keyframe_ = true;
can_update_offset_ = true;
scherkus (not reviewing) 2013/07/24 22:47:41 sanity check: does new_media_segment_ need to be u
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 No. We will always get an OnNewMediaSegment() call
}
@@ -362,43 +384,65 @@ bool SourceState::OnNewConfigs(bool allow_audio, bool allow_video,
return success;
}
-void SourceState::OnNewMediaSegment(
- const StreamParser::NewMediaSegmentCB& new_segment_cb,
- TimeDelta timestamp) {
+void SourceState::OnNewMediaSegment(TimeDelta timestamp) {
scherkus (not reviewing) 2013/07/24 22:47:41 |timestamp| isn't used anymore other than for DCHE
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 Done.
DCHECK(timestamp != kNoTimestamp());
- DVLOG(2) << "OnNewMediaSegment(" << timestamp.InSecondsF() << ")";
-
+ DVLOG(2) << "SourceState::OnNewMediaSegment("
+ << timestamp.InSecondsF() << ")";
can_update_offset_ = false;
- new_segment_cb.Run(timestamp + timestamp_offset_);
+ new_media_segment_ = true;
}
void SourceState::OnEndOfMediaSegment() {
DVLOG(2) << "OnEndOfMediaSegment()";
can_update_offset_ = true;
+ new_media_segment_ = false;
}
-bool SourceState::OnBuffers(DemuxerStream::Type type,
- const StreamParser::BufferQueue& buffers) {
- DCHECK(!buffers.empty());
- AdjustBufferTimestamps(buffers);
+bool SourceState::OnNewBuffers(const StreamParser::BufferQueue& audio_buffers,
+ const StreamParser::BufferQueue& video_buffers) {
+ DCHECK(!audio_buffers.empty() || !video_buffers.empty());
+ AdjustBufferTimestamps(audio_buffers);
+ AdjustBufferTimestamps(video_buffers);
- ChunkDemuxerStream* stream = NULL;
- switch (type) {
- case DemuxerStream::AUDIO:
- stream = audio_;
- break;
- case DemuxerStream::VIDEO:
- stream = video_;
- break;
- case DemuxerStream::UNKNOWN:
- case DemuxerStream::NUM_TYPES:
- NOTREACHED();
+ StreamParser::BufferQueue filtered_audio =
+ FilterWithAppendWindow(audio_buffers, &audio_needs_keyframe_);
+
+ StreamParser::BufferQueue filtered_video =
+ FilterWithAppendWindow(video_buffers, &video_needs_keyframe_);
+
+ if (filtered_audio.empty() && filtered_video.empty())
+ return true;
+
+ if (new_media_segment_) {
+ // Find the earliest timestamp in the filtered buffers and use
scherkus (not reviewing) 2013/07/24 22:47:41 nit: you can fit a few more words on this line
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 Done.
+ // that for the segment start timestamp.
+ TimeDelta segment_timestamp = kNoTimestamp();
+
+ if (!filtered_audio.empty())
+ segment_timestamp = filtered_audio.front()->GetDecodeTimestamp();
+
+ if (!filtered_video.empty() &&
+ (segment_timestamp == kNoTimestamp() ||
+ filtered_video.front()->GetDecodeTimestamp() < segment_timestamp)) {
+ segment_timestamp = filtered_video.front()->GetDecodeTimestamp();
+ }
+
+ new_media_segment_ = false;
scherkus (not reviewing) 2013/07/24 22:47:41 sanity check: does can_update_offset_ need to be u
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 No. can_update_offset_ tracks the actual media seg
+ new_segment_cb_.Run(segment_timestamp);
+ }
+
+ if (!filtered_audio.empty()) {
+ if (!audio_ || !audio_->Append(filtered_audio))
return false;
+ increase_duration_cb_.Run(filtered_audio.back()->timestamp(), audio_);
+ }
+
+ if (!filtered_video.empty()) {
+ if (!video_ || !video_->Append(filtered_video))
+ return false;
+ increase_duration_cb_.Run(filtered_video.back()->timestamp(), video_);
}
- if (!stream->Append(buffers))
- return false;
- increase_duration_cb_.Run(buffers.back()->timestamp(), stream);
return true;
}
@@ -414,6 +458,60 @@ bool SourceState::OnTextBuffers(
return new_buffers_cb.Run(text_track, buffers);
}
+StreamParser::BufferQueue SourceState::FilterWithAppendWindow(
scherkus (not reviewing) 2013/07/24 22:47:41 do we know how large this queue can get? any reas
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 Nope. Done.
+ const StreamParser::BufferQueue& buffers, bool* needs_keyframe) {
+ StreamParser::BufferQueue filtered_buffers;
+ DCHECK(needs_keyframe);
+
+ // This loop implements steps 1.9, 1.10, & 1.11 of the "Coded frame
+ // processing loop" in the Media Source Extensions spec.
+ // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-coded-frame-processing
+ // These steps filter out buffers that are not within the "append
+ // window" and handles resyncing on the next random access point
+ // (i.e., next keyframe) if a buffer gets dropped.
+ for (StreamParser::BufferQueue::const_iterator itr = buffers.begin();
+ itr != buffers.end(); ++itr) {
+ // Filter out buffers that are outside the append window. Anytime
+ // a buffer gets dropped we need to set |*needs_keyframe| to true
+ // because we can only resume decoding at keyframes.
+ TimeDelta presentation_timestamp = (*itr)->timestamp();
+
+ // TODO(acolwell): Change |frame_end_timestamp| value to
+ // |presentation_timestamp + (*itr)->duration()|, like the spec
+ // requires, once frame durations are actually present in all buffers.
+ TimeDelta frame_end_timestamp = presentation_timestamp;
+ if (presentation_timestamp < append_window_start_ ||
+ frame_end_timestamp > append_window_end_) {
+ DVLOG(1) << "Dropping buffer outside append window."
+ << " presentation_timestamp "
+ << presentation_timestamp.InSecondsF();
+ *needs_keyframe = true;
+
+ // This triggers a discontinuity so we need to treat the next frames
+ // appended within the append window as if they were the beginning of a
+ // new segment.
+ new_media_segment_ = true;
scherkus (not reviewing) 2013/07/24 22:47:41 sanity check: does can_update_offset_ need to be u
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 No. This is a "virtual" media segment (i.e., a new
+ continue;
+ }
+
+ // If |*needs_keyframe| is true then filter out buffers until we
+ // encounter the next keyframe.
+ if (*needs_keyframe) {
+ if (!(*itr)->IsKeyframe()) {
+ DVLOG(1) << "Dropping non-keyframe. presentation_timestamp "
+ << presentation_timestamp.InSecondsF();
+ continue;
+ }
+
+ *needs_keyframe = false;
+ }
+
+ filtered_buffers.push_back(*itr);
+ }
+
+ return filtered_buffers;
+}
+
ChunkDemuxerStream::ChunkDemuxerStream(Type type)
: type_(type),
state_(UNINITIALIZED) {
@@ -514,6 +612,8 @@ Ranges<TimeDelta> ChunkDemuxerStream::GetBufferedRanges(
}
void ChunkDemuxerStream::OnNewMediaSegment(TimeDelta start_timestamp) {
+ DVLOG(2) << "ChunkDemuxerStream::OnNewMediaSegment("
+ << start_timestamp.InSecondsF() << ")";
base::AutoLock auto_lock(lock_);
stream_->OnNewMediaSegment(start_timestamp);
}
@@ -802,15 +902,17 @@ ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
base::Bind(&ChunkDemuxer::CreateDemuxerStream,
base::Unretained(this)),
base::Bind(&ChunkDemuxer::IncreaseDurationIfNecessary,
- base::Unretained(this))));
+ base::Unretained(this)),
+ base::Bind(&ChunkDemuxer::OnNewMediaSegment,
+ base::Unretained(this), id)));
+
source_state->Init(
base::Bind(&ChunkDemuxer::OnSourceInitDone, base::Unretained(this)),
has_audio,
has_video,
base::Bind(&ChunkDemuxer::OnTextBuffers, base::Unretained(this)),
need_key_cb_,
- add_text_track_cb_,
- base::Bind(&ChunkDemuxer::OnNewMediaSegment, base::Unretained(this), id));
+ add_text_track_cb_);
source_state_map_[id] = source_state.release();
return kOk;
@@ -1093,6 +1195,22 @@ void ChunkDemuxer::UnmarkEndOfStream() {
video_->UnmarkEndOfStream();
}
+void ChunkDemuxer::SetAppendWindowStart(const std::string& id,
+ TimeDelta start) {
+ base::AutoLock auto_lock(lock_);
+ DVLOG(1) << "SetAppendWindowStart(" << id << ", "
+ << start.InSecondsF() << ")";
+ CHECK(IsValidId(id));
+ source_state_map_[id]->SetAppendWindowStart(start);
+}
+
+void ChunkDemuxer::SetAppendWindowEnd(const std::string& id, TimeDelta end) {
+ base::AutoLock auto_lock(lock_);
+ DVLOG(1) << "SetAppendWindowEnd(" << id << ", " << end.InSecondsF() << ")";
+ CHECK(IsValidId(id));
+ source_state_map_[id]->SetAppendWindowEnd(end);
+}
+
void ChunkDemuxer::Shutdown() {
DVLOG(1) << "Shutdown()";
base::AutoLock auto_lock(lock_);
@@ -1262,7 +1380,7 @@ bool ChunkDemuxer::OnTextBuffers(
void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id,
TimeDelta timestamp) {
DCHECK(timestamp != kNoTimestamp());
- DVLOG(2) << "OnNewMediaSegment(" << source_id << ", "
+ DVLOG(2) << "ChunkDemuxer::OnNewMediaSegment(" << source_id << ", "
scherkus (not reviewing) 2013/07/24 22:47:41 FYI the DVLOGs in this class are inconsistent re:
acolwell GONE FROM CHROMIUM 2013/07/25 20:39:33 Actually this method doesn't need to exist anymore
<< timestamp.InSecondsF() << ")";
lock_.AssertAcquired();

Powered by Google App Engine
This is Rietveld 408576698