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

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: A couple nits, still not ready for review. Created 6 years, 9 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 // 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698