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 |