diff options
author | strobe@google.com <strobe@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-15 17:00:02 +0000 |
---|---|---|
committer | strobe@google.com <strobe@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-15 17:00:02 +0000 |
commit | 8398ac4a8713c7382bdb88ff52c61903690b72bc (patch) | |
tree | 5c889ec3e111036649b93eb69fe032da105e6f55 /media/mp4/mp4_stream_parser.cc | |
parent | 57f2d412a609adafa29cd7f041ab5306a9986a04 (diff) | |
download | chromium_src-8398ac4a8713c7382bdb88ff52c61903690b72bc.zip chromium_src-8398ac4a8713c7382bdb88ff52c61903690b72bc.tar.gz chromium_src-8398ac4a8713c7382bdb88ff52c61903690b72bc.tar.bz2 |
Implement ISO BMFF support in Media Source (try 2).
Previous review: https://chromiumcodereview.appspot.com/10536014/
BUG=129072
TEST=MP4StreamParserTest
Review URL: https://chromiumcodereview.appspot.com/10534172
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@142409 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/mp4/mp4_stream_parser.cc')
-rw-r--r-- | media/mp4/mp4_stream_parser.cc | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/media/mp4/mp4_stream_parser.cc b/media/mp4/mp4_stream_parser.cc new file mode 100644 index 0000000..3d6a190 --- /dev/null +++ b/media/mp4/mp4_stream_parser.cc @@ -0,0 +1,341 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/mp4_stream_parser.h" + +#include "base/callback.h" +#include "base/logging.h" +#include "base/time.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/stream_parser_buffer.h" +#include "media/base/video_decoder_config.h" +#include "media/mp4/box_definitions.h" +#include "media/mp4/box_reader.h" +#include "media/mp4/rcheck.h" + +namespace media { +namespace mp4 { + +MP4StreamParser::MP4StreamParser() + : state_(kWaitingForInit), + moof_head_(0), + mdat_tail_(0), + has_audio_(false), + has_video_(false) { +} + +MP4StreamParser::~MP4StreamParser() {} + +void MP4StreamParser::Init(const InitCB& init_cb, + const NewConfigCB& config_cb, + const NewBuffersCB& audio_cb, + const NewBuffersCB& video_cb, + const KeyNeededCB& key_needed_cb, + const NewMediaSegmentCB& new_segment_cb) { + DCHECK_EQ(state_, kWaitingForInit); + DCHECK(init_cb_.is_null()); + DCHECK(!init_cb.is_null()); + DCHECK(!config_cb.is_null()); + DCHECK(!audio_cb.is_null() || !video_cb.is_null()); + DCHECK(!key_needed_cb.is_null()); + + ChangeState(kParsingBoxes); + init_cb_ = init_cb; + config_cb_ = config_cb; + audio_cb_ = audio_cb; + video_cb_ = video_cb; + key_needed_cb_ = key_needed_cb; + new_segment_cb_ = new_segment_cb; +} + +void MP4StreamParser::Flush() { + DCHECK_NE(state_, kWaitingForInit); + + queue_.Reset(); + moof_head_ = 0; + mdat_tail_ = 0; +} + +bool MP4StreamParser::Parse(const uint8* buf, int size) { + DCHECK_NE(state_, kWaitingForInit); + + if (state_ == kError) + return false; + + queue_.Push(buf, size); + + BufferQueue audio_buffers; + BufferQueue video_buffers; + + bool result, err = false; + + do { + if (state_ == kParsingBoxes) { + if (mdat_tail_ > queue_.head()) { + result = queue_.Trim(mdat_tail_); + } else { + result = ParseBox(&err); + } + } else { + DCHECK_EQ(kEmittingSamples, state_); + result = EnqueueSample(&audio_buffers, &video_buffers, &err); + if (result) { + int64 max_clear = runs_.GetMaxClearOffset() + moof_head_; + DCHECK(max_clear <= queue_.tail()); + err = !(ReadMDATsUntil(max_clear) && queue_.Trim(max_clear)); + } + } + } while (result && !err); + + if (err) { + DLOG(ERROR) << "Unknown error while parsing MP4"; + queue_.Reset(); + moov_.reset(); + ChangeState(kError); + return false; + } + + if (!audio_buffers.empty() && + (audio_cb_.is_null() || !audio_cb_.Run(audio_buffers))) + return false; + if (!video_buffers.empty() && + (video_cb_.is_null() || !video_cb_.Run(video_buffers))) + return false; + + return true; +} + +bool MP4StreamParser::ParseBox(bool* err) { + const uint8* buf; + int size; + queue_.Peek(&buf, &size); + if (!size) return false; + + scoped_ptr<BoxReader> reader(BoxReader::ReadTopLevelBox(buf, size, err)); + if (reader.get() == NULL) return false; + + if (reader->type() == FOURCC_MOOV) { + *err = !ParseMoov(reader.get()); + } else if (reader->type() == FOURCC_MOOF) { + moof_head_ = queue_.head(); + *err = !ParseMoof(reader.get()); + + // Set up first mdat offset for ParseMDATsUntil() + mdat_tail_ = queue_.head() + reader->size(); + } else { + DVLOG(2) << "Skipping unrecognized top-level box: " + << FourCCToString(reader->type()); + } + + queue_.Pop(reader->size()); + return !(*err); +} + + +bool MP4StreamParser::ParseMoov(BoxReader* reader) { + // TODO(strobe): Respect edit lists. + moov_.reset(new Movie); + + RCHECK(moov_->Parse(reader)); + + has_audio_ = false; + has_video_ = false; + parameter_sets_inserted_ = false; + + AudioDecoderConfig audio_config; + VideoDecoderConfig video_config; + + for (std::vector<Track>::const_iterator track = moov_->tracks.begin(); + track != moov_->tracks.end(); ++track) { + // TODO(strobe): Only the first audio and video track present in a file are + // used. (Track selection is better accomplished via Source IDs, though, so + // adding support for track selection within a stream is low-priority.) + const SampleDescription& samp_descr = + track->media.information.sample_table.description; + if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) { + RCHECK(!samp_descr.audio_entries.empty()); + const AudioSampleEntry& entry = samp_descr.audio_entries[0]; + + // TODO(strobe): We accept all format values, pending clarification on + // the formats used for encrypted media (http://crbug.com/132351). + // RCHECK(entry.format == FOURCC_MP4A || + // (entry.format == FOURCC_ENCA && + // entry.sinf.format.format == FOURCC_MP4A)); + + const ChannelLayout layout = + AVC::ConvertAACChannelCountToChannelLayout(entry.channelcount); + audio_config.Initialize(kCodecAAC, entry.samplesize, layout, + entry.samplerate, NULL, 0, false); + has_audio_ = true; + audio_track_id_ = track->header.track_id; + } + if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { + RCHECK(!samp_descr.video_entries.empty()); + const VideoSampleEntry& entry = samp_descr.video_entries[0]; + + // RCHECK(entry.format == FOURCC_AVC1 || + // (entry.format == FOURCC_ENCV && + // entry.sinf.format.format == FOURCC_AVC1)); + + // TODO(strobe): Recover correct crop box and pixel aspect ratio + video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, + gfx::Size(entry.width, entry.height), + gfx::Rect(0, 0, entry.width, entry.height), + // Bogus duration used for framerate, since real + // framerate may be variable + 1000, track->media.header.timescale, + 1, 1, + // No decoder-specific buffer needed for AVC; + // SPS/PPS are embedded in the video stream + NULL, 0, false); + has_video_ = true; + video_track_id_ = track->header.track_id; + + size_of_nalu_length_ = entry.avcc.length_size; + } + } + + RCHECK(config_cb_.Run(audio_config, video_config)); + + base::TimeDelta duration; + if (moov_->extends.header.fragment_duration > 0) { + duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration, + moov_->header.timescale); + } else if (moov_->header.duration > 0) { + duration = TimeDeltaFromFrac(moov_->header.duration, + moov_->header.timescale); + } else { + duration = kInfiniteDuration(); + } + + init_cb_.Run(true, duration); + return true; +} + +bool MP4StreamParser::ParseMoof(BoxReader* reader) { + RCHECK(moov_.get()); // Must already have initialization segment + MovieFragment moof; + RCHECK(moof.Parse(reader)); + RCHECK(runs_.Init(*moov_, moof)); + new_segment_cb_.Run(runs_.GetMinDecodeTimestamp()); + ChangeState(kEmittingSamples); + return true; +} + +bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, + BufferQueue* video_buffers, + bool* err) { + if (!runs_.RunValid()) { + ChangeState(kParsingBoxes); + return true; + } + + if (!runs_.SampleValid()) { + runs_.AdvanceRun(); + return true; + } + + DCHECK(!(*err)); + + const uint8* buf; + int size; + queue_.Peek(&buf, &size); + if (!size) return false; + + bool audio = has_audio_ && audio_track_id_ == runs_.track_id(); + bool video = has_video_ && video_track_id_ == runs_.track_id(); + + // Skip this entire track if it's not one we're interested in + if (!audio && !video) runs_.AdvanceRun(); + + // Attempt to cache the auxiliary information first. Aux info is usually + // placed in a contiguous block before the sample data, rather than being + // interleaved. If we didn't cache it, this would require that we retain the + // start of the segment buffer while reading samples. Aux info is typically + // quite small compared to sample data, so this pattern is useful on + // memory-constrained devices where the source buffer consumes a substantial + // portion of the total system memory. + if (runs_.NeedsCENC()) { + queue_.PeekAt(runs_.cenc_offset() + moof_head_, &buf, &size); + return runs_.CacheCENC(buf, size); + } + + queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size); + if (size < runs_.size()) return false; + + std::vector<uint8> frame_buf(buf, buf + runs_.size()); + if (video) { + RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, &frame_buf)); + if (!parameter_sets_inserted_) { + const AVCDecoderConfigurationRecord* avc_config = NULL; + for (size_t t = 0; t < moov_->tracks.size(); t++) { + if (moov_->tracks[t].header.track_id == runs_.track_id()) { + avc_config = &moov_->tracks[t].media.information. + sample_table.description.video_entries[0].avcc; + break; + } + } + RCHECK(avc_config != NULL); + RCHECK(AVC::InsertParameterSets(*avc_config, &frame_buf)); + parameter_sets_inserted_ = true; + } + } + + scoped_refptr<StreamParserBuffer> stream_buf = + StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), + runs_.is_keyframe()); + + stream_buf->SetDuration(runs_.duration()); + // We depend on the decoder performing frame reordering without reordering + // timestamps, and only provide the decode timestamp in the buffer. + stream_buf->SetTimestamp(runs_.dts()); + + DVLOG(3) << "Pushing frame: aud=" << audio + << ", key=" << runs_.is_keyframe() + << ", dur=" << runs_.duration().InMilliseconds() + << ", dts=" << runs_.dts().InMilliseconds() + << ", size=" << runs_.size(); + + if (audio) { + audio_buffers->push_back(stream_buf); + } else { + video_buffers->push_back(stream_buf); + } + + runs_.AdvanceSample(); + return true; +} + +bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) { + DCHECK(tgt_offset <= queue_.tail()); + + while (mdat_tail_ < tgt_offset) { + const uint8* buf; + int size; + queue_.PeekAt(mdat_tail_, &buf, &size); + + FourCC type; + int box_sz; + bool err; + if (!BoxReader::StartTopLevelBox(buf, size, &type, &box_sz, &err)) + return false; + + if (type != FOURCC_MDAT) { + DLOG(WARNING) << "Unexpected type while parsing MDATs: " + << FourCCToString(type); + } + + mdat_tail_ += box_sz; + } + + return true; +} + +void MP4StreamParser::ChangeState(State new_state) { + DVLOG(2) << "Changing state: " << new_state; + state_ = new_state; +} + +} // namespace mp4 +} // namespace media |