Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(126)

Side by Side Diff: media/filters/frame_processor.cc

Issue 180153003: Implement core of compliant MediaSource coded frame processing (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address CR comments Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 update_duration_cb_(update_duration_cb) {
16 DVLOG(2) << __FUNCTION__ << "()";
17 DCHECK(!update_duration_cb.is_null());
18 }
19
20 FrameProcessor::~FrameProcessor() {
21 DVLOG(2) << __FUNCTION__;
22 }
23
24 void FrameProcessor::SetSequenceMode(bool sequence_mode) {
25 DVLOG(2) << __FUNCTION__ << "(" << sequence_mode << ")";
26
27 // Per April 1, 2014 MSE spec editor's draft:
28 // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/media- source.html#widl-SourceBuffer-mode
29 // Step 7: If the new mode equals "sequence", then set the group start
30 // timestamp to the group end timestamp.
31 if (sequence_mode) {
32 DCHECK(kNoTimestamp() != group_end_timestamp_);
33 group_start_timestamp_ = group_end_timestamp_;
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 DCHECK(kNoTimestamp() != timestamp_offset);
44 if (sequence_mode_)
45 group_start_timestamp_ = timestamp_offset;
46 }
47
48 bool FrameProcessor::ProcessFrames(
49 const StreamParser::BufferQueue& audio_buffers,
50 const StreamParser::BufferQueue& video_buffers,
51 const StreamParser::TextBufferQueueMap& text_map,
52 base::TimeDelta append_window_start,
53 base::TimeDelta append_window_end,
54 bool* new_media_segment,
55 base::TimeDelta* timestamp_offset) {
56 StreamParser::BufferQueue frames;
57 if (!MergeBufferQueues(audio_buffers, video_buffers, text_map, &frames)) {
58 DVLOG(2) << "Parse error discovered while merging parser's buffers";
59 return false;
60 }
61
62 DCHECK(!frames.empty());
63
64 // Implements the coded frame processing algorithm's outer loop for step 1.
65 // Note that ProcessFrame() implements an inner loop for a single frame that
66 // handles "jump to the Loop Top step to restart processing of the current
67 // coded frame" per April 1, 2014 MSE spec editor's draft:
68 // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/media- source.html#sourcebuffer-coded-frame-processing
69 // 1. For each coded frame in the media segment run the following steps:
70 for (StreamParser::BufferQueue::const_iterator frames_itr = frames.begin();
71 frames_itr != frames.end(); ++frames_itr) {
72 if (!ProcessFrame(*frames_itr, append_window_start, append_window_end,
73 timestamp_offset, new_media_segment)) {
74 return false;
75 }
76 }
77
78 // 2. - 4. Are handled by the WebMediaPlayer / Pipeline / Media Element.
79
80 // Step 5:
81 update_duration_cb_.Run(group_end_timestamp_);
82
83 return true;
84 }
85
86 bool FrameProcessor::ProcessFrame(scoped_refptr<StreamParserBuffer> frame,
87 base::TimeDelta append_window_start,
88 base::TimeDelta append_window_end,
89 base::TimeDelta* timestamp_offset,
90 bool* new_media_segment) {
91 // Implements the loop within step 1 of the coded frame processing algorithm
92 // for a single input frame per April 1, 2014 MSE spec editor's draft:
acolwell GONE FROM CHROMIUM 2014/05/08 17:32:22 Please make sure you have a bug filed to track upd
wolenetz 2014/05/08 19:59:02 Good point. I filed bug 371499 and referenced it n
93 // https://dvcs.w3.org/hg/html-media/raw-file/d471a4412040/media-source/media- source.html#sourcebuffer-coded-frame-processing
94
95 while (true) {
96 // 1. Loop Top: Let presentation timestamp be a double precision floating
97 // point representation of the coded frame's presentation timestamp in
98 // seconds.
99 // 2. Let decode timestamp be a double precision floating point
100 // representation of the coded frame's decode timestamp in seconds.
101 // 3. Let frame duration be a double precision floating point representation
102 // of the coded frame's duration in seconds.
103 // We use base::TimeDelta instead of double.
104 base::TimeDelta presentation_timestamp = frame->timestamp();
105 base::TimeDelta decode_timestamp = frame->GetDecodeTimestamp();
106 base::TimeDelta frame_duration = frame->duration();
107
108 DVLOG(3) << __FUNCTION__ << ": Processing frame "
109 << "Type=" << frame->type()
110 << ", TrackID=" << frame->track_id()
111 << ", PTS=" << presentation_timestamp.InSecondsF()
112 << ", DTS=" << decode_timestamp.InSecondsF()
113 << ", DUR=" << frame_duration.InSecondsF();
114
115 // Sanity check the timestamps.
116 if (presentation_timestamp < base::TimeDelta()) {
117 DVLOG(2) << __FUNCTION__ << ": Negative or unknown frame PTS: "
118 << presentation_timestamp.InSecondsF();
119 return false;
120 }
121 if (decode_timestamp < base::TimeDelta()) {
122 DVLOG(2) << __FUNCTION__ << ": Negative or unknown frame DTS: "
123 << decode_timestamp.InSecondsF();
124 return false;
125 }
126 if (decode_timestamp > presentation_timestamp) {
127 // TODO(wolenetz): Determine whether DTS>PTS should really be allowed. See
128 // http://crbug.com/354518.
129 DVLOG(2) << __FUNCTION__ << ": WARNING: Frame DTS("
130 << decode_timestamp.InSecondsF() << ") > PTS("
131 << presentation_timestamp.InSecondsF() << ")";
132 }
133
134 // TODO(acolwell/wolenetz): All stream parsers must emit valid (positive)
135 // frame durations. For now, we allow non-negative frame duration.
136 // See http://crbug.com/351166.
137 if (frame_duration == kNoTimestamp()) {
138 DVLOG(2) << __FUNCTION__ << ": Frame missing duration (kNoTimestamp())";
139 return false;
140 }
141 if (frame_duration < base::TimeDelta()) {
142 DVLOG(2) << __FUNCTION__ << ": Frame duration negative: "
143 << frame_duration.InSecondsF();
144 return false;
145 }
146
147 // 4. If mode equals "sequence" and group start timestamp is set, then run
148 // the following steps:
149 if (sequence_mode_ && group_start_timestamp_ != kNoTimestamp()) {
150 // 4.1. Set timestampOffset equal to group start timestamp -
151 // presentation timestamp.
152 *timestamp_offset = group_start_timestamp_ - presentation_timestamp;
153
154 DVLOG(3) << __FUNCTION__ << ": updated timestampOffset is now "
155 << timestamp_offset->InSecondsF();
156
157 // 4.2. Set group end timestamp equal to group start timestamp.
158 group_end_timestamp_ = group_start_timestamp_;
159
160 // 4.3. Set the need random access point flag on all track buffers to
161 // true.
162 SetAllTrackBuffersNeedRandomAccessPoint();
163
164 // 4.4. Unset group start timestamp.
165 group_start_timestamp_ = kNoTimestamp();
166 }
167
168 // 5. If timestampOffset is not 0, then run the following steps:
169 if (*timestamp_offset != base::TimeDelta()) {
170 // 5.1. Add timestampOffset to the presentation timestamp.
171 // Note: |frame| PTS is only updated if it survives processing.
172 presentation_timestamp += *timestamp_offset;
173
174 // 5.2. Add timestampOffset to the decode timestamp.
175 // Frame DTS is only updated if it survives processing.
176 decode_timestamp += *timestamp_offset;
177 }
178
179 // 6. Let track buffer equal the track buffer that the coded frame will be
180 // added to.
181
182 // Remap audio and video track types to their special singleton identifiers.
183 StreamParser::TrackId track_id = kAudioTrackId;
184 switch (frame->type()) {
185 case DemuxerStream::AUDIO:
186 break;
187 case DemuxerStream::VIDEO:
188 track_id = kVideoTrackId;
189 break;
190 case DemuxerStream::TEXT:
191 track_id = frame->track_id();
192 break;
193 case DemuxerStream::UNKNOWN:
194 case DemuxerStream::NUM_TYPES:
195 DCHECK(false) << ": Invalid frame type " << frame->type();
196 return false;
197 }
198
199 MseTrackBuffer* track_buffer = FindTrack(track_id);
200 if (!track_buffer) {
201 DVLOG(2) << __FUNCTION__ << ": Unknown track: type=" << frame->type()
202 << ", frame processor track id=" << track_id
203 << ", parser track id=" << frame->track_id();
204 return false;
205 }
206
207 // 7. If last decode timestamp for track buffer is set and decode timestamp
208 // is less than last decode timestamp
209 // OR
210 // If last decode timestamp for track buffer is set and the difference
211 // between decode timestamp and last decode timestamp is greater than 2
212 // times last frame duration:
213 base::TimeDelta last_decode_timestamp =
214 track_buffer->last_decode_timestamp();
215 if (last_decode_timestamp != kNoTimestamp()) {
216 base::TimeDelta dts_delta = decode_timestamp - last_decode_timestamp;
217 if (dts_delta < base::TimeDelta() ||
218 dts_delta > 2 * track_buffer->last_frame_duration()) {
219 // 7.1. If mode equals "segments": Set group end timestamp to
220 // presentation timestamp.
221 // If mode equals "sequence": Set group start timestamp equal to
222 // the group end timestamp.
223 if (!sequence_mode_) {
224 group_end_timestamp_ = presentation_timestamp;
225 } else {
226 DCHECK(kNoTimestamp() != group_end_timestamp_);
227 group_start_timestamp_ = group_end_timestamp_;
228 }
229
230 // 7.2. - 7.5.:
231 Reset();
232
233 // This triggers a discontinuity so we need to treat the next frames
234 // appended within the append window as if they were the beginning of
235 // a new segment.
236 *new_media_segment = true;
237
238 // 7.6. Jump to the Loop Top step above to restart processing of the
239 // current coded frame.
240 DVLOG(3) << __FUNCTION__ << ": Discontinuity: reprocessing frame";
241 continue;
242 }
243 }
244
245 // 8. If the presentation timestamp or decode timestamp is less than the
246 // presentation start time, then run the end of stream algorithm with the
247 // error parameter set to "decode", and abort these steps.
248 if (presentation_timestamp < base::TimeDelta() ||
249 decode_timestamp < base::TimeDelta()) {
250 DVLOG(2) << __FUNCTION__
251 << ": frame PTS=" << presentation_timestamp.InSecondsF()
252 << " or DTS=" << decode_timestamp.InSecondsF()
253 << " negative after applying timestampOffset and handling any "
254 << " discontinuity";
255 return false;
256 }
257
258 // 9. Let frame end timestamp equal the sum of presentation timestamp and
259 // frame duration.
260 base::TimeDelta frame_end_timestamp = presentation_timestamp +
261 frame_duration;
262
263 // 10. If presentation timestamp is less than appendWindowStart, then set
264 // the need random access point flag to true, drop the coded frame, and
265 // jump to the top of the loop to start processing the next coded
266 // frame.
267 // Note: We keep the result of partial discard of a buffer that overlaps
268 // |append_window_start| and does not end after |append_window_end|.
269 // 11. If frame end timestamp is greater than appendWindowEnd, then set the
270 // need random access point flag to true, drop the coded frame, and jump
271 // to the top of the loop to start processing the next coded frame.
272 if (presentation_timestamp < append_window_start ||
273 frame_end_timestamp > append_window_end) {
274 // See if a partial discard can be done around |append_window_start|.
275 // TODO(wolenetz): Refactor this into a base helper across legacy and
276 // new frame processors?
277 if (track_buffer->stream()->supports_partial_append_window_trimming() &&
278 presentation_timestamp < append_window_start &&
279 frame_end_timestamp > append_window_start &&
280 frame_end_timestamp <= append_window_end) {
281 DCHECK(frame->IsKeyframe());
282 DVLOG(1) << "Truncating buffer which overlaps append window start."
283 << " presentation_timestamp "
284 << presentation_timestamp.InSecondsF()
285 << " append_window_start " << append_window_start.InSecondsF();
286
287 // Adjust the timestamp of this frame forward to |append_window_start|,
288 // while decreasing the duration appropriately.
289 frame->set_discard_padding(std::make_pair(
290 append_window_start - presentation_timestamp, base::TimeDelta()));
291 presentation_timestamp = append_window_start; // |frame| updated below.
292 decode_timestamp = append_window_start; // |frame| updated below.
293 frame_duration = frame_end_timestamp - presentation_timestamp;
294 frame->set_duration(frame_duration);
295
296 // TODO(dalecurtis): This could also be done with |append_window_end|,
297 // but is not necessary since splice frames covert the overlap there.
298 } else {
299 track_buffer->set_needs_random_access_point(true);
300 DVLOG(3) << "Dropping frame that is outside append window.";
301
302 // This also triggers a discontinuity so we need to treat the next
303 // frames appended within the append window as if they were the
304 // beginning of a new segment.
305 *new_media_segment = true;
306 return true;
307 }
308 }
309
310 // 12. If the need random access point flag on track buffer equals true,
311 // then run the following steps:
312 if (track_buffer->needs_random_access_point()) {
313 // 12.1. If the coded frame is not a random access point, then drop the
314 // coded frame and jump to the top of the loop to start processing
315 // the next coded frame.
316 if (!frame->IsKeyframe()) {
317 DVLOG(3) << __FUNCTION__
318 << ": Dropping frame that is not a random access point";
319 return true;
320 }
321
322 // 12.2. Set the need random access point flag on track buffer to false.
323 track_buffer->set_needs_random_access_point(false);
324 }
325
326 // We now have a processed buffer to append to the track buffer's stream.
327 // If it is the first in a new media segment or following a discontinuity,
328 // notify all the track buffers' streams that a new segment is beginning.
329 if (*new_media_segment) {
330 *new_media_segment = false;
331 NotifyNewMediaSegmentStarting(decode_timestamp);
332 }
333
334 DVLOG(3) << __FUNCTION__ << ": Sending processed frame to stream, "
335 << "PTS=" << presentation_timestamp.InSecondsF()
336 << ", DTS=" << decode_timestamp.InSecondsF();
337 frame->set_timestamp(presentation_timestamp);
338 frame->SetDecodeTimestamp(decode_timestamp);
339
340 // Steps 13-18:
341 // TODO(wolenetz): Collect and emit more than one buffer at a time, if
342 // possible. See http://crbug.com/371197.
acolwell GONE FROM CHROMIUM 2014/05/08 17:32:22 Another part of this may entail refactoring Source
wolenetz 2014/05/08 19:59:02 Good point. I've added your comment to crbug.com/3
343 StreamParser::BufferQueue buffer_to_append;
344 buffer_to_append.push_back(frame);
345 track_buffer->stream()->Append(buffer_to_append);
346
347 // 19. Set last decode timestamp for track buffer to decode timestamp.
348 track_buffer->set_last_decode_timestamp(decode_timestamp);
349
350 // 20. Set last frame duration for track buffer to frame duration.
351 track_buffer->set_last_frame_duration(frame_duration);
352
353 // 21. If highest presentation timestamp for track buffer is unset or frame
354 // end timestamp is greater than highest presentation timestamp, then
355 // set highest presentation timestamp for track buffer to frame end
356 // timestamp.
357 track_buffer->SetHighestPresentationTimestampIfIncreased(
358 frame_end_timestamp);
359
360 // 22. If frame end timestamp is greater than group end timestamp, then set
361 // group end timestamp equal to frame end timestamp.
362 DCHECK(group_end_timestamp_ >= base::TimeDelta());
363 if (frame_end_timestamp > group_end_timestamp_)
364 group_end_timestamp_ = frame_end_timestamp;
365
366 return true;
367 }
368
369 NOTREACHED();
370 return false;
371 }
372
373 void FrameProcessor::SetAllTrackBuffersNeedRandomAccessPoint() {
374 for (TrackBufferMap::iterator itr = track_buffers_.begin();
375 itr != track_buffers_.end(); ++itr) {
376 itr->second->set_needs_random_access_point(true);
377 }
378 }
379
380 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698