OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "media/filters/frame_processor.h" |
| 6 |
| 7 #include "base/stl_util.h" |
| 8 #include "media/base/buffers.h" |
| 9 #include "media/base/stream_parser_buffer.h" |
| 10 |
| 11 namespace media { |
| 12 |
| 13 FrameProcessor::FrameProcessor(const UpdateDurationCB& update_duration_cb) |
| 14 : group_start_timestamp_(kNoTimestamp()), |
| 15 group_start_timestamp_set_(false), |
| 16 update_duration_cb_(update_duration_cb) { |
| 17 DVLOG(2) << __FUNCTION__ << "()"; |
| 18 DCHECK(!update_duration_cb.is_null()); |
| 19 } |
| 20 |
| 21 FrameProcessor::~FrameProcessor() { |
| 22 DVLOG(2) << __FUNCTION__; |
| 23 } |
| 24 |
| 25 void FrameProcessor::SetSequenceMode(bool sequence_mode) { |
| 26 DVLOG(2) << __FUNCTION__ << "(" << sequence_mode << ")"; |
| 27 |
| 28 // http://www.w3.org/TR/media-source/#widl-SourceBuffer-mode |
| 29 // Step 7: If the new mode equals "sequence", then set the group start |
| 30 // timestamp to the highest presentation end timestamp. |
| 31 if (sequence_mode) { |
| 32 group_start_timestamp_ = highest_presentation_end_timestamp_; |
| 33 group_start_timestamp_set_ = true; |
| 34 } |
| 35 |
| 36 // Step 8: Update the attribute to new mode. |
| 37 sequence_mode_ = sequence_mode; |
| 38 } |
| 39 |
| 40 void FrameProcessor::SetGroupStartTimestampIfInSequenceMode( |
| 41 base::TimeDelta timestamp_offset) { |
| 42 DVLOG(2) << __FUNCTION__ << "(" << timestamp_offset.InSecondsF() << ")"; |
| 43 if (sequence_mode_) { |
| 44 group_start_timestamp_ = timestamp_offset; |
| 45 group_start_timestamp_set_ = true; |
| 46 } |
| 47 } |
| 48 |
| 49 bool FrameProcessor::ProcessFrames( |
| 50 const StreamParser::BufferQueue& audio_buffers, |
| 51 const StreamParser::BufferQueue& video_buffers, |
| 52 const StreamParser::TextBufferQueueMap& text_map, |
| 53 base::TimeDelta append_window_start, |
| 54 base::TimeDelta append_window_end, |
| 55 bool* new_media_segment, |
| 56 base::TimeDelta* timestamp_offset) { |
| 57 StreamParser::BufferQueue frames; |
| 58 if (!MergeBufferQueues(audio_buffers, video_buffers, text_map, &frames)) { |
| 59 DVLOG(2) << "Parse error discovered while merging parser's buffers"; |
| 60 return false; |
| 61 } |
| 62 |
| 63 DCHECK(!frames.empty()); |
| 64 |
| 65 // Implements the coded frame processing algorithm's outer loop for step 1. |
| 66 // Note that ProcessFrame() implements an inner loop for a single frame that |
| 67 // handles "jump to the Loop Top step to restart processing of the current |
| 68 // coded frame" per: |
| 69 // http://www.w3.org/TR/media-source/#sourcebuffer-coded-frame-processing |
| 70 // 1. For each coded frame in the media segment run the following steps: |
| 71 for (StreamParser::BufferQueue::const_iterator frames_itr = frames.begin(); |
| 72 frames_itr != frames.end(); ++frames_itr) { |
| 73 if (!ProcessFrame(*frames_itr, append_window_start, append_window_end, |
| 74 timestamp_offset, new_media_segment)) { |
| 75 return false; |
| 76 } |
| 77 } |
| 78 |
| 79 // 2. - 4. Are handled by the WebMediaPlayer / Pipeline / Media Element. |
| 80 // 5. If the media segment contains data beyond the current duration, then |
| 81 // run the duration change algorithm with new duration set to the maximum |
| 82 // of the current duration and the highest end timestamp reported by |
| 83 // HTMLMediaElement.buffered. |
| 84 update_duration_cb_.Run(highest_presentation_end_timestamp_); |
| 85 return true; |
| 86 } |
| 87 |
| 88 bool FrameProcessor::ProcessFrame(scoped_refptr<StreamParserBuffer> frame, |
| 89 base::TimeDelta append_window_start, |
| 90 base::TimeDelta append_window_end, |
| 91 base::TimeDelta* timestamp_offset, |
| 92 bool* new_media_segment) { |
| 93 // Implements the loop within step 1 of the coded frame processing algorithm |
| 94 // for a single input frame per: |
| 95 // http://www.w3.org/TR/media-source/#sourcebuffer-coded-frame-processing |
| 96 |
| 97 while (true) { |
| 98 // 1. Loop Top: Let presentation timestamp be a double precision floating |
| 99 // point representation of the coded frame's presentation timestamp in |
| 100 // seconds. |
| 101 // 2. Let decode timestamp be a double precision floating point |
| 102 // representation of the coded frame's decode timestamp in seconds. |
| 103 // 3. Let frame duration be a double precision floating point representation |
| 104 // of the coded frame's duration in seconds. |
| 105 // We use base::TimeDelta instead of double. |
| 106 base::TimeDelta presentation_timestamp = frame->timestamp(); |
| 107 base::TimeDelta decode_timestamp = frame->GetDecodeTimestamp(); |
| 108 base::TimeDelta frame_duration = frame->duration(); |
| 109 |
| 110 DVLOG(3) << __FUNCTION__ << ": Processing frame " |
| 111 << "Type=" << frame->type() |
| 112 << ", TrackID=" << frame->track_id() |
| 113 << ", PTS=" << presentation_timestamp.InSecondsF() |
| 114 << ", DTS=" << decode_timestamp.InSecondsF() |
| 115 << ", DUR=" << frame_duration.InSecondsF(); |
| 116 |
| 117 // Sanity check the timestamps. |
| 118 if (presentation_timestamp < base::TimeDelta()) { |
| 119 DVLOG(2) << __FUNCTION__ << ": Negative or unknown frame PTS: " |
| 120 << presentation_timestamp.InSecondsF(); |
| 121 return false; |
| 122 } |
| 123 if (decode_timestamp < base::TimeDelta()) { |
| 124 DVLOG(2) << __FUNCTION__ << ": Negative or unknown frame DTS: " |
| 125 << decode_timestamp.InSecondsF(); |
| 126 return false; |
| 127 } |
| 128 if (decode_timestamp > presentation_timestamp) { |
| 129 DVLOG(2) << __FUNCTION__ << ": Frame DTS(" |
| 130 << decode_timestamp.InSecondsF() << ") > PTS(" |
| 131 << presentation_timestamp.InSecondsF() << ")"; |
| 132 return false; |
| 133 } |
| 134 |
| 135 // TODO(acolwell/wolenetz): All stream parsers must emit valid (positive) |
| 136 // frame durations. For now, we allow non-negative frame duration. |
| 137 // See http://crbug.com/351166. |
| 138 if (frame_duration < base::TimeDelta()) { |
| 139 DVLOG(2) << __FUNCTION__ << ": Negative frame duration: " |
| 140 << frame_duration.InSecondsF(); |
| 141 return false; |
| 142 } |
| 143 |
| 144 // 4. If mode equals "sequence" and group start timestamp is set, then run |
| 145 // the following steps: |
| 146 if (sequence_mode_ && group_start_timestamp_set_) { |
| 147 // 4.1. Set timestampOffset equal to group start timestamp - |
| 148 // presentation timestamp. |
| 149 *timestamp_offset = group_start_timestamp_ - presentation_timestamp; |
| 150 |
| 151 DVLOG(3) << __FUNCTION__ << ": updated timestampOffset is now " |
| 152 << timestamp_offset->InSecondsF(); |
| 153 |
| 154 // 4.2. Set highest presentation end timestamp equal to group start |
| 155 // timestamp. |
| 156 highest_presentation_end_timestamp_ = group_start_timestamp_; |
| 157 |
| 158 // 4.3. Set the need random access point flag on all track buffers to |
| 159 // true. |
| 160 SetAllTrackBuffersNeedRandomAccessPoint(); |
| 161 |
| 162 // 4.4. Unset group start timestamp. |
| 163 group_start_timestamp_set_ = false; |
| 164 } |
| 165 |
| 166 // 5. If timestampOffset is not 0, then run the following steps: |
| 167 if (*timestamp_offset != base::TimeDelta()) { |
| 168 // 5.1. Add timestampOffset to the presentation timestamp. |
| 169 // Frame PTS is only updated if it survives processing. |
| 170 presentation_timestamp += *timestamp_offset; |
| 171 |
| 172 // 5.2. Add timestampOffset to the decode timestamp. |
| 173 // Frame DTS is only updated if it survives processing. |
| 174 decode_timestamp += *timestamp_offset; |
| 175 |
| 176 // 5.3. If the presentation timestamp or decode timestamp is less than the |
| 177 // presentation start time, then run the end of stream algorithm with |
| 178 // the error parameter set to "decode", and abort these steps. |
| 179 if (presentation_timestamp < base::TimeDelta() || |
| 180 decode_timestamp < base::TimeDelta()) { |
| 181 DVLOG(2) << __FUNCTION__ |
| 182 << ": frame PTS=" << presentation_timestamp.InSecondsF() |
| 183 << " or DTS=" << decode_timestamp.InSecondsF() |
| 184 << " negative after applying timestampOffset"; |
| 185 return false; |
| 186 } |
| 187 } |
| 188 |
| 189 // 6. Let track buffer equal the track buffer that the coded frame will be |
| 190 // added to. |
| 191 |
| 192 // Remap audio and video track types to their special singleton identifiers. |
| 193 StreamParser::TrackId track_id = kAudioTrackId; |
| 194 switch (frame->type()) { |
| 195 case DemuxerStream::AUDIO: |
| 196 break; |
| 197 case DemuxerStream::VIDEO: |
| 198 track_id = kVideoTrackId; |
| 199 break; |
| 200 case DemuxerStream::TEXT: |
| 201 track_id = frame->track_id(); |
| 202 break; |
| 203 case DemuxerStream::UNKNOWN: |
| 204 case DemuxerStream::NUM_TYPES: |
| 205 DCHECK(false) << ": Invalid frame type " << frame->type(); |
| 206 return false; |
| 207 } |
| 208 |
| 209 MseTrackBuffer* track_buffer = FindTrack(track_id); |
| 210 if (!track_buffer) { |
| 211 DVLOG(2) << __FUNCTION__ << ": Unknown track: type=" << frame->type() |
| 212 << ", frame processor track id=" << track_id |
| 213 << ", parser track id=" << frame->track_id(); |
| 214 return false; |
| 215 } |
| 216 |
| 217 // 7. If last decode timestamp for track buffer is set and decode timestamp |
| 218 // is less than last decode timestamp |
| 219 // OR |
| 220 // If last decode timestamp for track buffer is set and the difference |
| 221 // between decode timestamp and last decode timestamp is greater than 2 |
| 222 // times last frame duration: |
| 223 base::TimeDelta last_decode_timestamp = |
| 224 track_buffer->last_decode_timestamp(); |
| 225 if (last_decode_timestamp != kNoTimestamp()) { |
| 226 base::TimeDelta dts_delta = decode_timestamp - last_decode_timestamp; |
| 227 if (dts_delta < base::TimeDelta() || |
| 228 dts_delta > 2 * track_buffer->last_frame_duration()) { |
| 229 // 7.1. If mode equals "segments": Set highest presentation end |
| 230 // timestamp to presentation timestamp. |
| 231 // If mode equals "sequence": Set group start timestamp equal to |
| 232 // the highest presentation end timestamp. |
| 233 if (!sequence_mode_) { |
| 234 highest_presentation_end_timestamp_ = presentation_timestamp; |
| 235 } else { |
| 236 group_start_timestamp_ = highest_presentation_end_timestamp_; |
| 237 group_start_timestamp_set_ = true; |
| 238 } |
| 239 |
| 240 // 7.2. - 7.5.: |
| 241 Reset(); |
| 242 |
| 243 // This triggers a discontinuity so we need to treat the next frames |
| 244 // appended within the append window as if they were the beginning of |
| 245 // a new segment. |
| 246 *new_media_segment = true; |
| 247 |
| 248 // 7.6. Jump to the Loop Top step above to restart processing of the |
| 249 // current coded frame. |
| 250 DVLOG(3) << __FUNCTION__ << ": Discontinuity: reprocessing frame"; |
| 251 continue; |
| 252 } |
| 253 } |
| 254 |
| 255 // 8. Let frame end timestamp equal the sum of presentation timestamp and |
| 256 // frame duration. |
| 257 base::TimeDelta frame_end_timestamp = presentation_timestamp + |
| 258 frame_duration; |
| 259 |
| 260 // 9. If presentation timestamp is less than appendWindowStart, then set |
| 261 // the need random access point flag to true, drop the coded frame, and |
| 262 // jump to the top of the loop to start processing the next coded frame. |
| 263 // 10. If frame end timestamp is greater than appendWindowEnd, then set the |
| 264 // need random access point flag to true, drop the coded frame, and jump |
| 265 // to the top of the loop to start processing the next coded frame. |
| 266 if (presentation_timestamp < append_window_start || |
| 267 frame_end_timestamp > append_window_end) { |
| 268 track_buffer->set_needs_random_access_point(true); |
| 269 |
| 270 // This also triggers a discontinuity so we need to treat the next frames |
| 271 // appended within the append window as if they were the beginning of |
| 272 // a new segment. |
| 273 *new_media_segment = true; |
| 274 |
| 275 // TODO(wolenetz/acolwell): Collect this dropped frame for splicing? |
| 276 DVLOG(3) << __FUNCTION__ |
| 277 << ": Dropping frame that is not fully within append window"; |
| 278 return true; |
| 279 } |
| 280 |
| 281 // 11. If the need random access point flag on track buffer equals true, |
| 282 // then run the following steps: |
| 283 if (track_buffer->needs_random_access_point()) { |
| 284 // 11.1. If the coded frame is not a random access point, then drop the |
| 285 // coded frame and jump to the top of the loop to start processing |
| 286 // the next coded frame. |
| 287 if (!frame->IsKeyframe()) { |
| 288 DVLOG(3) << __FUNCTION__ |
| 289 << ": Dropping frame that is not a random access point"; |
| 290 return true; |
| 291 } |
| 292 |
| 293 // 11.2. Set the need random access point flag on track buffer to false. |
| 294 track_buffer->set_needs_random_access_point(false); |
| 295 } |
| 296 |
| 297 // We now have a processed buffer to append to the track buffer's stream. |
| 298 // If it is the first in a new media segment or following a discontinuity, |
| 299 // notify all the track buffers' streams that a new segment is beginning. |
| 300 if (*new_media_segment) { |
| 301 *new_media_segment = false; |
| 302 NotifyNewMediaSegmentStarting(decode_timestamp); |
| 303 } |
| 304 |
| 305 DVLOG(3) << __FUNCTION__ << ": Sending processed frame to stream, " |
| 306 << "PTS=" << presentation_timestamp.InSecondsF() |
| 307 << ", DTS=" << decode_timestamp.InSecondsF(); |
| 308 frame->set_timestamp(presentation_timestamp); |
| 309 frame->SetDecodeTimestamp(decode_timestamp); |
| 310 |
| 311 // Steps 12-17: |
| 312 // TODO(wolenetz/acolwell): Add a single buffer append method to |
| 313 // ChunkDemuxerStream and SourceBufferStream, and use it here. |
| 314 StreamParser::BufferQueue buffer_to_append; |
| 315 buffer_to_append.push_back(frame); |
| 316 track_buffer->stream()->Append(buffer_to_append); |
| 317 |
| 318 // 18. Set last decode timestamp for track buffer to decode timestamp. |
| 319 track_buffer->set_last_decode_timestamp(decode_timestamp); |
| 320 |
| 321 // 19. Set last frame duration for track buffer to frame duration. |
| 322 track_buffer->set_last_frame_duration(frame_duration); |
| 323 |
| 324 // 20. If highest presentation timestamp for track buffer is unset or frame |
| 325 // end timestamp is greater than highest presentation timestamp, then |
| 326 // set highest presentation timestamp for track buffer to frame end |
| 327 // timestamp. |
| 328 track_buffer->SetHighestPresentationTimestampIfIncreased( |
| 329 frame_end_timestamp); |
| 330 |
| 331 // 21. If highest presentation end timestamp is unset or frame end timestamp |
| 332 // is greater than highest presentation end timestamp, then set highest |
| 333 // presentation end timestamp equal to frame end timestamp. |
| 334 DCHECK(highest_presentation_end_timestamp_ >= base::TimeDelta()); |
| 335 if (frame_end_timestamp > highest_presentation_end_timestamp_) |
| 336 highest_presentation_end_timestamp_ = frame_end_timestamp; |
| 337 |
| 338 return true; |
| 339 } |
| 340 |
| 341 NOTREACHED(); |
| 342 return false; |
| 343 } |
| 344 |
| 345 void FrameProcessor::SetAllTrackBuffersNeedRandomAccessPoint() { |
| 346 for (TrackBufferMap::iterator itr = track_buffers_.begin(); |
| 347 itr != track_buffers_.end(); ++itr) { |
| 348 itr->second->set_needs_random_access_point(true); |
| 349 } |
| 350 } |
| 351 |
| 352 } // namespace media |
OLD | NEW |