| Index: media/filters/frame_processor.cc | 
| diff --git a/media/filters/frame_processor.cc b/media/filters/frame_processor.cc | 
| index 68f4c613d81aa4cfe71ed63d52a711a81de98ebb..8fb25c34e6c3f7fbc9c1ecd55f64f803badcc911 100644 | 
| --- a/media/filters/frame_processor.cc | 
| +++ b/media/filters/frame_processor.cc | 
| @@ -4,20 +4,137 @@ | 
|  | 
| #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_|. | 
| +  base::TimeDelta last_decode_timestamp() const { | 
| +    return last_decode_timestamp_; | 
| +  } | 
| +  void set_last_decode_timestamp(base::TimeDelta 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); | 
| + | 
| + private: | 
| +  // The decode timestamp of the last coded frame appended in the current coded | 
| +  // frame group. Initially kNoTimestamp(), meaning "unset". | 
| +  base::TimeDelta 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_; | 
| + | 
| +  DISALLOW_COPY_AND_ASSIGN(MseTrackBuffer); | 
| +}; | 
| + | 
| +MseTrackBuffer::MseTrackBuffer(ChunkDemuxerStream* stream) | 
| +    : last_decode_timestamp_(kNoTimestamp()), | 
| +      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_ = kNoTimestamp(); | 
| +  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; | 
| +  } | 
| +} | 
| + | 
| FrameProcessor::FrameProcessor(const UpdateDurationCB& update_duration_cb) | 
| -    : update_duration_cb_(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__; | 
| +  DVLOG(2) << __FUNCTION__ << "()"; | 
| +  STLDeleteValues(&track_buffers_); | 
| } | 
|  | 
| void FrameProcessor::SetSequenceMode(bool sequence_mode) { | 
| @@ -75,6 +192,166 @@ bool FrameProcessor::ProcessFrames( | 
| 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( | 
| +    base::TimeDelta 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::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()); | 
| + | 
| +  const base::TimeDelta frame_end_timestamp = | 
| +      buffer->timestamp() + buffer->duration(); | 
| + | 
| +  // Ignore any buffers which start after |append_window_start| or end after | 
| +  // |append_window_end|.  For simplicity, even those that start before | 
| +  // |append_window_start|. | 
| +  if (buffer->timestamp() > append_window_start || | 
| +      frame_end_timestamp > append_window_end) { | 
| +    // TODO(dalecurtis): Partial append window trimming could also be done | 
| +    // around |append_window_end|, but is not necessary since splice frames | 
| +    // cover overlaps there. | 
| +    return false; | 
| +  } | 
| + | 
| +  // 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; | 
| +  } | 
| + | 
| +  // There's nothing to be done if we have no preroll and the buffer starts on | 
| +  // the append window start. | 
| +  if (buffer->timestamp() == append_window_start && !audio_preroll_buffer_) | 
| +    return false; | 
| + | 
| +  // See if a partial discard can be done around |append_window_start|. | 
| +  DCHECK(buffer->timestamp() <= append_window_start); | 
| +  DCHECK(buffer->IsKeyframe()); | 
| +  DVLOG(1) << "Truncating buffer which overlaps append window start." | 
| +           << " presentation_timestamp " << buffer->timestamp().InSecondsF() | 
| +           << " append_window_start " << append_window_start.InSecondsF(); | 
| + | 
| +  // If this isn't the first buffer discarded by the append window, try to use | 
| +  // the last buffer discarded for preroll.  This ensures that the partially | 
| +  // trimmed buffer can be correctly decoded. | 
| +  if (audio_preroll_buffer_) { | 
| +    // 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()) { | 
| +      buffer->SetPrerollBuffer(audio_preroll_buffer_); | 
| +    } else { | 
| +      // TODO(dalecurtis): Add a MEDIA_LOG() for when this is dropped unused. | 
| +    } | 
| +    audio_preroll_buffer_ = NULL; | 
| +  } | 
| + | 
| +  // Decrease the duration appropriately.  We only need to shorten the buffer if | 
| +  // it overlaps |append_window_start|. | 
| +  if (buffer->timestamp() < append_window_start) { | 
| +    buffer->set_discard_padding(std::make_pair( | 
| +        append_window_start - buffer->timestamp(), base::TimeDelta())); | 
| +    buffer->set_duration(frame_end_timestamp - append_window_start); | 
| +  } | 
| + | 
| +  // Adjust the timestamp of this buffer forward to |append_window_start|.  The | 
| +  // timestamps are always set, even if |buffer|'s timestamp is already set to | 
| +  // |append_window_start|, to ensure the preroll buffer is setup correctly. | 
| +  buffer->set_timestamp(append_window_start); | 
| +  buffer->SetDecodeTimestamp(append_window_start); | 
| +  return true; | 
| +} | 
| + | 
| bool FrameProcessor::ProcessFrame( | 
| const scoped_refptr<StreamParserBuffer>& frame, | 
| base::TimeDelta append_window_start, | 
|  |