Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/mp4/mp4_stream_parser.h" | 5 #include "media/mp4/mp4_stream_parser.h" |
| 6 | 6 |
| 7 #include "base/callback.h" | 7 #include "base/callback.h" |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/time.h" | 9 #include "base/time.h" |
| 10 #include "media/base/audio_decoder_config.h" | 10 #include "media/base/audio_decoder_config.h" |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 93 } while (result && !err); | 93 } while (result && !err); |
| 94 | 94 |
| 95 if (err) { | 95 if (err) { |
| 96 DLOG(ERROR) << "Unknown error while parsing MP4"; | 96 DLOG(ERROR) << "Unknown error while parsing MP4"; |
| 97 queue_.Reset(); | 97 queue_.Reset(); |
| 98 moov_.reset(); | 98 moov_.reset(); |
| 99 ChangeState(kError); | 99 ChangeState(kError); |
| 100 return false; | 100 return false; |
| 101 } | 101 } |
| 102 | 102 |
| 103 if (!audio_buffers.empty() && | 103 if (!audio_buffers.empty()) { |
| 104 (audio_cb_.is_null() || !audio_cb_.Run(audio_buffers))) | 104 CHECK(!audio_cb_.is_null()); |
| 105 return false; | 105 RCHECK(audio_cb_.Run(audio_buffers)); |
| 106 if (!video_buffers.empty() && | 106 } |
| 107 (video_cb_.is_null() || !video_cb_.Run(video_buffers))) | 107 if (!video_buffers.empty()) { |
| 108 return false; | 108 CHECK(!video_cb_.is_null()); |
| 109 | 109 RCHECK(video_cb_.Run(video_buffers)); |
| 110 } | |
| 110 return true; | 111 return true; |
| 111 } | 112 } |
| 112 | 113 |
| 113 bool MP4StreamParser::ParseBox(bool* err) { | 114 bool MP4StreamParser::ParseBox(bool* err) { |
| 114 const uint8* buf; | 115 const uint8* buf; |
| 115 int size; | 116 int size; |
| 116 queue_.Peek(&buf, &size); | 117 queue_.Peek(&buf, &size); |
| 117 if (!size) return false; | 118 if (!size) return false; |
| 118 | 119 |
| 119 scoped_ptr<BoxReader> reader(BoxReader::ReadTopLevelBox(buf, size, err)); | 120 scoped_ptr<BoxReader> reader(BoxReader::ReadTopLevelBox(buf, size, err)); |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 159 track->media.information.sample_table.description; | 160 track->media.information.sample_table.description; |
| 160 if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) { | 161 if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) { |
| 161 RCHECK(!samp_descr.audio_entries.empty()); | 162 RCHECK(!samp_descr.audio_entries.empty()); |
| 162 const AudioSampleEntry& entry = samp_descr.audio_entries[0]; | 163 const AudioSampleEntry& entry = samp_descr.audio_entries[0]; |
| 163 | 164 |
| 164 // TODO(strobe): We accept all format values, pending clarification on | 165 // TODO(strobe): We accept all format values, pending clarification on |
| 165 // the formats used for encrypted media (http://crbug.com/132351). | 166 // the formats used for encrypted media (http://crbug.com/132351). |
| 166 // RCHECK(entry.format == FOURCC_MP4A || | 167 // RCHECK(entry.format == FOURCC_MP4A || |
| 167 // (entry.format == FOURCC_ENCA && | 168 // (entry.format == FOURCC_ENCA && |
| 168 // entry.sinf.format.format == FOURCC_MP4A)); | 169 // entry.sinf.format.format == FOURCC_MP4A)); |
| 170 RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption)); | |
| 169 | 171 |
| 170 const ChannelLayout layout = | 172 const ChannelLayout layout = |
| 171 AVC::ConvertAACChannelCountToChannelLayout(entry.channelcount); | 173 AVC::ConvertAACChannelCountToChannelLayout(entry.channelcount); |
| 172 audio_config.Initialize(kCodecAAC, entry.samplesize, layout, | 174 audio_config.Initialize(kCodecAAC, entry.samplesize, layout, |
| 173 entry.samplerate, NULL, 0, false); | 175 entry.samplerate, NULL, 0, false); |
| 174 has_audio_ = true; | 176 has_audio_ = true; |
| 175 audio_track_id_ = track->header.track_id; | 177 audio_track_id_ = track->header.track_id; |
| 176 } | 178 } |
| 177 if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { | 179 if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { |
| 178 RCHECK(!samp_descr.video_entries.empty()); | 180 RCHECK(!samp_descr.video_entries.empty()); |
| 179 const VideoSampleEntry& entry = samp_descr.video_entries[0]; | 181 const VideoSampleEntry& entry = samp_descr.video_entries[0]; |
| 180 | 182 |
| 181 // RCHECK(entry.format == FOURCC_AVC1 || | 183 // RCHECK(entry.format == FOURCC_AVC1 || |
| 182 // (entry.format == FOURCC_ENCV && | 184 // (entry.format == FOURCC_ENCV && |
| 183 // entry.sinf.format.format == FOURCC_AVC1)); | 185 // entry.sinf.format.format == FOURCC_AVC1)); |
| 186 RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption)); | |
| 184 | 187 |
| 185 // TODO(strobe): Recover correct crop box and pixel aspect ratio | 188 // TODO(strobe): Recover correct crop box and pixel aspect ratio |
| 186 video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, | 189 video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, |
| 187 gfx::Size(entry.width, entry.height), | 190 gfx::Size(entry.width, entry.height), |
| 188 gfx::Rect(0, 0, entry.width, entry.height), | 191 gfx::Rect(0, 0, entry.width, entry.height), |
| 189 // Bogus duration used for framerate, since real | 192 // Bogus duration used for framerate, since real |
| 190 // framerate may be variable | 193 // framerate may be variable |
| 191 1000, track->media.header.timescale, | 194 1000, track->media.header.timescale, |
| 192 1, 1, | 195 1, 1, |
| 193 // No decoder-specific buffer needed for AVC; | 196 // No decoder-specific buffer needed for AVC; |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 220 bool MP4StreamParser::ParseMoof(BoxReader* reader) { | 223 bool MP4StreamParser::ParseMoof(BoxReader* reader) { |
| 221 RCHECK(moov_.get()); // Must already have initialization segment | 224 RCHECK(moov_.get()); // Must already have initialization segment |
| 222 MovieFragment moof; | 225 MovieFragment moof; |
| 223 RCHECK(moof.Parse(reader)); | 226 RCHECK(moof.Parse(reader)); |
| 224 RCHECK(runs_.Init(*moov_, moof)); | 227 RCHECK(runs_.Init(*moov_, moof)); |
| 225 new_segment_cb_.Run(runs_.GetMinDecodeTimestamp()); | 228 new_segment_cb_.Run(runs_.GetMinDecodeTimestamp()); |
| 226 ChangeState(kEmittingSamples); | 229 ChangeState(kEmittingSamples); |
| 227 return true; | 230 return true; |
| 228 } | 231 } |
| 229 | 232 |
| 233 bool MP4StreamParser::EmitKeyNeeded(const TrackEncryption& track_encryption) { | |
| 234 if (!track_encryption.is_encrypted) return true; | |
| 235 scoped_array<uint8> kid(new uint8[track_encryption.default_kid.size()]); | |
| 236 memcpy(kid.get(), &track_encryption.default_kid[0], | |
| 237 track_encryption.default_kid.size()); | |
| 238 return need_key_cb_.Run(kid.Pass(), track_encryption.default_kid.size()); | |
|
ddorwin
2012/06/26 23:16:20
For ISO, the initData is supposed to be the PSSH.
strobe_
2012/06/27 02:01:21
Oh, duh, that makes a lot more sense! Where's that
ddorwin
2012/07/03 21:03:46
This is based on discussions with others, though t
strobe_
2012/07/13 00:47:07
OK, comment added.
| |
| 239 } | |
| 240 | |
| 230 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, | 241 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, |
| 231 BufferQueue* video_buffers, | 242 BufferQueue* video_buffers, |
| 232 bool* err) { | 243 bool* err) { |
| 233 if (!runs_.RunValid()) { | 244 if (!runs_.RunValid()) { |
| 234 ChangeState(kParsingBoxes); | 245 ChangeState(kParsingBoxes); |
| 235 return true; | 246 return true; |
| 236 } | 247 } |
| 237 | 248 |
| 238 if (!runs_.SampleValid()) { | 249 if (!runs_.SampleValid()) { |
| 239 runs_.AdvanceRun(); | 250 runs_.AdvanceRun(); |
| 240 return true; | 251 return true; |
| 241 } | 252 } |
| 242 | 253 |
| 243 DCHECK(!(*err)); | 254 DCHECK(!(*err)); |
| 244 | 255 |
| 245 const uint8* buf; | 256 const uint8* buf; |
| 246 int size; | 257 int size; |
|
ddorwin
2012/06/26 06:09:19
buf_size would make this function easier to read.
strobe_
2012/07/13 00:47:07
Done.
strobe_
2012/07/13 00:47:07
Done.
| |
| 247 queue_.Peek(&buf, &size); | 258 queue_.Peek(&buf, &size); |
| 248 if (!size) return false; | 259 if (!size) return false; |
| 249 | 260 |
| 250 bool audio = has_audio_ && audio_track_id_ == runs_.track_id(); | 261 bool audio = has_audio_ && audio_track_id_ == runs_.track_id(); |
| 251 bool video = has_video_ && video_track_id_ == runs_.track_id(); | 262 bool video = has_video_ && video_track_id_ == runs_.track_id(); |
| 252 | 263 |
| 253 // Skip this entire track if it's not one we're interested in | 264 // Skip this entire track if it's not one we're interested in |
| 254 if (!audio && !video) runs_.AdvanceRun(); | 265 if (!audio && !video) runs_.AdvanceRun(); |
| 255 | 266 |
| 256 // Attempt to cache the auxiliary information first. Aux info is usually | 267 // Attempt to cache the auxiliary information first. Aux info is usually |
| 257 // placed in a contiguous block before the sample data, rather than being | 268 // placed in a contiguous block before the sample data, rather than being |
| 258 // interleaved. If we didn't cache it, this would require that we retain the | 269 // interleaved. If we didn't cache it, this would require that we retain the |
| 259 // start of the segment buffer while reading samples. Aux info is typically | 270 // start of the segment buffer while reading samples. Aux info is typically |
| 260 // quite small compared to sample data, so this pattern is useful on | 271 // quite small compared to sample data, so this pattern is useful on |
| 261 // memory-constrained devices where the source buffer consumes a substantial | 272 // memory-constrained devices where the source buffer consumes a substantial |
| 262 // portion of the total system memory. | 273 // portion of the total system memory. |
| 263 if (runs_.NeedsCENC()) { | 274 if (runs_.AuxInfoRequired()) { |
| 264 queue_.PeekAt(runs_.cenc_offset() + moof_head_, &buf, &size); | 275 queue_.PeekAt(runs_.aux_info_offset() + moof_head_, &buf, &size); |
| 265 return runs_.CacheCENC(buf, size); | 276 if (size < runs_.aux_info_size()) return false; |
| 277 *err = !runs_.CacheAuxInfo(buf, size); | |
| 278 return !*err; | |
| 266 } | 279 } |
| 267 | 280 |
| 268 queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size); | 281 queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size); |
| 269 if (size < runs_.size()) return false; | 282 if (size < runs_.size()) return false; |
| 270 | 283 |
| 284 scoped_ptr<DecryptConfig> decrypt_config; | |
| 285 if (runs_.is_encrypted()) | |
| 286 decrypt_config = runs_.GetDecryptConfig(); | |
| 287 | |
| 271 std::vector<uint8> frame_buf(buf, buf + runs_.size()); | 288 std::vector<uint8> frame_buf(buf, buf + runs_.size()); |
| 272 if (video) { | 289 if (video) { |
| 290 // TODO(strobe): move all this to separate method, add unittest | |
| 273 RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, &frame_buf)); | 291 RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, &frame_buf)); |
| 292 | |
| 293 if (decrypt_config.get()) { | |
| 294 const int nalu_size_diff = 4 - size_of_nalu_length_; | |
| 295 size_t expected_size = runs_.size() + | |
|
ddorwin
2012/06/26 06:09:19
expected size of what?
what is runs_.size()? It se
strobe_
2012/06/27 02:01:21
size() is the size of the current sample; IV and s
| |
| 296 decrypt_config->subsample_count() * nalu_size_diff; | |
| 297 RCHECK(frame_buf.size() == expected_size); | |
| 298 for (int i = 0; i < decrypt_config->subsample_count(); i++) | |
| 299 decrypt_config->mutable_subsamples()[i].clear_bytes += nalu_size_diff; | |
|
ddorwin
2012/06/26 06:09:19
Rather than mutable_subsamples, you could have an
strobe_
2012/06/27 02:01:21
Two reasons we need to mutate:
1. To convert each
ddorwin
2012/07/03 21:03:46
Since we are just passing the DecryptConfig to som
strobe_
2012/07/13 00:47:07
Done.
| |
| 300 } | |
| 301 | |
| 274 if (!parameter_sets_inserted_) { | 302 if (!parameter_sets_inserted_) { |
| 303 if (!runs_.is_keyframe()) { | |
| 304 LOG(INFO) << "XXX skipping initial non-keyframe sample"; | |
| 305 runs_.AdvanceSample(); | |
| 306 return true; | |
| 307 } | |
| 308 | |
| 275 const AVCDecoderConfigurationRecord* avc_config = NULL; | 309 const AVCDecoderConfigurationRecord* avc_config = NULL; |
| 276 for (size_t t = 0; t < moov_->tracks.size(); t++) { | 310 for (size_t t = 0; t < moov_->tracks.size(); t++) { |
| 277 if (moov_->tracks[t].header.track_id == runs_.track_id()) { | 311 if (moov_->tracks[t].header.track_id == runs_.track_id()) { |
| 278 avc_config = &moov_->tracks[t].media.information. | 312 avc_config = &moov_->tracks[t].media.information. |
| 279 sample_table.description.video_entries[0].avcc; | 313 sample_table.description.video_entries[0].avcc; |
| 280 break; | 314 break; |
| 281 } | 315 } |
| 282 } | 316 } |
| 283 RCHECK(avc_config != NULL); | 317 RCHECK(avc_config != NULL); |
| 284 RCHECK(AVC::InsertParameterSets(*avc_config, &frame_buf)); | 318 std::vector<uint8> param_sets; |
| 319 RCHECK(AVC::ConvertParameterSets(*avc_config, ¶m_sets)); | |
| 320 frame_buf.insert(frame_buf.begin(), | |
| 321 param_sets.begin(), param_sets.end()); | |
| 322 if (decrypt_config.get()) { | |
| 323 decrypt_config->mutable_subsamples()[0].clear_bytes += | |
|
ddorwin
2012/06/26 06:09:19
Hmm, another mutable use. I guess it's not as simp
strobe_
2012/06/27 02:01:21
see above
| |
| 324 param_sets.size(); | |
| 325 } | |
| 285 parameter_sets_inserted_ = true; | 326 parameter_sets_inserted_ = true; |
| 286 } | 327 } |
| 287 } | 328 } |
| 288 | 329 |
| 289 scoped_refptr<StreamParserBuffer> stream_buf = | 330 scoped_refptr<StreamParserBuffer> stream_buf = |
| 290 StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), | 331 StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), |
| 291 runs_.is_keyframe()); | 332 runs_.is_keyframe()); |
| 292 | 333 |
| 334 if (runs_.is_encrypted()) | |
| 335 stream_buf->SetDecryptConfig(decrypt_config.Pass()); | |
| 336 | |
| 293 stream_buf->SetDuration(runs_.duration()); | 337 stream_buf->SetDuration(runs_.duration()); |
| 294 // We depend on the decoder performing frame reordering without reordering | 338 // We depend on the decoder performing frame reordering without reordering |
| 295 // timestamps, and only provide the decode timestamp in the buffer. | 339 // timestamps, and only provide the decode timestamp in the buffer. |
| 296 stream_buf->SetTimestamp(runs_.dts()); | 340 stream_buf->SetTimestamp(runs_.dts()); |
| 297 | 341 |
| 298 DVLOG(3) << "Pushing frame: aud=" << audio | 342 DVLOG(3) << "Pushing frame: aud=" << audio |
| 299 << ", key=" << runs_.is_keyframe() | 343 << ", key=" << runs_.is_keyframe() |
| 300 << ", dur=" << runs_.duration().InMilliseconds() | 344 << ", dur=" << runs_.duration().InMilliseconds() |
| 301 << ", dts=" << runs_.dts().InMilliseconds() | 345 << ", dts=" << runs_.dts().InMilliseconds() |
| 302 << ", size=" << runs_.size(); | 346 << ", size=" << runs_.size(); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 336 return true; | 380 return true; |
| 337 } | 381 } |
| 338 | 382 |
| 339 void MP4StreamParser::ChangeState(State new_state) { | 383 void MP4StreamParser::ChangeState(State new_state) { |
| 340 DVLOG(2) << "Changing state: " << new_state; | 384 DVLOG(2) << "Changing state: " << new_state; |
| 341 state_ = new_state; | 385 state_ = new_state; |
| 342 } | 386 } |
| 343 | 387 |
| 344 } // namespace mp4 | 388 } // namespace mp4 |
| 345 } // namespace media | 389 } // namespace media |
| OLD | NEW |