blob: 68f4c613d81aa4cfe71ed63d52a711a81de98ebb [file] [log] [blame]
// 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 "base/stl_util.h"
#include "media/base/buffers.h"
#include "media/base/stream_parser_buffer.h"
namespace media {
FrameProcessor::FrameProcessor(const UpdateDurationCB& update_duration_cb)
: update_duration_cb_(update_duration_cb) {
DVLOG(2) << __FUNCTION__ << "()";
DCHECK(!update_duration_cb.is_null());
}
FrameProcessor::~FrameProcessor() {
DVLOG(2) << __FUNCTION__;
}
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)) {
return false;
}
}
// 2. - 4. Are handled by the WebMediaPlayer / Pipeline / Media Element.
// Step 5:
update_duration_cb_.Run(group_end_timestamp_);
return true;
}
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 instead of double.
base::TimeDelta presentation_timestamp = frame->timestamp();
base::TimeDelta 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();
// Sanity check the timestamps.
if (presentation_timestamp == kNoTimestamp()) {
DVLOG(2) << __FUNCTION__ << ": Unknown frame PTS";
return false;
}
if (decode_timestamp == kNoTimestamp()) {
DVLOG(2) << __FUNCTION__ << ": Unknown frame DTS";
return false;
}
if (decode_timestamp > 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:
base::TimeDelta last_decode_timestamp =
track_buffer->last_decode_timestamp();
if (last_decode_timestamp != kNoTimestamp()) {
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.
const 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)) {
// If |frame| was shortened a discontinuity may exist, so treat the next
// frames appended as if they were the beginning of a new media segment.
if (frame->timestamp() != presentation_timestamp && !sequence_mode_)
*new_media_segment = true;
// |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();
// The end timestamp of the frame should be unchanged.
DCHECK(frame_end_timestamp == presentation_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.";
if (!sequence_mode_) {
// This also 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;
}
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 < base::TimeDelta()) {
// 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) {
*new_media_segment = false;
NotifyNewMediaSegmentStarting(decode_timestamp);
}
DVLOG(3) << __FUNCTION__ << ": Sending processed frame to stream, "
<< "PTS=" << presentation_timestamp.InSecondsF()
<< ", DTS=" << decode_timestamp.InSecondsF();
// Steps 13-18:
// TODO(wolenetz): Collect and emit more than one buffer at a time, if
// possible. Also refactor SourceBufferStream to conform to spec GC timing.
// See http://crbug.com/371197.
StreamParser::BufferQueue buffer_to_append;
buffer_to_append.push_back(frame);
if (!track_buffer->stream()->Append(buffer_to_append)) {
DVLOG(3) << __FUNCTION__ << ": Failure appending frame to stream";
return false;
}
// 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