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 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 | |
OLD | NEW |