Chromium Code Reviews| 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 // Per April 1, 2014 MSE spec editor's draft: | |
| 29 // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/media- source.html#attributes-1 | |
|
acolwell GONE FROM CHROMIUM
2014/05/08 00:06:43
nit: You can use https://dvcs.w3.org/hg/html-media
wolenetz
2014/05/08 02:35:28
Done (mode attribute, not timestampOffset)
| |
| 30 // Step 7: If the new mode equals "sequence", then set the group start | |
| 31 // timestamp to the group end timestamp. | |
| 32 if (sequence_mode) { | |
| 33 group_start_timestamp_ = group_end_timestamp_; | |
| 34 group_start_timestamp_set_ = true; | |
| 35 } | |
| 36 | |
| 37 // Step 8: Update the attribute to new mode. | |
| 38 sequence_mode_ = sequence_mode; | |
| 39 } | |
| 40 | |
| 41 void FrameProcessor::SetGroupStartTimestampIfInSequenceMode( | |
| 42 base::TimeDelta timestamp_offset) { | |
| 43 DVLOG(2) << __FUNCTION__ << "(" << timestamp_offset.InSecondsF() << ")"; | |
| 44 if (sequence_mode_) { | |
| 45 group_start_timestamp_ = timestamp_offset; | |
| 46 group_start_timestamp_set_ = true; | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 bool FrameProcessor::ProcessFrames( | |
| 51 const StreamParser::BufferQueue& audio_buffers, | |
| 52 const StreamParser::BufferQueue& video_buffers, | |
| 53 const StreamParser::TextBufferQueueMap& text_map, | |
| 54 base::TimeDelta append_window_start, | |
| 55 base::TimeDelta append_window_end, | |
| 56 bool* new_media_segment, | |
| 57 base::TimeDelta* timestamp_offset) { | |
| 58 StreamParser::BufferQueue frames; | |
| 59 if (!MergeBufferQueues(audio_buffers, video_buffers, text_map, &frames)) { | |
| 60 DVLOG(2) << "Parse error discovered while merging parser's buffers"; | |
| 61 return false; | |
| 62 } | |
| 63 | |
| 64 DCHECK(!frames.empty()); | |
| 65 | |
| 66 // Implements the coded frame processing algorithm's outer loop for step 1. | |
| 67 // Note that ProcessFrame() implements an inner loop for a single frame that | |
| 68 // handles "jump to the Loop Top step to restart processing of the current | |
| 69 // coded frame" per April 1, 2014 MSE spec editor's draft: | |
| 70 // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/media- source.html#sourcebuffer-coded-frame-processing | |
| 71 // 1. For each coded frame in the media segment run the following steps: | |
| 72 for (StreamParser::BufferQueue::const_iterator frames_itr = frames.begin(); | |
| 73 frames_itr != frames.end(); ++frames_itr) { | |
| 74 if (!ProcessFrame(*frames_itr, append_window_start, append_window_end, | |
| 75 timestamp_offset, new_media_segment)) { | |
| 76 return false; | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 // 2. - 4. Are handled by the WebMediaPlayer / Pipeline / Media Element. | |
| 81 | |
| 82 // Step 5: | |
| 83 update_duration_cb_.Run(group_end_timestamp_); | |
| 84 | |
| 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 April 1, 2014 MSE spec editor's draft: | |
| 95 // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/media- source.html#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 // TODO(wolenetz): Determine whether DTS>PTS should really be allowed. See | |
| 130 // http://crbug.com/354518. | |
| 131 DVLOG(2) << __FUNCTION__ << ": WARNING: Frame DTS(" | |
| 132 << decode_timestamp.InSecondsF() << ") > PTS(" | |
| 133 << presentation_timestamp.InSecondsF() << ")"; | |
| 134 } | |
| 135 | |
| 136 // TODO(acolwell/wolenetz): All stream parsers must emit valid (positive) | |
| 137 // frame durations. For now, we allow non-negative frame duration. | |
| 138 // See http://crbug.com/351166. | |
| 139 if (frame_duration == kNoTimestamp()) { | |
| 140 DVLOG(2) << __FUNCTION__ << ": Frame missing duration (kNoTimestamp())"; | |
| 141 return false; | |
| 142 } | |
| 143 if (frame_duration < base::TimeDelta()) { | |
| 144 DVLOG(2) << __FUNCTION__ << ": Frame duration negative: " | |
| 145 << frame_duration.InSecondsF(); | |
| 146 return false; | |
| 147 } | |
| 148 | |
| 149 // 4. If mode equals "sequence" and group start timestamp is set, then run | |
| 150 // the following steps: | |
| 151 if (sequence_mode_ && group_start_timestamp_set_) { | |
| 152 // 4.1. Set timestampOffset equal to group start timestamp - | |
| 153 // presentation timestamp. | |
| 154 *timestamp_offset = group_start_timestamp_ - presentation_timestamp; | |
| 155 | |
| 156 DVLOG(3) << __FUNCTION__ << ": updated timestampOffset is now " | |
| 157 << timestamp_offset->InSecondsF(); | |
| 158 | |
| 159 // 4.2. Set group end timestamp equal to group start timestamp. | |
| 160 group_end_timestamp_ = group_start_timestamp_; | |
| 161 | |
| 162 // 4.3. Set the need random access point flag on all track buffers to | |
| 163 // true. | |
| 164 SetAllTrackBuffersNeedRandomAccessPoint(); | |
| 165 | |
| 166 // 4.4. Unset group start timestamp. | |
| 167 group_start_timestamp_set_ = false; | |
| 168 } | |
| 169 | |
| 170 // 5. If timestampOffset is not 0, then run the following steps: | |
| 171 if (*timestamp_offset != base::TimeDelta()) { | |
| 172 // 5.1. Add timestampOffset to the presentation timestamp. | |
| 173 // Frame PTS is only updated if it survives processing. | |
|
acolwell GONE FROM CHROMIUM
2014/05/08 00:06:43
nit:s/Frame/Note: |frame|/ so it is clear that th
wolenetz
2014/05/08 02:35:28
Done.
| |
| 174 presentation_timestamp += *timestamp_offset; | |
| 175 | |
| 176 // 5.2. Add timestampOffset to the decode timestamp. | |
| 177 // Frame DTS is only updated if it survives processing. | |
| 178 decode_timestamp += *timestamp_offset; | |
| 179 } | |
| 180 | |
| 181 // 6. Let track buffer equal the track buffer that the coded frame will be | |
| 182 // added to. | |
| 183 | |
| 184 // Remap audio and video track types to their special singleton identifiers. | |
| 185 StreamParser::TrackId track_id = kAudioTrackId; | |
| 186 switch (frame->type()) { | |
| 187 case DemuxerStream::AUDIO: | |
| 188 break; | |
| 189 case DemuxerStream::VIDEO: | |
| 190 track_id = kVideoTrackId; | |
|
acolwell GONE FROM CHROMIUM
2014/05/08 00:06:43
Can't this and the audio ID be hidden in frame->tr
wolenetz
2014/05/08 02:35:28
For audio/video track buffers, stream parsers curr
| |
| 191 break; | |
| 192 case DemuxerStream::TEXT: | |
| 193 track_id = frame->track_id(); | |
| 194 break; | |
| 195 case DemuxerStream::UNKNOWN: | |
| 196 case DemuxerStream::NUM_TYPES: | |
| 197 DCHECK(false) << ": Invalid frame type " << frame->type(); | |
| 198 return false; | |
| 199 } | |
| 200 | |
| 201 MseTrackBuffer* track_buffer = FindTrack(track_id); | |
| 202 if (!track_buffer) { | |
| 203 DVLOG(2) << __FUNCTION__ << ": Unknown track: type=" << frame->type() | |
| 204 << ", frame processor track id=" << track_id | |
| 205 << ", parser track id=" << frame->track_id(); | |
| 206 return false; | |
| 207 } | |
| 208 | |
| 209 // 7. If last decode timestamp for track buffer is set and decode timestamp | |
| 210 // is less than last decode timestamp | |
| 211 // OR | |
| 212 // If last decode timestamp for track buffer is set and the difference | |
| 213 // between decode timestamp and last decode timestamp is greater than 2 | |
| 214 // times last frame duration: | |
| 215 base::TimeDelta last_decode_timestamp = | |
| 216 track_buffer->last_decode_timestamp(); | |
| 217 if (last_decode_timestamp != kNoTimestamp()) { | |
| 218 base::TimeDelta dts_delta = decode_timestamp - last_decode_timestamp; | |
| 219 if (dts_delta < base::TimeDelta() || | |
| 220 dts_delta > 2 * track_buffer->last_frame_duration()) { | |
| 221 // 7.1. If mode equals "segments": Set group end timestamp to | |
| 222 // presentation timestamp. | |
| 223 // If mode equals "sequence": Set group start timestamp equal to | |
| 224 // the group end timestamp. | |
| 225 if (!sequence_mode_) { | |
| 226 group_end_timestamp_ = presentation_timestamp; | |
| 227 } else { | |
| 228 group_start_timestamp_ = group_end_timestamp_; | |
| 229 group_start_timestamp_set_ = true; | |
| 230 } | |
| 231 | |
| 232 // 7.2. - 7.5.: | |
| 233 Reset(); | |
| 234 | |
| 235 // This triggers a discontinuity so we need to treat the next frames | |
| 236 // appended within the append window as if they were the beginning of | |
| 237 // a new segment. | |
| 238 *new_media_segment = true; | |
| 239 | |
| 240 // 7.6. Jump to the Loop Top step above to restart processing of the | |
| 241 // current coded frame. | |
| 242 DVLOG(3) << __FUNCTION__ << ": Discontinuity: reprocessing frame"; | |
| 243 continue; | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 // 8. If the presentation timestamp or decode timestamp is less than the | |
| 248 // presentation start time, then run the end of stream algorithm with the | |
| 249 // error parameter set to "decode", and abort these steps. | |
| 250 if (presentation_timestamp < base::TimeDelta() || | |
| 251 decode_timestamp < base::TimeDelta()) { | |
| 252 DVLOG(2) << __FUNCTION__ | |
| 253 << ": frame PTS=" << presentation_timestamp.InSecondsF() | |
| 254 << " or DTS=" << decode_timestamp.InSecondsF() | |
| 255 << " negative after applying timestampOffset and handling any " | |
| 256 << " discontinuity"; | |
| 257 return false; | |
| 258 } | |
| 259 | |
| 260 // 9. Let frame end timestamp equal the sum of presentation timestamp and | |
| 261 // frame duration. | |
| 262 base::TimeDelta frame_end_timestamp = presentation_timestamp + | |
| 263 frame_duration; | |
| 264 | |
| 265 // 10. If presentation timestamp is less than appendWindowStart, then set | |
| 266 // the need random access point flag to true, drop the coded frame, and | |
| 267 // jump to the top of the loop to start processing the next coded | |
| 268 // frame. | |
| 269 // Note: We keep the result of partial discard of a buffer that overlaps | |
| 270 // |append_window_start| and does not end after |append_window_end|. | |
| 271 // 11. If frame end timestamp is greater than appendWindowEnd, then set the | |
| 272 // need random access point flag to true, drop the coded frame, and jump | |
| 273 // to the top of the loop to start processing the next coded frame. | |
| 274 if (presentation_timestamp < append_window_start || | |
| 275 frame_end_timestamp > append_window_end) { | |
| 276 // See if a partial discard can be done around |append_window_start|. | |
| 277 // TODO(wolenetz): Refactor this into a base helper across legacy and | |
| 278 // new frame processors? | |
| 279 if (track_buffer->stream()->supports_partial_append_window_trimming() && | |
| 280 presentation_timestamp < append_window_start && | |
| 281 frame_end_timestamp > append_window_start && | |
| 282 frame_end_timestamp <= append_window_end) { | |
| 283 DCHECK(frame->IsKeyframe()); | |
| 284 DVLOG(1) << "Truncating buffer which overlaps append window start." | |
| 285 << " presentation_timestamp " | |
| 286 << presentation_timestamp.InSecondsF() | |
| 287 << " append_window_start " << append_window_start.InSecondsF(); | |
| 288 | |
| 289 // Adjust the timestamp of this frame forward to |append_window_start|, | |
| 290 // while decreasing the duration appropriately. | |
| 291 frame->set_discard_padding(std::make_pair( | |
| 292 append_window_start - presentation_timestamp, base::TimeDelta())); | |
| 293 presentation_timestamp = append_window_start; // Frame updated, below. | |
|
acolwell GONE FROM CHROMIUM
2014/05/08 00:06:43
nit: s/Frame/|frame|/ here and below
wolenetz
2014/05/08 02:35:28
Done.
| |
| 294 decode_timestamp = append_window_start; // Frame updated, below. | |
| 295 frame_duration = frame_end_timestamp - append_window_start; | |
|
acolwell GONE FROM CHROMIUM
2014/05/08 00:06:43
nit: Perhaps use presentation_timestamp here since
wolenetz
2014/05/08 02:35:28
Done.
| |
| 296 frame->set_duration(frame_duration); | |
| 297 | |
| 298 // TODO(dalecurtis): This could also be done with |append_window_end|, | |
| 299 // but is not necessary since splice frames covert the overlap there. | |
| 300 } else { | |
| 301 track_buffer->set_needs_random_access_point(true); | |
| 302 DVLOG(3) << "Dropping frame that is outside append window."; | |
| 303 | |
| 304 // This also triggers a discontinuity so we need to treat the next | |
| 305 // frames appended within the append window as if they were the | |
| 306 // beginning of a new segment. | |
| 307 *new_media_segment = true; | |
| 308 return true; | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 // 12. If the need random access point flag on track buffer equals true, | |
| 313 // then run the following steps: | |
| 314 if (track_buffer->needs_random_access_point()) { | |
| 315 // 12.1. If the coded frame is not a random access point, then drop the | |
| 316 // coded frame and jump to the top of the loop to start processing | |
| 317 // the next coded frame. | |
| 318 if (!frame->IsKeyframe()) { | |
| 319 DVLOG(3) << __FUNCTION__ | |
| 320 << ": Dropping frame that is not a random access point"; | |
| 321 return true; | |
| 322 } | |
| 323 | |
| 324 // 12.2. Set the need random access point flag on track buffer to false. | |
| 325 track_buffer->set_needs_random_access_point(false); | |
| 326 } | |
| 327 | |
| 328 // We now have a processed buffer to append to the track buffer's stream. | |
| 329 // If it is the first in a new media segment or following a discontinuity, | |
| 330 // notify all the track buffers' streams that a new segment is beginning. | |
| 331 if (*new_media_segment) { | |
| 332 *new_media_segment = false; | |
| 333 NotifyNewMediaSegmentStarting(decode_timestamp); | |
| 334 } | |
| 335 | |
| 336 DVLOG(3) << __FUNCTION__ << ": Sending processed frame to stream, " | |
| 337 << "PTS=" << presentation_timestamp.InSecondsF() | |
| 338 << ", DTS=" << decode_timestamp.InSecondsF(); | |
| 339 frame->set_timestamp(presentation_timestamp); | |
| 340 frame->SetDecodeTimestamp(decode_timestamp); | |
| 341 | |
| 342 // Steps 13-18: | |
| 343 // TODO(wolenetz/acolwell): Add a single buffer append method to | |
| 344 // ChunkDemuxerStream and SourceBufferStream, and use it here. | |
| 345 StreamParser::BufferQueue buffer_to_append; | |
| 346 buffer_to_append.push_back(frame); | |
| 347 track_buffer->stream()->Append(buffer_to_append); | |
|
acolwell GONE FROM CHROMIUM
2014/05/08 00:06:43
This is OK for now, but I'm a little concerned abo
wolenetz
2014/05/08 02:35:28
Good point. I've adjusted the TODO to reference ne
| |
| 348 | |
| 349 // 19. Set last decode timestamp for track buffer to decode timestamp. | |
| 350 track_buffer->set_last_decode_timestamp(decode_timestamp); | |
| 351 | |
| 352 // 20. Set last frame duration for track buffer to frame duration. | |
| 353 track_buffer->set_last_frame_duration(frame_duration); | |
| 354 | |
| 355 // 21. If highest presentation timestamp for track buffer is unset or frame | |
| 356 // end timestamp is greater than highest presentation timestamp, then | |
| 357 // set highest presentation timestamp for track buffer to frame end | |
| 358 // timestamp. | |
| 359 track_buffer->SetHighestPresentationTimestampIfIncreased( | |
| 360 frame_end_timestamp); | |
| 361 | |
| 362 // 22. If frame end timestamp is greater than group end timestamp, then set | |
| 363 // group end timestamp equal to frame end timestamp. | |
| 364 DCHECK(group_end_timestamp_ >= base::TimeDelta()); | |
| 365 if (frame_end_timestamp > group_end_timestamp_) | |
| 366 group_end_timestamp_ = frame_end_timestamp; | |
| 367 | |
| 368 return true; | |
| 369 } | |
| 370 | |
| 371 NOTREACHED(); | |
| 372 return false; | |
| 373 } | |
| 374 | |
| 375 void FrameProcessor::SetAllTrackBuffersNeedRandomAccessPoint() { | |
| 376 for (TrackBufferMap::iterator itr = track_buffers_.begin(); | |
| 377 itr != track_buffers_.end(); ++itr) { | |
| 378 itr->second->set_needs_random_access_point(true); | |
| 379 } | |
| 380 } | |
| 381 | |
| 382 } // namespace media | |
| OLD | NEW |