summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authoracolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-29 16:37:46 +0000
committeracolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-29 16:37:46 +0000
commit020fba32dc0e07d855d644bd575d88365b7e2deb (patch)
tree73cce4387cebbc4497b746f3641a60b66e0ff3be /media
parent8914a69a5973071c57b47d4a0c6c43b7330ff9b1 (diff)
downloadchromium_src-020fba32dc0e07d855d644bd575d88365b7e2deb.zip
chromium_src-020fba32dc0e07d855d644bd575d88365b7e2deb.tar.gz
chromium_src-020fba32dc0e07d855d644bd575d88365b7e2deb.tar.bz2
Adding ChunkDemuxer implementation.
BUG=86536 TEST=ChunkDemuxerTest.* Review URL: http://codereview.chromium.org/7203002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@90966 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/ffmpeg/ffmpeg_common.cc24
-rw-r--r--media/ffmpeg/ffmpeg_common.h4
-rw-r--r--media/filters/audio_file_reader.cc42
-rw-r--r--media/filters/audio_file_reader.h22
-rw-r--r--media/filters/chunk_demuxer.cc619
-rw-r--r--media/filters/chunk_demuxer.h100
-rw-r--r--media/filters/chunk_demuxer_factory.cc165
-rw-r--r--media/filters/chunk_demuxer_factory.h71
-rw-r--r--media/filters/chunk_demuxer_unittest.cc411
-rw-r--r--media/filters/ffmpeg_demuxer.cc20
-rw-r--r--media/filters/in_memory_url_protocol.cc59
-rw-r--r--media/filters/in_memory_url_protocol.h42
-rw-r--r--media/media.gyp22
-rw-r--r--media/test/data/webm_info_elementbin0 -> 36 bytes
-rw-r--r--media/test/data/webm_vorbis_track_entrybin0 -> 3360 bytes
-rw-r--r--media/test/data/webm_vp8_track_entrybin0 -> 53 bytes
-rw-r--r--media/webm/cluster_builder.cc128
-rw-r--r--media/webm/cluster_builder.h55
-rw-r--r--media/webm/webm_cluster_parser.cc133
-rw-r--r--media/webm/webm_cluster_parser.h65
-rw-r--r--media/webm/webm_constants.h70
-rw-r--r--media/webm/webm_info_parser.cc75
-rw-r--r--media/webm/webm_info_parser.h46
-rw-r--r--media/webm/webm_parser.cc485
-rw-r--r--media/webm/webm_parser.h46
-rw-r--r--media/webm/webm_tracks_parser.cc123
-rw-r--r--media/webm/webm_tracks_parser.h61
27 files changed, 2804 insertions, 84 deletions
diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc
index 7af9635..c86acb5 100644
--- a/media/ffmpeg/ffmpeg_common.cc
+++ b/media/ffmpeg/ffmpeg_common.cc
@@ -206,4 +206,28 @@ int GetSurfaceWidth(AVStream* stream) {
return width & ~1;
}
+void DestroyAVFormatContext(AVFormatContext* format_context) {
+ DCHECK(format_context);
+
+ // Iterate each stream and destroy each one of them.
+ int streams = format_context->nb_streams;
+ for (int i = 0; i < streams; ++i) {
+ AVStream* stream = format_context->streams[i];
+
+ // The conditions for calling avcodec_close():
+ // 1. AVStream is alive.
+ // 2. AVCodecContext in AVStream is alive.
+ // 3. AVCodec in AVCodecContext is alive.
+ // Notice that closing a codec context without prior avcodec_open() will
+ // result in a crash in FFmpeg.
+ if (stream && stream->codec && stream->codec->codec) {
+ stream->discard = AVDISCARD_ALL;
+ avcodec_close(stream->codec);
+ }
+ }
+
+ // Then finally cleanup the format context.
+ av_close_input_file(format_context);
+}
+
} // namespace media
diff --git a/media/ffmpeg/ffmpeg_common.h b/media/ffmpeg/ffmpeg_common.h
index 07daae1..5ac0607 100644
--- a/media/ffmpeg/ffmpeg_common.h
+++ b/media/ffmpeg/ffmpeg_common.h
@@ -109,6 +109,10 @@ bool GetStreamByteCountOverRange(AVStream* stream,
int GetSurfaceHeight(AVStream* stream);
int GetSurfaceWidth(AVStream* stream);
+// Closes & destroys all AVStreams in the context and then closes &
+// destroys the AVFormatContext.
+void DestroyAVFormatContext(AVFormatContext* format_context);
+
} // namespace media
#endif // MEDIA_FFMPEG_FFMPEG_COMMON_H_
diff --git a/media/filters/audio_file_reader.cc b/media/filters/audio_file_reader.cc
index a9304ca..2405ed1 100644
--- a/media/filters/audio_file_reader.cc
+++ b/media/filters/audio_file_reader.cc
@@ -180,46 +180,4 @@ bool AudioFileReader::Read(const std::vector<float*>& audio_data,
return true;
}
-InMemoryDataReader::InMemoryDataReader(const char* data, int64 size)
- : data_(data),
- size_(size),
- position_(0) {
-}
-
-int InMemoryDataReader::Read(int size, uint8* data) {
- if (size < 0)
- return -1;
-
- int available_bytes = static_cast<int>(size_ - position_);
- if (size > available_bytes)
- size = available_bytes;
-
- memcpy(data, data_ + position_, size);
- position_ += size;
- return size;
-}
-
-bool InMemoryDataReader::GetPosition(int64* position_out) {
- if (position_out)
- *position_out = position_;
- return true;
-}
-
-bool InMemoryDataReader::SetPosition(int64 position) {
- if (position >= size_)
- return false;
- position_ = position;
- return true;
-}
-
-bool InMemoryDataReader::GetSize(int64* size_out) {
- if (size_out)
- *size_out = size_;
- return true;
-}
-
-bool InMemoryDataReader::IsStreaming() {
- return false;
-}
-
} // namespace media
diff --git a/media/filters/audio_file_reader.h b/media/filters/audio_file_reader.h
index ae929b6..88a484c 100644
--- a/media/filters/audio_file_reader.h
+++ b/media/filters/audio_file_reader.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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.
@@ -54,26 +54,6 @@ class AudioFileReader {
DISALLOW_COPY_AND_ASSIGN(AudioFileReader);
};
-class InMemoryDataReader : public FFmpegURLProtocol {
- public:
- // Ownership of |data| is not taken, instead it simply maintains
- // a weak reference.
- InMemoryDataReader(const char* data, int64 size);
-
- virtual int Read(int size, uint8* data);
- virtual bool GetPosition(int64* position_out);
- virtual bool SetPosition(int64 position);
- virtual bool GetSize(int64* size_out);
- virtual bool IsStreaming();
-
- private:
- const char* data_;
- int64 size_;
- int64 position_;
-
- DISALLOW_COPY_AND_ASSIGN(InMemoryDataReader);
-};
-
} // namespace media
#endif // MEDIA_FILTERS_AUDIO_FILE_READER_H_
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
new file mode 100644
index 0000000..2bedcdd
--- /dev/null
+++ b/media/filters/chunk_demuxer.cc
@@ -0,0 +1,619 @@
+// Copyright (c) 2011 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.
+
+// 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"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "media/base/filter_host.h"
+#include "media/base/data_buffer.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 {
+
+// 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 once GetAVStream() has been removed from
+// the DemuxerStream interface.
+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)
+};
+
+static Buffer* CreateBuffer(const uint8* data, size_t size) {
+ scoped_array<uint8> buf(new uint8[size]);
+ memcpy(buf.get(), data, size);
+ return new DataBuffer(buf.release(), size);
+}
+
+class ChunkDemuxerStream : public DemuxerStream {
+ public:
+ typedef std::deque<scoped_refptr<Buffer> > BufferQueue;
+ typedef std::deque<ReadCallback> ReadCBQueue;
+
+ ChunkDemuxerStream(Type type, AVStream* stream);
+ virtual ~ChunkDemuxerStream();
+
+ void Flush();
+
+ // Checks if it is ok to add the |buffers| to the stream.
+ bool CanAddBuffers(const BufferQueue& buffers) const;
+
+ void AddBuffers(const BufferQueue& buffers);
+ void Shutdown();
+
+ bool GetLastBufferTimestamp(base::TimeDelta* timestamp) const;
+
+ // DemuxerStream methods.
+ virtual void Read(const ReadCallback& read_callback);
+ virtual Type type();
+ virtual const MediaFormat& media_format();
+ virtual void EnableBitstreamConverter();
+ virtual AVStream* GetAVStream();
+
+ private:
+ static void RunCallback(ReadCallback cb, scoped_refptr<Buffer> buffer);
+
+ Type type_;
+ MediaFormat media_format_;
+ AVStream* av_stream_;
+
+ mutable base::Lock lock_;
+ ReadCBQueue read_cbs_;
+ BufferQueue buffers_;
+ bool shutdown_called_;
+
+ // Keeps track of the timestamp of the last buffer we have
+ // added to |buffers_|. This is used to enforce buffers with strictly
+ // monotonically increasing timestamps.
+ base::TimeDelta last_buffer_timestamp_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream);
+};
+
+ChunkDemuxerStream::ChunkDemuxerStream(Type type, AVStream* stream)
+ : type_(type),
+ av_stream_(stream),
+ shutdown_called_(false),
+ last_buffer_timestamp_(kNoTimestamp) {
+}
+
+ChunkDemuxerStream::~ChunkDemuxerStream() {}
+
+void ChunkDemuxerStream::Flush() {
+ VLOG(1) << "Flush()";
+ base::AutoLock auto_lock(lock_);
+ buffers_.clear();
+ last_buffer_timestamp_ = kNoTimestamp;
+}
+
+bool ChunkDemuxerStream::CanAddBuffers(const BufferQueue& buffers) const {
+ base::AutoLock auto_lock(lock_);
+
+ // If we haven't seen any buffers yet than anything can be added.
+ if (last_buffer_timestamp_ == kNoTimestamp)
+ return true;
+
+ if (buffers.empty())
+ return true;
+
+ return (buffers.front()->GetTimestamp() > last_buffer_timestamp_);
+}
+
+void ChunkDemuxerStream::AddBuffers(const BufferQueue& buffers) {
+ if (buffers.empty())
+ return;
+
+ std::deque<base::Closure> callbacks;
+ {
+ base::AutoLock auto_lock(lock_);
+
+ for (BufferQueue::const_iterator itr = buffers.begin();
+ itr != buffers.end(); itr++) {
+
+ base::TimeDelta current_ts = (*itr)->GetTimestamp();
+ if (last_buffer_timestamp_ != kNoTimestamp) {
+ DCHECK_GT(current_ts.ToInternalValue(),
+ last_buffer_timestamp_.ToInternalValue());
+ }
+
+ last_buffer_timestamp_ = current_ts;
+
+ buffers_.push_back(*itr);
+ }
+
+ while (!buffers_.empty() && !read_cbs_.empty()) {
+ callbacks.push_back(base::Bind(&ChunkDemuxerStream::RunCallback,
+ read_cbs_.front(),
+ buffers_.front()));
+ buffers_.pop_front();
+ read_cbs_.pop_front();
+ }
+ }
+
+ while (!callbacks.empty()) {
+ callbacks.front().Run();
+ callbacks.pop_front();
+ }
+}
+
+void ChunkDemuxerStream::Shutdown() {
+ std::deque<ReadCallback> callbacks;
+ {
+ base::AutoLock auto_lock(lock_);
+ shutdown_called_ = true;
+
+ // Collect all the pending Read() callbacks.
+ while (!read_cbs_.empty()) {
+ callbacks.push_back(read_cbs_.front());
+ read_cbs_.pop_front();
+ }
+ }
+
+ // Pass NULL to all callbacks to signify read failure.
+ while (!callbacks.empty()) {
+ callbacks.front().Run(NULL);
+ callbacks.pop_front();
+ }
+}
+
+bool ChunkDemuxerStream::GetLastBufferTimestamp(
+ base::TimeDelta* timestamp) const {
+ base::AutoLock auto_lock(lock_);
+
+ if (buffers_.empty())
+ return false;
+
+ *timestamp = buffers_.back()->GetTimestamp();
+ return true;
+}
+
+// Helper function used to make Closures for ReadCallbacks.
+//static
+void ChunkDemuxerStream::RunCallback(ReadCallback cb,
+ scoped_refptr<Buffer> buffer) {
+ cb.Run(buffer);
+}
+
+// Helper function that makes sure |read_callback| runs on |message_loop|.
+static void RunOnMessageLoop(const DemuxerStream::ReadCallback& read_callback,
+ MessageLoop* message_loop,
+ Buffer* buffer) {
+ if (MessageLoop::current() != message_loop) {
+ message_loop->PostTask(FROM_HERE,
+ NewRunnableFunction(&RunOnMessageLoop,
+ read_callback,
+ message_loop,
+ scoped_refptr<Buffer>(buffer)));
+ return;
+ }
+
+ read_callback.Run(buffer);
+}
+
+// DemuxerStream methods.
+void ChunkDemuxerStream::Read(const ReadCallback& read_callback) {
+ scoped_refptr<Buffer> buffer;
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ if (!shutdown_called_) {
+ if (buffers_.empty()) {
+ // Wrap & store |read_callback| so that it will
+ // get called on the current MessageLoop.
+ read_cbs_.push_back(base::Bind(&RunOnMessageLoop,
+ read_callback,
+ MessageLoop::current()));
+ return;
+ }
+
+ if (!read_cbs_.empty()) {
+ // Wrap & store |read_callback| so that it will
+ // get called on the current MessageLoop.
+ read_cbs_.push_back(base::Bind(&RunOnMessageLoop,
+ read_callback,
+ MessageLoop::current()));
+ return;
+ }
+
+ buffer = buffers_.front();
+ buffers_.pop_front();
+ }
+ }
+
+ read_callback.Run(buffer);
+}
+
+DemuxerStream::Type ChunkDemuxerStream::type() { return type_; }
+
+const MediaFormat& ChunkDemuxerStream::media_format() { return media_format_; }
+
+void ChunkDemuxerStream::EnableBitstreamConverter() {}
+
+AVStream* ChunkDemuxerStream::GetAVStream() { return av_stream_; }
+
+ChunkDemuxer::ChunkDemuxer()
+ : state_(WAITING_FOR_INIT),
+ format_context_(NULL),
+ buffered_bytes_(0),
+ seek_waits_for_data_(true) {
+}
+
+ChunkDemuxer::~ChunkDemuxer() {
+ DCHECK_NE(state_, INITIALIZED);
+
+ if (!format_context_)
+ return;
+
+ DestroyAVFormatContext(format_context_);
+ format_context_ = NULL;
+}
+
+bool ChunkDemuxer::Init(const uint8* data, int size) {
+ DCHECK(data);
+ DCHECK_GT(size, 0);
+
+ base::AutoLock auto_lock(lock_);
+ DCHECK_EQ(state_, WAITING_FOR_INIT);
+
+ const uint8* cur = data;
+ int cur_size = size;
+ WebMInfoParser info_parser;
+ int res = info_parser.Parse(cur, cur_size);
+
+ if (res <= 0) {
+ ChangeState(INIT_ERROR);
+ return false;
+ }
+
+ cur += res;
+ cur_size -= res;
+
+ WebMTracksParser tracks_parser(info_parser.timecode_scale());
+ res = tracks_parser.Parse(cur, cur_size);
+
+ if (res <= 0) {
+ ChangeState(INIT_ERROR);
+ return false;
+ }
+
+ 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, size);
+
+ if (!format_context_ || !SetupStreams() || !ParsePendingBuffers()) {
+ ChangeState(INIT_ERROR);
+ return false;
+ }
+
+ ChangeState(INITIALIZED);
+ return true;
+}
+
+// Filter implementation.
+void ChunkDemuxer::set_host(FilterHost* filter_host) {
+ Demuxer::set_host(filter_host);
+ filter_host->SetDuration(duration_);
+ filter_host->SetCurrentReadPosition(0);
+}
+
+void ChunkDemuxer::Stop(FilterCallback* callback) {
+ VLOG(1) << "Stop()";
+
+ callback->Run();
+ delete callback;
+}
+
+void ChunkDemuxer::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
+ VLOG(1) << "Seek(" << time.InSecondsF() << ")";
+
+ {
+ base::AutoLock auto_lock(lock_);
+
+ if (seek_waits_for_data_) {
+ seek_cb_ = cb;
+ return;
+ }
+ }
+
+ cb.Run(PIPELINE_OK);
+}
+
+void ChunkDemuxer::OnAudioRendererDisabled() {
+ base::AutoLock auto_lock(lock_);
+ audio_ = NULL;
+}
+
+void ChunkDemuxer::SetPreload(Preload preload) {}
+
+// Demuxer implementation.
+scoped_refptr<DemuxerStream> ChunkDemuxer::GetStream(
+ DemuxerStream::Type type) {
+ if (type == DemuxerStream::VIDEO)
+ return video_;
+
+ if (type == DemuxerStream::AUDIO)
+ return audio_;
+
+ return NULL;
+}
+
+base::TimeDelta ChunkDemuxer::GetStartTime() const {
+ VLOG(1) << "GetStartTime()";
+ // TODO(acolwell) : Fix this so it uses the time on the first packet.
+ return base::TimeDelta();
+}
+
+void ChunkDemuxer::FlushData() {
+ base::AutoLock auto_lock(lock_);
+ if (audio_.get())
+ audio_->Flush();
+
+ if (video_.get())
+ video_->Flush();
+
+ pending_buffers_.clear();
+ seek_waits_for_data_ = true;
+}
+
+bool ChunkDemuxer::AddData(const uint8* data, unsigned length) {
+ VLOG(1) << "AddData(" << length << ")";
+ DCHECK(data);
+ DCHECK_GT(length, 0u);
+
+ int64 buffered_bytes = 0;
+ base::TimeDelta buffered_ts = base::TimeDelta::FromSeconds(-1);
+
+ FilterStatusCB cb;
+ {
+ base::AutoLock auto_lock(lock_);
+
+ switch(state_) {
+ case WAITING_FOR_INIT:
+ pending_buffers_.push_back(CreateBuffer(data, length));
+ return true;
+ break;
+
+ case INITIALIZED:
+ if (!ParseAndAddData_Locked(data, length)) {
+ VLOG(1) << "AddData(): parsing data failed";
+ return false;
+ }
+ break;
+
+ case INIT_ERROR:
+ case SHUTDOWN:
+ VLOG(1) << "AddData(): called in unexpected state " << state_;
+ return false;
+ break;
+ }
+
+ seek_waits_for_data_ = false;
+
+ base::TimeDelta tmp;
+ if (audio_.get() && audio_->GetLastBufferTimestamp(&tmp) &&
+ tmp > buffered_ts) {
+ buffered_ts = tmp;
+ }
+
+ if (video_.get() && video_->GetLastBufferTimestamp(&tmp) &&
+ tmp > buffered_ts) {
+ buffered_ts = tmp;
+ }
+
+ buffered_bytes = buffered_bytes_;
+
+ if (!seek_cb_.is_null())
+ std::swap(cb, seek_cb_);
+ }
+
+ // Notify the host of 'network activity' because we got data.
+ if (host()) {
+ host()->SetBufferedBytes(buffered_bytes);
+
+ if (buffered_ts.InSeconds() >= 0) {
+ host()->SetBufferedTime(buffered_ts);
+ }
+
+ host()->SetNetworkActivity(true);
+ }
+
+ if (!cb.is_null())
+ cb.Run(PIPELINE_OK);
+
+ return true;
+}
+
+void ChunkDemuxer::Shutdown() {
+ FilterStatusCB cb;
+ {
+ base::AutoLock auto_lock(lock_);
+
+ std::swap(cb, seek_cb_);
+
+ if (audio_.get())
+ audio_->Shutdown();
+
+ if (video_.get())
+ video_->Shutdown();
+
+ ChangeState(SHUTDOWN);
+ }
+
+ if (!cb.is_null())
+ cb.Run(PIPELINE_ERROR_ABORT);
+}
+
+void ChunkDemuxer::ChangeState(State new_state) {
+ lock_.AssertAcquired();
+ state_ = new_state;
+}
+
+AVFormatContext* ChunkDemuxer::CreateFormatContext(const uint8* data,
+ int size) const {
+ int segment_size = size + sizeof(kEmptyCluster);
+ int buf_size = sizeof(kWebMHeader) + segment_size;
+ scoped_array<uint8> buf(new uint8[buf_size]);
+ memcpy(buf.get(), kWebMHeader, sizeof(kWebMHeader));
+ memcpy(buf.get() + sizeof(kWebMHeader), data, size);
+ memcpy(buf.get() + 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;
+ }
+
+ InMemoryUrlProtocol imup(buf.get(), buf_size, true);
+ std::string key = FFmpegGlue::GetInstance()->AddProtocol(&imup);
+
+ // Open FFmpeg AVFormatContext.
+ AVFormatContext* context = NULL;
+ int result = av_open_input_file(&context, key.c_str(), NULL, 0, NULL);
+
+ // Remove ourself from protocol list.
+ FFmpegGlue::GetInstance()->RemoveProtocol(&imup);
+
+ 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;
+ CodecType codec_type = codec_context->codec_type;
+
+ if (codec_type == CODEC_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 == CODEC_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;
+}
+
+bool ChunkDemuxer::ParsePendingBuffers() {
+ bool had_pending_buffers = !pending_buffers_.empty();
+ // Handle any buffers that came in between the time the pipeline was
+ // started and Init() was called.
+ while(!pending_buffers_.empty()) {
+ scoped_refptr<media::Buffer> buf = pending_buffers_.front();
+ pending_buffers_.pop_front();
+
+ if (!ParseAndAddData_Locked(buf->GetData(), buf->GetDataSize())) {
+ pending_buffers_.clear();
+ ChangeState(INIT_ERROR);
+ return false;
+ }
+ }
+
+ seek_waits_for_data_ = !had_pending_buffers;
+ return true;
+}
+
+bool ChunkDemuxer::ParseAndAddData_Locked(const uint8* data, int length) {
+ if (!cluster_parser_.get())
+ return false;
+
+ const uint8* cur = data;
+ int cur_size = length;
+
+ while (cur_size > 0) {
+ int res = cluster_parser_->Parse(cur, cur_size);
+
+ if (res <= 0) {
+ VLOG(1) << "ParseAndAddData_Locked() : cluster parsing failed.";
+ return false;
+ }
+
+ // Make sure we can add the buffers to both streams before we acutally
+ // 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 false;
+ }
+
+ if (audio_.get())
+ audio_->AddBuffers(cluster_parser_->audio_buffers());
+
+ if (video_.get())
+ video_->AddBuffers(cluster_parser_->video_buffers());
+
+ cur += res;
+ cur_size -= res;
+ }
+
+ // TODO(acolwell) : make this more representative of what is actually
+ // buffered.
+ buffered_bytes_ += length;
+
+ return true;
+}
+
+} // namespace media
diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h
new file mode 100644
index 0000000..7f599e6
--- /dev/null
+++ b/media/filters/chunk_demuxer.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2011 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_FILTERS_CHUNK_DEMUXER_H_
+#define MEDIA_FILTERS_CHUNK_DEMUXER_H_
+
+#include <list>
+
+#include "base/synchronization/lock.h"
+#include "media/base/filters.h"
+#include "media/webm/webm_cluster_parser.h"
+
+struct AVFormatContext;
+
+namespace media {
+
+class ChunkDemuxerStream;
+
+// Demuxer implementation that allows chunks of WebM media data to be passed
+// from JavaScript to the media stack.
+class ChunkDemuxer : public Demuxer {
+ public:
+ ChunkDemuxer();
+ virtual ~ChunkDemuxer();
+
+ bool Init(const uint8* data, int size);
+
+ // Filter implementation.
+ virtual void set_host(FilterHost* filter_host);
+ virtual void Stop(FilterCallback* callback);
+ virtual void Seek(base::TimeDelta time, const FilterStatusCB& cb);
+ virtual void OnAudioRendererDisabled();
+ virtual void SetPreload(Preload preload);
+
+ // Demuxer implementation.
+ virtual scoped_refptr<DemuxerStream> GetStream(DemuxerStream::Type type);
+ virtual base::TimeDelta GetStartTime() const;
+
+ // Methods used by MediaDataSink
+ void FlushData();
+ bool AddData(const uint8* data, unsigned length);
+ void Shutdown();
+
+ private:
+ enum State {
+ WAITING_FOR_INIT,
+ INITIALIZED,
+ INIT_ERROR,
+ SHUTDOWN,
+ };
+
+ void ChangeState(State new_state);
+
+ // Generates an AVFormatContext for the INFO & TRACKS elements contained
+ // in |data|. Returns NULL if parsing |data| fails.
+ AVFormatContext* CreateFormatContext(const uint8* data, int size) const;
+
+ // 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 all the buffers in |pending_buffers_|. Returns false if parsing one
+ // of the buffers fails.
+ bool ParsePendingBuffers();
+
+ // Parse a buffer that was passed to AddData(). |data| is expected to contain
+ // one or more WebM Clusters. Returns false if parsing the data fails.
+ bool ParseAndAddData_Locked(const uint8* data, int length);
+
+ base::Lock lock_;
+ State state_;
+
+ FilterStatusCB seek_cb_;
+
+ scoped_refptr<ChunkDemuxerStream> audio_;
+ scoped_refptr<ChunkDemuxerStream> video_;
+
+ AVFormatContext* format_context_;
+
+ int64 buffered_bytes_;
+
+ base::TimeDelta duration_;
+
+ scoped_ptr<WebMClusterParser> cluster_parser_;
+
+ typedef std::list<scoped_refptr<media::Buffer> > BufferList;
+ BufferList pending_buffers_;
+
+ // Should a Seek() call wait for more data before calling the
+ // callback.
+ bool seek_waits_for_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChunkDemuxer);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_CHUNK_DEMUXER_H_
diff --git a/media/filters/chunk_demuxer_factory.cc b/media/filters/chunk_demuxer_factory.cc
new file mode 100644
index 0000000..4cb81ce
--- /dev/null
+++ b/media/filters/chunk_demuxer_factory.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2011 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/filters/chunk_demuxer_factory.h"
+
+#include "base/message_loop.h"
+#include "media/filters/chunk_demuxer.h"
+
+namespace media {
+
+MediaDataSink::MediaDataSink(const scoped_refptr<ChunkDemuxer>& demuxer)
+ : demuxer_(demuxer) {
+}
+
+MediaDataSink::~MediaDataSink() {}
+
+void MediaDataSink::Flush() {
+ demuxer_->FlushData();
+}
+
+bool MediaDataSink::AddData(const uint8* data, unsigned length) {
+ return demuxer_->AddData(data, length);
+}
+
+void MediaDataSink::Shutdown() {
+ demuxer_->Shutdown();
+}
+
+const char ChunkDemuxerFactory::kURLPrefix[] = "x-media-chunks:";
+
+class ChunkDemuxerFactory::BuildState
+ : public base::RefCountedThreadSafe<BuildState> {
+ public:
+ static const int64 kMaxInfoSize = 32678;
+
+ BuildState(const std::string& url, BuildCallback* cb,
+ const scoped_refptr<ChunkDemuxer>& demuxer)
+ : url_(url),
+ cb_(cb),
+ demuxer_(demuxer),
+ read_buffer_(NULL),
+ message_loop_(MessageLoop::current()) {
+ AddRef();
+ }
+
+ virtual ~BuildState() {}
+
+ void OnBuildDone(PipelineStatus status, DataSource* data_source) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &BuildState::DoBuildDone,
+ status, scoped_refptr<DataSource>(data_source)));
+ }
+
+ void DoBuildDone(PipelineStatus status, DataSource* data_source) {
+ if (status != PIPELINE_OK) {
+ cb_->Run(status, static_cast<Demuxer*>(NULL));
+ Release();
+ return;
+ }
+
+ data_source_ = data_source;
+
+ int64 size = 0;
+
+ if (!data_source_->GetSize(&size) || size >= kMaxInfoSize) {
+ RunCallbackAndStop(DEMUXER_ERROR_COULD_NOT_OPEN);
+ return;
+ }
+
+ DCHECK(!read_buffer_.get());
+ read_buffer_.reset(new uint8[size]);
+ data_source_->Read(0, size, read_buffer_.get(),
+ NewCallback(this, &BuildState::OnReadDone));
+ }
+
+ void OnReadDone(size_t size) {
+ message_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &BuildState::DoReadDone,
+ size));
+ }
+
+ void DoReadDone(size_t size) {
+ if (size == DataSource::kReadError) {
+ RunCallbackAndStop(PIPELINE_ERROR_READ);
+ return;
+ }
+
+ if (!demuxer_->Init(read_buffer_.get(), size)) {
+ RunCallbackAndStop(DEMUXER_ERROR_COULD_NOT_OPEN);
+ return;
+ }
+
+ RunCallbackAndStop(PIPELINE_OK);
+ }
+
+ void RunCallbackAndStop(PipelineStatus status) {
+ scoped_refptr<Demuxer> demuxer;
+
+ if (status == PIPELINE_OK)
+ demuxer = demuxer_.get();
+
+ cb_->Run(status, demuxer.get());
+ data_source_->Stop(NewCallback(this, &BuildState::OnStopDone));
+ }
+
+ void OnStopDone() {
+ message_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &BuildState::DoStopDone));
+ }
+
+ void DoStopDone() { Release(); }
+
+ private:
+ std::string url_;
+ scoped_ptr<BuildCallback> cb_;
+ scoped_refptr<ChunkDemuxer> demuxer_;
+
+ scoped_refptr<DataSource> data_source_;
+ scoped_array<uint8> read_buffer_;
+ MessageLoop* message_loop_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(BuildState);
+};
+
+ChunkDemuxerFactory::ChunkDemuxerFactory(
+ DataSourceFactory* data_source_factory)
+ : data_source_factory_(data_source_factory) {
+}
+
+ChunkDemuxerFactory::~ChunkDemuxerFactory() {}
+
+bool ChunkDemuxerFactory::IsUrlSupported(const std::string& url) const {
+ return (url.find(kURLPrefix) == 0);
+}
+
+MediaDataSink* ChunkDemuxerFactory::CreateMediaDataSink() {
+ demuxer_ = new ChunkDemuxer();
+ return new MediaDataSink(demuxer_);
+}
+
+void ChunkDemuxerFactory::Build(const std::string& url, BuildCallback* cb) {
+ if (!IsUrlSupported(url) || !demuxer_.get()) {
+ cb->Run(DEMUXER_ERROR_COULD_NOT_OPEN, static_cast<Demuxer*>(NULL));
+ delete cb;
+ return;
+ }
+
+ std::string info_url = url.substr(strlen(kURLPrefix));
+
+ data_source_factory_->Build(
+ info_url,
+ NewCallback(new BuildState(info_url, cb, demuxer_),
+ &ChunkDemuxerFactory::BuildState::OnBuildDone));
+ demuxer_ = NULL;
+}
+
+DemuxerFactory* ChunkDemuxerFactory::Clone() const {
+ return new ChunkDemuxerFactory(data_source_factory_->Clone());
+}
+
+} // namespace media
diff --git a/media/filters/chunk_demuxer_factory.h b/media/filters/chunk_demuxer_factory.h
new file mode 100644
index 0000000..df6f742
--- /dev/null
+++ b/media/filters/chunk_demuxer_factory.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2011 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_FILTERS_CHUNK_DEMUXER_FACTORY_H_
+#define MEDIA_FILTERS_CHUNK_DEMUXER_FACTORY_H_
+
+#include "base/scoped_ptr.h"
+#include "media/base/filter_factories.h"
+
+namespace media {
+
+class ChunkDemuxer;
+
+// Class used by an external object to send media data to the
+// Demuxer. This object is created by the DemuxerFactory and
+// contains the Demuxer that will be returned in the next Build()
+// call on the factory. The external object tells the factory
+// to create one of these objects before it starts the Pipeline.
+// It does this because the external object may need to make AddData()
+// calls before the pipeline has completely initialized. This class
+// allows data from these calls to be queued until initialization
+// completes. It represents the minimal operations needed by
+// the external object to talk to the Demuxer. It also allows
+// the external object to have a longer lifetime than the pipeline.
+class MediaDataSink {
+ public:
+ MediaDataSink(const scoped_refptr<ChunkDemuxer>& demuxer);
+ ~MediaDataSink();
+
+ // Flush all data passed via AddData().
+ void Flush();
+
+ // Sends media data to the demuxer. Returns true if the data is valid.
+ bool AddData(const uint8* data, unsigned length);
+
+ // Signals that playback is shutting down and further AddData() calls
+ // should fail. This also cancels pending Read()s on DemuxerStreams.
+ void Shutdown();
+
+ private:
+ scoped_refptr<ChunkDemuxer> demuxer_;
+ DISALLOW_IMPLICIT_CONSTRUCTORS(MediaDataSink);
+};
+
+class ChunkDemuxerFactory : public DemuxerFactory {
+ public:
+ // Takes a reference to |demuxer_factory|.
+ ChunkDemuxerFactory(DataSourceFactory* data_source_factory);
+ virtual ~ChunkDemuxerFactory();
+
+ bool IsUrlSupported(const std::string& url) const;
+ MediaDataSink* CreateMediaDataSink();
+
+ // DemuxerFactory methods.
+ virtual void Build(const std::string& url, BuildCallback* cb);
+ virtual DemuxerFactory* Clone() const;
+
+ private:
+ static const char kURLPrefix[];
+ class BuildState;
+
+ scoped_ptr<DataSourceFactory> data_source_factory_;
+ scoped_refptr<ChunkDemuxer> demuxer_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerFactory);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_CHUNK_DEMUXER_FACTORY_H_
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc
new file mode 100644
index 0000000..53d2084
--- /dev/null
+++ b/media/filters/chunk_demuxer_unittest.cc
@@ -0,0 +1,411 @@
+// Copyright (c) 2011 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 "base/base_paths.h"
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "media/base/media.h"
+#include "media/base/mock_callback.h"
+#include "media/base/mock_ffmpeg.h"
+#include "media/filters/chunk_demuxer.h"
+#include "media/webm/cluster_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::_;
+
+namespace media {
+
+static const uint8 kTracksHeader[] = {
+ 0x16, 0x54, 0xAE, 0x6B, // Tracks ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tracks(size = 0)
+};
+
+static const int kTracksHeaderSize = sizeof(kTracksHeader);
+static const int kTracksSizeOffset = 4;
+
+static const int kVideoTrackNum = 1;
+static const int kAudioTrackNum = 2;
+
+class ChunkDemuxerTest : public testing::Test{
+ protected:
+ enum CodecsIndex {
+ AUDIO,
+ VIDEO,
+ MAX_CODECS_INDEX
+ };
+
+ ChunkDemuxerTest()
+ : demuxer_(new ChunkDemuxer()) {
+ memset(&format_context_, 0, sizeof(format_context_));
+ memset(&streams_, 0, sizeof(streams_));
+ memset(&codecs_, 0, sizeof(codecs_));
+
+ codecs_[VIDEO].codec_type = CODEC_TYPE_VIDEO;
+ codecs_[VIDEO].codec_id = CODEC_ID_VP8;
+ codecs_[VIDEO].width = 320;
+ codecs_[VIDEO].height = 240;
+
+ codecs_[AUDIO].codec_type = CODEC_TYPE_AUDIO;
+ codecs_[AUDIO].codec_id = CODEC_ID_VORBIS;
+ codecs_[AUDIO].channels = 2;
+ codecs_[AUDIO].sample_rate = 44100;
+ }
+
+ virtual ~ChunkDemuxerTest() {
+ if (demuxer_.get())
+ demuxer_->Shutdown();
+ }
+
+ void ReadFile(const std::string& name, scoped_array<uint8>* buffer,
+ int* size) {
+ FilePath file_path;
+ EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
+ file_path = file_path.Append(FILE_PATH_LITERAL("media"))
+ .Append(FILE_PATH_LITERAL("test"))
+ .Append(FILE_PATH_LITERAL("data"))
+ .AppendASCII(name);
+
+ int64 tmp = 0;
+ EXPECT_TRUE(file_util::GetFileSize(file_path, &tmp));
+ EXPECT_LT(tmp, 32768);
+ int file_size = static_cast<int>(tmp);
+
+ buffer->reset(new uint8[file_size]);
+ EXPECT_EQ(file_size,
+ file_util::ReadFile(file_path,
+ reinterpret_cast<char*>(buffer->get()),
+ file_size));
+ *size = file_size;
+ }
+
+ void CreateInfoTracks(bool has_audio, bool has_video,
+ scoped_array<uint8>* buffer, int* size) {
+ scoped_array<uint8> info;
+ int info_size = 0;
+ scoped_array<uint8> audio_track_entry;
+ int audio_track_entry_size = 0;
+ scoped_array<uint8> video_track_entry;
+ int video_track_entry_size = 0;
+
+ ReadFile("webm_info_element", &info, &info_size);
+ ReadFile("webm_vorbis_track_entry", &audio_track_entry,
+ &audio_track_entry_size);
+ ReadFile("webm_vp8_track_entry", &video_track_entry,
+ &video_track_entry_size);
+
+ int tracks_element_size = 0;
+
+ if (has_audio)
+ tracks_element_size += audio_track_entry_size;
+
+ if (has_video)
+ tracks_element_size += video_track_entry_size;
+
+ *size = info_size + kTracksHeaderSize + tracks_element_size;
+
+ buffer->reset(new uint8[*size]);
+
+ uint8* buf = buffer->get();
+ memcpy(buf, info.get(), info_size);
+ buf += info_size;
+
+ memcpy(buf, kTracksHeader, kTracksHeaderSize);
+
+ int tmp = tracks_element_size;
+ for (int i = 7; i > 0; i--) {
+ buf[kTracksSizeOffset + i] = tmp & 0xff;
+ tmp >>= 8;
+ }
+
+ buf += kTracksHeaderSize;
+
+ if (has_audio) {
+ memcpy(buf, audio_track_entry.get(), audio_track_entry_size);
+ buf += audio_track_entry_size;
+ }
+
+ if (has_video) {
+ memcpy(buf, video_track_entry.get(), video_track_entry_size);
+ buf += video_track_entry_size;
+ }
+ }
+
+ void SetupAVFormatContext(bool has_audio, bool has_video) {
+ int i = 0;
+ if (has_audio) {
+ format_context_.streams[i] = &streams_[i];
+ streams_[i].codec = &codecs_[AUDIO];
+ streams_[i].duration = 100;
+ streams_[i].time_base.den = base::Time::kMicrosecondsPerSecond;
+ streams_[i].time_base.num = 1;
+ i++;
+ }
+
+ if (has_video) {
+ format_context_.streams[i] = &streams_[i];
+ streams_[i].codec = &codecs_[VIDEO];
+ streams_[i].duration = 100;
+ streams_[i].time_base.den = base::Time::kMicrosecondsPerSecond;
+ streams_[i].time_base.num = 1;
+ i++;
+ }
+
+ format_context_.nb_streams = i;
+ }
+
+void InitDemuxer(bool has_audio, bool has_video) {
+ EXPECT_CALL(mock_ffmpeg_, AVOpenInputFile(_, _, NULL, 0, NULL))
+ .WillOnce(DoAll(SetArgumentPointee<0>(&format_context_),
+ Return(0)));
+
+ EXPECT_CALL(mock_ffmpeg_, AVFindStreamInfo(&format_context_))
+ .WillOnce(Return(0));
+
+ EXPECT_CALL(mock_ffmpeg_, AVCloseInputFile(&format_context_));
+
+ EXPECT_CALL(mock_ffmpeg_, AVRegisterLockManager(_))
+ .WillRepeatedly(Return(0));
+
+ scoped_array<uint8> info_tracks;
+ int info_tracks_size = 0;
+ CreateInfoTracks(has_audio, has_video, &info_tracks, &info_tracks_size);
+
+ SetupAVFormatContext(has_audio, has_video);
+
+ EXPECT_EQ(demuxer_->Init(info_tracks.get(), info_tracks_size),
+ has_audio || has_video);
+ }
+
+ void AddSimpleBlock(ClusterBuilder* cb, int track_num, int64 timecode) {
+ uint8 data[] = { 0x00 };
+ cb->AddSimpleBlock(track_num, timecode, 0, data, sizeof(data));
+ }
+
+ MOCK_METHOD1(Checkpoint, void(int id));
+
+ MockFFmpeg mock_ffmpeg_;
+
+ AVFormatContext format_context_;
+ AVCodecContext codecs_[MAX_CODECS_INDEX];
+ AVStream streams_[MAX_CODECS_INDEX];
+
+ scoped_refptr<ChunkDemuxer> demuxer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChunkDemuxerTest);
+};
+
+TEST_F(ChunkDemuxerTest, TestInit) {
+ // Test no streams, audio-only, video-only, and audio & video scenarios.
+ for (int i = 0; i < 4; i++) {
+ bool has_audio = (i & 0x1) != 0;
+ bool has_video = (i & 0x2) != 0;
+
+ demuxer_ = new ChunkDemuxer();
+ InitDemuxer(has_audio, has_video);
+ EXPECT_EQ(demuxer_->GetStream(DemuxerStream::AUDIO).get() != NULL,
+ has_audio);
+ EXPECT_EQ(demuxer_->GetStream(DemuxerStream::VIDEO).get() != NULL,
+ has_video);
+ demuxer_->Shutdown();
+ demuxer_ = NULL;
+ }
+}
+
+// Makes sure that Seek() reports an error if Shutdown()
+// is called before the first cluster is passed to the demuxer.
+TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) {
+ InitDemuxer(true, true);
+
+ demuxer_->Seek(base::TimeDelta::FromSeconds(0),
+ NewExpectedStatusCB(PIPELINE_ERROR_ABORT));
+}
+
+// Test that Seek() completes successfully when the first cluster
+// arrives.
+TEST_F(ChunkDemuxerTest, TestAddDataAfterSeek) {
+ InitDemuxer(true, true);
+
+ InSequence s;
+
+ EXPECT_CALL(*this, Checkpoint(1));
+
+ demuxer_->Seek(base::TimeDelta::FromSeconds(0),
+ NewExpectedStatusCB(PIPELINE_OK));
+
+ EXPECT_CALL(*this, Checkpoint(2));
+
+ ClusterBuilder cb;
+ cb.SetClusterTimecode(0);
+ AddSimpleBlock(&cb, kVideoTrackNum, 0);
+ scoped_ptr<Cluster> cluster(cb.Finish());
+
+ Checkpoint(1);
+
+ EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size()));
+
+ Checkpoint(2);
+}
+
+// Test the case where AddData() is called before Init(). This can happen
+// when JavaScript starts sending data before the pipeline is completely
+// initialized.
+TEST_F(ChunkDemuxerTest, TestAddDataBeforeInit) {
+ ClusterBuilder cb;
+ cb.SetClusterTimecode(0);
+ AddSimpleBlock(&cb, kVideoTrackNum, 0);
+ scoped_ptr<Cluster> cluster(cb.Finish());
+
+ EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size()));
+
+ InitDemuxer(true, true);
+
+ demuxer_->Seek(base::TimeDelta::FromSeconds(0),
+ NewExpectedStatusCB(PIPELINE_OK));
+}
+
+static void OnReadDone(const base::TimeDelta& expected_time,
+ bool* called,
+ Buffer* buffer) {
+ EXPECT_EQ(expected_time, buffer->GetTimestamp());
+ *called = true;
+}
+
+// Make sure Read() callbacks are dispatched with the proper data.
+TEST_F(ChunkDemuxerTest, TestRead) {
+ InitDemuxer(true, true);
+
+ scoped_refptr<DemuxerStream> audio =
+ demuxer_->GetStream(DemuxerStream::AUDIO);
+ scoped_refptr<DemuxerStream> video =
+ demuxer_->GetStream(DemuxerStream::VIDEO);
+
+ bool audio_read_done = false;
+ bool video_read_done = false;
+ audio->Read(base::Bind(&OnReadDone,
+ base::TimeDelta::FromMilliseconds(32),
+ &audio_read_done));
+
+ video->Read(base::Bind(&OnReadDone,
+ base::TimeDelta::FromMilliseconds(123),
+ &video_read_done));
+
+ ClusterBuilder cb;
+ cb.SetClusterTimecode(0);
+ AddSimpleBlock(&cb, kAudioTrackNum, 32);
+ AddSimpleBlock(&cb, kVideoTrackNum, 123);
+ scoped_ptr<Cluster> cluster(cb.Finish());
+
+ EXPECT_TRUE(demuxer_->AddData(cluster->data(), cluster->size()));
+
+ EXPECT_TRUE(audio_read_done);
+ EXPECT_TRUE(video_read_done);
+}
+
+TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) {
+ InitDemuxer(true, true);
+
+ ClusterBuilder cb;
+
+ cb.SetClusterTimecode(10);
+ AddSimpleBlock(&cb, kAudioTrackNum, 10);
+ AddSimpleBlock(&cb, kVideoTrackNum, 10);
+ AddSimpleBlock(&cb, kAudioTrackNum, 33);
+ AddSimpleBlock(&cb, kVideoTrackNum, 43);
+ scoped_ptr<Cluster> clusterA(cb.Finish());
+
+ EXPECT_TRUE(demuxer_->AddData(clusterA->data(), clusterA->size()));
+
+ // Cluster B starts before clusterA and has data
+ // that overlaps.
+ cb.SetClusterTimecode(5);
+ AddSimpleBlock(&cb, kAudioTrackNum, 5);
+ AddSimpleBlock(&cb, kVideoTrackNum, 7);
+ AddSimpleBlock(&cb, kAudioTrackNum, 28);
+ AddSimpleBlock(&cb, kVideoTrackNum, 40);
+ scoped_ptr<Cluster> clusterB(cb.Finish());
+
+ // Make sure that AddData() fails because this cluster data
+ // is before previous data.
+ EXPECT_FALSE(demuxer_->AddData(clusterB->data(), clusterB->size()));
+
+ // Cluster C starts after clusterA.
+ cb.SetClusterTimecode(56);
+ AddSimpleBlock(&cb, kAudioTrackNum, 56);
+ AddSimpleBlock(&cb, kVideoTrackNum, 76);
+ AddSimpleBlock(&cb, kAudioTrackNum, 79);
+ AddSimpleBlock(&cb, kVideoTrackNum, 109);
+ scoped_ptr<Cluster> clusterC(cb.Finish());
+
+ // Verify that clusterC is accepted.
+ EXPECT_TRUE(demuxer_->AddData(clusterC->data(), clusterC->size()));
+
+ // Flush and try clusterB again.
+ demuxer_->FlushData();
+ EXPECT_TRUE(demuxer_->AddData(clusterB->data(), clusterB->size()));
+
+ // Following that with clusterC should work too since it doesn't
+ // overlap with clusterB.
+ EXPECT_TRUE(demuxer_->AddData(clusterC->data(), clusterC->size()));
+}
+
+TEST_F(ChunkDemuxerTest, TestInvalidBlockSequences) {
+ InitDemuxer(true, true);
+
+ ClusterBuilder cb;
+
+ // Test the case where timecode is not monotonically
+ // increasing but stays above the cluster timecode.
+ cb.SetClusterTimecode(5);
+ AddSimpleBlock(&cb, kAudioTrackNum, 5);
+ AddSimpleBlock(&cb, kVideoTrackNum, 10);
+ AddSimpleBlock(&cb, kAudioTrackNum, 7);
+ AddSimpleBlock(&cb, kVideoTrackNum, 15);
+ scoped_ptr<Cluster> clusterA(cb.Finish());
+
+ EXPECT_FALSE(demuxer_->AddData(clusterA->data(), clusterA->size()));
+
+ // Test timecodes going backwards before cluster timecode.
+ cb.SetClusterTimecode(5);
+ AddSimpleBlock(&cb, kAudioTrackNum, 5);
+ AddSimpleBlock(&cb, kVideoTrackNum, 5);
+ AddSimpleBlock(&cb, kAudioTrackNum, 3);
+ AddSimpleBlock(&cb, kVideoTrackNum, 3);
+ scoped_ptr<Cluster> clusterB(cb.Finish());
+
+ EXPECT_FALSE(demuxer_->AddData(clusterB->data(), clusterB->size()));
+
+ // Test strict monotonic increasing timestamps on a per stream
+ // basis.
+ cb.SetClusterTimecode(5);
+ AddSimpleBlock(&cb, kAudioTrackNum, 5);
+ AddSimpleBlock(&cb, kVideoTrackNum, 5);
+ AddSimpleBlock(&cb, kAudioTrackNum, 5);
+ AddSimpleBlock(&cb, kVideoTrackNum, 7);
+ scoped_ptr<Cluster> clusterC(cb.Finish());
+
+ EXPECT_FALSE(demuxer_->AddData(clusterC->data(), clusterC->size()));
+
+ // Test strict monotonic increasing timestamps on a per stream
+ // basis across clusters.
+ cb.SetClusterTimecode(5);
+ AddSimpleBlock(&cb, kAudioTrackNum, 5);
+ AddSimpleBlock(&cb, kVideoTrackNum, 5);
+ scoped_ptr<Cluster> clusterD(cb.Finish());
+
+ EXPECT_TRUE(demuxer_->AddData(clusterD->data(), clusterD->size()));
+
+ cb.SetClusterTimecode(5);
+ AddSimpleBlock(&cb, kAudioTrackNum, 5);
+ AddSimpleBlock(&cb, kVideoTrackNum, 7);
+ scoped_ptr<Cluster> clusterE(cb.Finish());
+
+ EXPECT_FALSE(demuxer_->AddData(clusterE->data(), clusterE->size()));
+}
+
+} // namespace media
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index 0ee711f..58082b1 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -280,25 +280,7 @@ FFmpegDemuxer::~FFmpegDemuxer() {
if (!format_context_)
return;
- // Iterate each stream and destroy each one of them.
- int streams = format_context_->nb_streams;
- for (int i = 0; i < streams; ++i) {
- AVStream* stream = format_context_->streams[i];
-
- // The conditions for calling avcodec_close():
- // 1. AVStream is alive.
- // 2. AVCodecContext in AVStream is alive.
- // 3. AVCodec in AVCodecContext is alive.
- // Notice that closing a codec context without prior avcodec_open() will
- // result in a crash in FFmpeg.
- if (stream && stream->codec && stream->codec->codec) {
- stream->discard = AVDISCARD_ALL;
- avcodec_close(stream->codec);
- }
- }
-
- // Then finally cleanup the format context.
- av_close_input_file(format_context_);
+ DestroyAVFormatContext(format_context_);
format_context_ = NULL;
}
diff --git a/media/filters/in_memory_url_protocol.cc b/media/filters/in_memory_url_protocol.cc
new file mode 100644
index 0000000..dd7d984b
--- /dev/null
+++ b/media/filters/in_memory_url_protocol.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 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/filters/in_memory_url_protocol.h"
+
+namespace media {
+
+InMemoryUrlProtocol::InMemoryUrlProtocol(const uint8* data, int64 size,
+ bool streaming)
+ : data_(data),
+ size_(size),
+ position_(0),
+ streaming_(streaming) {
+}
+
+InMemoryUrlProtocol::~InMemoryUrlProtocol() {}
+
+int InMemoryUrlProtocol::Read(int size, uint8* data) {
+ if (size < 0)
+ return -1;
+
+ int available_bytes = static_cast<int>(size_ - position_);
+ if (size > available_bytes)
+ size = available_bytes;
+
+ memcpy(data, data_ + position_, size);
+ position_ += size;
+ return size;
+}
+
+bool InMemoryUrlProtocol::GetPosition(int64* position_out) {
+ if (!position_out)
+ return false;
+
+ *position_out = position_;
+ return true;
+}
+
+bool InMemoryUrlProtocol::SetPosition(int64 position) {
+ if (position < 0 || position >= size_)
+ return false;
+ position_ = position;
+ return true;
+}
+
+bool InMemoryUrlProtocol::GetSize(int64* size_out) {
+ if (!size_out)
+ return false;
+
+ *size_out = size_;
+ return true;
+}
+
+bool InMemoryUrlProtocol::IsStreaming() {
+ return streaming_;
+}
+
+} // namespace media
diff --git a/media/filters/in_memory_url_protocol.h b/media/filters/in_memory_url_protocol.h
new file mode 100644
index 0000000..7bad5e3
--- /dev/null
+++ b/media/filters/in_memory_url_protocol.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2011 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_FILTERS_IN_MEMORY_URL_PROTOCOL_H_
+#define MEDIA_FILTERS_IN_MEMORY_URL_PROTOCOL_H_
+
+#include "media/filters/ffmpeg_glue.h"
+
+#include "base/basictypes.h"
+
+namespace media {
+
+// Simple FFmpegURLProtocol that reads from a buffer.
+// NOTE: This object does not copy the buffer so the
+// buffer pointer passed into the constructor
+// needs to remain valid for the entire lifetime of
+// this object.
+class InMemoryUrlProtocol : public FFmpegURLProtocol {
+ public:
+ InMemoryUrlProtocol(const uint8* buf, int64 size, bool streaming);
+ virtual ~InMemoryUrlProtocol();
+
+ // FFmpegURLProtocol methods.
+ virtual int Read(int size, uint8* data);
+ virtual bool GetPosition(int64* position_out);
+ virtual bool SetPosition(int64 position);
+ virtual bool GetSize(int64* size_out);
+ virtual bool IsStreaming();
+
+ private:
+ const uint8* data_;
+ int64 size_;
+ int64 position_;
+ bool streaming_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(InMemoryUrlProtocol);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_IN_MEMORY_URL_PROTOCOL_H_
diff --git a/media/media.gyp b/media/media.gyp
index 5356f2a..07a622e 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -151,11 +151,17 @@
'filters/audio_renderer_impl.h',
'filters/bitstream_converter.cc',
'filters/bitstream_converter.h',
+ 'filters/chunk_demuxer.cc',
+ 'filters/chunk_demuxer.h',
+ 'filters/chunk_demuxer_factory.cc',
+ 'filters/chunk_demuxer_factory.h',
'filters/decoder_base.h',
'filters/ffmpeg_audio_decoder.cc',
'filters/ffmpeg_audio_decoder.h',
'filters/ffmpeg_demuxer.cc',
'filters/ffmpeg_demuxer.h',
+ 'filters/ffmpeg_demuxer_factory.cc',
+ 'filters/ffmpeg_demuxer_factory.h',
'filters/ffmpeg_h264_bitstream_converter.cc',
'filters/ffmpeg_h264_bitstream_converter.h',
'filters/ffmpeg_glue.cc',
@@ -164,10 +170,10 @@
'filters/ffmpeg_video_decoder.h',
'filters/file_data_source.cc',
'filters/file_data_source.h',
- 'filters/ffmpeg_demuxer_factory.cc',
- 'filters/ffmpeg_demuxer_factory.h',
'filters/file_data_source_factory.cc',
'filters/file_data_source_factory.h',
+ 'filters/in_memory_url_protocol.cc',
+ 'filters/in_memory_url_protocol.h',
'filters/null_audio_renderer.cc',
'filters/null_audio_renderer.h',
'filters/null_video_renderer.cc',
@@ -192,6 +198,15 @@
'video/video_decode_accelerator.cc',
'video/video_decode_accelerator.h',
'video/video_decode_engine.h',
+ 'webm/webm_constants.h',
+ 'webm/webm_cluster_parser.cc',
+ 'webm/webm_cluster_parser.h',
+ 'webm/webm_info_parser.cc',
+ 'webm/webm_info_parser.h',
+ 'webm/webm_parser.cc',
+ 'webm/webm_parser.h',
+ 'webm/webm_tracks_parser.cc',
+ 'webm/webm_tracks_parser.h',
],
'direct_dependent_settings': {
'include_dirs': [
@@ -401,6 +416,7 @@
'filters/audio_renderer_algorithm_ola_unittest.cc',
'filters/audio_renderer_base_unittest.cc',
'filters/bitstream_converter_unittest.cc',
+ 'filters/chunk_demuxer_unittest.cc',
'filters/decoder_base_unittest.cc',
'filters/ffmpeg_demuxer_unittest.cc',
'filters/ffmpeg_glue_unittest.cc',
@@ -410,6 +426,8 @@
'filters/rtc_video_decoder_unittest.cc',
'filters/video_renderer_base_unittest.cc',
'video/ffmpeg_video_decode_engine_unittest.cc',
+ 'webm/cluster_builder.cc',
+ 'webm/cluster_builder.h',
],
'conditions': [
['os_posix==1 and OS!="mac"', {
diff --git a/media/test/data/webm_info_element b/media/test/data/webm_info_element
new file mode 100644
index 0000000..1097d13
--- /dev/null
+++ b/media/test/data/webm_info_element
Binary files differ
diff --git a/media/test/data/webm_vorbis_track_entry b/media/test/data/webm_vorbis_track_entry
new file mode 100644
index 0000000..a873521
--- /dev/null
+++ b/media/test/data/webm_vorbis_track_entry
Binary files differ
diff --git a/media/test/data/webm_vp8_track_entry b/media/test/data/webm_vp8_track_entry
new file mode 100644
index 0000000..f544e9d
--- /dev/null
+++ b/media/test/data/webm_vp8_track_entry
Binary files differ
diff --git a/media/webm/cluster_builder.cc b/media/webm/cluster_builder.cc
new file mode 100644
index 0000000..eb5fbfb
--- /dev/null
+++ b/media/webm/cluster_builder.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 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/cluster_builder.h"
+
+#include "base/logging.h"
+#include "media/base/data_buffer.h"
+
+namespace media {
+
+static const uint8 kClusterHeader[] = {
+ 0x1F, 0x43, 0xB6, 0x75, // CLUSTER ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cluster(size = 0)
+ 0xE7, // Timecode ID
+ 0x88, // timecode(size=8)
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // timecode value
+};
+
+const int kClusterHeaderSize = sizeof(kClusterHeader);
+const int kClusterSizeOffset = 4;
+const int kClusterTimecodeOffset = 14;
+
+static const uint8 kSimpleBlockHeader[] = {
+ 0xA3, // SimpleBlock ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SimpleBlock(size = 0)
+};
+
+const int kSimpleBlockHeaderSize = sizeof(kSimpleBlockHeader);
+const int kSimpleBlockSizeOffset = 1;
+
+const int kInitialBufferSize = 32768;
+
+Cluster::Cluster(const uint8* data, int size) : data_(data), size_(size) {}
+Cluster::~Cluster() {}
+
+ClusterBuilder::ClusterBuilder() { Reset(); }
+
+void ClusterBuilder::SetClusterTimecode(int64 cluster_timecode) {
+ DCHECK_EQ(cluster_timecode_, -1);
+
+ cluster_timecode_ = cluster_timecode;
+
+ // Write the timecode into the header.
+ uint8* buf = buffer_.get() + kClusterTimecodeOffset;
+ for (int i = 7; i >= 0; --i) {
+ buf[i] = cluster_timecode & 0xff;
+ cluster_timecode >>= 8;
+ }
+}
+
+void ClusterBuilder::AddSimpleBlock(int track_num, int64 timecode, int flags,
+ const uint8* data, int size) {
+ DCHECK_GE(track_num, 0);
+ DCHECK_LE(track_num, 126);
+ DCHECK_GE(flags, 0);
+ DCHECK_LE(flags, 0xff);
+ DCHECK(data);
+ DCHECK_GT(size, 0);
+ DCHECK_NE(cluster_timecode_, -1);
+
+ int64 timecode_delta = timecode - cluster_timecode_;
+ DCHECK_GE(timecode_delta, -32768);
+ DCHECK_LE(timecode_delta, 32767);
+
+ int block_size = 4 + size;
+ int bytes_needed = kSimpleBlockHeaderSize + block_size;
+ if (bytes_needed > (buffer_size_ - bytes_used_))
+ ExtendBuffer(bytes_needed);
+
+ uint8* buf = buffer_.get() + bytes_used_;
+ int block_offset = bytes_used_;
+ memcpy(buf, kSimpleBlockHeader, kSimpleBlockHeaderSize);
+ UpdateUInt64(block_offset + kSimpleBlockSizeOffset, block_size);
+ buf += kSimpleBlockHeaderSize;
+
+ buf[0] = 0x80 | (track_num & 0x7F);
+ buf[1] = (timecode_delta >> 8) & 0xff;
+ buf[2] = timecode_delta & 0xff;
+ buf[3] = flags & 0xff;
+ memcpy(buf + 4, data, size);
+
+ bytes_used_ += bytes_needed;
+}
+
+Cluster* ClusterBuilder::Finish() {
+ DCHECK_NE(cluster_timecode_, -1);
+
+ UpdateUInt64(kClusterSizeOffset, bytes_used_ - (kClusterSizeOffset + 8));
+
+ scoped_ptr<Cluster> ret(new Cluster(buffer_.release(), bytes_used_));
+ Reset();
+ return ret.release();
+}
+
+void ClusterBuilder::Reset() {
+ buffer_size_ = kInitialBufferSize;
+ buffer_.reset(new uint8[buffer_size_]);
+ memcpy(buffer_.get(), kClusterHeader, kClusterHeaderSize);
+ bytes_used_ = kClusterHeaderSize;
+ cluster_timecode_ = -1;
+}
+
+void ClusterBuilder::ExtendBuffer(int bytes_needed) {
+ int new_buffer_size = 2 * buffer_size_;
+
+ while ((new_buffer_size - bytes_used_) < bytes_needed)
+ new_buffer_size *= 2;
+
+ scoped_array<uint8> new_buffer(new uint8[new_buffer_size]);
+
+ memcpy(new_buffer.get(), buffer_.get(), bytes_used_);
+ buffer_.reset(new_buffer.release());
+ buffer_size_ = new_buffer_size;
+}
+
+void ClusterBuilder::UpdateUInt64(int offset, int64 value) {
+ DCHECK_LE(offset + 7, buffer_size_);
+ uint8* buf = buffer_.get() + offset;
+
+ // Fill the last 7 bytes of size field in big-endian order.
+ for (int i = 7; i > 0; i--) {
+ buf[i] = value & 0xff;
+ value >>= 8;
+ }
+}
+
+} // namespace media
diff --git a/media/webm/cluster_builder.h b/media/webm/cluster_builder.h
new file mode 100644
index 0000000..3ff30ab
--- /dev/null
+++ b/media/webm/cluster_builder.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 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_CLUSTER_BUILDER_H_
+#define MEDIA_WEBM_CLUSTER_BUILDER_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "media/base/buffers.h"
+
+namespace media {
+
+class Cluster {
+ public:
+ // Takes ownership of |data|
+ Cluster(const uint8* data, int size);
+ ~Cluster();
+
+ const uint8* data() const { return data_.get(); }
+ int size() const { return size_; }
+
+ private:
+ scoped_array<const uint8> data_;
+ int size_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(Cluster);
+};
+
+class ClusterBuilder {
+ public:
+ ClusterBuilder();
+
+ void SetClusterTimecode(int64 cluster_timecode);
+ void AddSimpleBlock(int track_num, int64 timecode, int flags,
+ const uint8* data, int size);
+
+ Cluster* Finish();
+
+ private:
+ void Reset();
+ void ExtendBuffer(int bytes_needed);
+ void UpdateUInt64(int offset, int64 value);
+
+ scoped_array<uint8> buffer_;
+ int buffer_size_;
+ int bytes_used_;
+ int64 cluster_timecode_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClusterBuilder);
+};
+
+} // namespace media
+
+#endif // MEDIA_WEBM_CLUSTER_BUILDER_H_
diff --git a/media/webm/webm_cluster_parser.cc b/media/webm/webm_cluster_parser.cc
new file mode 100644
index 0000000..8eb045c
--- /dev/null
+++ b/media/webm/webm_cluster_parser.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 2011 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_cluster_parser.h"
+
+#include "base/logging.h"
+#include "media/base/data_buffer.h"
+#include "media/webm/webm_constants.h"
+
+namespace media {
+
+static Buffer* CreateBuffer(const uint8* data, size_t size) {
+ scoped_array<uint8> buf(new uint8[size]);
+ memcpy(buf.get(), data, size);
+ return new DataBuffer(buf.release(), size);
+}
+
+WebMClusterParser::WebMClusterParser(int64 timecode_scale,
+ int audio_track_num,
+ base::TimeDelta audio_default_duration,
+ int video_track_num,
+ base::TimeDelta video_default_duration)
+ : timecode_multiplier_(timecode_scale / 1000.0),
+ audio_track_num_(audio_track_num),
+ audio_default_duration_(audio_default_duration),
+ video_track_num_(video_track_num),
+ video_default_duration_(video_default_duration),
+ last_block_timecode_(-1),
+ cluster_timecode_(-1) {
+}
+
+WebMClusterParser::~WebMClusterParser() {}
+
+int WebMClusterParser::Parse(const uint8* buf, int size) {
+ last_block_timecode_ = -1;
+ cluster_timecode_ = -1;
+ audio_buffers_.clear();
+ video_buffers_.clear();
+
+ return WebMParseListElement(buf, size, kWebMIdCluster, 1, this);
+}
+
+bool WebMClusterParser::OnListStart(int id) {
+ if (id == kWebMIdCluster)
+ cluster_timecode_ = -1;
+
+ return true;
+}
+
+bool WebMClusterParser::OnListEnd(int id) {
+ if (id == kWebMIdCluster)
+ cluster_timecode_ = -1;
+
+ return true;
+}
+
+bool WebMClusterParser::OnUInt(int id, int64 val) {
+ if (id == kWebMIdTimecode) {
+ if (cluster_timecode_ != -1)
+ return false;
+
+ cluster_timecode_ = val;
+ }
+
+ return true;
+}
+
+bool WebMClusterParser::OnFloat(int id, double val) {
+ VLOG(1) << "Unexpected float element with ID " << std::hex << id;
+ return false;
+}
+
+bool WebMClusterParser::OnBinary(int id, const uint8* data, int size) {
+ VLOG(1) << "Unexpected binary element with ID " << std::hex << id;
+ return false;
+}
+
+bool WebMClusterParser::OnString(int id, const std::string& str) {
+ VLOG(1) << "Unexpected string element with ID " << std::hex << id;
+ return false;
+}
+
+bool WebMClusterParser::OnSimpleBlock(int track_num, int timecode,
+ int flags,
+ const uint8* data, int size) {
+ if (cluster_timecode_ == -1) {
+ VLOG(1) << "Got SimpleBlock before cluster timecode.";
+ return false;
+ }
+
+ if (timecode < 0) {
+ VLOG(1) << "Got SimpleBlock with negative timecode offset " << timecode;
+ return false;
+ }
+
+ if (last_block_timecode_ != -1 && timecode < last_block_timecode_) {
+ VLOG(1) << "Got SimpleBlock with a timecode before the previous block.";
+ return false;
+ }
+
+ last_block_timecode_ = timecode;
+
+ base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds(
+ (cluster_timecode_ + timecode) * timecode_multiplier_);
+
+ scoped_refptr<Buffer> buffer(CreateBuffer(data, size));
+ buffer->SetTimestamp(timestamp);
+ BufferQueue* queue = NULL;
+
+ if (track_num == audio_track_num_) {
+ buffer->SetDuration(audio_default_duration_);
+ queue = &audio_buffers_;
+ } else if (track_num == video_track_num_) {
+ buffer->SetDuration(video_default_duration_);
+ queue = &video_buffers_;
+ } else {
+ VLOG(1) << "Unexpected track number " << track_num;
+ return false;
+ }
+
+ if (!queue->empty() &&
+ buffer->GetTimestamp() == queue->back()->GetTimestamp()) {
+ VLOG(1) << "Got SimpleBlock timecode is not strictly monotonically "
+ << "increasing for track " << track_num;
+ return false;
+ }
+
+ queue->push_back(buffer);
+ return true;
+}
+
+} // namespace media
diff --git a/media/webm/webm_cluster_parser.h b/media/webm/webm_cluster_parser.h
new file mode 100644
index 0000000..98c5854
--- /dev/null
+++ b/media/webm/webm_cluster_parser.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2011 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_CLUSTER_PARSER_H_
+#define MEDIA_WEBM_WEBM_CLUSTER_PARSER_H_
+
+#include <deque>
+
+#include "base/scoped_ptr.h"
+#include "media/base/buffers.h"
+#include "media/webm/webm_parser.h"
+
+namespace media {
+
+class WebMClusterParser : public WebMParserClient {
+ public:
+ typedef std::deque<scoped_refptr<Buffer> > BufferQueue;
+
+ WebMClusterParser(int64 timecode_scale,
+ int audio_track_num,
+ base::TimeDelta audio_default_duration,
+ int video_track_num,
+ base::TimeDelta video_default_duration);
+ virtual ~WebMClusterParser();
+
+ // Parses a WebM cluster element in |buf|.
+ //
+ // Returns the number of bytes parsed on success. Returns -1
+ // if a parse error occurs.
+ int Parse(const uint8* buf, int size);
+
+ const BufferQueue& audio_buffers() const { return audio_buffers_; }
+ const BufferQueue& video_buffers() const { return video_buffers_; }
+
+ private:
+ // WebMParserClient methods.
+ virtual bool OnListStart(int id);
+ virtual bool OnListEnd(int id);
+ virtual bool OnUInt(int id, int64 val);
+ virtual bool OnFloat(int id, double val);
+ virtual bool OnBinary(int id, const uint8* data, int size);
+ virtual bool OnString(int id, const std::string& str);
+ virtual bool OnSimpleBlock(int track_num, int timecode, int flags,
+ const uint8* data, int size);
+
+ double timecode_multiplier_; // Multiplier used to convert timecodes into
+ // microseconds.
+ int audio_track_num_;
+ base::TimeDelta audio_default_duration_;
+ int video_track_num_;
+ base::TimeDelta video_default_duration_;
+
+ int64 last_block_timecode_;
+
+ int64 cluster_timecode_;
+ BufferQueue audio_buffers_;
+ BufferQueue video_buffers_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(WebMClusterParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_WEBM_WEBM_CLUSTER_PARSER_H_
diff --git a/media/webm/webm_constants.h b/media/webm/webm_constants.h
new file mode 100644
index 0000000..aa6dda0
--- /dev/null
+++ b/media/webm/webm_constants.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 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_CONSTANTS_H_
+#define MEDIA_WEBM_WEBM_CONSTANTS_H_
+
+namespace media {
+
+// WebM element IDs.
+// This is a subset of the IDs in the Matroska spec.
+// http://www.matroska.org/technical/specs/index.html
+const int kWebMIdAspectRatioType = 0x54B3;
+const int kWebMIdAudio = 0xE1;
+const int kWebMIdBitDepth = 0x6264;
+const int kWebMIdBlock = 0xA1;
+const int kWebMIdBlockGroup = 0xA0;
+const int kWebMIdChannels = 0x9F;
+const int kWebMIdCluster = 0x1f43b675;
+const int kWebMIdCodecID = 0x86;
+const int kWebMIdCodecName = 0x258688;
+const int kWebMIdCodecPrivate = 0x63A2;
+const int kWebMIdDateUTC = 0x4461;
+const int kWebMIdDefaultDuration = 0x23E383;
+const int kWebMIdDisplayHeight = 0x54BA;
+const int kWebMIdDisplayUnit = 0x54B2;
+const int kWebMIdDisplayWidth = 0x54B0;
+const int kWebMIdDuration = 0x4489;
+const int kWebMIdFlagDefault = 0x88;
+const int kWebMIdFlagEnabled = 0xB9;
+const int kWebMIdFlagForced = 0x55AA;
+const int kWebMIdFlagInterlaced = 0x9A;
+const int kWebMIdFlagLacing = 0x9C;
+const int kWebMIdInfo = 0x1549A966;
+const int kWebMIdLanguage = 0x22B59C;
+const int kWebMIdMuxingApp = 0x4D80;
+const int kWebMIdName = 0x536E;
+const int kWebMIdOutputSamplingFrequency = 0x78B5;
+const int kWebMIdPixelCropBottom = 0x54AA;
+const int kWebMIdPixelCropLeft = 0x54CC;
+const int kWebMIdPixelCropRight = 0x54DD;
+const int kWebMIdPixelCropTop = 0x54BB;
+const int kWebMIdPixelHeight = 0xBA;
+const int kWebMIdPixelWidth = 0xB0;
+const int kWebMIdSamplingFrequency = 0xB5;
+const int kWebMIdSegmentUID = 0x73A4;
+const int kWebMIdSimpleBlock = 0xA3;
+const int kWebMIdStereoMode = 0x53B8;
+const int kWebMIdTimecode = 0xE7;
+const int kWebMIdTimecodeScale = 0x2AD7B1;
+const int kWebMIdTitle = 0x7BA9;
+const int kWebMIdTrackEntry = 0xAE;
+const int kWebMIdTrackNumber = 0xD7;
+const int kWebMIdTrackType = 0x83;
+const int kWebMIdTrackUID = 0x73C5;
+const int kWebMIdTracks = 0x1654AE6B;
+const int kWebMIdVideo = 0xE0;
+const int kWebMIdWritingApp = 0x5741;
+
+// Default timecode scale if the TimecodeScale element is
+// not specified in the INFO element.
+const int kWebMDefaultTimecodeScale = 1000000;
+
+// Values for TrackType element.
+const int kWebMTrackTypeVideo = 1;
+const int kWebMTrackTypeAudio = 2;
+
+} // namespace media
+
+#endif // MEDIA_WEBM_WEBM_CONSTANTS_H_
diff --git a/media/webm/webm_info_parser.cc b/media/webm/webm_info_parser.cc
new file mode 100644
index 0000000..41e1a25
--- /dev/null
+++ b/media/webm/webm_info_parser.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2011 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_info_parser.h"
+
+#include "base/logging.h"
+#include "media/webm/webm_constants.h"
+
+namespace media {
+
+WebMInfoParser::WebMInfoParser()
+ : timecode_scale_(-1),
+ duration_(-1) {
+}
+
+WebMInfoParser::~WebMInfoParser() {}
+
+int WebMInfoParser::Parse(const uint8* buf, int size) {
+ return WebMParseListElement(buf, size, kWebMIdInfo, 1, this);
+}
+
+bool WebMInfoParser::OnListStart(int id) { return true; }
+
+bool WebMInfoParser::OnListEnd(int id) {
+ if (id == kWebMIdInfo && timecode_scale_ == -1) {
+ // Set timecode scale to default value if it isn't present in
+ // the Info element.
+ timecode_scale_ = kWebMDefaultTimecodeScale;
+ }
+ return true;
+}
+
+bool WebMInfoParser::OnUInt(int id, int64 val) {
+ if (id != kWebMIdTimecodeScale)
+ return true;
+
+ if (timecode_scale_ != -1) {
+ VLOG(1) << "Multiple values for id " << std::hex << id << " specified";
+ return false;
+ }
+
+ timecode_scale_ = val;
+ return true;
+}
+
+bool WebMInfoParser::OnFloat(int id, double val) {
+ if (id != kWebMIdDuration) {
+ VLOG(1) << "Unexpected float for id" << std::hex << id;
+ return false;
+ }
+
+ if (duration_ != -1) {
+ VLOG(1) << "Multiple values for duration.";
+ return false;
+ }
+
+ duration_ = val;
+ return true;
+}
+
+bool WebMInfoParser::OnBinary(int id, const uint8* data, int size) {
+ return true;
+}
+
+bool WebMInfoParser::OnString(int id, const std::string& str) {
+ return true;
+}
+
+bool WebMInfoParser::OnSimpleBlock(int track_num, int timecode, int flags,
+ const uint8* data, int size) {
+ return false;
+}
+
+} // namespace media
diff --git a/media/webm/webm_info_parser.h b/media/webm/webm_info_parser.h
new file mode 100644
index 0000000..6391f8b
--- /dev/null
+++ b/media/webm/webm_info_parser.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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_INFO_PARSER_H_
+#define MEDIA_WEBM_WEBM_INFO_PARSER_H_
+
+#include "media/webm/webm_parser.h"
+
+namespace media {
+
+// Parser for WebM Info element.
+class WebMInfoParser : public WebMParserClient {
+ public:
+ WebMInfoParser();
+ virtual ~WebMInfoParser();
+
+ // Parses a WebM Info element in |buf|.
+ //
+ // Returns the number of bytes parsed on success. Returns -1
+ // on error.
+ int Parse(const uint8* buf, int size);
+
+ int64 timecode_scale() const { return timecode_scale_; }
+ double duration() const { return duration_; }
+
+ private:
+ // WebMParserClient methods
+ virtual bool OnListStart(int id);
+ virtual bool OnListEnd(int id);
+ virtual bool OnUInt(int id, int64 val);
+ virtual bool OnFloat(int id, double val);
+ virtual bool OnBinary(int id, const uint8* data, int size);
+ virtual bool OnString(int id, const std::string& str);
+ virtual bool OnSimpleBlock(int track_num, int timecode, int flags,
+ const uint8* data, int size);
+
+ int64 timecode_scale_;
+ double duration_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMInfoParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_WEBM_WEBM_INFO_PARSER_H_
diff --git a/media/webm/webm_parser.cc b/media/webm/webm_parser.cc
new file mode 100644
index 0000000..42d826c1
--- /dev/null
+++ b/media/webm/webm_parser.cc
@@ -0,0 +1,485 @@
+// Copyright (c) 2011 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_parser.h"
+
+// This file contains code to parse WebM file elements. It was created
+// from information in the Matroska spec.
+// http://www.matroska.org/technical/specs/index.html
+
+#include <iomanip>
+
+#include "base/logging.h"
+#include "media/webm/webm_constants.h"
+
+namespace media {
+
+// Maximum depth of WebM elements. Some WebM elements are lists of
+// other elements. This limits the number levels of recursion allowed.
+static const int kMaxLevelDepth = 6;
+
+enum ElementType {
+ LIST,
+ UINT,
+ FLOAT,
+ BINARY,
+ STRING,
+ SBLOCK,
+ SKIP,
+};
+
+struct ElementIdInfo {
+ int level_;
+ ElementType type_;
+ int id_;
+};
+
+struct ListElementInfo {
+ int id_;
+ const ElementIdInfo* id_info_;
+ int id_info_size_;
+};
+
+// The following are tables indicating what IDs are valid sub-elements
+// of particular elements. If an element is encountered that doesn't
+// appear in the list, a parsing error is signalled. Some elements are
+// marked as SKIP because they are valid, but we don't care about them
+// right now.
+static const ElementIdInfo kClusterIds[] = {
+ {2, UINT, kWebMIdTimecode},
+ {2, SBLOCK, kWebMIdSimpleBlock},
+ {2, LIST, kWebMIdBlockGroup},
+};
+
+static const ElementIdInfo kInfoIds[] = {
+ {2, SKIP, kWebMIdSegmentUID},
+ {2, UINT, kWebMIdTimecodeScale},
+ {2, FLOAT, kWebMIdDuration},
+ {2, SKIP, kWebMIdDateUTC},
+ {2, SKIP, kWebMIdTitle},
+ {2, SKIP, kWebMIdMuxingApp},
+ {2, SKIP, kWebMIdWritingApp},
+};
+
+static const ElementIdInfo kTracksIds[] = {
+ {2, LIST, kWebMIdTrackEntry},
+};
+
+static const ElementIdInfo kTrackEntryIds[] = {
+ {3, UINT, kWebMIdTrackNumber},
+ {3, SKIP, kWebMIdTrackUID},
+ {3, UINT, kWebMIdTrackType},
+ {3, SKIP, kWebMIdFlagEnabled},
+ {3, SKIP, kWebMIdFlagDefault},
+ {3, SKIP, kWebMIdFlagForced},
+ {3, UINT, kWebMIdFlagLacing},
+ {3, UINT, kWebMIdDefaultDuration},
+ {3, SKIP, kWebMIdName},
+ {3, SKIP, kWebMIdLanguage},
+ {3, STRING, kWebMIdCodecID},
+ {3, BINARY, kWebMIdCodecPrivate},
+ {3, SKIP, kWebMIdCodecName},
+ {3, LIST, kWebMIdVideo},
+ {3, LIST, kWebMIdAudio},
+};
+
+static const ElementIdInfo kVideoIds[] = {
+ {4, SKIP, kWebMIdFlagInterlaced},
+ {4, SKIP, kWebMIdStereoMode},
+ {4, UINT, kWebMIdPixelWidth},
+ {4, UINT, kWebMIdPixelHeight},
+ {4, SKIP, kWebMIdPixelCropBottom},
+ {4, SKIP, kWebMIdPixelCropTop},
+ {4, SKIP, kWebMIdPixelCropLeft},
+ {4, SKIP, kWebMIdPixelCropRight},
+ {4, SKIP, kWebMIdDisplayWidth},
+ {4, SKIP, kWebMIdDisplayHeight},
+ {4, SKIP, kWebMIdDisplayUnit},
+ {4, SKIP, kWebMIdAspectRatioType},
+};
+
+static const ElementIdInfo kAudioIds[] = {
+ {4, SKIP, kWebMIdSamplingFrequency},
+ {4, SKIP, kWebMIdOutputSamplingFrequency},
+ {4, UINT, kWebMIdChannels},
+ {4, SKIP, kWebMIdBitDepth},
+};
+
+static const ElementIdInfo kClustersOnly[] = {
+ {1, LIST, kWebMIdCluster},
+};
+
+static const ListElementInfo kListElementInfo[] = {
+ { kWebMIdCluster, kClusterIds, sizeof(kClusterIds) },
+ { kWebMIdInfo, kInfoIds, sizeof(kInfoIds) },
+ { kWebMIdTracks, kTracksIds, sizeof(kTracksIds) },
+ { kWebMIdTrackEntry, kTrackEntryIds, sizeof(kTrackEntryIds) },
+ { kWebMIdVideo, kVideoIds, sizeof(kVideoIds) },
+ { kWebMIdAudio, kAudioIds, sizeof(kAudioIds) },
+};
+
+// Number of elements in kListElementInfo.
+const int kListElementInfoCount =
+ sizeof(kListElementInfo) / sizeof(ListElementInfo);
+
+WebMParserClient::~WebMParserClient() {}
+
+// Parses an element header id or size field. These fields are variable length
+// encoded. The first byte indicates how many bytes the field occupies.
+// |buf| - The buffer to parse.
+// |size| - The number of bytes in |buf|
+// |max_bytes| - The maximum number of bytes the field can be. ID fields
+// set this to 4 & element size fields set this to 8. If the
+// first byte indicates a larger field size than this it is a
+// parser error.
+// |mask_first_byte| - For element size fields the field length encoding bits
+// need to be masked off. This parameter is true for
+// element size fields and is false for ID field values.
+//
+// Returns: The number of bytes parsed on success. -1 on error.
+static int ParseWebMElementHeaderField(const uint8* buf, int size,
+ int max_bytes, bool mask_first_byte,
+ int64* num) {
+ DCHECK(buf);
+ DCHECK(num);
+
+ if (size <= 0)
+ return -1;
+
+ int mask = 0x80;
+ uint8 ch = buf[0];
+ int extra_bytes = -1;
+ for (int i = 0; i < max_bytes; ++i) {
+ if ((ch & mask) == mask) {
+ *num = mask_first_byte ? ch & ~mask : ch;
+ extra_bytes = i;
+ break;
+ }
+ mask >>= 1;
+ }
+
+ if ((extra_bytes == -1) || ((1 + extra_bytes) > size))
+ return -1;
+
+ int bytes_used = 1;
+
+ for (int i = 0; i < extra_bytes; ++i)
+ *num = (*num << 8) | (0xff & buf[bytes_used++]);
+
+ return bytes_used;
+}
+
+// Parses an element header & returns the ID and element size.
+//
+// Returns: The number of bytes parsed on success. -1 on error.
+// |*id| contains the element ID on success & undefined on error.
+// |*element_size| contains the element size on success & undefined on error.
+static int ParseWebMElementHeader(const uint8* buf, int size,
+ int* id, int64* element_size) {
+ DCHECK(buf);
+ DCHECK_GE(size, 0);
+ DCHECK(id);
+ DCHECK(element_size);
+
+ if (size == 0)
+ return 0;
+
+ int64 tmp;
+ int num_id_bytes = ParseWebMElementHeaderField(buf, size, 4, false, &tmp);
+
+ if (num_id_bytes <= 0)
+ return num_id_bytes;
+
+ *id = static_cast<int>(tmp);
+
+ int num_size_bytes = ParseWebMElementHeaderField(buf + num_id_bytes,
+ size - num_id_bytes,
+ 8, true, &tmp);
+
+ if (num_size_bytes <= 0)
+ return num_size_bytes;
+
+ *element_size = tmp;
+ return num_id_bytes + num_size_bytes;
+}
+
+// Finds ElementIdInfo for a specific ID.
+static const ElementIdInfo* FindIdInfo(int id,
+ const ElementIdInfo* id_info,
+ int id_info_size) {
+ int count = id_info_size / sizeof(*id_info);
+ for (int i = 0; i < count; ++i) {
+ if (id == id_info[i].id_)
+ return &id_info[i];
+ }
+
+ return NULL;
+}
+
+// Finds ListElementInfo for a specific ID.
+static const ListElementInfo* FindListInfo(int id) {
+ for (int i = 0; i < kListElementInfoCount; ++i) {
+ if (id == kListElementInfo[i].id_)
+ return &kListElementInfo[i];
+ }
+
+ return NULL;
+}
+
+static int ParseSimpleBlock(const uint8* buf, int size,
+ WebMParserClient* client) {
+ if (size < 4)
+ return -1;
+
+ // Return an error if the trackNum > 127. We just aren't
+ // going to support large track numbers right now.
+ if ((buf[0] & 0x80) != 0x80) {
+ VLOG(1) << "TrackNumber over 127 not supported";
+ return -1;
+ }
+
+ int track_num = buf[0] & 0x7f;
+ int timecode = buf[1] << 8 | buf[2];
+ int flags = buf[3] & 0xff;
+ int lacing = (flags >> 1) & 0x3;
+
+ if (lacing != 0) {
+ VLOG(1) << "Lacing " << lacing << " not supported yet.";
+ return -1;
+ }
+
+ // Sign extend negative timecode offsets.
+ if (timecode & 0x8000)
+ timecode |= (-1 << 16);
+
+ const uint8* frame_data = buf + 4;
+ int frame_size = size - (frame_data - buf);
+ if (!client->OnSimpleBlock(track_num, timecode, flags,
+ frame_data, frame_size)) {
+ return -1;
+ }
+
+ return size;
+}
+
+static int ParseElements(const ElementIdInfo* id_info,
+ int id_info_size,
+ const uint8* buf, int size, int level,
+ WebMParserClient* client);
+
+static int ParseElementList(const uint8* buf, int size,
+ int id, int level,
+ WebMParserClient* client) {
+ const ListElementInfo* list_info = FindListInfo(id);
+
+ if (!list_info) {
+ VLOG(1) << "Failed to find list info for ID " << std::hex << id;
+ return -1;
+ }
+
+ if (!client->OnListStart(id))
+ return -1;
+
+ int res = ParseElements(list_info->id_info_,
+ list_info->id_info_size_,
+ buf, size,
+ level + 1,
+ client);
+
+ if (res < 0)
+ return -1;
+
+ if (!client->OnListEnd(id))
+ return -1;
+
+ DCHECK_EQ(res, size);
+ return res;
+}
+
+static int ParseUInt(const uint8* buf, int size, int id,
+ WebMParserClient* client) {
+ if ((size <= 0) || (size > 8))
+ return -1;
+
+ // Read in the big-endian integer.
+ int64 value = 0;
+ for (int i = 0; i < size; ++i)
+ value = (value << 8) | buf[i];
+
+ if (!client->OnUInt(id, value))
+ return -1;
+
+ return size;
+}
+
+static int ParseFloat(const uint8* buf, int size, int id,
+ WebMParserClient* client) {
+
+ if ((size != 4) && (size != 8))
+ return -1;
+
+ double value = -1;
+
+ // Read the bytes from big-endian form into a native endian integer.
+ int64 tmp = 0;
+ for (int i = 0; i < size; ++i)
+ tmp = (tmp << 8) | buf[i];
+
+ // Use a union to convert the integer bit pattern into a floating point
+ // number.
+ if (size == 4) {
+ union {
+ int32 src;
+ float dst;
+ } tmp2;
+ tmp2.src = static_cast<int32>(tmp);
+ value = tmp2.dst;
+ } else if (size == 8) {
+ union {
+ int64 src;
+ double dst;
+ } tmp2;
+ tmp2.src = tmp;
+ value = tmp2.dst;
+ } else {
+ return -1;
+ }
+
+ if (!client->OnFloat(id, value))
+ return -1;
+
+ return size;
+}
+
+static int ParseElements(const ElementIdInfo* id_info,
+ int id_info_size,
+ const uint8* buf, int size, int level,
+ WebMParserClient* client) {
+ DCHECK_GE(id_info_size, 0);
+ DCHECK_GE(size, 0);
+ DCHECK_GE(level, 0);
+
+ const uint8* cur = buf;
+ int cur_size = size;
+ int used = 0;
+
+ if (level > kMaxLevelDepth)
+ return -1;
+
+ while (cur_size > 0) {
+ int id;
+ int64 element_size;
+ int res = ParseWebMElementHeader(cur, cur_size, &id, &element_size);
+
+ if (res < 0)
+ return res;
+
+ if (res == 0)
+ break;
+
+ cur += res;
+ cur_size -= res;
+ used += res;
+
+ // Check to see if the element is larger than the remaining data.
+ if (element_size > cur_size)
+ return -1;
+
+ const ElementIdInfo* info = FindIdInfo(id, id_info, id_info_size);
+
+ if (info == NULL) {
+ VLOG(1) << "No info for ID " << std::hex << id;
+
+ // TODO(acolwell): Change this to return -1 after the API has solidified.
+ // We don't want to allow elements we don't recognize.
+ cur += element_size;
+ cur_size -= element_size;
+ used += element_size;
+ continue;
+ }
+
+ if (info->level_ != level) {
+ VLOG(1) << "ID " << std::hex << id << std::dec << " at level "
+ << level << " instead of " << info->level_;
+ return -1;
+ }
+
+ switch(info->type_) {
+ case SBLOCK:
+ if (ParseSimpleBlock(cur, element_size, client) <= 0)
+ return -1;
+ break;
+ case LIST:
+ if (ParseElementList(cur, element_size, id, level, client) < 0)
+ return -1;
+ break;
+ case UINT:
+ if (ParseUInt(cur, element_size, id, client) <= 0)
+ return -1;
+ break;
+ case FLOAT:
+ if (ParseFloat(cur, element_size, id, client) <= 0)
+ return -1;
+ break;
+ case BINARY:
+ if (!client->OnBinary(id, cur, element_size))
+ return -1;
+ break;
+ case STRING:
+ if (!client->OnString(id,
+ std::string(reinterpret_cast<const char*>(cur),
+ element_size)))
+ return -1;
+ break;
+ case SKIP:
+ // Do nothing.
+ break;
+ default:
+ VLOG(1) << "Unhandled id type " << info->type_;
+ return -1;
+ };
+
+ cur += element_size;
+ cur_size -= element_size;
+ used += element_size;
+ }
+
+ return used;
+}
+
+// Parses a single list element that matches |id|. This method fails if the
+// buffer points to an element that does not match |id|.
+int WebMParseListElement(const uint8* buf, int size, int id,
+ int level, WebMParserClient* client) {
+ if (size == 0)
+ return -1;
+
+ const uint8* cur = buf;
+ int cur_size = size;
+
+ int element_id = 0;
+ int64 element_size = 0;
+ int res = ParseWebMElementHeader(cur, cur_size, &element_id, &element_size);
+
+ if (res <= 0)
+ return res;
+
+ cur += res;
+ cur_size -= res;
+
+ if (element_id != id || element_size > cur_size)
+ return -1;
+
+ res = ParseElementList(cur, element_size, element_id, level, client);
+
+ if (res < 0)
+ return -1;
+
+ cur += res;
+ cur_size -= res;
+
+ return size - cur_size;
+}
+
+} // namespace media
diff --git a/media/webm/webm_parser.h b/media/webm/webm_parser.h
new file mode 100644
index 0000000..0716b80
--- /dev/null
+++ b/media/webm/webm_parser.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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_PARSER_H_
+#define MEDIA_WEBM_WEBM_PARSER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+namespace media {
+
+// Interface for receiving WebM parser events.
+//
+// Each method is called when an element of the specified type is parsed.
+// The ID of the element that was parsed is given along with the value
+// stored in the element. List elements generate calls at the start and
+// end of the list. Any pointers passed to these methods are only guaranteed
+// to be valid for the life of that call. Each method returns a bool that
+// indicates whether the parsed data is valid. If false is returned
+// then the parse is immediately terminated and an error is reported by the
+// parser.
+class WebMParserClient {
+ public:
+ virtual ~WebMParserClient();
+
+ virtual bool OnListStart(int id) = 0;
+ virtual bool OnListEnd(int id) = 0;
+ virtual bool OnUInt(int id, int64 val) = 0;
+ virtual bool OnFloat(int id, double val) = 0;
+ virtual bool OnBinary(int id, const uint8* data, int size) = 0;
+ virtual bool OnString(int id, const std::string& str) = 0;
+ virtual bool OnSimpleBlock(int track_num, int timecode,
+ int flags,
+ const uint8* data, int size) = 0;
+};
+
+// Parses a single list element that matches |id|. This method fails if the
+// buffer points to an element that does not match |id|.
+int WebMParseListElement(const uint8* buf, int size, int id,
+ int level, WebMParserClient* client);
+
+} // namespace media
+
+#endif // MEDIA_WEBM_WEBM_PARSER_H_
diff --git a/media/webm/webm_tracks_parser.cc b/media/webm/webm_tracks_parser.cc
new file mode 100644
index 0000000..29b4f33
--- /dev/null
+++ b/media/webm/webm_tracks_parser.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2011 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_tracks_parser.h"
+
+#include "base/logging.h"
+#include "media/webm/webm_constants.h"
+
+namespace media {
+
+WebMTracksParser::WebMTracksParser(int64 timecode_scale)
+ : timecode_scale_(timecode_scale),
+ track_type_(-1),
+ track_num_(-1),
+ track_default_duration_(-1),
+ audio_track_num_(-1),
+ audio_default_duration_(base::TimeDelta::FromMicroseconds(-1)),
+ video_track_num_(-1),
+ video_default_duration_(base::TimeDelta::FromMicroseconds(-1)) {
+}
+
+WebMTracksParser::~WebMTracksParser() {}
+
+int WebMTracksParser::Parse(const uint8* buf, int size) {
+ return WebMParseListElement(buf, size, kWebMIdTracks, 1, this);
+}
+
+
+bool WebMTracksParser::OnListStart(int id) {
+ if (id == kWebMIdTrackEntry) {
+ track_type_ = -1;
+ track_num_ = -1;
+ track_default_duration_ = -1;
+ }
+
+ return true;
+}
+
+bool WebMTracksParser::OnListEnd(int id) {
+ if (id == kWebMIdTrackEntry) {
+ if (track_type_ == -1 || track_num_ == -1) {
+ VLOG(1) << "Missing TrackEntry data"
+ << " TrackType " << track_type_
+ << " TrackNum " << track_num_;
+ return false;
+ }
+
+ // Convert nanoseconds to base::TimeDelta.
+ base::TimeDelta default_duration = base::TimeDelta::FromMicroseconds(
+ track_default_duration_ / 1000.0);
+
+ if (track_type_ == kWebMTrackTypeVideo) {
+ video_track_num_ = track_num_;
+ video_default_duration_ = default_duration;
+ } else if (track_type_ == kWebMTrackTypeAudio) {
+ audio_track_num_ = track_num_;
+ audio_default_duration_ = default_duration;
+ } else {
+ VLOG(1) << "Unexpected TrackType " << track_type_;
+ return false;
+ }
+
+ track_type_ = -1;
+ track_num_ = -1;
+ }
+
+ return true;
+}
+
+bool WebMTracksParser::OnUInt(int id, int64 val) {
+ int64* dst = NULL;
+
+ switch (id) {
+ case kWebMIdTrackNumber:
+ dst = &track_num_;
+ break;
+ case kWebMIdTrackType:
+ dst = &track_type_;
+ break;
+ case kWebMIdDefaultDuration:
+ dst = &track_default_duration_;
+ break;
+ default:
+ return true;
+ }
+
+ if (*dst != -1) {
+ VLOG(1) << "Multiple values for id " << std::hex << id << " specified";
+ return false;
+ }
+
+ *dst = val;
+ return true;
+}
+
+bool WebMTracksParser::OnFloat(int id, double val) {
+ VLOG(1) << "Unexpected float for id" << std::hex << id;
+ return false;
+}
+
+bool WebMTracksParser::OnBinary(int id, const uint8* data, int size) {
+ return true;
+}
+
+bool WebMTracksParser::OnString(int id, const std::string& str) {
+ if (id != kWebMIdCodecID)
+ return false;
+
+ if (str != "A_VORBIS" && str != "V_VP8") {
+ VLOG(1) << "Unexpected CodecID " << str;
+ return false;
+ }
+
+ return true;
+}
+
+bool WebMTracksParser::OnSimpleBlock(int track_num, int timecode, int flags,
+ const uint8* data, int size) {
+ return false;
+}
+
+} // namespace media
diff --git a/media/webm/webm_tracks_parser.h b/media/webm/webm_tracks_parser.h
new file mode 100644
index 0000000..a1d0081
--- /dev/null
+++ b/media/webm/webm_tracks_parser.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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_TRACKS_PARSER_H_
+#define MEDIA_WEBM_WEBM_TRACKS_PARSER_H_
+
+#include "media/webm/webm_parser.h"
+
+#include "base/time.h"
+
+namespace media {
+
+// Parser for WebM Tracks element.
+class WebMTracksParser : public WebMParserClient {
+ public:
+ WebMTracksParser(int64 timecode_scale);
+ virtual ~WebMTracksParser();
+
+ // Parses a WebM Tracks element in |buf|.
+ //
+ // Returns the number of bytes parsed on success. Returns -1
+ // on error.
+ int Parse(const uint8* buf, int size);
+
+ int64 audio_track_num() const { return audio_track_num_; }
+ base::TimeDelta audio_default_duration() const {
+ return audio_default_duration_;
+ }
+
+ int64 video_track_num() const { return video_track_num_; }
+ base::TimeDelta video_default_duration() const {
+ return video_default_duration_;
+ }
+
+ private:
+ // WebMParserClient methods
+ virtual bool OnListStart(int id);
+ virtual bool OnListEnd(int id);
+ virtual bool OnUInt(int id, int64 val);
+ virtual bool OnFloat(int id, double val);
+ virtual bool OnBinary(int id, const uint8* data, int size);
+ virtual bool OnString(int id, const std::string& str);
+ virtual bool OnSimpleBlock(int track_num, int timecode, int flags,
+ const uint8* data, int size);
+ int64 timecode_scale_;
+
+ int64 track_type_;
+ int64 track_num_;
+ int64 track_default_duration_;
+ int64 audio_track_num_;
+ base::TimeDelta audio_default_duration_;
+ int64 video_track_num_;
+ base::TimeDelta video_default_duration_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(WebMTracksParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_WEBM_WEBM_TRACKS_PARSER_H_