Index: media/filters/chunk_demuxer.cc |
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc |
index 29bfc6fb5577860040173e8d7fff5362ded679b7..6e360bb1cdaa907d8e1af0b367f77090295357ea 100644 |
--- a/media/filters/chunk_demuxer.cc |
+++ b/media/filters/chunk_demuxer.cc |
@@ -44,8 +44,7 @@ class SourceState { |
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 +61,11 @@ class SourceState { |
TimeDelta timestamp_offset() const { return timestamp_offset_; } |
+ void set_append_window_start(TimeDelta start) { |
+ append_window_start_ = start; |
+ } |
+ void set_append_window_end(TimeDelta end) { append_window_end_ = end; } |
+ |
private: |
// Called by the |stream_parser_| when a new initialization segment is |
// encountered. |
@@ -72,23 +76,19 @@ class SourceState { |
const VideoDecoderConfig& video_config); |
// 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(); |
// 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,34 @@ 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|. |
+ // Buffers that are inside the append window are appended to the end |
+ // of |filtered_buffers|. |
+ void FilterWithAppendWindow(const StreamParser::BufferQueue& buffers, |
+ bool* needs_keyframe, |
+ StreamParser::BufferQueue* filtered_buffers); |
+ |
CreateDemuxerStreamCB create_demuxer_stream_cb_; |
IncreaseDurationCB increase_duration_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 +137,10 @@ class SourceState { |
scoped_ptr<StreamParser> stream_parser_; |
ChunkDemuxerStream* audio_; |
+ bool audio_needs_keyframe_; |
+ |
ChunkDemuxerStream* video_; |
+ bool video_needs_keyframe_; |
LogCB log_cb_; |
@@ -220,10 +245,14 @@ SourceState::SourceState(scoped_ptr<StreamParser> stream_parser, |
const IncreaseDurationCB& increase_duration_cb) |
: create_demuxer_stream_cb_(create_demuxer_stream_cb), |
increase_duration_cb_(increase_duration_cb), |
+ 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()); |
@@ -234,34 +263,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 +298,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; |
} |
@@ -362,43 +381,71 @@ bool SourceState::OnNewConfigs(bool allow_audio, bool allow_video, |
return success; |
} |
-void SourceState::OnNewMediaSegment( |
- const StreamParser::NewMediaSegmentCB& new_segment_cb, |
- TimeDelta timestamp) { |
- DCHECK(timestamp != kNoTimestamp()); |
- DVLOG(2) << "OnNewMediaSegment(" << timestamp.InSecondsF() << ")"; |
- |
+void SourceState::OnNewMediaSegment() { |
+ DVLOG(2) << "OnNewMediaSegment()"; |
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; |
+ StreamParser::BufferQueue filtered_video; |
+ |
+ FilterWithAppendWindow(audio_buffers, &audio_needs_keyframe_, |
+ &filtered_audio); |
+ |
+ FilterWithAppendWindow(video_buffers, &video_needs_keyframe_, |
+ &filtered_video); |
+ |
+ if (filtered_audio.empty() && filtered_video.empty()) |
+ return true; |
+ |
+ if (new_media_segment_) { |
+ // Find the earliest timestamp in the filtered buffers and use 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; |
+ |
+ if (audio_) |
+ audio_->OnNewMediaSegment(segment_timestamp); |
+ |
+ if (video_) |
+ video_->OnNewMediaSegment(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 +461,58 @@ bool SourceState::OnTextBuffers( |
return new_buffers_cb.Run(text_track, buffers); |
} |
+void SourceState::FilterWithAppendWindow( |
+ const StreamParser::BufferQueue& buffers, bool* needs_keyframe, |
+ StreamParser::BufferQueue* filtered_buffers) { |
+ DCHECK(needs_keyframe); |
+ DCHECK(filtered_buffers); |
+ |
+ // This loop implements steps 1.9, 1.10, & 1.11 of the "Coded frame |
+ // processing loop" in the Media Source Extensions spec. |
+ // 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; |
+ 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); |
+ } |
+} |
+ |
ChunkDemuxerStream::ChunkDemuxerStream(Type type) |
: type_(type), |
state_(UNINITIALIZED) { |
@@ -514,6 +613,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); |
} |
@@ -803,14 +904,14 @@ ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id, |
base::Unretained(this)), |
base::Bind(&ChunkDemuxer::IncreaseDurationIfNecessary, |
base::Unretained(this)))); |
+ |
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 +1194,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]->set_append_window_start(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]->set_append_window_end(end); |
+} |
+ |
void ChunkDemuxer::Shutdown() { |
DVLOG(1) << "Shutdown()"; |
base::AutoLock auto_lock(lock_); |
@@ -1259,20 +1376,6 @@ bool ChunkDemuxer::OnTextBuffers( |
return true; |
} |
-void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id, |
- TimeDelta timestamp) { |
- DCHECK(timestamp != kNoTimestamp()); |
- DVLOG(2) << "OnNewMediaSegment(" << source_id << ", " |
- << timestamp.InSecondsF() << ")"; |
- lock_.AssertAcquired(); |
- |
- CHECK(IsValidId(source_id)); |
- if (audio_ && source_id == source_id_audio_) |
- audio_->OnNewMediaSegment(timestamp); |
- if (video_ && source_id == source_id_video_) |
- video_->OnNewMediaSegment(timestamp); |
-} |
- |
bool ChunkDemuxer::IsValidId(const std::string& source_id) const { |
lock_.AssertAcquired(); |
return source_state_map_.count(source_id) > 0u; |