| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/filters/frame_processor.h" |
| |
| #include <cstdlib> |
| |
| #include "base/stl_util.h" |
| #include "media/base/buffers.h" |
| #include "media/base/stream_parser_buffer.h" |
| |
| namespace media { |
| |
| // Helper class to capture per-track details needed by a frame processor. Some |
| // of this information may be duplicated in the short-term in the associated |
| // ChunkDemuxerStream and SourceBufferStream for a track. |
| // This parallels the MSE spec each of a SourceBuffer's Track Buffers at |
| // http://www.w3.org/TR/media-source/#track-buffers. |
| class MseTrackBuffer { |
| public: |
| explicit MseTrackBuffer(ChunkDemuxerStream* stream); |
| ~MseTrackBuffer(); |
| |
| // Get/set |last_decode_timestamp_|. |
| DecodeTimestamp last_decode_timestamp() const { |
| return last_decode_timestamp_; |
| } |
| void set_last_decode_timestamp(DecodeTimestamp timestamp) { |
| last_decode_timestamp_ = timestamp; |
| } |
| |
| // Get/set |last_frame_duration_|. |
| base::TimeDelta last_frame_duration() const { |
| return last_frame_duration_; |
| } |
| void set_last_frame_duration(base::TimeDelta duration) { |
| last_frame_duration_ = duration; |
| } |
| |
| // Gets |highest_presentation_timestamp_|. |
| base::TimeDelta highest_presentation_timestamp() const { |
| return highest_presentation_timestamp_; |
| } |
| |
| // Get/set |needs_random_access_point_|. |
| bool needs_random_access_point() const { |
| return needs_random_access_point_; |
| } |
| void set_needs_random_access_point(bool needs_random_access_point) { |
| needs_random_access_point_ = needs_random_access_point; |
| } |
| |
| // Gets a pointer to this track's ChunkDemuxerStream. |
| ChunkDemuxerStream* stream() const { return stream_; } |
| |
| // Unsets |last_decode_timestamp_|, unsets |last_frame_duration_|, |
| // unsets |highest_presentation_timestamp_|, and sets |
| // |needs_random_access_point_| to true. |
| void Reset(); |
| |
| // If |highest_presentation_timestamp_| is unset or |timestamp| is greater |
| // than |highest_presentation_timestamp_|, sets |
| // |highest_presentation_timestamp_| to |timestamp|. Note that bidirectional |
| // prediction between coded frames can cause |timestamp| to not be |
| // monotonically increasing even though the decode timestamps are |
| // monotonically increasing. |
| void SetHighestPresentationTimestampIfIncreased(base::TimeDelta timestamp); |
| |
| // Adds |frame| to the end of |processed_frames_|. |
| void EnqueueProcessedFrame(const scoped_refptr<StreamParserBuffer>& frame); |
| |
| // Appends |processed_frames_|, if not empty, to |stream_| and clears |
| // |processed_frames_|. Returns false if append failed, true otherwise. |
| // |processed_frames_| is cleared in both cases. |
| bool FlushProcessedFrames(); |
| |
| private: |
| // The decode timestamp of the last coded frame appended in the current coded |
| // frame group. Initially kNoTimestamp(), meaning "unset". |
| DecodeTimestamp last_decode_timestamp_; |
| |
| // The coded frame duration of the last coded frame appended in the current |
| // coded frame group. Initially kNoTimestamp(), meaning "unset". |
| base::TimeDelta last_frame_duration_; |
| |
| // The highest presentation timestamp encountered in a coded frame appended |
| // in the current coded frame group. Initially kNoTimestamp(), meaning |
| // "unset". |
| base::TimeDelta highest_presentation_timestamp_; |
| |
| // Keeps track of whether the track buffer is waiting for a random access |
| // point coded frame. Initially set to true to indicate that a random access |
| // point coded frame is needed before anything can be added to the track |
| // buffer. |
| bool needs_random_access_point_; |
| |
| // Pointer to the stream associated with this track. The stream is not owned |
| // by |this|. |
| ChunkDemuxerStream* const stream_; |
| |
| // Queue of processed frames that have not yet been appended to |stream_|. |
| // EnqueueProcessedFrame() adds to this queue, and FlushProcessedFrames() |
| // clears it. |
| StreamParser::BufferQueue processed_frames_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MseTrackBuffer); |
| }; |
| |
| MseTrackBuffer::MseTrackBuffer(ChunkDemuxerStream* stream) |
| : last_decode_timestamp_(kNoDecodeTimestamp()), |
| last_frame_duration_(kNoTimestamp()), |
| highest_presentation_timestamp_(kNoTimestamp()), |
| needs_random_access_point_(true), |
| stream_(stream) { |
| DCHECK(stream_); |
| } |
| |
| MseTrackBuffer::~MseTrackBuffer() { |
| DVLOG(2) << __FUNCTION__ << "()"; |
| } |
| |
| void MseTrackBuffer::Reset() { |
| DVLOG(2) << __FUNCTION__ << "()"; |
| |
| last_decode_timestamp_ = kNoDecodeTimestamp(); |
| last_frame_duration_ = kNoTimestamp(); |
| highest_presentation_timestamp_ = kNoTimestamp(); |
| needs_random_access_point_ = true; |
| } |
| |
| void MseTrackBuffer::SetHighestPresentationTimestampIfIncreased( |
| base::TimeDelta timestamp) { |
| if (highest_presentation_timestamp_ == kNoTimestamp() || |
| timestamp > highest_presentation_timestamp_) { |
| highest_presentation_timestamp_ = timestamp; |
| } |
| } |
| |
| void MseTrackBuffer::EnqueueProcessedFrame( |
| const scoped_refptr<StreamParserBuffer>& frame) { |
| processed_frames_.push_back(frame); |
| } |
| |
| bool MseTrackBuffer::FlushProcessedFrames() { |
| if (processed_frames_.empty()) |
| return true; |
| |
| bool result = stream_->Append(processed_frames_); |
| processed_frames_.clear(); |
| DVLOG_IF(3, !result) << __FUNCTION__ |
| << "(): Failure appending processed frames to stream"; |
| |
| return result; |
| } |
| |
| FrameProcessor::FrameProcessor(const UpdateDurationCB& update_duration_cb) |
| : sequence_mode_(false), |
| group_start_timestamp_(kNoTimestamp()), |
| update_duration_cb_(update_duration_cb) { |
| DVLOG(2) << __FUNCTION__ << "()"; |
| DCHECK(!update_duration_cb.is_null()); |
| } |
| |
| FrameProcessor::~FrameProcessor() { |
| DVLOG(2) << __FUNCTION__ << "()"; |
| STLDeleteValues(&track_buffers_); |
| } |
| |
| void FrameProcessor::SetSequenceMode(bool sequence_mode) { |
| DVLOG(2) << __FUNCTION__ << "(" << sequence_mode << ")"; |
| |
| // Per April 1, 2014 MSE spec editor's draft: |
| // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/media-source.html#widl-SourceBuffer-mode |
| // Step 7: If the new mode equals "sequence", then set the group start |
| // timestamp to the group end timestamp. |
| if (sequence_mode) { |
| DCHECK(kNoTimestamp() != group_end_timestamp_); |
| group_start_timestamp_ = group_end_timestamp_; |
| } |
| |
| // Step 8: Update the attribute to new mode. |
| sequence_mode_ = sequence_mode; |
| } |
| |
| bool FrameProcessor::ProcessFrames( |
| const StreamParser::BufferQueue& audio_buffers, |
| const StreamParser::BufferQueue& video_buffers, |
| const StreamParser::TextBufferQueueMap& text_map, |
| base::TimeDelta append_window_start, |
| base::TimeDelta append_window_end, |
| bool* new_media_segment, |
| base::TimeDelta* timestamp_offset) { |
| StreamParser::BufferQueue frames; |
| if (!MergeBufferQueues(audio_buffers, video_buffers, text_map, &frames)) { |
| DVLOG(2) << "Parse error discovered while merging parser's buffers"; |
| return false; |
| } |
| |
| DCHECK(!frames.empty()); |
| |
| // Implements the coded frame processing algorithm's outer loop for step 1. |
| // Note that ProcessFrame() implements an inner loop for a single frame that |
| // handles "jump to the Loop Top step to restart processing of the current |
| // coded frame" per April 1, 2014 MSE spec editor's draft: |
| // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/ |
| // media-source.html#sourcebuffer-coded-frame-processing |
| // 1. For each coded frame in the media segment run the following steps: |
| for (StreamParser::BufferQueue::const_iterator frames_itr = frames.begin(); |
| frames_itr != frames.end(); ++frames_itr) { |
| if (!ProcessFrame(*frames_itr, append_window_start, append_window_end, |
| timestamp_offset, new_media_segment)) { |
| FlushProcessedFrames(); |
| return false; |
| } |
| } |
| |
| if (!FlushProcessedFrames()) |
| return false; |
| |
| // 2. - 4. Are handled by the WebMediaPlayer / Pipeline / Media Element. |
| |
| // Step 5: |
| update_duration_cb_.Run(group_end_timestamp_); |
| |
| return true; |
| } |
| |
| void FrameProcessor::SetGroupStartTimestampIfInSequenceMode( |
| base::TimeDelta timestamp_offset) { |
| DVLOG(2) << __FUNCTION__ << "(" << timestamp_offset.InSecondsF() << ")"; |
| DCHECK(kNoTimestamp() != timestamp_offset); |
| if (sequence_mode_) |
| group_start_timestamp_ = timestamp_offset; |
| |
| // Changes to timestampOffset should invalidate the preroll buffer. |
| audio_preroll_buffer_ = NULL; |
| } |
| |
| bool FrameProcessor::AddTrack(StreamParser::TrackId id, |
| ChunkDemuxerStream* stream) { |
| DVLOG(2) << __FUNCTION__ << "(): id=" << id; |
| |
| MseTrackBuffer* existing_track = FindTrack(id); |
| DCHECK(!existing_track); |
| if (existing_track) |
| return false; |
| |
| track_buffers_[id] = new MseTrackBuffer(stream); |
| return true; |
| } |
| |
| bool FrameProcessor::UpdateTrack(StreamParser::TrackId old_id, |
| StreamParser::TrackId new_id) { |
| DVLOG(2) << __FUNCTION__ << "() : old_id=" << old_id << ", new_id=" << new_id; |
| |
| if (old_id == new_id || !FindTrack(old_id) || FindTrack(new_id)) |
| return false; |
| |
| track_buffers_[new_id] = track_buffers_[old_id]; |
| CHECK_EQ(1u, track_buffers_.erase(old_id)); |
| return true; |
| } |
| |
| void FrameProcessor::SetAllTrackBuffersNeedRandomAccessPoint() { |
| for (TrackBufferMap::iterator itr = track_buffers_.begin(); |
| itr != track_buffers_.end(); |
| ++itr) { |
| itr->second->set_needs_random_access_point(true); |
| } |
| } |
| |
| void FrameProcessor::Reset() { |
| DVLOG(2) << __FUNCTION__ << "()"; |
| for (TrackBufferMap::iterator itr = track_buffers_.begin(); |
| itr != track_buffers_.end(); ++itr) { |
| itr->second->Reset(); |
| } |
| } |
| |
| void FrameProcessor::OnPossibleAudioConfigUpdate( |
| const AudioDecoderConfig& config) { |
| DCHECK(config.IsValidConfig()); |
| |
| // Always clear the preroll buffer when a config update is received. |
| audio_preroll_buffer_ = NULL; |
| |
| if (config.Matches(current_audio_config_)) |
| return; |
| |
| current_audio_config_ = config; |
| sample_duration_ = base::TimeDelta::FromSecondsD( |
| 1.0 / current_audio_config_.samples_per_second()); |
| } |
| |
| MseTrackBuffer* FrameProcessor::FindTrack(StreamParser::TrackId id) { |
| TrackBufferMap::iterator itr = track_buffers_.find(id); |
| if (itr == track_buffers_.end()) |
| return NULL; |
| |
| return itr->second; |
| } |
| |
| void FrameProcessor::NotifyNewMediaSegmentStarting( |
| DecodeTimestamp segment_timestamp) { |
| DVLOG(2) << __FUNCTION__ << "(" << segment_timestamp.InSecondsF() << ")"; |
| |
| for (TrackBufferMap::iterator itr = track_buffers_.begin(); |
| itr != track_buffers_.end(); |
| ++itr) { |
| itr->second->stream()->OnNewMediaSegment(segment_timestamp); |
| } |
| } |
| |
| bool FrameProcessor::FlushProcessedFrames() { |
| DVLOG(2) << __FUNCTION__ << "()"; |
| |
| bool result = true; |
| for (TrackBufferMap::iterator itr = track_buffers_.begin(); |
| itr != track_buffers_.end(); |
| ++itr) { |
| if (!itr->second->FlushProcessedFrames()) |
| result = false; |
| } |
| |
| return result; |
| } |
| |
| bool FrameProcessor::HandlePartialAppendWindowTrimming( |
| base::TimeDelta append_window_start, |
| base::TimeDelta append_window_end, |
| const scoped_refptr<StreamParserBuffer>& buffer) { |
| DCHECK(buffer->duration() > base::TimeDelta()); |
| DCHECK_EQ(DemuxerStream::AUDIO, buffer->type()); |
| DCHECK(buffer->IsKeyframe()); |
| |
| const base::TimeDelta frame_end_timestamp = |
| buffer->timestamp() + buffer->duration(); |
| |
| // If the buffer is entirely before |append_window_start|, save it as preroll |
| // for the first buffer which overlaps |append_window_start|. |
| if (buffer->timestamp() < append_window_start && |
| frame_end_timestamp <= append_window_start) { |
| audio_preroll_buffer_ = buffer; |
| return false; |
| } |
| |
| // If the buffer is entirely after |append_window_end| there's nothing to do. |
| if (buffer->timestamp() >= append_window_end) |
| return false; |
| |
| DCHECK(buffer->timestamp() >= append_window_start || |
| frame_end_timestamp > append_window_start); |
| |
| bool processed_buffer = false; |
| |
| // If we have a preroll buffer see if we can attach it to the first buffer |
| // overlapping or after |append_window_start|. |
| if (audio_preroll_buffer_.get()) { |
| // We only want to use the preroll buffer if it directly precedes (less |
| // than one sample apart) the current buffer. |
| const int64 delta = std::abs((audio_preroll_buffer_->timestamp() + |
| audio_preroll_buffer_->duration() - |
| buffer->timestamp()).InMicroseconds()); |
| if (delta < sample_duration_.InMicroseconds()) { |
| DVLOG(1) << "Attaching audio preroll buffer [" |
| << audio_preroll_buffer_->timestamp().InSecondsF() << ", " |
| << (audio_preroll_buffer_->timestamp() + |
| audio_preroll_buffer_->duration()).InSecondsF() << ") to " |
| << buffer->timestamp().InSecondsF(); |
| buffer->SetPrerollBuffer(audio_preroll_buffer_); |
| processed_buffer = true; |
| } else { |
| // TODO(dalecurtis): Add a MEDIA_LOG() for when this is dropped unused. |
| } |
| audio_preroll_buffer_ = NULL; |
| } |
| |
| // See if a partial discard can be done around |append_window_start|. |
| if (buffer->timestamp() < append_window_start) { |
| DVLOG(1) << "Truncating buffer which overlaps append window start." |
| << " presentation_timestamp " << buffer->timestamp().InSecondsF() |
| << " frame_end_timestamp " << frame_end_timestamp.InSecondsF() |
| << " append_window_start " << append_window_start.InSecondsF(); |
| |
| // Mark the overlapping portion of the buffer for discard. |
| buffer->set_discard_padding(std::make_pair( |
| append_window_start - buffer->timestamp(), base::TimeDelta())); |
| |
| // Adjust the timestamp of this buffer forward to |append_window_start| and |
| // decrease the duration to compensate. |
| buffer->set_timestamp(append_window_start); |
| buffer->SetDecodeTimestamp( |
| DecodeTimestamp::FromPresentationTime(append_window_start)); |
| buffer->set_duration(frame_end_timestamp - append_window_start); |
| processed_buffer = true; |
| } |
| |
| // See if a partial discard can be done around |append_window_end|. |
| if (frame_end_timestamp > append_window_end) { |
| DVLOG(1) << "Truncating buffer which overlaps append window end." |
| << " presentation_timestamp " << buffer->timestamp().InSecondsF() |
| << " frame_end_timestamp " << frame_end_timestamp.InSecondsF() |
| << " append_window_end " << append_window_end.InSecondsF(); |
| |
| // Mark the overlapping portion of the buffer for discard. |
| buffer->set_discard_padding( |
| std::make_pair(buffer->discard_padding().first, |
| frame_end_timestamp - append_window_end)); |
| |
| // Decrease the duration of the buffer to remove the discarded portion. |
| buffer->set_duration(append_window_end - buffer->timestamp()); |
| processed_buffer = true; |
| } |
| |
| return processed_buffer; |
| } |
| |
| bool FrameProcessor::ProcessFrame( |
| const scoped_refptr<StreamParserBuffer>& frame, |
| base::TimeDelta append_window_start, |
| base::TimeDelta append_window_end, |
| base::TimeDelta* timestamp_offset, |
| bool* new_media_segment) { |
| // Implements the loop within step 1 of the coded frame processing algorithm |
| // for a single input frame per April 1, 2014 MSE spec editor's draft: |
| // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/ |
| // media-source.html#sourcebuffer-coded-frame-processing |
| |
| while (true) { |
| // 1. Loop Top: Let presentation timestamp be a double precision floating |
| // point representation of the coded frame's presentation timestamp in |
| // seconds. |
| // 2. Let decode timestamp be a double precision floating point |
| // representation of the coded frame's decode timestamp in seconds. |
| // 3. Let frame duration be a double precision floating point representation |
| // of the coded frame's duration in seconds. |
| // We use base::TimeDelta and DecodeTimestamp instead of double. |
| base::TimeDelta presentation_timestamp = frame->timestamp(); |
| DecodeTimestamp decode_timestamp = frame->GetDecodeTimestamp(); |
| base::TimeDelta frame_duration = frame->duration(); |
| |
| DVLOG(3) << __FUNCTION__ << ": Processing frame " |
| << "Type=" << frame->type() |
| << ", TrackID=" << frame->track_id() |
| << ", PTS=" << presentation_timestamp.InSecondsF() |
| << ", DTS=" << decode_timestamp.InSecondsF() |
| << ", DUR=" << frame_duration.InSecondsF() |
| << ", RAP=" << frame->IsKeyframe(); |
| |
| // Sanity check the timestamps. |
| if (presentation_timestamp == kNoTimestamp()) { |
| DVLOG(2) << __FUNCTION__ << ": Unknown frame PTS"; |
| return false; |
| } |
| if (decode_timestamp == kNoDecodeTimestamp()) { |
| DVLOG(2) << __FUNCTION__ << ": Unknown frame DTS"; |
| return false; |
| } |
| if (decode_timestamp.ToPresentationTime() > presentation_timestamp) { |
| // TODO(wolenetz): Determine whether DTS>PTS should really be allowed. See |
| // http://crbug.com/354518. |
| DVLOG(2) << __FUNCTION__ << ": WARNING: Frame DTS(" |
| << decode_timestamp.InSecondsF() << ") > PTS(" |
| << presentation_timestamp.InSecondsF() << ")"; |
| } |
| |
| // TODO(acolwell/wolenetz): All stream parsers must emit valid (positive) |
| // frame durations. For now, we allow non-negative frame duration. |
| // See http://crbug.com/351166. |
| if (frame_duration == kNoTimestamp()) { |
| DVLOG(2) << __FUNCTION__ << ": Frame missing duration (kNoTimestamp())"; |
| return false; |
| } |
| if (frame_duration < base::TimeDelta()) { |
| DVLOG(2) << __FUNCTION__ << ": Frame duration negative: " |
| << frame_duration.InSecondsF(); |
| return false; |
| } |
| |
| // 4. If mode equals "sequence" and group start timestamp is set, then run |
| // the following steps: |
| if (sequence_mode_ && group_start_timestamp_ != kNoTimestamp()) { |
| // 4.1. Set timestampOffset equal to group start timestamp - |
| // presentation timestamp. |
| *timestamp_offset = group_start_timestamp_ - presentation_timestamp; |
| |
| DVLOG(3) << __FUNCTION__ << ": updated timestampOffset is now " |
| << timestamp_offset->InSecondsF(); |
| |
| // 4.2. Set group end timestamp equal to group start timestamp. |
| group_end_timestamp_ = group_start_timestamp_; |
| |
| // 4.3. Set the need random access point flag on all track buffers to |
| // true. |
| SetAllTrackBuffersNeedRandomAccessPoint(); |
| |
| // 4.4. Unset group start timestamp. |
| group_start_timestamp_ = kNoTimestamp(); |
| } |
| |
| // 5. If timestampOffset is not 0, then run the following steps: |
| if (*timestamp_offset != base::TimeDelta()) { |
| // 5.1. Add timestampOffset to the presentation timestamp. |
| // Note: |frame| PTS is only updated if it survives discontinuity |
| // processing. |
| presentation_timestamp += *timestamp_offset; |
| |
| // 5.2. Add timestampOffset to the decode timestamp. |
| // Frame DTS is only updated if it survives discontinuity processing. |
| decode_timestamp += *timestamp_offset; |
| } |
| |
| // 6. Let track buffer equal the track buffer that the coded frame will be |
| // added to. |
| |
| // Remap audio and video track types to their special singleton identifiers. |
| StreamParser::TrackId track_id = kAudioTrackId; |
| switch (frame->type()) { |
| case DemuxerStream::AUDIO: |
| break; |
| case DemuxerStream::VIDEO: |
| track_id = kVideoTrackId; |
| break; |
| case DemuxerStream::TEXT: |
| track_id = frame->track_id(); |
| break; |
| case DemuxerStream::UNKNOWN: |
| case DemuxerStream::NUM_TYPES: |
| DCHECK(false) << ": Invalid frame type " << frame->type(); |
| return false; |
| } |
| |
| MseTrackBuffer* track_buffer = FindTrack(track_id); |
| if (!track_buffer) { |
| DVLOG(2) << __FUNCTION__ << ": Unknown track: type=" << frame->type() |
| << ", frame processor track id=" << track_id |
| << ", parser track id=" << frame->track_id(); |
| return false; |
| } |
| |
| // 7. If last decode timestamp for track buffer is set and decode timestamp |
| // is less than last decode timestamp |
| // OR |
| // If last decode timestamp for track buffer is set and the difference |
| // between decode timestamp and last decode timestamp is greater than 2 |
| // times last frame duration: |
| DecodeTimestamp last_decode_timestamp = |
| track_buffer->last_decode_timestamp(); |
| if (last_decode_timestamp != kNoDecodeTimestamp()) { |
| base::TimeDelta dts_delta = decode_timestamp - last_decode_timestamp; |
| if (dts_delta < base::TimeDelta() || |
| dts_delta > 2 * track_buffer->last_frame_duration()) { |
| // 7.1. If mode equals "segments": Set group end timestamp to |
| // presentation timestamp. |
| // If mode equals "sequence": Set group start timestamp equal to |
| // the group end timestamp. |
| if (!sequence_mode_) { |
| group_end_timestamp_ = presentation_timestamp; |
| // 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; |
| } else { |
| DVLOG(3) << __FUNCTION__ << " : Sequence mode discontinuity, GETS: " |
| << group_end_timestamp_.InSecondsF(); |
| DCHECK(kNoTimestamp() != group_end_timestamp_); |
| group_start_timestamp_ = group_end_timestamp_; |
| } |
| |
| // 7.2. - 7.5.: |
| Reset(); |
| |
| // 7.6. Jump to the Loop Top step above to restart processing of the |
| // current coded frame. |
| DVLOG(3) << __FUNCTION__ << ": Discontinuity: reprocessing frame"; |
| continue; |
| } |
| } |
| |
| // 9. Let frame end timestamp equal the sum of presentation timestamp and |
| // frame duration. |
| base::TimeDelta frame_end_timestamp = |
| presentation_timestamp + frame_duration; |
| |
| // 10. If presentation timestamp is less than appendWindowStart, then set |
| // the need random access point flag to true, drop the coded frame, and |
| // jump to the top of the loop to start processing the next coded |
| // frame. |
| // Note: We keep the result of partial discard of a buffer that overlaps |
| // |append_window_start| and does not end after |append_window_end|. |
| // 11. If frame end timestamp is greater than appendWindowEnd, then set the |
| // need random access point flag to true, drop the coded frame, and jump |
| // to the top of the loop to start processing the next coded frame. |
| frame->set_timestamp(presentation_timestamp); |
| frame->SetDecodeTimestamp(decode_timestamp); |
| if (track_buffer->stream()->supports_partial_append_window_trimming() && |
| HandlePartialAppendWindowTrimming(append_window_start, |
| append_window_end, |
| frame)) { |
| // |frame| has been partially trimmed or had preroll added. Though |
| // |frame|'s duration may have changed, do not update |frame_duration| |
| // here, so |track_buffer|'s last frame duration update uses original |
| // frame duration and reduces spurious discontinuity detection. |
| decode_timestamp = frame->GetDecodeTimestamp(); |
| presentation_timestamp = frame->timestamp(); |
| frame_end_timestamp = frame->timestamp() + frame->duration(); |
| } |
| |
| if (presentation_timestamp < append_window_start || |
| frame_end_timestamp > append_window_end) { |
| track_buffer->set_needs_random_access_point(true); |
| DVLOG(3) << "Dropping frame that is outside append window."; |
| return true; |
| } |
| |
| // Note: This step is relocated, versus April 1 spec, to allow append window |
| // processing to first filter coded frames shifted by |timestamp_offset_| in |
| // such a way that their PTS is negative. |
| // 8. If the presentation timestamp or decode timestamp is less than the |
| // presentation start time, then run the end of stream algorithm with the |
| // error parameter set to "decode", and abort these steps. |
| DCHECK(presentation_timestamp >= base::TimeDelta()); |
| if (decode_timestamp < DecodeTimestamp()) { |
| // B-frames may still result in negative DTS here after being shifted by |
| // |timestamp_offset_|. |
| DVLOG(2) << __FUNCTION__ |
| << ": frame PTS=" << presentation_timestamp.InSecondsF() |
| << " has negative DTS=" << decode_timestamp.InSecondsF() |
| << " after applying timestampOffset, handling any discontinuity," |
| << " and filtering against append window"; |
| return false; |
| } |
| |
| // 12. If the need random access point flag on track buffer equals true, |
| // then run the following steps: |
| if (track_buffer->needs_random_access_point()) { |
| // 12.1. If the coded frame is not a random access point, then drop the |
| // coded frame and jump to the top of the loop to start processing |
| // the next coded frame. |
| if (!frame->IsKeyframe()) { |
| DVLOG(3) << __FUNCTION__ |
| << ": Dropping frame that is not a random access point"; |
| return true; |
| } |
| |
| // 12.2. Set the need random access point flag on track buffer to false. |
| track_buffer->set_needs_random_access_point(false); |
| } |
| |
| // We now have a processed buffer to append to the track buffer's stream. |
| // If it is the first in a new media segment or following a discontinuity, |
| // notify all the track buffers' streams that a new segment is beginning. |
| if (*new_media_segment) { |
| // First, complete the append to track buffer streams of previous media |
| // segment's frames, if any. |
| if (!FlushProcessedFrames()) |
| return false; |
| |
| *new_media_segment = false; |
| |
| // TODO(acolwell/wolenetz): This should be changed to a presentation |
| // timestamp. See http://crbug.com/402502 |
| NotifyNewMediaSegmentStarting(decode_timestamp); |
| } |
| |
| DVLOG(3) << __FUNCTION__ << ": Sending processed frame to stream, " |
| << "PTS=" << presentation_timestamp.InSecondsF() |
| << ", DTS=" << decode_timestamp.InSecondsF(); |
| |
| // Steps 13-18: Note, we optimize by appending groups of contiguous |
| // processed frames for each track buffer at end of ProcessFrames() or prior |
| // to NotifyNewMediaSegmentStarting(). |
| // TODO(wolenetz): Refactor SourceBufferStream to conform to spec GC timing. |
| // See http://crbug.com/371197. |
| track_buffer->EnqueueProcessedFrame(frame); |
| |
| // 19. Set last decode timestamp for track buffer to decode timestamp. |
| track_buffer->set_last_decode_timestamp(decode_timestamp); |
| |
| // 20. Set last frame duration for track buffer to frame duration. |
| track_buffer->set_last_frame_duration(frame_duration); |
| |
| // 21. If highest presentation timestamp for track buffer is unset or frame |
| // end timestamp is greater than highest presentation timestamp, then |
| // set highest presentation timestamp for track buffer to frame end |
| // timestamp. |
| track_buffer->SetHighestPresentationTimestampIfIncreased( |
| frame_end_timestamp); |
| |
| // 22. If frame end timestamp is greater than group end timestamp, then set |
| // group end timestamp equal to frame end timestamp. |
| if (frame_end_timestamp > group_end_timestamp_) |
| group_end_timestamp_ = frame_end_timestamp; |
| DCHECK(group_end_timestamp_ >= base::TimeDelta()); |
| |
| return true; |
| } |
| |
| NOTREACHED(); |
| return false; |
| } |
| |
| } // namespace media |