Chromium Code Reviews| Index: media/filters/chunk_demuxer.cc |
| diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..36c9b1b309d5cf5f63f567d22207a516bf250350 |
| --- /dev/null |
| +++ b/media/filters/chunk_demuxer.cc |
| @@ -0,0 +1,956 @@ |
| +// 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/ffmpeg/ffmpeg_common.h" |
| +#include "media/filters/ffmpeg_glue.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. |
| +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) |
| +}; |
| + |
| +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(); |
| +} |
| + |
| +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(); |
| + 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_; |
| + |
| + DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream); |
| +}; |
| + |
| +// Helper class used to parse WebM clusters. |
| +class ClusterWebMParserClient : public WebMParserClient { |
| + public: |
| + ClusterWebMParserClient(int64 timecode_scale, |
| + int audio_track_num, |
| + int audio_default_duration, |
| + int video_track_num, |
| + int video_default_duration) |
| + : timecode_multiplier_(timecode_scale / 1000.0), // will convert timecodes |
| + // to microseconds. |
| + audio_track_num_(audio_track_num), |
| + audio_default_duration_(audio_default_duration), |
| + video_track_num_(video_track_num), |
| + video_default_duration_(video_default_duration), |
| + cluster_timecode_(-1) { |
| + VLOG(1) << "tc_s " << timecode_multiplier_ |
| + << " atn " << audio_track_num_ |
| + << " add " << audio_default_duration_ |
| + << " vtn " << video_track_num_ |
| + << " vdd " << video_default_duration_; |
| + } |
| + |
| + const ChunkDemuxerStream::BufferQueue& audio_buffers() const { |
| + return audio_buffers_; |
| + }; |
| + |
| + const ChunkDemuxerStream::BufferQueue& video_buffers() const { |
| + return video_buffers_; |
| + }; |
| + |
| + virtual bool OnListStart(int id) { |
| + if (id == kWebMIdCluster) |
| + cluster_timecode_ = -1; |
| + |
| + return true; |
| + } |
| + |
| + virtual bool OnListEnd(int id) { |
| + if (id == kWebMIdCluster) |
| + cluster_timecode_ = -1; |
| + |
| + return true; |
| + } |
| + |
| + virtual bool OnUInt(int id, int64 val) { |
| + if (id == kWebMIdTimecode) { |
| + if (cluster_timecode_ != -1) |
| + return false; |
| + |
| + cluster_timecode_ = val; |
| + } |
| + |
| + return true; |
| + } |
| + |
| + virtual bool OnFloat(int id, double val) { |
| + VLOG(1) << "Unexpected float element with ID " << std::hex << id; |
| + return false; |
| + } |
| + |
| + virtual bool OnBinary(int id, const uint8* data, int size) { |
| + VLOG(1) << "Unexpected binary element with ID " << std::hex << id; |
| + return false; |
| + } |
| + |
| + virtual bool OnString(int id, const std::string& str) { |
| + VLOG(1) << "Unexpected string element with ID " << std::hex << id; |
| + return false; |
| + } |
| + |
| + virtual bool 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; |
| + } |
| + |
| + //VLOG(1) << "SimpleBlock : tn " << track_num |
| + // << " tc " << (cluster_timecode_ + timecode) |
| + // << "(" << timecode << ")" |
| + // << " flags " << std::hex << flags << std::dec |
| + // << " size " << size; |
| + |
| + base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds( |
| + (cluster_timecode_ + timecode) * timecode_multiplier_); |
| + |
| + if (track_num == audio_track_num_) { |
| + base::TimeDelta audio_duration = base::TimeDelta::FromMicroseconds( |
| + audio_default_duration_ / 1000.0); |
| + |
| + audio_buffers_.push_back(new BufferImpl(timestamp, audio_duration, |
| + data, size)); |
| + return true; |
| + } |
| + |
| + if (track_num == video_track_num_) { |
| + base::TimeDelta video_duration = base::TimeDelta::FromMicroseconds( |
| + video_default_duration_ / 1000.0); |
| + |
| + video_buffers_.push_back(new BufferImpl(timestamp, video_duration, |
| + data, size)); |
| + return true; |
| + } |
| + |
| + VLOG(1) << "Unexpected track number " << track_num; |
| + return false; |
| + } |
| + |
| + private: |
| + double timecode_multiplier_; |
| + int audio_track_num_; |
| + int audio_default_duration_; |
| + int video_track_num_; |
| + int video_default_duration_; |
| + |
| + int64 cluster_timecode_; |
| + |
| + ChunkDemuxerStream::BufferQueue audio_buffers_; |
| + ChunkDemuxerStream::BufferQueue video_buffers_; |
| +}; |
| + |
| +ChunkDemuxerStream::ChunkDemuxerStream(Type type, AVStream* stream) |
| + : type_(type), |
| + av_stream_(stream), |
| + shutdown_called_(false) { |
| +} |
| + |
| +ChunkDemuxerStream::~ChunkDemuxerStream() {} |
| + |
| +void ChunkDemuxerStream::Flush() { |
| + VLOG(1) << "Flush()"; |
| + base::AutoLock auto_lock(lock_); |
| + buffers_.clear(); |
| +} |
| + |
| +void ChunkDemuxerStream::AddBuffers(const BufferQueue& buffers) { |
| + std::deque<base::Closure> callbacks; |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + |
| + for (BufferQueue::const_iterator itr = buffers.begin(); |
| + itr != buffers.end(); itr++) { |
| + 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(); |
| + } |
| + |
| + if (!buffers_.empty()) { |
| + base::TimeDelta fts = buffers_.front()->GetTimestamp(); |
| + base::TimeDelta bts = buffers_.back()->GetTimestamp(); |
| + base::TimeDelta dlt = bts - fts; |
| + VLOG(1) << "AddBuffers() : ct " << buffers_.size() |
| + << " fts " << fts.InSecondsF() |
| + << " bts " << bts.InSecondsF() |
| + << " dlt " << dlt.InSecondsF(); |
| + } |
| + } |
| + |
| + 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, |
| + scoped_refptr<Buffer> buffer) { |
| + if (MessageLoop::current() != message_loop) { |
| + message_loop->PostTask(FROM_HERE, |
| + NewRunnableFunction(&RunOnMessageLoop, |
| + read_callback, |
| + message_loop, |
| + 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), |
| + first_seek_(true) { |
| +} |
| + |
| +ChunkDemuxer::~ChunkDemuxer() { |
| + DCHECK_NE(state_, INITIALIZED); |
| + |
| + DestroyAVFormatContext(format_context_); |
| + format_context_ = NULL; |
| +} |
| + |
| +bool ChunkDemuxer::Init(const uint8* data, int size) { |
| + DCHECK(data); |
| + DCHECK_GT(size, 0); |
| + VLOG(1) << "Init(" << size << ")"; |
| + |
| + base::AutoLock auto_lock(lock_); |
| + DCHECK_EQ(state_, WAITING_FOR_INIT); |
| + |
| + int res = webm_parse_headers(&info_, data, size); |
| + |
| + if (res < 0) { |
| + ChangeState(INIT_ERROR); |
| + return false; |
| + } |
| + |
| + 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); |
| + double mult = info_.timecode_scale() / 1000.0; |
| + |
| + base::TimeDelta duration = |
| + base::TimeDelta::FromMicroseconds(info_.duration() * mult); |
| + |
| + 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() << ")"; |
| + |
| + bool run_callback = false; |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + |
| + if (first_seek_) { |
| + first_seek_ = false; |
| + run_callback = true; |
| + } else { |
| + seek_cb_ = cb; |
| + seek_time_ = time; |
| + } |
| + } |
| + |
| + if (run_callback) |
| + 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) { |
| + VLOG(1) << "GetStream(" << 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(); |
| +} |
| + |
| +bool ChunkDemuxer::AddData(const uint8* data, unsigned length) { |
| + VLOG(1) << "AddData(" << length << ")"; |
| + |
| + 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(new media::BufferImpl(base::TimeDelta(), |
| + base::TimeDelta(), |
| + data, |
| + length)); |
| + return false; |
| + 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; |
| + } |
| + |
| + 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_OK); |
| +} |
| + |
| +void ChunkDemuxer::ChangeState(State new_state) { |
| + 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; |
| + uint8* buf = new uint8[buf_size]; |
|
scherkus (not reviewing)
2011/06/22 17:31:09
scoped_ptr seems nice here considering this isn't
acolwell GONE FROM CHROMIUM
2011/06/23 16:51:28
Done. Changed this to scoped_array<uint8>
|
| + 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; |
| + } |
| + |
| + BufferUrlProtocol bup(buf, buf_size, true); |
| + std::string key = FFmpegGlue::GetInstance()->AddProtocol(&bup); |
| + |
| + // 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(&bup); |
| + |
| + delete buf; |
| + |
| + 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() { |
| + // 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; |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool ChunkDemuxer::ParseAndAddData_Locked(const uint8* data, int length) { |
| + ClusterWebMParserClient client(info_.timecode_scale(), |
| + info_.audio_track_num(), |
| + info_.audio_default_duration(), |
| + info_.video_track_num(), |
| + info_.video_default_duration()); |
| + int res = webm_parse_cluster(&client, data, length); |
| + |
| + if (res <= 0) |
| + return false; |
| + |
| + if (audio_.get()) |
| + audio_->AddBuffers(client.audio_buffers()); |
| + |
| + if (video_.get()) |
| + video_->AddBuffers(client.video_buffers()); |
| + |
| + // TODO(acolwell) : make this more representative of what is actually |
| + // buffered. |
| + buffered_bytes_ += length; |
| + |
| + return true; |
| +} |
| + |
| +ChunkDemuxer::InfoTrackWebMParserClient::InfoTrackWebMParserClient() |
| + : timecode_scale_(-1), |
| + duration_(-1), |
| + track_type_(-1), |
| + track_num_(-1), |
| + track_default_duration_(-1), |
| + audio_track_num_(-1), |
| + audio_default_duration_(-1), |
| + video_track_num_(-1), |
| + video_default_duration_(-1) { |
| +} |
| + |
| +int64 ChunkDemuxer::InfoTrackWebMParserClient::timecode_scale() const { |
| + return timecode_scale_; |
| +} |
| + |
| +double ChunkDemuxer::InfoTrackWebMParserClient::duration() const { |
| + return duration_; |
| +} |
| + |
| +int64 ChunkDemuxer::InfoTrackWebMParserClient::audio_track_num() const { |
| + return audio_track_num_; |
| +} |
| + |
| +int64 ChunkDemuxer::InfoTrackWebMParserClient::audio_default_duration() const { |
| + return audio_default_duration_; |
| +} |
| + |
| +int64 ChunkDemuxer::InfoTrackWebMParserClient::video_track_num() const { |
| + return video_track_num_; |
| +} |
| + |
| +int64 ChunkDemuxer::InfoTrackWebMParserClient::video_default_duration() const { |
| + return video_default_duration_; |
| +} |
| + |
| +bool ChunkDemuxer::InfoTrackWebMParserClient::OnListStart(int id) { |
| + if (id == kWebMIdTrackEntry) { |
| + track_type_ = -1; |
| + track_num_ = -1; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool ChunkDemuxer::InfoTrackWebMParserClient::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; |
| + } |
| + |
| + if (track_type_ == 1) { |
| + video_track_num_ = track_num_; |
| + video_default_duration_ = track_default_duration_; |
| + } else if (track_type_ == 2) { |
| + audio_track_num_ = track_num_; |
| + audio_default_duration_ = track_default_duration_; |
| + } else { |
| + VLOG(1) << "Unexpected TrackType " << track_type_; |
| + return false; |
| + } |
| + |
| + track_type_ = -1; |
| + track_num_ = -1; |
| + } else if (id == kWebMIdInfo && timecode_scale_ == -1) { |
| + // Set timecode scale to default value if it isn't present in |
| + // the Info element. |
| + timecode_scale_ = 1000000; |
| + } |
| + return true; |
| +} |
| + |
| +bool ChunkDemuxer::InfoTrackWebMParserClient::OnUInt(int id, int64 val) { |
| + int64* dst = NULL; |
| + |
| + switch (id) { |
| + case kWebMIdTimecodeScale: |
| + dst = &timecode_scale_; |
| + break; |
| + 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 ChunkDemuxer::InfoTrackWebMParserClient::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 ChunkDemuxer::InfoTrackWebMParserClient::OnBinary(int id, const |
| + uint8* data, |
| + int size) { |
| + return true; |
| +} |
| + |
| +bool ChunkDemuxer::InfoTrackWebMParserClient::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 ChunkDemuxer::InfoTrackWebMParserClient::OnSimpleBlock( |
| + int track_num, int timecode, int flags, const uint8* data, int size) { |
| + return false; |
| +} |
| + |
| +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), |
| + read_size_(0), |
| + message_loop_(MessageLoop::current()) { |
| + AddRef(); |
| + } |
| + |
| + ~BuildState() { delete read_buffer_; } |
| + |
| + void BuildDone(PipelineStatus status, DataSource* data_source) { |
| + if (message_loop_ != MessageLoop::current()) { |
| + message_loop_->PostTask( |
| + FROM_HERE, |
| + NewRunnableMethod(this, |
| + &BuildState::BuildDone, |
| + status, |
| + scoped_refptr<DataSource>(data_source))); |
| + return; |
| + } |
| + |
| + 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) { |
| + cb_->Run(DEMUXER_ERROR_COULD_NOT_OPEN, |
| + static_cast<Demuxer*>(NULL)); |
| + data_source_->Stop(NewCallback(this, &BuildState::StopDone)); |
| + return; |
| + } |
| + |
| + DCHECK(!read_buffer_); |
| + read_size_ = size; |
| + read_buffer_ = new uint8[read_size_]; |
| + data_source_->Read(0, read_size_, read_buffer_, |
| + NewCallback(this, &BuildState::ReadDone)); |
| + } |
| + |
| + void ReadDone(size_t size) { |
| + if (message_loop_ != MessageLoop::current()) { |
| + message_loop_->PostTask(FROM_HERE, |
| + NewRunnableMethod(this, |
| + &BuildState::ReadDone, |
| + size)); |
| + return; |
| + } |
| + |
| + if (size == DataSource::kReadError) { |
| + cb_->Run(PIPELINE_ERROR_READ, static_cast<Demuxer*>(NULL)); |
| + data_source_->Stop(NewCallback(this, &BuildState::StopDone)); |
| + return; |
| + } |
| + |
| + if (demuxer_->Init(read_buffer_, size)) { |
| + cb_->Run(PIPELINE_OK, demuxer_.get()); |
| + } else { |
| + cb_->Run(DEMUXER_ERROR_COULD_NOT_OPEN, static_cast<Demuxer*>(NULL)); |
| + } |
| + |
| + data_source_->Stop(NewCallback(this, &BuildState::StopDone)); |
| + } |
| + |
| + void StopDone() { Release(); } |
| + |
| + private: |
| + std::string url_; |
| + scoped_ptr<BuildCallback> cb_; |
| + scoped_refptr<ChunkDemuxer> demuxer_; |
| + |
| + scoped_refptr<DataSource> data_source_; |
| + uint8* read_buffer_; |
| + int read_size_; |
| + MessageLoop* message_loop_; |
| +}; |
| + |
| +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::BuildDone)); |
| + demuxer_ = NULL; |
| +} |
| + |
| +DemuxerFactory* ChunkDemuxerFactory::Clone() const { |
| + return new ChunkDemuxerFactory(data_source_factory_->Clone()); |
| +} |
| + |
| +} // namespace media |