diff options
author | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-10 23:26:07 +0000 |
---|---|---|
committer | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-10 23:26:07 +0000 |
commit | c851cd6fb95e44c4208e0db3cc86822ce8b09144 (patch) | |
tree | f3d1ca6a36fc2d73823f4692f0978ba82a03cb91 /media | |
parent | 40c4cbb72decbe9b5c66c6753dddeb3b683a2562 (diff) | |
download | chromium_src-c851cd6fb95e44c4208e0db3cc86822ce8b09144.zip chromium_src-c851cd6fb95e44c4208e0db3cc86822ce8b09144.tar.gz chromium_src-c851cd6fb95e44c4208e0db3cc86822ce8b09144.tar.bz2 |
Move WebM specific code from ChunkDemuxer to WebMStreamParser.
BUG=108329
TEST=Existing ChunkDemuxer unittests
TBR=acolwell@chromium.org
Review URL: http://codereview.chromium.org/9170002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@117113 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/base/audio_decoder_config.cc | 45 | ||||
-rw-r--r-- | media/base/audio_decoder_config.h | 8 | ||||
-rw-r--r-- | media/base/stream_parser.cc | 17 | ||||
-rw-r--r-- | media/base/stream_parser.h | 86 | ||||
-rw-r--r-- | media/base/video_decoder_config.cc | 38 | ||||
-rw-r--r-- | media/base/video_decoder_config.h | 8 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_common.cc | 8 | ||||
-rw-r--r-- | media/filters/chunk_demuxer.cc | 402 | ||||
-rw-r--r-- | media/filters/chunk_demuxer.h | 61 | ||||
-rw-r--r-- | media/filters/ffmpeg_audio_decoder_unittest.cc | 5 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder_unittest.cc | 4 | ||||
-rw-r--r-- | media/media.gyp | 4 | ||||
-rw-r--r-- | media/webm/webm_stream_parser.cc | 350 | ||||
-rw-r--r-- | media/webm/webm_stream_parser.h | 67 |
14 files changed, 727 insertions, 376 deletions
diff --git a/media/base/audio_decoder_config.cc b/media/base/audio_decoder_config.cc index ece264b..38e0136 100644 --- a/media/base/audio_decoder_config.cc +++ b/media/base/audio_decoder_config.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -25,7 +25,7 @@ AudioDecoderConfig::AudioDecoderConfig(AudioCodec codec, const uint8* extra_data, size_t extra_data_size) { Initialize(codec, bits_per_channel, channel_layout, samples_per_second, - extra_data, extra_data_size); + extra_data, extra_data_size, true); } // Helper enum used only for histogramming samples-per-second. Put @@ -63,21 +63,26 @@ void AudioDecoderConfig::Initialize(AudioCodec codec, ChannelLayout channel_layout, int samples_per_second, const uint8* extra_data, - size_t extra_data_size) { + size_t extra_data_size, + bool record_stats) { CHECK((extra_data_size != 0) == (extra_data != NULL)); - UMA_HISTOGRAM_ENUMERATION("Media.AudioCodec", codec, kAudioCodecMax + 1); - // Fake enum histogram to get exact integral buckets. Expect to never see - // any values over 32 and even that is huge. - UMA_HISTOGRAM_ENUMERATION("Media.AudioBitsPerChannel", bits_per_channel, 40); - UMA_HISTOGRAM_ENUMERATION( - "Media.AudioChannelLayout", channel_layout, CHANNEL_LAYOUT_MAX); - AudioSamplesPerSecond asps = AsAudioSamplesPerSecond(samples_per_second); - if (asps != kUnexpected) { - UMA_HISTOGRAM_ENUMERATION("Media.AudioSamplesPerSecond", asps, kUnexpected); - } else { - UMA_HISTOGRAM_COUNTS( - "Media.AudioSamplesPerSecondUnexpected", samples_per_second); + if (record_stats) { + UMA_HISTOGRAM_ENUMERATION("Media.AudioCodec", codec, kAudioCodecMax + 1); + // Fake enum histogram to get exact integral buckets. Expect to never see + // any values over 32 and even that is huge. + UMA_HISTOGRAM_ENUMERATION("Media.AudioBitsPerChannel", bits_per_channel, + 40); + UMA_HISTOGRAM_ENUMERATION( + "Media.AudioChannelLayout", channel_layout, CHANNEL_LAYOUT_MAX); + AudioSamplesPerSecond asps = AsAudioSamplesPerSecond(samples_per_second); + if (asps != kUnexpected) { + UMA_HISTOGRAM_ENUMERATION("Media.AudioSamplesPerSecond", asps, + kUnexpected); + } else { + UMA_HISTOGRAM_COUNTS( + "Media.AudioSamplesPerSecondUnexpected", samples_per_second); + } } codec_ = codec; @@ -105,6 +110,16 @@ bool AudioDecoderConfig::IsValidConfig() const { samples_per_second_ <= limits::kMaxSampleRate; } +void AudioDecoderConfig::CopyFrom(const AudioDecoderConfig& audio_config) { + Initialize(audio_config.codec(), + audio_config.bits_per_channel(), + audio_config.channel_layout(), + audio_config.samples_per_second(), + audio_config.extra_data(), + audio_config.extra_data_size(), + false); +} + AudioCodec AudioDecoderConfig::codec() const { return codec_; } diff --git a/media/base/audio_decoder_config.h b/media/base/audio_decoder_config.h index 168a941..90f51c1 100644 --- a/media/base/audio_decoder_config.h +++ b/media/base/audio_decoder_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -52,7 +52,11 @@ class MEDIA_EXPORT AudioDecoderConfig { // Resets the internal state of this object. void Initialize(AudioCodec codec, int bits_per_channel, ChannelLayout channel_layout, int samples_per_second, - const uint8* extra_data, size_t extra_data_size); + const uint8* extra_data, size_t extra_data_size, + bool record_stats); + + // Deep copies |audio_config|. + void CopyFrom(const AudioDecoderConfig& audio_config); // Returns true if this object has appropriate configuration values, false // otherwise. diff --git a/media/base/stream_parser.cc b/media/base/stream_parser.cc new file mode 100644 index 0000000..255cc6f --- /dev/null +++ b/media/base/stream_parser.cc @@ -0,0 +1,17 @@ +// 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/base/stream_parser.h" + +namespace media { + +StreamParserHost::StreamParserHost() {} + +StreamParserHost::~StreamParserHost() {} + +StreamParser::StreamParser() {} + +StreamParser::~StreamParser() {} + +} // namespace media diff --git a/media/base/stream_parser.h b/media/base/stream_parser.h new file mode 100644 index 0000000..8397fad1 --- /dev/null +++ b/media/base/stream_parser.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef MEDIA_BASE_STREAM_PARSER_H_ +#define MEDIA_BASE_STREAM_PARSER_H_ + +#include <deque> + +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "base/time.h" +#include "media/base/media_export.h" + +namespace media { + +class AudioDecoderConfig; +class Buffer; +class VideoDecoderConfig; + +// Provides callback methods for StreamParser to report information +// about the media stream. +class MEDIA_EXPORT StreamParserHost { + public: + typedef std::deque<scoped_refptr<Buffer> > BufferQueue; + + StreamParserHost(); + virtual ~StreamParserHost(); + + // A new audio decoder configuration was encountered. All audio buffers + // after this call will be associated with this configuration. + virtual bool OnNewAudioConfig(const AudioDecoderConfig& config) = 0; + + // A new video decoder configuration was encountered. All video buffers + // after this call will be associated with this configuration. + virtual bool OnNewVideoConfig(const VideoDecoderConfig& config) = 0; + + // New audio buffers have been received. + virtual bool OnAudioBuffers(const BufferQueue& buffers) = 0; + + // New video buffers have been received. + virtual bool OnVideoBuffers(const BufferQueue& buffers) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(StreamParserHost); +}; + +// Abstract interface for parsing media byte streams. +class MEDIA_EXPORT StreamParser { + public: + StreamParser(); + virtual ~StreamParser(); + + // Indicates completion of parser initialization. + // First parameter - Indicates initialization success. Set to true if + // initialization was successful. False if an error + // occurred. + // Second parameter - Indicates the stream duration. Only contains a valid + // value if the first parameter is true. + typedef base::Callback<void(bool, base::TimeDelta)> InitCB; + + // Initialize the parser with necessary callbacks. Must be called before any + // data is passed to Parse(). |init_cb| will be called once enough data has + // been parsed to determine the initial stream configurations, presentation + // start time, and duration. + virtual void Init(const InitCB& init_cb, StreamParserHost* host) = 0; + + // Called when a seek occurs. This flushes the current parser state + // and puts the parser in a state where it can receive data for the new seek + // point. + virtual void Flush() = 0; + + // Called when there is new data to parse. + // + // Returns < 0 if the parse fails. + // Returns 0 if more data is needed. + // Returning > 0 indicates success & the number of bytes parsed. + virtual int Parse(const uint8* buf, int size) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(StreamParser); +}; + +} // namespace media + +#endif // MEDIA_BASE_STREAM_PARSER_H_ diff --git a/media/base/video_decoder_config.cc b/media/base/video_decoder_config.cc index 07bf9a5..4966dfb 100644 --- a/media/base/video_decoder_config.cc +++ b/media/base/video_decoder_config.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -37,7 +37,7 @@ VideoDecoderConfig::VideoDecoderConfig(VideoCodec codec, Initialize(codec, profile, format, coded_size, visible_rect, frame_rate_numerator, frame_rate_denominator, aspect_ratio_numerator, aspect_ratio_denominator, - extra_data, extra_data_size); + extra_data, extra_data_size, true); } VideoDecoderConfig::~VideoDecoderConfig() {} @@ -74,16 +74,19 @@ void VideoDecoderConfig::Initialize(VideoCodec codec, int aspect_ratio_numerator, int aspect_ratio_denominator, const uint8* extra_data, - size_t extra_data_size) { + size_t extra_data_size, + bool record_stats) { CHECK((extra_data_size != 0) == (extra_data != NULL)); - UMA_HISTOGRAM_ENUMERATION("Media.VideoCodec", codec, kVideoCodecMax + 1); - UMA_HISTOGRAM_ENUMERATION("Media.VideoCodecProfile", profile, - VIDEO_CODEC_PROFILE_MAX + 1); - UMA_HISTOGRAM_COUNTS_10000("Media.VideoCodedWidth", coded_size.width()); - UmaHistogramAspectRatio("Media.VideoCodedAspectRatio", coded_size); - UMA_HISTOGRAM_COUNTS_10000("Media.VideoVisibleWidth", visible_rect.width()); - UmaHistogramAspectRatio("Media.VideoVisibleAspectRatio", visible_rect); + if (record_stats) { + UMA_HISTOGRAM_ENUMERATION("Media.VideoCodec", codec, kVideoCodecMax + 1); + UMA_HISTOGRAM_ENUMERATION("Media.VideoCodecProfile", profile, + VIDEO_CODEC_PROFILE_MAX + 1); + UMA_HISTOGRAM_COUNTS_10000("Media.VideoCodedWidth", coded_size.width()); + UmaHistogramAspectRatio("Media.VideoCodedAspectRatio", coded_size); + UMA_HISTOGRAM_COUNTS_10000("Media.VideoVisibleWidth", visible_rect.width()); + UmaHistogramAspectRatio("Media.VideoVisibleAspectRatio", visible_rect); + } codec_ = codec; profile_ = profile; @@ -120,6 +123,21 @@ void VideoDecoderConfig::Initialize(VideoCodec codec, natural_size_.SetSize(width & ~1, height); } +void VideoDecoderConfig::CopyFrom(const VideoDecoderConfig& video_config) { + Initialize(video_config.codec(), + video_config.profile(), + video_config.format(), + video_config.coded_size(), + video_config.visible_rect(), + video_config.frame_rate_numerator(), + video_config.frame_rate_denominator(), + video_config.aspect_ratio_numerator(), + video_config.aspect_ratio_denominator(), + video_config.extra_data(), + video_config.extra_data_size(), + false); +} + bool VideoDecoderConfig::IsValidConfig() const { return codec_ != kUnknownVideoCodec && format_ != VideoFrame::INVALID && diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h index 28b1106..96dd677 100644 --- a/media/base/video_decoder_config.h +++ b/media/base/video_decoder_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -82,7 +82,11 @@ class MEDIA_EXPORT VideoDecoderConfig { const gfx::Rect& visible_rect, int frame_rate_numerator, int frame_rate_denominator, int aspect_ratio_numerator, int aspect_ratio_denominator, - const uint8* extra_data, size_t extra_data_size); + const uint8* extra_data, size_t extra_data_size, + bool record_stats); + + // Deep copies |video_config|. + void CopyFrom(const VideoDecoderConfig& video_config); // Returns true if this object has appropriate configuration values, false // otherwise. diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc index 4f3dd0f..ca7efa1 100644 --- a/media/ffmpeg/ffmpeg_common.cc +++ b/media/ffmpeg/ffmpeg_common.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -190,7 +190,8 @@ void AVCodecContextToAudioDecoderConfig( channel_layout, samples_per_second, codec_context->extradata, - codec_context->extradata_size); + codec_context->extradata_size, + true); } void AudioDecoderConfigToAVCodecContext(const AudioDecoderConfig& config, @@ -258,7 +259,8 @@ void AVStreamToVideoDecoderConfig( aspect_ratio.num, aspect_ratio.den, stream->codec->extradata, - stream->codec->extradata_size); + stream->codec->extradata_size, + true); } void VideoDecoderConfigToAVCodecContext( diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index 79f6178..ba3224b 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Implements a Demuxer that can switch among different data sources mid-stream. -// Uses FFmpegDemuxer under the covers, so see the caveats at the top of -// ffmpeg_demuxer.h. - #include "media/filters/chunk_demuxer.h" #include "base/bind.h" @@ -14,47 +10,11 @@ #include "media/base/audio_decoder_config.h" #include "media/base/data_buffer.h" #include "media/base/video_decoder_config.h" -#include "media/ffmpeg/ffmpeg_common.h" #include "media/filters/chunk_demuxer_client.h" -#include "media/filters/ffmpeg_glue.h" -#include "media/filters/in_memory_url_protocol.h" -#include "media/webm/webm_cluster_parser.h" -#include "media/webm/webm_constants.h" -#include "media/webm/webm_info_parser.h" -#include "media/webm/webm_tracks_parser.h" +#include "media/webm/webm_stream_parser.h" namespace media { -// WebM File Header. This is prepended to the INFO & TRACKS -// data passed to Init() before handing it to FFmpeg. Essentially -// we are making the INFO & TRACKS data look like a small WebM -// file so we can use FFmpeg to initialize the AVFormatContext. -// -// TODO(acolwell): Remove this when we construct AudioDecoderConfig and -// VideoDecoderConfig without requiring an AVStream object. -static const uint8 kWebMHeader[] = { - 0x1A, 0x45, 0xDF, 0xA3, 0x9F, // EBML (size = 0x1f) - 0x42, 0x86, 0x81, 0x01, // EBMLVersion = 1 - 0x42, 0xF7, 0x81, 0x01, // EBMLReadVersion = 1 - 0x42, 0xF2, 0x81, 0x04, // EBMLMaxIDLength = 4 - 0x42, 0xF3, 0x81, 0x08, // EBMLMaxSizeLength = 8 - 0x42, 0x82, 0x84, 0x77, 0x65, 0x62, 0x6D, // DocType = "webm" - 0x42, 0x87, 0x81, 0x02, // DocTypeVersion = 2 - 0x42, 0x85, 0x81, 0x02, // DocTypeReadVersion = 2 - // EBML end - 0x18, 0x53, 0x80, 0x67, // Segment - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segment(size = 0) - // INFO goes here. -}; - -// Offset of the segment size field in kWebMHeader. Used to update -// the segment size field before handing the buffer to FFmpeg. -static const int kSegmentSizeOffset = sizeof(kWebMHeader) - 8; - -static const uint8 kEmptyCluster[] = { - 0x1F, 0x43, 0xB6, 0x75, 0x80 // CLUSTER (size = 0) -}; - // Create an "end of stream" buffer. static Buffer* CreateEOSBuffer() { return new DataBuffer(0); @@ -65,7 +25,8 @@ class ChunkDemuxerStream : public DemuxerStream { typedef std::deque<scoped_refptr<Buffer> > BufferQueue; typedef std::deque<ReadCallback> ReadCBQueue; - ChunkDemuxerStream(Type type, AVStream* stream); + explicit ChunkDemuxerStream(const AudioDecoderConfig& audio_config); + explicit ChunkDemuxerStream(const VideoDecoderConfig& video_config); virtual ~ChunkDemuxerStream(); void Flush(); @@ -87,7 +48,6 @@ class ChunkDemuxerStream : public DemuxerStream { private: Type type_; - AVStream* av_stream_; AudioDecoderConfig audio_config_; VideoDecoderConfig video_config_; @@ -105,23 +65,27 @@ class ChunkDemuxerStream : public DemuxerStream { DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream); }; -ChunkDemuxerStream::ChunkDemuxerStream(Type type, AVStream* stream) - : type_(type), - av_stream_(stream), +ChunkDemuxerStream::ChunkDemuxerStream(const AudioDecoderConfig& audio_config) + : type_(AUDIO), shutdown_called_(false), received_end_of_stream_(false), last_buffer_timestamp_(kNoTimestamp) { - if (type_ == AUDIO) { - AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_); - } else if (type_ == VIDEO) { - AVStreamToVideoDecoderConfig(stream, &video_config_); - } + audio_config_.CopyFrom(audio_config); +} + + +ChunkDemuxerStream::ChunkDemuxerStream(const VideoDecoderConfig& video_config) + : type_(VIDEO), + shutdown_called_(false), + received_end_of_stream_(false), + last_buffer_timestamp_(kNoTimestamp) { + video_config_.CopyFrom(video_config); } ChunkDemuxerStream::~ChunkDemuxerStream() {} void ChunkDemuxerStream::Flush() { - VLOG(1) << "Flush()"; + DVLOG(1) << "Flush()"; base::AutoLock auto_lock(lock_); buffers_.clear(); received_end_of_stream_ = false; @@ -289,7 +253,6 @@ const VideoDecoderConfig& ChunkDemuxerStream::video_decoder_config() { ChunkDemuxer::ChunkDemuxer(ChunkDemuxerClient* client) : state_(WAITING_FOR_INIT), client_(client), - format_context_(NULL), buffered_bytes_(0), seek_waits_for_data_(true) { DCHECK(client); @@ -297,28 +260,21 @@ ChunkDemuxer::ChunkDemuxer(ChunkDemuxerClient* client) ChunkDemuxer::~ChunkDemuxer() { DCHECK_NE(state_, INITIALIZED); - - if (!format_context_) - return; - - DestroyAVFormatContext(format_context_); - format_context_ = NULL; - - if (url_protocol_.get()) { - FFmpegGlue::GetInstance()->RemoveProtocol(url_protocol_.get()); - url_protocol_.reset(); - url_protocol_buffer_.reset(); - } } void ChunkDemuxer::Init(const PipelineStatusCB& cb) { - VLOG(1) << "Init()"; + DVLOG(1) << "Init()"; { base::AutoLock auto_lock(lock_); DCHECK_EQ(state_, WAITING_FOR_INIT); ChangeState_Locked(INITIALIZING); init_cb_ = cb; + stream_parser_.reset(new WebMStreamParser()); + + stream_parser_->Init( + base::Bind(&ChunkDemuxer::OnStreamParserInitDone, this), + this); } client_->DemuxerOpened(this); @@ -332,13 +288,13 @@ void ChunkDemuxer::set_host(DemuxerHost* host) { } void ChunkDemuxer::Stop(const base::Closure& callback) { - VLOG(1) << "Stop()"; + DVLOG(1) << "Stop()"; Shutdown(); callback.Run(); } void ChunkDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) { - VLOG(1) << "Seek(" << time.InSecondsF() << ")"; + DVLOG(1) << "Seek(" << time.InSecondsF() << ")"; PipelineStatus status = PIPELINE_ERROR_INVALID_STATE; { @@ -346,7 +302,7 @@ void ChunkDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) { if (state_ == INITIALIZED || state_ == ENDED) { if (seek_waits_for_data_) { - VLOG(1) << "Seek() : waiting for more data to arrive."; + DVLOG(1) << "Seek() : waiting for more data to arrive."; seek_cb_ = cb; return; } @@ -393,13 +349,13 @@ scoped_refptr<DemuxerStream> ChunkDemuxer::GetStream( } base::TimeDelta ChunkDemuxer::GetStartTime() const { - VLOG(1) << "GetStartTime()"; + DVLOG(1) << "GetStartTime()"; // TODO(acolwell) : Fix this so it uses the time on the first packet. return base::TimeDelta(); } void ChunkDemuxer::FlushData() { - VLOG(1) << "FlushData()"; + DVLOG(1) << "FlushData()"; base::AutoLock auto_lock(lock_); DCHECK(state_ == INITIALIZED || state_ == ENDED || state_ == SHUTDOWN); @@ -413,14 +369,14 @@ void ChunkDemuxer::FlushData() { video_->Flush(); byte_queue_.Reset(); - cluster_parser_->Reset(); + stream_parser_->Flush(); seek_waits_for_data_ = true; ChangeState_Locked(INITIALIZED); } bool ChunkDemuxer::AppendData(const uint8* data, size_t length) { - VLOG(1) << "AppendData(" << length << ")"; + DVLOG(1) << "AppendData(" << length << ")"; if (!data || length == 0u) return false; @@ -438,40 +394,37 @@ bool ChunkDemuxer::AppendData(const uint8* data, size_t length) { int cur_size = 0; int bytes_parsed = 0; int result = -1; - bool can_complete_seek = false; + + // Capture |seek_waits_for_data_| state before we start parsing. + // Its state can be changed by OnAudioBuffers() or OnVideoBuffers() + // calls during the parse. + bool old_seek_waits_for_data = seek_waits_for_data_; byte_queue_.Peek(&cur, &cur_size); do { switch(state_) { case INITIALIZING: - result = ParseInfoAndTracks_Locked(cur, cur_size); + result = stream_parser_->Parse(cur, cur_size); if (result < 0) { - VLOG(1) << "AppendData(): parsing info & tracks failed"; ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN); return true; } break; case INITIALIZED: { - bool buffers_added = false; - result = ParseCluster_Locked(cur, cur_size, &buffers_added); + result = stream_parser_->Parse(cur, cur_size); if (result < 0) { - VLOG(1) << "AppendData(): parsing data failed"; ReportError_Locked(PIPELINE_ERROR_DECODE); return true; } - - // We can complete the seek if we have successfully parsed - // some data and buffers were added to one of the DemuxerStreams. - can_complete_seek |= (result > 0 && buffers_added); } break; case WAITING_FOR_INIT: case ENDED: case PARSE_ERROR: case SHUTDOWN: - VLOG(1) << "AppendData(): called in unexpected state " << state_; + DVLOG(1) << "AppendData(): called in unexpected state " << state_; return false; } @@ -484,11 +437,11 @@ bool ChunkDemuxer::AppendData(const uint8* data, size_t length) { byte_queue_.Pop(bytes_parsed); - if (can_complete_seek && seek_waits_for_data_) { - seek_waits_for_data_ = false; - - if (!seek_cb_.is_null()) - std::swap(cb, seek_cb_); + // Check to see if parsing triggered seek_waits_for_data_ to go from true to + // false. This indicates we have parsed enough data to complete the seek. + if (old_seek_waits_for_data && !seek_waits_for_data_ && + !seek_cb_.is_null()) { + std::swap(cb, seek_cb_); } base::TimeDelta tmp; @@ -523,7 +476,7 @@ bool ChunkDemuxer::AppendData(const uint8* data, size_t length) { } void ChunkDemuxer::EndOfStream(PipelineStatus status) { - VLOG(1) << "EndOfStream(" << status << ")"; + DVLOG(1) << "EndOfStream(" << status << ")"; base::AutoLock auto_lock(lock_); DCHECK_NE(state_, WAITING_FOR_INIT); DCHECK_NE(state_, ENDED); @@ -560,7 +513,7 @@ bool ChunkDemuxer::HasEnded() { } void ChunkDemuxer::Shutdown() { - VLOG(1) << "Shutdown()"; + DVLOG(1) << "Shutdown()"; PipelineStatusCB cb; { base::AutoLock auto_lock(lock_); @@ -576,6 +529,8 @@ void ChunkDemuxer::Shutdown() { if (video_.get()) video_->Shutdown(); + stream_parser_.reset(); + ChangeState_Locked(SHUTDOWN); } @@ -590,207 +545,6 @@ void ChunkDemuxer::ChangeState_Locked(State new_state) { state_ = new_state; } -int ChunkDemuxer::ParseInfoAndTracks_Locked(const uint8* data, int size) { - lock_.AssertAcquired(); - DCHECK(data); - DCHECK_GT(size, 0); - - DCHECK_EQ(state_, INITIALIZING); - - const uint8* cur = data; - int cur_size = size; - int bytes_parsed = 0; - - int id; - int64 element_size; - int result = WebMParseElementHeader(cur, cur_size, &id, &element_size); - - if (result <= 0) - return result; - - switch (id) { - case kWebMIdEBML : - case kWebMIdSeekHead : - case kWebMIdVoid : - case kWebMIdCRC32 : - case kWebMIdCues : - if (cur_size < (result + element_size)) { - // We don't have the whole element yet. Signal we need more data. - return 0; - } - // Skip the element. - return result + element_size; - break; - case kWebMIdSegment : - // Just consume the segment header. - return result; - break; - case kWebMIdInfo : - // We've found the element we are looking for. - break; - default: - VLOG(1) << "Unexpected ID 0x" << std::hex << id; - return -1; - } - - WebMInfoParser info_parser; - result = info_parser.Parse(cur, cur_size); - - if (result <= 0) - return result; - - cur += result; - cur_size -= result; - bytes_parsed += result; - - WebMTracksParser tracks_parser(info_parser.timecode_scale()); - result = tracks_parser.Parse(cur, cur_size); - - if (result <= 0) - return result; - - bytes_parsed += result; - - double mult = info_parser.timecode_scale() / 1000.0; - duration_ = base::TimeDelta::FromMicroseconds(info_parser.duration() * mult); - - cluster_parser_.reset(new WebMClusterParser( - info_parser.timecode_scale(), - tracks_parser.audio_track_num(), - tracks_parser.audio_default_duration(), - tracks_parser.video_track_num(), - tracks_parser.video_default_duration())); - - format_context_ = CreateFormatContext(data, bytes_parsed); - if (!format_context_ || !SetupStreams()) - return -1; - - ChangeState_Locked(INITIALIZED); - init_cb_.Run(PIPELINE_OK); - init_cb_.Reset(); - return bytes_parsed; -} - -AVFormatContext* ChunkDemuxer::CreateFormatContext(const uint8* data, - int size) { - DCHECK(!url_protocol_.get()); - DCHECK(!url_protocol_buffer_.get()); - - int segment_size = size + sizeof(kEmptyCluster); - int buf_size = sizeof(kWebMHeader) + segment_size; - url_protocol_buffer_.reset(new uint8[buf_size]); - uint8* buf = url_protocol_buffer_.get(); - memcpy(buf, kWebMHeader, sizeof(kWebMHeader)); - memcpy(buf + sizeof(kWebMHeader), data, size); - memcpy(buf + sizeof(kWebMHeader) + size, kEmptyCluster, - sizeof(kEmptyCluster)); - - // Update the segment size in the buffer. - int64 tmp = (segment_size & GG_LONGLONG(0x00FFFFFFFFFFFFFF)) | - GG_LONGLONG(0x0100000000000000); - for (int i = 0; i < 8; i++) { - buf[kSegmentSizeOffset + i] = (tmp >> (8 * (7 - i))) & 0xff; - } - - url_protocol_.reset(new InMemoryUrlProtocol(buf, buf_size, true)); - std::string key = FFmpegGlue::GetInstance()->AddProtocol(url_protocol_.get()); - - // Open FFmpeg AVFormatContext. - AVFormatContext* context = NULL; - int result = av_open_input_file(&context, key.c_str(), NULL, 0, NULL); - - if (result < 0) - return NULL; - - return context; -} - -bool ChunkDemuxer::SetupStreams() { - int result = av_find_stream_info(format_context_); - - if (result < 0) - return false; - - bool no_supported_streams = true; - for (size_t i = 0; i < format_context_->nb_streams; ++i) { - AVStream* stream = format_context_->streams[i]; - AVCodecContext* codec_context = stream->codec; - AVMediaType codec_type = codec_context->codec_type; - - if (codec_type == AVMEDIA_TYPE_AUDIO && - stream->codec->codec_id == CODEC_ID_VORBIS && - !audio_.get()) { - audio_ = new ChunkDemuxerStream(DemuxerStream::AUDIO, stream); - no_supported_streams = false; - continue; - } - - if (codec_type == AVMEDIA_TYPE_VIDEO && - stream->codec->codec_id == CODEC_ID_VP8 && - !video_.get()) { - video_ = new ChunkDemuxerStream(DemuxerStream::VIDEO, stream); - no_supported_streams = false; - continue; - } - } - - return !no_supported_streams; -} - -int ChunkDemuxer::ParseCluster_Locked(const uint8* data, int size, - bool* buffers_added) { - lock_.AssertAcquired(); - if (!cluster_parser_.get()) - return -1; - - int id; - int64 element_size; - int result = WebMParseElementHeader(data, size, &id, &element_size); - - if (result <= 0) - return result; - - if (id == kWebMIdCues) { - if (size < (result + element_size)) { - // We don't have the whole element yet. Signal we need more data. - return 0; - } - // Skip the element. - return result + element_size; - } - - int bytes_parsed = cluster_parser_->Parse(data, size); - - if (bytes_parsed <= 0) - return bytes_parsed; - - if (!cluster_parser_->audio_buffers().empty() || - !cluster_parser_->video_buffers().empty()) { - // Make sure we can add the buffers to both streams before we actually - // add them. This allows us to accept all of the data or none of it. - if ((audio_.get() && - !audio_->CanAddBuffers(cluster_parser_->audio_buffers())) || - (video_.get() && - !video_->CanAddBuffers(cluster_parser_->video_buffers()))) { - return -1; - } - - if (audio_.get()) - audio_->AddBuffers(cluster_parser_->audio_buffers()); - - if (video_.get()) - video_->AddBuffers(cluster_parser_->video_buffers()); - - *buffers_added = true; - } - - // TODO(acolwell) : make this more representative of what is actually - // buffered. - buffered_bytes_ += bytes_parsed; - - return bytes_parsed; -} - void ChunkDemuxer::ReportError_Locked(PipelineStatus error) { lock_.AssertAcquired(); DCHECK_NE(error, PIPELINE_OK); @@ -822,4 +576,68 @@ void ChunkDemuxer::ReportError_Locked(PipelineStatus error) { } } +void ChunkDemuxer::OnStreamParserInitDone(bool success, + base::TimeDelta duration) { + lock_.AssertAcquired(); + DCHECK_EQ(state_, INITIALIZING); + if (!success || (!audio_.get() && !video_.get())) { + ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN); + return; + } + + duration_ = duration; + + ChangeState_Locked(INITIALIZED); + PipelineStatusCB cb; + std::swap(cb, init_cb_); + cb.Run(PIPELINE_OK); +} + +bool ChunkDemuxer::OnNewAudioConfig(const AudioDecoderConfig& config) { + lock_.AssertAcquired(); + // Only allow a single audio config for now. + if (audio_.get()) + return false; + + audio_ = new ChunkDemuxerStream(config); + return true; +} + +bool ChunkDemuxer::OnNewVideoConfig(const VideoDecoderConfig& config) { + lock_.AssertAcquired(); + // Only allow a single video config for now. + if (video_.get()) + return false; + + video_ = new ChunkDemuxerStream(config); + return true; +} + + +bool ChunkDemuxer::OnAudioBuffers(const BufferQueue& buffers) { + if (!audio_.get()) + return false; + + if (!audio_->CanAddBuffers(buffers)) + return false; + + audio_->AddBuffers(buffers); + seek_waits_for_data_ = false; + + return true; +} + +bool ChunkDemuxer::OnVideoBuffers(const BufferQueue& buffers) { + if (!video_.get()) + return false; + + if (!video_->CanAddBuffers(buffers)) + return false; + + video_->AddBuffers(buffers); + seek_waits_for_data_ = false; + + return true; +} + } // namespace media diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h index 3313c11..9ce5845 100644 --- a/media/filters/chunk_demuxer.h +++ b/media/filters/chunk_demuxer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -10,9 +10,7 @@ #include "base/synchronization/lock.h" #include "media/base/byte_queue.h" #include "media/base/demuxer.h" -#include "media/webm/webm_cluster_parser.h" - -struct AVFormatContext; +#include "media/base/stream_parser.h" namespace media { @@ -20,9 +18,9 @@ class ChunkDemuxerClient; class ChunkDemuxerStream; class FFmpegURLProtocol; -// Demuxer implementation that allows chunks of WebM media data to be passed +// Demuxer implementation that allows chunks of media data to be passed // from JavaScript to the media stack. -class MEDIA_EXPORT ChunkDemuxer : public Demuxer { +class MEDIA_EXPORT ChunkDemuxer : public Demuxer, public StreamParserHost { public: explicit ChunkDemuxer(ChunkDemuxerClient* client); virtual ~ChunkDemuxer(); @@ -64,40 +62,18 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { void ChangeState_Locked(State new_state); - // Parses a buffer that contains an INFO & TRACKS element. This method handles - // calling & clearing |init_cb_| before it returns. - // - // Returns -1 if the parse fails. - // Returns 0 if more data is needed. - // Returns the number of bytes parsed on success. - int ParseInfoAndTracks_Locked(const uint8* data, int size); - - // Generates an AVFormatContext for the INFO & TRACKS elements contained - // in |data|. Returns NULL if parsing |data| fails. - AVFormatContext* CreateFormatContext(const uint8* data, int size); - - // Sets up |audio_| & |video_| DemuxerStreams based on the data in - // |format_context_|. Returns false if no valid audio or video stream were - // found. - bool SetupStreams(); - - // Parse a cluster and add the buffers to the appropriate DemuxerStream. This - // method also skips over CUES elements if it happens to encounter them. - // - // |data| is expected to point to the beginning of an element. - // - // |buffers_added| - Indicates whether Buffers were added to DemuxerStreams - // during the call. This is only valid if the return value > 0. - // - // Returns -1 if the parse fails. - // Returns 0 if more data is needed. - // Returns the number of bytes parsed on success. - int ParseCluster_Locked(const uint8* data, int size, bool* buffers_added); - // Reports an error and puts the demuxer in a state where it won't accept more // data. void ReportError_Locked(PipelineStatus error); + void OnStreamParserInitDone(bool success, base::TimeDelta duration); + + // StreamParserHost implementation. + virtual bool OnNewAudioConfig(const AudioDecoderConfig& config) OVERRIDE; + virtual bool OnNewVideoConfig(const VideoDecoderConfig& config) OVERRIDE; + virtual bool OnAudioBuffers(const BufferQueue& buffer) OVERRIDE; + virtual bool OnVideoBuffers(const BufferQueue& buffer) OVERRIDE; + base::Lock lock_; State state_; @@ -108,22 +84,11 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { scoped_refptr<ChunkDemuxerStream> audio_; scoped_refptr<ChunkDemuxerStream> video_; - // Backing buffer for |url_protocol_|. - scoped_array<uint8> url_protocol_buffer_; - - // Protocol used by |format_context_|. It must outlive the context object. - scoped_ptr<FFmpegURLProtocol> url_protocol_; - - // FFmpeg format context for this demuxer. It is created by - // av_open_input_file() during demuxer initialization and cleaned up with - // DestroyAVFormatContext() in the destructor. - AVFormatContext* format_context_; - int64 buffered_bytes_; base::TimeDelta duration_; - scoped_ptr<WebMClusterParser> cluster_parser_; + scoped_ptr<StreamParser> stream_parser_; // Should a Seek() call wait for more data before calling the // callback. diff --git a/media/filters/ffmpeg_audio_decoder_unittest.cc b/media/filters/ffmpeg_audio_decoder_unittest.cc index f8ba102..12e2ce6 100644 --- a/media/filters/ffmpeg_audio_decoder_unittest.cc +++ b/media/filters/ffmpeg_audio_decoder_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -59,7 +59,8 @@ class FFmpegAudioDecoderTest : public testing::Test { CHANNEL_LAYOUT_STEREO, 44100, vorbis_extradata_.get(), - vorbis_extradata_size_); + vorbis_extradata_size_, + true); } virtual ~FFmpegAudioDecoderTest() {} diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index b072993..d9c1fd4 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -62,7 +62,7 @@ class FFmpegVideoDecoderTest : public testing::Test { kVideoFormat, kCodedSize, kVisibleRect, kFrameRate.num, kFrameRate.den, kAspectRatio.num, kAspectRatio.den, - NULL, 0); + NULL, 0, true); } virtual ~FFmpegVideoDecoderTest() {} diff --git a/media/media.gyp b/media/media.gyp index d218f32..00b04b8 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -161,6 +161,8 @@ 'base/seekable_buffer.h', 'base/state_matrix.cc', 'base/state_matrix.h', + 'base/stream_parser.cc', + 'base/stream_parser.h', 'base/video_decoder_config.cc', 'base/video_decoder_config.h', 'base/video_frame.cc', @@ -253,6 +255,8 @@ 'webm/webm_info_parser.h', 'webm/webm_parser.cc', 'webm/webm_parser.h', + 'webm/webm_stream_parser.cc', + 'webm/webm_stream_parser.h', 'webm/webm_tracks_parser.cc', 'webm/webm_tracks_parser.h', ], diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc new file mode 100644 index 0000000..45c57f3 --- /dev/null +++ b/media/webm/webm_stream_parser.cc @@ -0,0 +1,350 @@ +// 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/webm/webm_stream_parser.h" + +#include "base/callback.h" +#include "base/logging.h" +#include "media/ffmpeg/ffmpeg_common.h" +#include "media/filters/ffmpeg_glue.h" +#include "media/filters/in_memory_url_protocol.h" +#include "media/webm/webm_cluster_parser.h" +#include "media/webm/webm_constants.h" +#include "media/webm/webm_info_parser.h" +#include "media/webm/webm_tracks_parser.h" + +namespace media { + +// Helper class that uses FFmpeg to create AudioDecoderConfig & +// VideoDecoderConfig objects. +// +// This dependency on FFmpeg can be removed once we update WebMTracksParser +// to parse the necessary data to construct AudioDecoderConfig & +// VideoDecoderConfig objects. http://crbug.com/108756 +class FFmpegConfigHelper { + public: + FFmpegConfigHelper(); + ~FFmpegConfigHelper(); + + bool Parse(const uint8* data, int size); + + const AudioDecoderConfig& audio_config() const; + const VideoDecoderConfig& video_config() const; + + private: + AVFormatContext* CreateFormatContext(const uint8* data, int size); + bool SetupStreamConfigs(); + + AudioDecoderConfig audio_config_; + VideoDecoderConfig video_config_; + + // Backing buffer for |url_protocol_|. + scoped_array<uint8> url_protocol_buffer_; + + // Protocol used by |format_context_|. It must outlive the context object. + scoped_ptr<FFmpegURLProtocol> url_protocol_; + + // FFmpeg format context for this demuxer. It is created by + // av_open_input_file() during demuxer initialization and cleaned up with + // DestroyAVFormatContext() in the destructor. + AVFormatContext* format_context_; + + static const uint8 kWebMHeader[]; + static const int kSegmentSizeOffset; + static const uint8 kEmptyCluster[]; + + DISALLOW_COPY_AND_ASSIGN(FFmpegConfigHelper); +}; + +// WebM File Header. This is prepended to the INFO & TRACKS +// data passed to Init() before handing it to FFmpeg. Essentially +// we are making the INFO & TRACKS data look like a small WebM +// file so we can use FFmpeg to initialize the AVFormatContext. +const uint8 FFmpegConfigHelper::kWebMHeader[] = { + 0x1A, 0x45, 0xDF, 0xA3, 0x9F, // EBML (size = 0x1f) + 0x42, 0x86, 0x81, 0x01, // EBMLVersion = 1 + 0x42, 0xF7, 0x81, 0x01, // EBMLReadVersion = 1 + 0x42, 0xF2, 0x81, 0x04, // EBMLMaxIDLength = 4 + 0x42, 0xF3, 0x81, 0x08, // EBMLMaxSizeLength = 8 + 0x42, 0x82, 0x84, 0x77, 0x65, 0x62, 0x6D, // DocType = "webm" + 0x42, 0x87, 0x81, 0x02, // DocTypeVersion = 2 + 0x42, 0x85, 0x81, 0x02, // DocTypeReadVersion = 2 + // EBML end + 0x18, 0x53, 0x80, 0x67, // Segment + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segment(size = 0) + // INFO goes here. +}; + +// Offset of the segment size field in kWebMHeader. Used to update +// the segment size field before handing the buffer to FFmpeg. +const int FFmpegConfigHelper::kSegmentSizeOffset = sizeof(kWebMHeader) - 8; + +const uint8 FFmpegConfigHelper::kEmptyCluster[] = { + 0x1F, 0x43, 0xB6, 0x75, 0x80 // CLUSTER (size = 0) +}; + +FFmpegConfigHelper::FFmpegConfigHelper() : format_context_(NULL) {} + +FFmpegConfigHelper::~FFmpegConfigHelper() { + if (!format_context_) + return; + + DestroyAVFormatContext(format_context_); + format_context_ = NULL; + + if (url_protocol_.get()) { + FFmpegGlue::GetInstance()->RemoveProtocol(url_protocol_.get()); + url_protocol_.reset(); + url_protocol_buffer_.reset(); + } +} + +bool FFmpegConfigHelper::Parse(const uint8* data, int size) { + format_context_ = CreateFormatContext(data, size); + return format_context_ && SetupStreamConfigs(); +} + +const AudioDecoderConfig& FFmpegConfigHelper::audio_config() const { + return audio_config_; +} + +const VideoDecoderConfig& FFmpegConfigHelper::video_config() const { + return video_config_; +} + +AVFormatContext* FFmpegConfigHelper::CreateFormatContext(const uint8* data, + int size) { + DCHECK(!url_protocol_.get()); + DCHECK(!url_protocol_buffer_.get()); + + int segment_size = size + sizeof(kEmptyCluster); + int buf_size = sizeof(kWebMHeader) + segment_size; + url_protocol_buffer_.reset(new uint8[buf_size]); + uint8* buf = url_protocol_buffer_.get(); + memcpy(buf, kWebMHeader, sizeof(kWebMHeader)); + memcpy(buf + sizeof(kWebMHeader), data, size); + memcpy(buf + sizeof(kWebMHeader) + size, kEmptyCluster, + sizeof(kEmptyCluster)); + + // Update the segment size in the buffer. + int64 tmp = (segment_size & GG_LONGLONG(0x00FFFFFFFFFFFFFF)) | + GG_LONGLONG(0x0100000000000000); + for (int i = 0; i < 8; i++) { + buf[kSegmentSizeOffset + i] = (tmp >> (8 * (7 - i))) & 0xff; + } + + url_protocol_.reset(new InMemoryUrlProtocol(buf, buf_size, true)); + std::string key = FFmpegGlue::GetInstance()->AddProtocol(url_protocol_.get()); + + // Open FFmpeg AVFormatContext. + AVFormatContext* context = NULL; + int result = av_open_input_file(&context, key.c_str(), NULL, 0, NULL); + + if (result < 0) + return NULL; + + return context; +} + +bool FFmpegConfigHelper::SetupStreamConfigs() { + int result = av_find_stream_info(format_context_); + + if (result < 0) + return false; + + bool no_supported_streams = true; + for (size_t i = 0; i < format_context_->nb_streams; ++i) { + AVStream* stream = format_context_->streams[i]; + AVCodecContext* codec_context = stream->codec; + AVMediaType codec_type = codec_context->codec_type; + + if (codec_type == AVMEDIA_TYPE_AUDIO && + stream->codec->codec_id == CODEC_ID_VORBIS && + !audio_config_.IsValidConfig()) { + AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_); + no_supported_streams = false; + continue; + } + + if (codec_type == AVMEDIA_TYPE_VIDEO && + stream->codec->codec_id == CODEC_ID_VP8 && + !video_config_.IsValidConfig()) { + AVStreamToVideoDecoderConfig(stream, &video_config_); + no_supported_streams = false; + continue; + } + } + + return !no_supported_streams; +} + +WebMStreamParser::WebMStreamParser() + : state_(WAITING_FOR_INIT), + host_(NULL) { +} + +WebMStreamParser::~WebMStreamParser() {} + +void WebMStreamParser::Init(const InitCB& init_cb, StreamParserHost* host) { + DCHECK_EQ(state_, WAITING_FOR_INIT); + DCHECK(init_cb_.is_null()); + DCHECK(!host_); + DCHECK(!init_cb.is_null()); + DCHECK(host); + + ChangeState(PARSING_HEADERS); + init_cb_ = init_cb; + host_ = host; +} + +void WebMStreamParser::Flush() { + DCHECK_NE(state_, WAITING_FOR_INIT); + + if (state_ != PARSING_CLUSTERS) + return; + + cluster_parser_->Reset(); +} + +int WebMStreamParser::Parse(const uint8* buf, int size) { + DCHECK_NE(state_, WAITING_FOR_INIT); + + if (state_ == PARSING_HEADERS) + return ParseInfoAndTracks(buf, size); + + if (state_ == PARSING_CLUSTERS) + return ParseCluster(buf, size); + + return -1; +} + +void WebMStreamParser::ChangeState(State new_state) { + state_ = new_state; +} + +int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) { + DCHECK(data); + DCHECK_GT(size, 0); + + const uint8* cur = data; + int cur_size = size; + int bytes_parsed = 0; + + int id; + int64 element_size; + int result = WebMParseElementHeader(cur, cur_size, &id, &element_size); + + if (result <= 0) + return result; + + switch (id) { + case kWebMIdEBML: + case kWebMIdSeekHead: + case kWebMIdVoid: + case kWebMIdCRC32: + case kWebMIdCues: + if (cur_size < (result + element_size)) { + // We don't have the whole element yet. Signal we need more data. + return 0; + } + // Skip the element. + return result + element_size; + break; + case kWebMIdSegment: + // Just consume the segment header. + return result; + break; + case kWebMIdInfo: + // We've found the element we are looking for. + break; + default: + DVLOG(1) << "Unexpected ID 0x" << std::hex << id; + return -1; + } + + WebMInfoParser info_parser; + result = info_parser.Parse(cur, cur_size); + + if (result <= 0) + return result; + + cur += result; + cur_size -= result; + bytes_parsed += result; + + WebMTracksParser tracks_parser(info_parser.timecode_scale()); + result = tracks_parser.Parse(cur, cur_size); + + if (result <= 0) + return result; + + bytes_parsed += result; + + double mult = info_parser.timecode_scale() / 1000.0; + base::TimeDelta duration = + base::TimeDelta::FromMicroseconds(info_parser.duration() * mult); + + FFmpegConfigHelper config_helper; + + if (!config_helper.Parse(data, bytes_parsed)) + return -1; + + if (config_helper.audio_config().IsValidConfig()) + host_->OnNewAudioConfig(config_helper.audio_config()); + + if (config_helper.video_config().IsValidConfig()) + host_->OnNewVideoConfig(config_helper.video_config()); + + cluster_parser_.reset(new WebMClusterParser( + info_parser.timecode_scale(), + tracks_parser.audio_track_num(), + tracks_parser.audio_default_duration(), + tracks_parser.video_track_num(), + tracks_parser.video_default_duration())); + + ChangeState(PARSING_CLUSTERS); + init_cb_.Run(true, duration); + + return bytes_parsed; +} + +int WebMStreamParser::ParseCluster(const uint8* data, int size) { + if (!cluster_parser_.get()) + return -1; + + int id; + int64 element_size; + int result = WebMParseElementHeader(data, size, &id, &element_size); + + if (result <= 0) + return result; + + if (id == kWebMIdCues) { + if (size < (result + element_size)) { + // We don't have the whole element yet. Signal we need more data. + return 0; + } + // Skip the element. + return result + element_size; + } + + int bytes_parsed = cluster_parser_->Parse(data, size); + + if (bytes_parsed <= 0) + return bytes_parsed; + + if (cluster_parser_->audio_buffers().empty() && + cluster_parser_->video_buffers().empty()) + return bytes_parsed; + + if (!host_->OnAudioBuffers(cluster_parser_->audio_buffers())) + return -1; + + if (!host_->OnVideoBuffers(cluster_parser_->video_buffers())) + return -1; + + return bytes_parsed; +} + +} // namespace media diff --git a/media/webm/webm_stream_parser.h b/media/webm/webm_stream_parser.h new file mode 100644 index 0000000..9b45c44 --- /dev/null +++ b/media/webm/webm_stream_parser.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef MEDIA_WEBM_WEBM_STREAM_PARSER_H_ +#define MEDIA_WEBM_WEBM_STREAM_PARSER_H_ + +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/buffers.h" +#include "media/base/stream_parser.h" +#include "media/base/video_decoder_config.h" +#include "media/webm/webm_cluster_parser.h" + +namespace media { + +class WebMStreamParser : public StreamParser { + public: + WebMStreamParser(); + virtual ~WebMStreamParser(); + + // StreamParser implementation. + virtual void Init(const InitCB& init_cb, StreamParserHost* host) OVERRIDE; + virtual void Flush() OVERRIDE; + virtual int Parse(const uint8* buf, int size) OVERRIDE; + + private: + enum State { + WAITING_FOR_INIT, + PARSING_HEADERS, + PARSING_CLUSTERS + }; + + void ChangeState(State new_state); + + // Parses WebM Header, Info, Tracks elements. It also skips other level 1 + // elements that are not used right now. Once the Info & Tracks elements have + // been parsed, this method will transition the parser from PARSING_HEADERS to + // PARSING_CLUSTERS. + // + // Returns < 0 if the parse fails. + // Returns 0 if more data is needed. + // Returning > 0 indicates success & the number of bytes parsed. + int ParseInfoAndTracks(const uint8* data, int size); + + // Incrementally parses WebM cluster elements. This method also skips + // CUES elements if they are encountered since we currently don't use the + // data in these elements. + // + // Returns < 0 if the parse fails. + // Returns 0 if more data is needed. + // Returning > 0 indicates success & the number of bytes parsed. + int ParseCluster(const uint8* data, int size); + + State state_; + InitCB init_cb_; + StreamParserHost* host_; + + scoped_ptr<WebMClusterParser> cluster_parser_; + + DISALLOW_COPY_AND_ASSIGN(WebMStreamParser); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_STREAM_PARSER_H_ |