Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| OLD | NEW |