blob: c5b29917c492dc5bca80f130c19d58ce6b7997b2 [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/media/sandbox/demuxer_helper.h"
#include <queue>
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "cobalt/media/fetcher_buffered_data_source.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/bind_to_loop.h"
#include "media/base/decoder_buffer.h"
#include "media/base/video_decoder_config.h"
#include "media/progressive/progressive_demuxer.h"
namespace cobalt {
namespace media {
namespace sandbox {
namespace {
using ::media::AudioDecoderConfig;
using ::media::BindToCurrentLoop;
using ::media::DecoderBuffer;
using ::media::DemuxerStream;
using ::media::VideoDecoderConfig;
// A DemuxerStream that caches media data in memory. Note that it is only for
// test purpose and no synchronization is provided.
class DemuxerStreamCache : public ::media::DemuxerStream {
public:
explicit DemuxerStreamCache(const AudioDecoderConfig& audio_decoder_config)
: type_(AUDIO) {
audio_decoder_config_.CopyFrom(audio_decoder_config);
}
explicit DemuxerStreamCache(const VideoDecoderConfig& video_decoder_config)
: type_(VIDEO) {
video_decoder_config_.CopyFrom(video_decoder_config);
}
void Append(const scoped_refptr<DecoderBuffer>& buffer) {
DCHECK(buffer);
DCHECK(!buffer->IsEndOfStream());
buffers_.push(buffer);
}
private:
// DemuxerStream methods.
void Read(const ReadCB& read_cb) override {
scoped_refptr<DecoderBuffer> buffer;
if (buffers_.empty()) {
buffer = DecoderBuffer::CreateEOSBuffer(base::TimeDelta());
} else {
buffer = buffers_.front();
buffers_.pop();
}
read_cb.Run(kOk, buffer);
}
const AudioDecoderConfig& audio_decoder_config() override {
DCHECK_EQ(type_, AUDIO);
return audio_decoder_config_;
}
const VideoDecoderConfig& video_decoder_config() override {
DCHECK_EQ(type_, VIDEO);
return video_decoder_config_;
}
Type type() override { return type_; }
void EnableBitstreamConverter() override {}
bool StreamWasEncrypted() const override { return false; }
Type type_;
AudioDecoderConfig audio_decoder_config_;
VideoDecoderConfig video_decoder_config_;
std::queue<scoped_refptr<DecoderBuffer> > buffers_;
};
// A Demuxer that caches media data in memory. Note that it is only for test
// purpose and no synchronization is provided.
class DemuxerCache : public ::media::Demuxer {
public:
DemuxerCache(const scoped_refptr<Demuxer>& demuxer, uint64 bytes_to_cache,
const DemuxerHelper::DemuxerReadyCB& demuxer_ready_cb) {
if (demuxer->GetStream(DemuxerStream::AUDIO)) {
audio_stream_ = new DemuxerStreamCache(
demuxer->GetStream(DemuxerStream::AUDIO)->audio_decoder_config());
}
if (demuxer->GetStream(DemuxerStream::VIDEO)) {
video_stream_ = new DemuxerStreamCache(
demuxer->GetStream(DemuxerStream::VIDEO)->video_decoder_config());
}
DCHECK(audio_stream_ || video_stream_);
CacheState cache_state;
cache_state.demuxer = demuxer;
cache_state.bytes_to_cache = bytes_to_cache;
cache_state.audio_eos = !audio_stream_;
cache_state.video_eos = !video_stream_;
cache_state.demuxer_ready_cb = demuxer_ready_cb;
if (audio_stream_) {
cache_state.last_read_type = DemuxerStream::AUDIO;
} else {
cache_state.last_read_type = DemuxerStream::VIDEO;
}
demuxer->GetStream(cache_state.last_read_type)
->Read(BindToCurrentLoop(
base::Bind(&DemuxerCache::CacheBuffer, this, cache_state)));
}
private:
struct CacheState {
scoped_refptr<Demuxer> demuxer;
uint64 bytes_to_cache;
base::TimeDelta last_audio_buffer_timestamp;
bool audio_eos;
base::TimeDelta last_video_buffer_timestamp;
bool video_eos;
DemuxerHelper::DemuxerReadyCB demuxer_ready_cb;
DemuxerStream::Type last_read_type;
};
void CacheBuffer(CacheState cache_state, DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
DCHECK_EQ(status, DemuxerStream::kOk);
DCHECK(buffer);
if (cache_state.last_read_type == DemuxerStream::AUDIO) {
if (buffer->IsEndOfStream()) {
cache_state.audio_eos = true;
} else {
cache_state.last_audio_buffer_timestamp = buffer->GetTimestamp();
if (cache_state.bytes_to_cache >= buffer->GetDataSize()) {
cache_state.bytes_to_cache -= buffer->GetDataSize();
audio_stream_->Append(buffer);
} else {
cache_state.bytes_to_cache = 0;
}
}
} else {
DCHECK_EQ(cache_state.last_read_type, DemuxerStream::VIDEO);
if (buffer->IsEndOfStream()) {
cache_state.video_eos = true;
} else {
cache_state.last_video_buffer_timestamp = buffer->GetTimestamp();
if (cache_state.bytes_to_cache >= buffer->GetDataSize()) {
cache_state.bytes_to_cache -= buffer->GetDataSize();
video_stream_->Append(buffer);
} else {
cache_state.bytes_to_cache = 0;
}
}
}
if ((cache_state.audio_eos && cache_state.video_eos) ||
cache_state.bytes_to_cache == 0) {
cache_state.demuxer->Stop(base::Bind(cache_state.demuxer_ready_cb,
scoped_refptr<Demuxer>(this)));
return;
}
cache_state.last_read_type = DemuxerStream::AUDIO;
if (cache_state.audio_eos) {
cache_state.last_read_type = DemuxerStream::VIDEO;
} else if (!cache_state.video_eos) {
if (cache_state.last_video_buffer_timestamp <
cache_state.last_audio_buffer_timestamp) {
cache_state.last_read_type = DemuxerStream::VIDEO;
}
}
cache_state.demuxer->GetStream(cache_state.last_read_type)
->Read(BindToCurrentLoop(
base::Bind(&DemuxerCache::CacheBuffer, this, cache_state)));
}
// Demuxer methods.
void Initialize(::media::DemuxerHost* host,
const ::media::PipelineStatusCB& status_cb) override {
NOTREACHED();
}
void Stop(const base::Closure& callback) override {
DCHECK(!callback.is_null());
callback.Run();
}
scoped_refptr<DemuxerStream> GetStream(DemuxerStream::Type type) override {
if (type == DemuxerStream::AUDIO) {
return audio_stream_;
} else if (type == DemuxerStream::VIDEO) {
return video_stream_;
}
NOTREACHED();
return NULL;
}
base::TimeDelta GetStartTime() const override { return base::TimeDelta(); }
scoped_refptr<DemuxerStreamCache> audio_stream_;
scoped_refptr<DemuxerStreamCache> video_stream_;
};
} // namespace
class DemuxerHelper::DemuxerHostStub : public ::media::DemuxerHost {
private:
// DataSourceHost methods
void SetTotalBytes(int64 total_bytes) override {
}
void AddBufferedByteRange(int64 start, int64 end) override {
}
void AddBufferedTimeRange(base::TimeDelta start,
base::TimeDelta end) override {
}
// DemuxerHost methods
void SetDuration(base::TimeDelta duration) override {
}
void OnDemuxerError(::media::PipelineStatus error) override {
}
};
DemuxerHelper::DemuxerHelper(
const scoped_refptr<base::SingleThreadTaskRunner>& media_message_loop,
loader::FetcherFactory* fetcher_factory, const GURL& video_url,
const DemuxerReadyCB& demuxer_ready_cb)
: host_(new DemuxerHostStub) {
CreateDemuxer(media_message_loop, fetcher_factory, video_url,
demuxer_ready_cb, 0);
}
DemuxerHelper::DemuxerHelper(
const scoped_refptr<base::SingleThreadTaskRunner>& media_message_loop,
loader::FetcherFactory* fetcher_factory, const GURL& video_url,
const DemuxerReadyCB& demuxer_ready_cb, uint64 bytes_to_cache)
: host_(new DemuxerHostStub) {
DCHECK_GE(bytes_to_cache, 0);
CreateDemuxer(media_message_loop, fetcher_factory, video_url,
demuxer_ready_cb, bytes_to_cache);
}
DemuxerHelper::~DemuxerHelper() { delete host_; }
void DemuxerHelper::CreateDemuxer(
const scoped_refptr<base::SingleThreadTaskRunner>& media_message_loop,
loader::FetcherFactory* fetcher_factory, const GURL& video_url,
const DemuxerReadyCB& demuxer_ready_cb, uint64 bytes_to_cache) {
DCHECK(media_message_loop);
DCHECK(fetcher_factory);
DCHECK(!demuxer_ready_cb.is_null());
scoped_refptr<FetcherBufferedDataSource> data_source(
new FetcherBufferedDataSource(media_message_loop, video_url,
csp::SecurityCallback(),
fetcher_factory->network_module(),
loader::kNoCORSMode, loader::Origin()));
scoped_refptr<Demuxer> demuxer =
new ::media::ProgressiveDemuxer(media_message_loop, data_source);
demuxer->Initialize(
host_, base::Bind(&DemuxerHelper::OnDemuxerReady, base::Unretained(this),
demuxer, demuxer_ready_cb, bytes_to_cache));
}
void DemuxerHelper::OnDemuxerReady(const scoped_refptr<Demuxer>& demuxer,
const DemuxerReadyCB& demuxer_ready_cb,
uint64 bytes_to_cache,
::media::PipelineStatus status) {
DCHECK_EQ(status, ::media::PIPELINE_OK);
if (bytes_to_cache == 0) {
demuxer_ready_cb.Run(demuxer);
return;
}
// The newly created object is reference counted and will be passed back
// through demuxer_ready_cb.
new DemuxerCache(demuxer, bytes_to_cache, demuxer_ready_cb);
}
} // namespace sandbox
} // namespace media
} // namespace cobalt