blob: f2c401398b035e96c115460b359d14f220e02570 [file] [log] [blame]
// Copyright 2016 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/media_source_demuxer.h"
#include <algorithm>
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/sys_byteorder.h"
#include "cobalt/media/base/bind_to_current_loop.h"
#include "cobalt/media/base/demuxer.h"
#include "cobalt/media/base/pipeline_status.h"
#include "cobalt/media/filters/chunk_demuxer.h"
#include "starboard/memory.h"
namespace cobalt {
namespace media {
namespace sandbox {
namespace {
using base::Bind;
using ::media::BindToCurrentLoop;
using ::media::DecoderBuffer;
using ::media::DemuxerStream;
using ::media::ChunkDemuxer;
typedef base::Callback<void(::media::DecoderBuffer*)> AppendBufferCB;
const char kSourceId[] = "id";
// Stub log function.
void Log(const std::string& message) {}
// Stub need key callback.
void NeedKeyCB(const std::string& type, std::unique_ptr<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:
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_) {
base::RunLoop().RunUntilIdle();
}
}
bool valid() const { return valid_; }
const ::media::VideoDecoderConfig& config() const { return config_; }
private:
void SetTotalBytes(int64 total_bytes) override {
}
void AddBufferedByteRange(int64 start, int64 end) override {
}
void AddBufferedTimeRange(base::TimeDelta start,
base::TimeDelta end) override {
}
void SetDuration(base::TimeDelta duration) override {
}
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();
base::RunLoop().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_;
};
// This is a very loose IVF parser for fuzzing purpose and it ignores any
// invalid structure and just retrieves the frame data.
// IVF format (all data is little endian):
// 32 bytes file header starts with DKIF
// (4 bytes frame size + 8 bytes timestamp + <size> bytes frame data)*
bool LoadIVF(const std::vector<uint8>& content,
const AppendBufferCB& append_buffer_cb) {
const size_t kIVFFileHeaderSize = 32;
const size_t kIVFFrameHeaderSize = 12;
if (content.size() < kIVFFileHeaderSize ||
memcmp(&content[0], "DKIF", 4) != 0) {
return false;
}
size_t offset = kIVFFileHeaderSize;
while (offset + kIVFFrameHeaderSize < content.size()) {
uint32 frame_size = base::ByteSwapToLE32(
*reinterpret_cast<const uint32*>(&content[offset]));
offset += kIVFFrameHeaderSize;
if (offset + frame_size > content.size()) {
break;
}
append_buffer_cb.Run(::media::StreamParserBuffer::CopyFrom(
&content[offset], frame_size, false));
offset += frame_size;
}
return true;
}
} // namespace
MediaSourceDemuxer::MediaSourceDemuxer(const std::vector<uint8>& content)
: valid_(true) {
// Try to load it as an ivf first.
if (LoadIVF(content, Bind(&MediaSourceDemuxer::AppendBuffer,
base::Unretained(this)))) {
config_.Initialize(
::media::kCodecVP9, ::media::VP9PROFILE_MAIN, ::media::VideoFrame::YV12,
::media::COLOR_SPACE_HD_REC709, math::Size(1, 1), math::Rect(1, 1),
math::Size(1, 1), NULL, 0, false, false);
valid_ = descs_.size() > 0;
return;
}
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::DecoderBuffer* buffer) {
AUDescriptor desc = {0};
desc.is_keyframe = buffer->IsKeyframe();
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