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

Side by Side Diff: media/mp4/mp4_stream_parser.cc

Issue 10536014: Implement ISO BMFF support in Media Source. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 8 years, 6 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
OLDNEW
(Empty)
1 // Copyright (c) 2012 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/mp4/mp4_stream_parser.h"
6
7 #include "base/callback.h"
8 #include "base/logging.h"
9 #include "base/time.h"
10 #include "media/base/stream_parser_buffer.h"
11 #include "media/filters/ffmpeg_glue.h"
12 #include "media/mp4/box_definitions.h"
13 #include "media/mp4/box_reader.h"
14 #include "media/mp4/rcheck.h"
15 #include "openssl/aes.h"
16
17 namespace media {
18
19 using mp4::BoxReader;
20
21 OffsetByteQueue::OffsetByteQueue() : buf_(NULL), size_(0), head_(0) {}
22 OffsetByteQueue::~OffsetByteQueue() {};
23
24 void OffsetByteQueue::Reset() {
25 queue_.Reset();
26 buf_ = NULL;
27 size_ = 0;
28 head_ = 0;
29 }
30
31 void OffsetByteQueue::Push(const uint8* buf, size_t size) {
32 queue_.Push(buf, size);
33 Sync();
34 DVLOG(4) << "Buffer pushed. head=" << head() << " tail=" << tail();
35 }
36
37 void OffsetByteQueue::Peek(const uint8** buf, size_t* size) {
38 *buf = buf_;
39 *size = size_;
40 }
41
42 void OffsetByteQueue::Pop(size_t count) {
43 queue_.Pop(count);
44 head_ += count;
45 Sync();
46 }
47
48 void OffsetByteQueue::PeekAt(size_t offset, const uint8** buf, size_t* size) {
49 DCHECK(offset >= head());
50 if (offset >= head() && offset < tail()) {
51 *buf = &buf_[offset - head()];
52 *size = tail() - offset;
53 } else {
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 early return and drop else
strobe_ 2012/06/07 16:33:54 Done.
54 *buf = NULL;
55 *size = 0;
56 }
57 }
58
59 bool OffsetByteQueue::Trim(size_t max_offset) {
60 if (max_offset < head_) return true;
61 if (max_offset > tail()) {
62 Pop(size_);
63 return false;
64 }
65 Pop(max_offset - head_);
66 return true;
67 }
68
69 void OffsetByteQueue::Sync() {
70 int size;
71 queue_.Peek(&buf_, &size);
72 size_ = size;
73 }
74
75 MP4StreamParser::MP4StreamParser()
76 : state_(kWaitingForInit),
77 moof_head_(0),
78 mdat_tail_(0),
79 has_audio_(false),
80 has_video_(false) {}
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 } on next line.
strobe_ 2012/06/07 16:33:54 Done.
81
82 MP4StreamParser::~MP4StreamParser() {}
83
84 void MP4StreamParser::Init(const InitCB& init_cb,
85 const NewConfigCB& config_cb,
86 const NewBuffersCB& audio_cb,
87 const NewBuffersCB& video_cb,
88 const KeyNeededCB& key_needed_cb) {
89 DCHECK_EQ(state_, kWaitingForInit);
90 DCHECK(init_cb_.is_null());
91 DCHECK(!init_cb.is_null());
92 DCHECK(!config_cb.is_null());
93 DCHECK(!audio_cb.is_null() || !video_cb.is_null());
94 DCHECK(!key_needed_cb.is_null());
95
96 // TODO(strobe): Codec searches fail unless the FFmpegGlue singleton is
97 // initialized. Find the appropriate place to put this initialization.
98 FFmpegGlue::GetInstance();
99
100 ChangeState(kParsingBoxes);
101 init_cb_ = init_cb;
102 config_cb_ = config_cb;
103 audio_cb_ = audio_cb;
104 video_cb_ = video_cb;
105 key_needed_cb_ = key_needed_cb;
106 }
107
108 void MP4StreamParser::Flush() {
109 DCHECK_NE(state_, kWaitingForInit);
110
111 queue_.Reset();
112 moof_head_ = 0;
113 mdat_tail_ = 0;
114
115 if (state_ != kParsingBoxes || kEmittingSamples)
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 why is this here? It looks wrong and isn't doing a
strobe_ 2012/06/07 16:33:54 Done.
116 return;
117 }
118
119 bool MP4StreamParser::Parse(const uint8* buf, int size) {
120 DCHECK_NE(state_, kWaitingForInit);
121
122 if (state_ == kError)
123 return false;
124
125 queue_.Push(buf, size);
126
127 BufferQueue audio_buffers;
128 BufferQueue video_buffers;
129
130 bool result, err = false;
131
132 do {
133 if (state_ == kParsingBoxes) {
134 if (mdat_tail_ > queue_.head()) {
135 result = queue_.Trim(mdat_tail_);
136 } else {
137 result = ParseBox(&err);
138 DCHECK(!err); // XXX
139 }
140 } else {
141 DCHECK_EQ(kEmittingSamples, state_);
142 result = EnqueueSample(&audio_buffers, &video_buffers, &err);
143 if (result) {
144 size_t max_clear = runs_.GetMaxClearOffset() + moof_head_;
145 DCHECK(max_clear <= queue_.tail());
146 err = !(ReadMDATsUntil(max_clear) && queue_.Trim(max_clear));
147 }
148 }
149 } while (result && !err);
150
151 if (err) {
152 DLOG(ERROR) << "Unknown error while parsing MP4";
153 // XXX flush byte queue
154 ChangeState(kError);
155 return false;
156 }
157
158 if (!audio_buffers.empty()) DCHECK(audio_cb_.Run(audio_buffers));
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 You don't want DCHECK here. If the callback return
strobe_ 2012/06/07 16:33:54 Done.
159 if (!video_buffers.empty()) DCHECK(video_cb_.Run(video_buffers));
160 return true;
161 }
162
163 bool MP4StreamParser::ParseBox(bool* err) {
164 const uint8* buf;
165 size_t size;
166 queue_.Peek(&buf, &size);
167 if (!size) return false;
168
169 scoped_ptr<BoxReader> r(BoxReader::ReadTopLevelBox(buf, size, err));
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 r -> reader
strobe_ 2012/06/07 16:33:54 Done.
170 if (r.get() == NULL) return false;
171
172 if (r->type() == FOURCC_MOOV) {
173 *err = !ParseMoov(r.get());
174 } else if (r->type() == FOURCC_MOOF) {
175 moof_head_ = queue_.head();
176 *err = !ParseMoof(r.get());
177
178 // Set up first mdat offset for ParseMDATsUntil()
179 mdat_tail_ = queue_.head() + r->size();
180 } else {
181 DVLOG(2) << "Skipping unrecognized top-level box: "
182 << mp4::FourCCToString(r->type());
183 }
184
185 queue_.Pop(r->size());
186 return !(*err);
187 }
188
189
190 bool MP4StreamParser::ParseMoov(BoxReader* r) {
191 // TODO(strobe): respect edit lists
192 if (moov_.get()) {
193 DLOG(FATAL) << "TODO: Reinitialization not yet supported.";
194 return false;
195 }
196
197 moov_.reset(new mp4::Movie);
198
199 RCHECK(mp4::Parse(r, moov_.get()));
200
201 has_audio_ = false;
202 has_video_ = false;
203
204 // Select first video and audio track
205 AudioDecoderConfig ac;
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 ac -> audio_config vc -> video_config
strobe_ 2012/06/07 16:33:54 Done.
206 VideoDecoderConfig vc;
207
208 for (auto track = moov_->tracks.begin();
209 track != moov_->tracks.end(); ++track) {
210 // TODO(strobe): We only take the first of each type of track for now
211 const mp4::SampleDescription& samp_descr =
212 track->media.information.sample_table.description;
213 if (track->media.handler.type == mp4::kAudio && !ac.IsValidConfig()) {
214 RCHECK(!samp_descr.audio_entries.empty());
215 const mp4::AudioSampleEntry& entry = samp_descr.audio_entries[0];
216
217 // TODO(strobe): this does not conform to specs for protected audio in the
218 // test files provided by Widevine. Need clarification on the scheme and
219 // whether we expect to deviate from it.
220 //RCHECK(entry.format == FOURCC_MP4A ||
221 // (entry.format == FOURCC_ENCA &&
222 // entry.sinf.format.format == FOURCC_MP4A));
223
224 const ChannelLayout layout =
225 mp4::ConvertAACChannelCountToChannelLayout(entry.channelcount);
226 ac.Initialize(kCodecAAC, entry.samplesize, layout,
227 entry.samplerate, NULL, 0, false);
228 has_audio_ = true;
229 audio_track_id_ = track->header.track_id;
230 }
231 if (track->media.handler.type == mp4::kVideo && !vc.IsValidConfig()) {
232 RCHECK(!samp_descr.video_entries.empty());
233 const mp4::VideoSampleEntry& entry = samp_descr.video_entries[0];
234
235 //RCHECK(entry.format == FOURCC_AVC1 ||
236 // (entry.format == FOURCC_ENCV &&
237 // entry.sinf.format.format == FOURCC_AVC1));
238
239 // TODO(strobe): Profile level recovery
240 // TODO(strobe): Crop box (if needed)
241 // TODO(strobe): Rectangle semantics
242 // TODO(strobe): PAR (if needed)
243 // TODO(strobe): Ensure framerate is not used
244 vc.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12,
245 gfx::Size(entry.width, entry.height),
246 gfx::Rect(0, 0, entry.width, entry.height),
247 1000, track->media.header.timescale,
248 1, 1,
249 NULL, 0, false);
250 has_video_ = true;
251 video_track_id_ = track->header.track_id;
252
253 size_of_nalu_length_ = entry.avcc.length_size;
254
255 }
256 }
257
258 RCHECK(config_cb_.Run(ac, vc));
259
260 base::TimeDelta duration;
261 if (moov_->extends.header.fragment_duration > 0) {
262 duration = mp4::TimeDeltaFromFrac(moov_->extends.header.fragment_duration,
263 moov_->header.timescale);
264 } else if (moov_->header.duration > 0) {
265 duration = mp4::TimeDeltaFromFrac(moov_->header.duration,
266 moov_->header.timescale);
267 } else {
268 duration = kInfiniteDuration();
269 }
270
271 init_cb_.Run(true, duration);
272 return true;
273 }
274
275 bool MP4StreamParser::ParseMoof(BoxReader* r) {
276 mp4::MovieFragment moof;
277 RCHECK(mp4::Parse(r, &moof));
278 RCHECK(runs_.Init(*moov_, moof));
279 ChangeState(kEmittingSamples);
280 return true;
281 }
282
283 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
284 BufferQueue* video_buffers,
285 bool* err) {
286 DCHECK(!(*err));
287
288 const uint8* buf;
289 size_t size;
290 queue_.Peek(&buf, &size);
291 if (!size) return false;
292 if (runs_.RunValid()) {
293 bool audio = has_audio_ && audio_track_id_ == runs_.track_id();
294 bool video = has_video_ && video_track_id_ == runs_.track_id();
295
296 if (!audio && !video) runs_.AdvanceRun();
297
298 if (runs_.NeedsCENC()) {
299 queue_.PeekAt(runs_.cenc_offset() + moof_head_, &buf, &size);
300 return runs_.CacheCENC(buf, size);
301 } else if (runs_.SampleValid()) {
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 remove else
strobe_ 2012/06/07 16:33:54 Done.
302 queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size);
303 if (size < runs_.size()) return false;
304
305 std::vector<uint8> frame_buf(buf, buf + runs_.size());
306 if (video) {
307 // TODO(strobe): reinject SPS/PPS at every keyframe (or whatever the
308 // spec says to do).
309 mp4::ConvertAVCCToAnnexB(size_of_nalu_length_, &frame_buf);
310 }
311
312 scoped_refptr<StreamParserBuffer> stream_buf =
313 StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(),
314 runs_.is_keyframe());
315
316 stream_buf->SetDuration(runs_.duration());
317 stream_buf->SetTimestamp(runs_.dts());
318 // TODO(strobe): verify that Chrome's various media backends perform
319 // B-frame reordering by zipping the un-reordered frame timestamp stream
320 // with the reordered frame data stream, such that we only need to pass
321 // DTS to the source buffer. (Doing so will always carry a risk for
322 // bizarre videos that do things like have a DTS stream that isn't a
323 // strict reordering of the CTS stream, but I'm in favor of ignoring that
324 // case.)
325
326 DVLOG(3) << "Pushing frame: aud=" << audio
327 << ", dur=" << runs_.duration().InMilliseconds()
328 << ", dts=" << runs_.dts().InMilliseconds()
329 << ", size=" << runs_.size();
330
331 if (audio) {
332 audio_buffers->push_back(stream_buf);
333 } else {
334 video_buffers->push_back(stream_buf);
335 }
336 runs_.AdvanceSample();
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 looks like you can remove the else below and then
strobe_ 2012/06/07 16:33:54 Done.
337 } else {
338 runs_.AdvanceRun();
339 }
340 } else {
341 ChangeState(kParsingBoxes);
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 reverse condition in if above, change state and re
strobe_ 2012/06/07 16:33:54 Done.
342 }
343 return true;
344 }
345
346 bool MP4StreamParser::ReadMDATsUntil(const size_t tgt_offset) {
347 DCHECK(tgt_offset <= queue_.tail());
348
349 while (mdat_tail_ < tgt_offset) {
350 const uint8* buf;
351 size_t size;
352 queue_.PeekAt(mdat_tail_, &buf, &size);
353
354 FourCC type;
355 size_t box_sz;
356 bool err;
357 if (!BoxReader::StartTopLevelBox(buf, size, &type, &box_sz, &err))
358 return false;
359
360 if (type != FOURCC_MDAT)
acolwell GONE FROM CHROMIUM 2012/06/06 17:32:39 Add {} since the body spans multiple lines.
strobe_ 2012/06/07 16:33:54 Done.
361 DLOG(WARNING) << "Unexpected type while parsing MDATs: "
362 << mp4::FourCCToString(type);
363
364 mdat_tail_ += box_sz;
365 }
366
367 return true;
368 }
369
370 void MP4StreamParser::ChangeState(State new_state) {
371 DVLOG(2) << "Changing state: " << new_state;
372 state_ = new_state;
373 }
374
375 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698