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

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: Aligned with April 1 spec editor's draft, tests added 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 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698