blob: b657f275da943a39b6d6c337dbf10abfccc8f027 [file] [log] [blame]
/*
* Copyright 2016 Google Inc. 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/media_source_demuxer.h"
#include <algorithm>
#include <string>
#include "base/bind.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "media/base/bind_to_loop.h"
#include "media/base/demuxer.h"
#include "media/base/pipeline_status.h"
#include "media/filters/chunk_demuxer.h"
namespace cobalt {
namespace media {
namespace sandbox {
namespace {
using base::Bind;
using ::media::BindToCurrentLoop;
using ::media::DecoderBuffer;
using ::media::DemuxerStream;
using ::media::ChunkDemuxer;
const char kSourceId[] = "id";
// Stub log function.
void Log(const std::string& message) { UNREFERENCED_PARAMETER(message); }
// Stub need key callback.
void NeedKeyCB(const std::string& type, scoped_array<uint8> init_data,
int init_data_size) {
NOTREACHED();
}
bool IsMP4(const std::vector<uint8>& content) {
return content.size() >= 8 && memcmp(&content[4], "ftyp", 4) == 0;
}
std::vector<std::string> MakeStringVector(const char* string) {
std::vector<std::string> result;
result.push_back(string);
return result;
}
bool AddSourceBuffer(bool is_mp4, ChunkDemuxer* demuxer) {
const char kMP4Mime[] = "video/mp4";
const char kAVCCodecs[] = "avc1.640028";
const char kWebMMime[] = "video/webm";
const char kVp9Codecs[] = "vp9";
std::vector<std::string> codecs =
MakeStringVector(is_mp4 ? kAVCCodecs : kVp9Codecs);
ChunkDemuxer::Status status =
demuxer->AddId(kSourceId, is_mp4 ? kMP4Mime : kWebMMime, codecs);
CHECK_EQ(status, ChunkDemuxer::kOk);
return status == ChunkDemuxer::kOk;
}
// Helper class to load an adaptive video file and call |append_buffer_cb| for
// every frame.
class Loader : public ::media::DemuxerHost {
public:
typedef base::Callback<void(::media::Buffer*)> AppendBufferCB;
Loader(const std::vector<uint8>& content,
const AppendBufferCB& append_buffer_cb)
: valid_(true),
ended_(false),
append_buffer_cb_(append_buffer_cb),
content_(content),
offset_(0),
read_in_progress_(false) {
DCHECK(!append_buffer_cb.is_null());
demuxer_ = new ChunkDemuxer(
BindToCurrentLoop(Bind(&Loader::OnDemuxerOpen, base::Unretained(this),
IsMP4(content))),
Bind(NeedKeyCB), Bind(Log));
demuxer_->Initialize(
this, ::media::BindToCurrentLoop(
Bind(&Loader::OnDemuxerStatus, base::Unretained(this))));
while (valid_ && !ended_) {
MessageLoop::current()->RunUntilIdle();
}
}
bool valid() const { return valid_; }
const ::media::VideoDecoderConfig& config() const { return config_; }
private:
void SetTotalBytes(int64 total_bytes) OVERRIDE {
UNREFERENCED_PARAMETER(total_bytes);
}
void AddBufferedByteRange(int64 start, int64 end) OVERRIDE {
UNREFERENCED_PARAMETER(start);
UNREFERENCED_PARAMETER(end);
}
void AddBufferedTimeRange(base::TimeDelta start,
base::TimeDelta end) OVERRIDE {
UNREFERENCED_PARAMETER(start);
UNREFERENCED_PARAMETER(end);
}
void SetDuration(base::TimeDelta duration) OVERRIDE {
UNREFERENCED_PARAMETER(duration);
}
void OnDemuxerError(::media::PipelineStatus error) OVERRIDE {
valid_ = false;
}
void OnDemuxerOpen(bool is_mp4) {
valid_ &= AddSourceBuffer(is_mp4, demuxer_);
if (!valid_) {
return;
}
ConsumeContent();
if (!demuxer_->GetStream(DemuxerStream::VIDEO)) {
valid_ = false;
return;
}
while (valid_ && !ended_) {
ConsumeContent();
ProduceBuffer();
MessageLoop::current()->RunUntilIdle();
}
}
void OnDemuxerStatus(::media::PipelineStatus status) {
if (status == ::media::PIPELINE_OK) {
config_.CopyFrom(
demuxer_->GetStream(DemuxerStream::VIDEO)->video_decoder_config());
} else {
valid_ = false;
}
}
void ConsumeContent() {
const float kLowWaterMarkInSeconds = 1.f;
const size_t kMaxBytesToAppend = 5 * 1024 * 1024;
::media::Ranges<base::TimeDelta> ranges =
demuxer_->GetBufferedRanges(kSourceId);
if (offset_ < content_.size()) {
base::TimeDelta range_end;
if (ranges.size() != 0) {
range_end = ranges.end(ranges.size() - 1);
}
if ((range_end - timestamp_of_last_buffer_).InSecondsF() >
kLowWaterMarkInSeconds) {
return;
}
size_t bytes_to_append =
std::min(kMaxBytesToAppend, content_.size() - offset_);
demuxer_->AppendData(kSourceId, &content_[0] + offset_, bytes_to_append);
offset_ += bytes_to_append;
}
if (offset_ == content_.size()) {
demuxer_->EndOfStream(::media::PIPELINE_OK);
++offset_;
}
}
void ProduceBuffer() {
if (!read_in_progress_) {
read_in_progress_ = true;
demuxer_->GetStream(DemuxerStream::VIDEO)
->Read(Bind(&Loader::OnDemuxerRead, base::Unretained(this)));
}
}
void OnDemuxerRead(DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
// This should only happen during seeking which won't happen to this class.
DCHECK_NE(status, DemuxerStream::kAborted);
if (status == DemuxerStream::kConfigChanged) {
config_.CopyFrom(
demuxer_->GetStream(DemuxerStream::VIDEO)->video_decoder_config());
ProduceBuffer();
return;
}
DCHECK_EQ(status, DemuxerStream::kOk);
if (buffer->IsEndOfStream()) {
ended_ = true;
} else {
timestamp_of_last_buffer_ = buffer->GetTimestamp();
append_buffer_cb_.Run(buffer);
}
read_in_progress_ = false;
}
bool valid_;
bool ended_;
AppendBufferCB append_buffer_cb_;
const std::vector<uint8>& content_;
size_t offset_;
bool read_in_progress_;
base::TimeDelta timestamp_of_last_buffer_;
scoped_refptr<ChunkDemuxer> demuxer_;
::media::VideoDecoderConfig config_;
};
} // namespace
MediaSourceDemuxer::MediaSourceDemuxer(const std::vector<uint8>& content)
: valid_(true) {
Loader loader(
content, Bind(&MediaSourceDemuxer::AppendBuffer, base::Unretained(this)));
valid_ = loader.valid();
if (valid_) {
config_.CopyFrom(loader.config());
} else {
au_data_.clear();
descs_.clear();
}
}
void MediaSourceDemuxer::AppendBuffer(::media::Buffer* buffer) {
AUDescriptor desc = {0};
desc.offset = au_data_.size();
desc.size = buffer->GetDataSize();
desc.timestamp = buffer->GetTimestamp();
au_data_.insert(au_data_.end(), buffer->GetData(),
buffer->GetData() + buffer->GetDataSize());
descs_.push_back(desc);
}
const MediaSourceDemuxer::AUDescriptor& MediaSourceDemuxer::GetFrame(
size_t index) const {
DCHECK_LT(index, descs_.size());
return descs_[index];
}
} // namespace sandbox
} // namespace media
} // namespace cobalt