| 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/audio_decoder_config.h" |
| 11 #include "media/base/stream_parser_buffer.h" |
| 12 #include "media/base/video_decoder_config.h" |
| 13 #include "media/filters/ffmpeg_glue.h" |
| 14 #include "media/mp4/box_definitions.h" |
| 15 #include "media/mp4/box_reader.h" |
| 16 #include "media/mp4/rcheck.h" |
| 17 |
| 18 namespace media { |
| 19 namespace mp4 { |
| 20 |
| 21 MP4StreamParser::MP4StreamParser() |
| 22 : state_(kWaitingForInit), |
| 23 moof_head_(0), |
| 24 mdat_tail_(0), |
| 25 has_audio_(false), |
| 26 has_video_(false) { |
| 27 } |
| 28 |
| 29 MP4StreamParser::~MP4StreamParser() {} |
| 30 |
| 31 void MP4StreamParser::Init(const InitCB& init_cb, |
| 32 const NewConfigCB& config_cb, |
| 33 const NewBuffersCB& audio_cb, |
| 34 const NewBuffersCB& video_cb, |
| 35 const KeyNeededCB& key_needed_cb) { |
| 36 DCHECK_EQ(state_, kWaitingForInit); |
| 37 DCHECK(init_cb_.is_null()); |
| 38 DCHECK(!init_cb.is_null()); |
| 39 DCHECK(!config_cb.is_null()); |
| 40 DCHECK(!audio_cb.is_null() || !video_cb.is_null()); |
| 41 DCHECK(!key_needed_cb.is_null()); |
| 42 |
| 43 // TODO(strobe): Codec searches fail unless the FFmpegGlue singleton is |
| 44 // initialized. Find the appropriate place to put this initialization. |
| 45 FFmpegGlue::GetInstance(); |
| 46 |
| 47 ChangeState(kParsingBoxes); |
| 48 init_cb_ = init_cb; |
| 49 config_cb_ = config_cb; |
| 50 audio_cb_ = audio_cb; |
| 51 video_cb_ = video_cb; |
| 52 key_needed_cb_ = key_needed_cb; |
| 53 } |
| 54 |
| 55 void MP4StreamParser::Flush() { |
| 56 DCHECK_NE(state_, kWaitingForInit); |
| 57 |
| 58 queue_.Reset(); |
| 59 moof_head_ = 0; |
| 60 mdat_tail_ = 0; |
| 61 } |
| 62 |
| 63 bool MP4StreamParser::Parse(const uint8* buf, int size) { |
| 64 DCHECK_NE(state_, kWaitingForInit); |
| 65 |
| 66 if (state_ == kError) |
| 67 return false; |
| 68 |
| 69 queue_.Push(buf, size); |
| 70 |
| 71 BufferQueue audio_buffers; |
| 72 BufferQueue video_buffers; |
| 73 |
| 74 bool result, err = false; |
| 75 |
| 76 do { |
| 77 if (state_ == kParsingBoxes) { |
| 78 if (mdat_tail_ > queue_.head()) { |
| 79 result = queue_.Trim(mdat_tail_); |
| 80 } else { |
| 81 result = ParseBox(&err); |
| 82 } |
| 83 } else { |
| 84 DCHECK_EQ(kEmittingSamples, state_); |
| 85 result = EnqueueSample(&audio_buffers, &video_buffers, &err); |
| 86 if (result) { |
| 87 int64 max_clear = runs_.GetMaxClearOffset() + moof_head_; |
| 88 DCHECK(max_clear <= queue_.tail()); |
| 89 err = !(ReadMDATsUntil(max_clear) && queue_.Trim(max_clear)); |
| 90 } |
| 91 } |
| 92 } while (result && !err); |
| 93 |
| 94 if (err) { |
| 95 DLOG(ERROR) << "Unknown error while parsing MP4"; |
| 96 queue_.Reset(); |
| 97 moov_.reset(); |
| 98 ChangeState(kError); |
| 99 return false; |
| 100 } |
| 101 |
| 102 if (!audio_buffers.empty() && |
| 103 (audio_cb_.is_null() || !audio_cb_.Run(audio_buffers))) |
| 104 return false; |
| 105 if (!video_buffers.empty() && |
| 106 (video_cb_.is_null() || !video_cb_.Run(video_buffers))) |
| 107 return false; |
| 108 |
| 109 return true; |
| 110 } |
| 111 |
| 112 bool MP4StreamParser::ParseBox(bool* err) { |
| 113 const uint8* buf; |
| 114 int size; |
| 115 queue_.Peek(&buf, &size); |
| 116 if (!size) return false; |
| 117 |
| 118 scoped_ptr<BoxReader> reader(BoxReader::ReadTopLevelBox(buf, size, err)); |
| 119 if (reader.get() == NULL) return false; |
| 120 |
| 121 if (reader->type() == FOURCC_MOOV) { |
| 122 *err = !ParseMoov(reader.get()); |
| 123 } else if (reader->type() == FOURCC_MOOF) { |
| 124 moof_head_ = queue_.head(); |
| 125 *err = !ParseMoof(reader.get()); |
| 126 |
| 127 // Set up first mdat offset for ParseMDATsUntil() |
| 128 mdat_tail_ = queue_.head() + reader->size(); |
| 129 } else { |
| 130 DVLOG(2) << "Skipping unrecognized top-level box: " |
| 131 << FourCCToString(reader->type()); |
| 132 } |
| 133 |
| 134 queue_.Pop(reader->size()); |
| 135 return !(*err); |
| 136 } |
| 137 |
| 138 |
| 139 bool MP4StreamParser::ParseMoov(BoxReader* reader) { |
| 140 // TODO(strobe): respect edit lists |
| 141 if (moov_.get()) { |
| 142 DLOG(FATAL) << "TODO: Reinitialization not yet supported."; |
| 143 return false; |
| 144 } |
| 145 |
| 146 moov_.reset(new Movie); |
| 147 |
| 148 RCHECK(moov_->Parse(reader)); |
| 149 |
| 150 has_audio_ = false; |
| 151 has_video_ = false; |
| 152 |
| 153 // Select first video and audio track |
| 154 AudioDecoderConfig audio_config; |
| 155 VideoDecoderConfig video_config; |
| 156 |
| 157 for (auto track = moov_->tracks.begin(); |
| 158 track != moov_->tracks.end(); ++track) { |
| 159 // TODO(strobe): We only take the first of each type of track for now |
| 160 const SampleDescription& samp_descr = |
| 161 track->media.information.sample_table.description; |
| 162 if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) { |
| 163 RCHECK(!samp_descr.audio_entries.empty()); |
| 164 const AudioSampleEntry& entry = samp_descr.audio_entries[0]; |
| 165 |
| 166 // TODO(strobe): this does not conform to specs for protected audio in the |
| 167 // test files provided by Widevine. Need clarification on the scheme and |
| 168 // whether we expect to deviate from it. |
| 169 //RCHECK(entry.format == FOURCC_MP4A || |
| 170 // (entry.format == FOURCC_ENCA && |
| 171 // entry.sinf.format.format == FOURCC_MP4A)); |
| 172 |
| 173 const ChannelLayout layout = |
| 174 ConvertAACChannelCountToChannelLayout(entry.channelcount); |
| 175 audio_config.Initialize(kCodecAAC, entry.samplesize, layout, |
| 176 entry.samplerate, NULL, 0, false); |
| 177 has_audio_ = true; |
| 178 audio_track_id_ = track->header.track_id; |
| 179 } |
| 180 if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { |
| 181 RCHECK(!samp_descr.video_entries.empty()); |
| 182 const VideoSampleEntry& entry = samp_descr.video_entries[0]; |
| 183 |
| 184 //RCHECK(entry.format == FOURCC_AVC1 || |
| 185 // (entry.format == FOURCC_ENCV && |
| 186 // entry.sinf.format.format == FOURCC_AVC1)); |
| 187 |
| 188 // TODO(strobe): Profile level recovery |
| 189 // TODO(strobe): Crop box (if needed) |
| 190 // TODO(strobe): Rectangle semantics |
| 191 // TODO(strobe): PAR (if needed) |
| 192 // TODO(strobe): Ensure framerate is not used |
| 193 video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, |
| 194 gfx::Size(entry.width, entry.height), |
| 195 gfx::Rect(0, 0, entry.width, entry.height), |
| 196 1000, track->media.header.timescale, |
| 197 1, 1, |
| 198 NULL, 0, false); |
| 199 has_video_ = true; |
| 200 video_track_id_ = track->header.track_id; |
| 201 |
| 202 size_of_nalu_length_ = entry.avcc.length_size; |
| 203 } |
| 204 } |
| 205 |
| 206 RCHECK(config_cb_.Run(audio_config, video_config)); |
| 207 |
| 208 base::TimeDelta duration; |
| 209 if (moov_->extends.header.fragment_duration > 0) { |
| 210 duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration, |
| 211 moov_->header.timescale); |
| 212 } else if (moov_->header.duration > 0) { |
| 213 duration = TimeDeltaFromFrac(moov_->header.duration, |
| 214 moov_->header.timescale); |
| 215 } else { |
| 216 duration = kInfiniteDuration(); |
| 217 } |
| 218 |
| 219 init_cb_.Run(true, duration); |
| 220 return true; |
| 221 } |
| 222 |
| 223 bool MP4StreamParser::ParseMoof(BoxReader* reader) { |
| 224 RCHECK(moov_.get()); // Must already have initialization segment |
| 225 MovieFragment moof; |
| 226 RCHECK(moof.Parse(reader)); |
| 227 RCHECK(runs_.Init(*moov_, moof)); |
| 228 ChangeState(kEmittingSamples); |
| 229 return true; |
| 230 } |
| 231 |
| 232 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, |
| 233 BufferQueue* video_buffers, |
| 234 bool* err) { |
| 235 if (!runs_.RunValid()) { |
| 236 ChangeState(kParsingBoxes); |
| 237 return true; |
| 238 } |
| 239 |
| 240 if (!runs_.SampleValid()) { |
| 241 runs_.AdvanceRun(); |
| 242 return true; |
| 243 } |
| 244 |
| 245 DCHECK(!(*err)); |
| 246 |
| 247 const uint8* buf; |
| 248 int size; |
| 249 queue_.Peek(&buf, &size); |
| 250 if (!size) return false; |
| 251 |
| 252 bool audio = has_audio_ && audio_track_id_ == runs_.track_id(); |
| 253 bool video = has_video_ && video_track_id_ == runs_.track_id(); |
| 254 |
| 255 // Skip this entire track if it's not one we're interested in |
| 256 if (!audio && !video) runs_.AdvanceRun(); |
| 257 |
| 258 // Attempt to cache the auxiliary information first. Aux info is usually |
| 259 // placed in a contiguous block before the sample data, rather than being |
| 260 // interleaved. If we didn't cache it, this would require that we retain the |
| 261 // start of the segment buffer while reading samples. Aux info is typically |
| 262 // quite small compared to sample data, so this pattern is useful on |
| 263 // memory-constrained devices where the source buffer consumes a substantial |
| 264 // portion of the total system memory. |
| 265 if (runs_.NeedsCENC()) { |
| 266 queue_.PeekAt(runs_.cenc_offset() + moof_head_, &buf, &size); |
| 267 return runs_.CacheCENC(buf, size); |
| 268 } |
| 269 |
| 270 queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size); |
| 271 if (size < runs_.size()) return false; |
| 272 |
| 273 std::vector<uint8> frame_buf(buf, buf + runs_.size()); |
| 274 if (video) { |
| 275 // TODO(strobe): reinject SPS/PPS at every keyframe (or whatever the |
| 276 // spec says to do). |
| 277 RCHECK(ConvertAVCCToAnnexB(size_of_nalu_length_, &frame_buf)); |
| 278 } |
| 279 |
| 280 scoped_refptr<StreamParserBuffer> stream_buf = |
| 281 StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), |
| 282 runs_.is_keyframe()); |
| 283 |
| 284 stream_buf->SetDuration(runs_.duration()); |
| 285 stream_buf->SetTimestamp(runs_.dts()); |
| 286 // TODO(strobe): verify that Chrome's various media backends perform |
| 287 // B-frame reordering by zipping the un-reordered frame timestamp stream |
| 288 // with the reordered frame data stream, such that we only need to pass |
| 289 // DTS to the source buffer. (Doing so will always carry a risk for |
| 290 // bizarre videos that do things like have a DTS stream that isn't a |
| 291 // strict reordering of the CTS stream, but I'm in favor of ignoring that |
| 292 // case.) |
| 293 |
| 294 DVLOG(3) << "Pushing frame: aud=" << audio |
| 295 << ", dur=" << runs_.duration().InMilliseconds() |
| 296 << ", dts=" << runs_.dts().InMilliseconds() |
| 297 << ", size=" << runs_.size(); |
| 298 |
| 299 if (audio) { |
| 300 audio_buffers->push_back(stream_buf); |
| 301 } else { |
| 302 video_buffers->push_back(stream_buf); |
| 303 } |
| 304 |
| 305 runs_.AdvanceSample(); |
| 306 return true; |
| 307 } |
| 308 |
| 309 bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) { |
| 310 DCHECK(tgt_offset <= queue_.tail()); |
| 311 |
| 312 while (mdat_tail_ < tgt_offset) { |
| 313 const uint8* buf; |
| 314 int size; |
| 315 queue_.PeekAt(mdat_tail_, &buf, &size); |
| 316 |
| 317 FourCC type; |
| 318 int box_sz; |
| 319 bool err; |
| 320 if (!BoxReader::StartTopLevelBox(buf, size, &type, &box_sz, &err)) |
| 321 return false; |
| 322 |
| 323 if (type != FOURCC_MDAT) { |
| 324 DLOG(WARNING) << "Unexpected type while parsing MDATs: " |
| 325 << FourCCToString(type); |
| 326 } |
| 327 |
| 328 mdat_tail_ += box_sz; |
| 329 } |
| 330 |
| 331 return true; |
| 332 } |
| 333 |
| 334 void MP4StreamParser::ChangeState(State new_state) { |
| 335 DVLOG(2) << "Changing state: " << new_state; |
| 336 state_ = new_state; |
| 337 } |
| 338 |
| 339 } // namespace mp4 |
| 340 } // namespace media |
| OLD | NEW |