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