blob: c9470203a84155077960a5e636870547b4ab2410 [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 <algorithm>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/threading/platform_thread.h"
#include "cobalt/base/wrap_main.h"
#include "cobalt/media/sandbox/format_guesstimator.h"
#include "cobalt/media/sandbox/media_sandbox.h"
#include "cobalt/media/sandbox/web_media_player_helper.h"
#include "cobalt/render_tree/image.h"
#include "starboard/event.h"
#include "starboard/file.h"
#include "starboard/log.h"
#include "starboard/system.h"
namespace cobalt {
namespace media {
namespace sandbox {
namespace {
using base::TimeDelta;
using render_tree::Image;
using starboard::ScopedFile;
void PrintUsage(const char* executable_path_name) {
std::string executable_file_name =
base::FilePath(executable_path_name).BaseName().value();
const char kExampleAdaptiveAudioPathName[] =
"cobalt/demos/media-element-demo/dash-audio.mp4";
const char kExampleAdaptiveVideoPathName[] =
"cobalt/demos/media-element-demo/dash-video-1080p.mp4";
const char kExampleProgressiveUrl[] =
"https://storage.googleapis.com/yt-cobalt-media-element-demo/"
"progressive.mp4";
std::stringstream ss;
// Head
ss << "\n\n"
<< "======================== " << executable_file_name
<< " ========================\n";
// Basic usage
ss << "Usage: " << executable_file_name
<< " [OPTIONS] <adaptive audio file path>\n"
<< " or: " << executable_file_name
<< " [OPTIONS] <adaptive video file path>\n"
<< " or: " << executable_file_name
<< " [OPTIONS] <adaptive audio file path> "
<< " <adaptive video file path>\n"
<< " or: " << executable_file_name
<< " [OPTIONS] <progressive video path or url>\n"
<< "Play adaptive audio/video or progressive video\n\n";
// Options
ss << "OPTIONS:\n"
<< " --dump_video_data: Dump video data into .dmp files\n"
<< " --use_stub_audio_decoder: Use stub audio decoder to play the video\n"
<< " --use_stub_audio_sink: Use stub audio sink to play the video\n"
<< " --use_stub_video_decoder: Use stub video decoder to play the video\n"
<< "\n";
// Usage examples
ss << "For example:\n " << executable_file_name << " --dump_video_data "
<< kExampleAdaptiveAudioPathName << "\n " << executable_file_name << " "
<< kExampleAdaptiveVideoPathName << "\n " << executable_file_name << " "
<< kExampleAdaptiveAudioPathName << " " << kExampleAdaptiveVideoPathName
<< "\n " << executable_file_name << " " << kExampleProgressiveUrl
<< "\n\n";
SbLogRaw(ss.str().c_str());
}
void OnInitSegmentReceived(std::unique_ptr<MediaTracks> tracks) {
}
class InitCobaltHelper {
public:
InitCobaltHelper(int argc, char* argv[]) {
cobalt::InitCobalt(argc, argv, NULL);
}
private:
base::AtExitManager at_exit_manager_;
};
class Application {
public:
Application(int argc, char* argv[])
: init_cobalt_helper_(argc, argv),
media_sandbox_(argc, argv,
base::FilePath(FILE_PATH_LITERAL(
"media_source_sandbox_trace.json"))) {
if (argc > 1) {
FormatGuesstimator guesstimator1(argv[argc - 1],
media_sandbox_.GetMediaModule());
FormatGuesstimator guesstimator2(argv[argc - 2],
media_sandbox_.GetMediaModule());
if (!guesstimator1.is_valid()) {
SB_LOG(ERROR) << "Invalid path or url: " << argv[argc - 1];
// Fall off to PrintUsage() and terminate.
} else if (guesstimator1.is_progressive()) {
InitializeProgressivePlayback(guesstimator1);
return;
} else if (!guesstimator2.is_adaptive()) {
InitializeAdaptivePlayback(guesstimator1);
return;
} else if (guesstimator1.is_audio() && guesstimator2.is_audio()) {
SB_LOG(ERROR) << "Failed to play because both " << argv[argc - 1]
<< " and " << argv[argc - 2]
<< " are audio streams, check usage for more details.";
// Fall off to PrintUsage() and terminate.
} else if (!guesstimator1.is_audio() && !guesstimator2.is_audio()) {
SB_LOG(ERROR) << "Failed to play because both " << argv[argc - 1]
<< " and " << argv[argc - 2]
<< " are video streams, check usage for more details.";
// Fall off to PrintUsage() and terminate.
} else if (guesstimator1.is_audio()) {
InitializeAdaptivePlayback(guesstimator1, guesstimator2);
return;
} else {
InitializeAdaptivePlayback(guesstimator2, guesstimator1);
return;
}
}
PrintUsage(argv[0]);
SbSystemRequestStop(0);
}
~Application() { media_sandbox_.RegisterFrameCB(MediaSandbox::FrameCB()); }
private:
void InitializeAdaptivePlayback(const FormatGuesstimator& guesstimator) {
is_adaptive_playback_ = true;
std::unique_ptr<ScopedFile>& file =
guesstimator.is_audio() ? audio_file_ : video_file_;
file.reset(new ScopedFile(guesstimator.adaptive_path().c_str(),
kSbFileOpenOnly | kSbFileRead));
if (!file->IsValid()) {
LOG(ERROR) << "Failed to open file: " << guesstimator.adaptive_path();
SbSystemRequestStop(0);
return;
}
player_helper_.reset(new WebMediaPlayerHelper(
media_sandbox_.GetMediaModule(),
base::Bind(&Application::OnChunkDemuxerOpened, base::Unretained(this)),
media_sandbox_.GetViewportSize()));
// |chunk_demuxer_| will be set inside OnChunkDemuxerOpened()
// asynchronously during initialization of |player_helper_|. Wait until
// it is set before proceed.
while (!chunk_demuxer_) {
base::RunLoop().RunUntilIdle();
}
LOG(INFO) << "Playing " << guesstimator.adaptive_path();
std::string id = guesstimator.is_audio() ? kAudioId : kVideoId;
auto status = chunk_demuxer_->AddId(id, guesstimator.mime_type());
CHECK_EQ(status, ChunkDemuxer::kOk);
chunk_demuxer_->SetTracksWatcher(id, base::Bind(OnInitSegmentReceived));
player_ = player_helper_->player();
media_sandbox_.RegisterFrameCB(
base::Bind(&Application::FrameCB, base::Unretained(this)));
timer_event_id_ =
SbEventSchedule(Application::OnTimer, this, kSbTimeSecond / 10);
}
void InitializeAdaptivePlayback(
const FormatGuesstimator& audio_guesstimator,
const FormatGuesstimator& video_guesstimator) {
is_adaptive_playback_ = true;
audio_file_.reset(new ScopedFile(audio_guesstimator.adaptive_path().c_str(),
kSbFileOpenOnly | kSbFileRead));
video_file_.reset(new ScopedFile(video_guesstimator.adaptive_path().c_str(),
kSbFileOpenOnly | kSbFileRead));
if (!audio_file_->IsValid()) {
LOG(ERROR) << "Failed to open audio file: "
<< audio_guesstimator.adaptive_path();
SbSystemRequestStop(0);
return;
}
if (!video_file_->IsValid()) {
LOG(ERROR) << "Failed to open video file: "
<< video_guesstimator.adaptive_path();
SbSystemRequestStop(0);
return;
}
player_helper_.reset(new WebMediaPlayerHelper(
media_sandbox_.GetMediaModule(),
base::Bind(&Application::OnChunkDemuxerOpened, base::Unretained(this)),
media_sandbox_.GetViewportSize()));
// |chunk_demuxer_| will be set inside OnChunkDemuxerOpened()
// asynchronously during initialization of |player_helper_|. Wait until
// it is set before proceed.
while (!chunk_demuxer_) {
base::RunLoop().RunUntilIdle();
}
LOG(INFO) << "Playing " << audio_guesstimator.adaptive_path() << " and "
<< video_guesstimator.adaptive_path();
auto status =
chunk_demuxer_->AddId(kAudioId, audio_guesstimator.mime_type());
CHECK_EQ(status, ChunkDemuxer::kOk);
status = chunk_demuxer_->AddId(kVideoId, video_guesstimator.mime_type());
CHECK_EQ(status, ChunkDemuxer::kOk);
chunk_demuxer_->SetTracksWatcher(kAudioId,
base::Bind(OnInitSegmentReceived));
chunk_demuxer_->SetTracksWatcher(kVideoId,
base::Bind(OnInitSegmentReceived));
player_ = player_helper_->player();
media_sandbox_.RegisterFrameCB(
base::Bind(&Application::FrameCB, base::Unretained(this)));
timer_event_id_ =
SbEventSchedule(Application::OnTimer, this, kSbTimeSecond / 10);
}
void InitializeProgressivePlayback(const FormatGuesstimator& guesstimator) {
LOG(INFO) << "Playing " << guesstimator.progressive_url();
is_adaptive_playback_ = false;
player_helper_.reset(new WebMediaPlayerHelper(
media_sandbox_.GetMediaModule(), media_sandbox_.GetFetcherFactory(),
guesstimator.progressive_url(), media_sandbox_.GetViewportSize()));
player_ = player_helper_->player();
media_sandbox_.RegisterFrameCB(
base::Bind(&Application::FrameCB, base::Unretained(this)));
timer_event_id_ =
SbEventSchedule(Application::OnTimer, this, kSbTimeSecond / 10);
}
static void OnTimer(void* context) {
Application* application = static_cast<Application*>(context);
DCHECK(application);
application->Tick();
}
void Tick() {
if (player_helper_->IsPlaybackFinished()) {
media_sandbox_.RegisterFrameCB(MediaSandbox::FrameCB());
LOG(INFO) << "Playback finished.";
SbEventCancel(timer_event_id_);
SbSystemRequestStop(0);
return;
}
if (is_adaptive_playback_ && !eos_appended_) {
if (audio_file_) {
AppendData(kAudioId, audio_file_.get(), &audio_offset_);
}
if (video_file_) {
AppendData(kVideoId, video_file_.get(), &video_offset_);
}
bool audio_eos = !audio_file_ || audio_offset_ == audio_file_->GetSize();
bool video_eos = !video_file_ || video_offset_ == video_file_->GetSize();
if (audio_eos && video_eos) {
chunk_demuxer_->MarkEndOfStream(PIPELINE_OK);
eos_appended_ = true;
}
}
base::RunLoop().RunUntilIdle();
timer_event_id_ =
SbEventSchedule(Application::OnTimer, this, kSbTimeSecond / 10);
}
void OnChunkDemuxerOpened(ChunkDemuxer* chunk_demuxer) {
CHECK(chunk_demuxer);
CHECK(!chunk_demuxer_);
chunk_demuxer_ = chunk_demuxer;
}
void AppendData(const std::string& id, ScopedFile* file, int64* offset) {
const float kLowWaterMarkInSeconds = 5.f;
const int64 kMaxBytesToAppend = 1024 * 1024;
std::vector<uint8_t> buffer(kMaxBytesToAppend);
while (*offset < file->GetSize()) {
Ranges<TimeDelta> ranges = chunk_demuxer_->GetBufferedRanges(id);
float end_of_buffer =
ranges.size() == 0 ? 0.f : ranges.end(ranges.size() - 1).InSecondsF();
if (end_of_buffer - player_->GetCurrentTime() > kLowWaterMarkInSeconds) {
break;
}
int64 bytes_to_append =
std::min(kMaxBytesToAppend, file->GetSize() - *offset);
auto current_time = player_ ? player_->GetCurrentTime() : 0;
auto evicted = chunk_demuxer_->EvictCodedFrames(
id, base::TimeDelta::FromSecondsD(current_time), bytes_to_append);
SB_DCHECK(evicted);
file->Read(reinterpret_cast<char*>(buffer.data()), bytes_to_append);
base::TimeDelta timestamp_offset;
auto appended = chunk_demuxer_->AppendData(
id, buffer.data(), bytes_to_append, base::TimeDelta(),
media::kInfiniteDuration, &timestamp_offset);
SB_DCHECK(appended);
*offset += bytes_to_append;
}
}
scoped_refptr<Image> FrameCB(const base::TimeDelta& time) {
SbDecodeTarget decode_target = player_helper_->GetCurrentDecodeTarget();
if (SbDecodeTargetIsValid(decode_target)) {
return media_sandbox_.resource_provider()->CreateImageFromSbDecodeTarget(
decode_target);
}
return NULL;
}
const std::string kAudioId = "audio";
const std::string kVideoId = "video";
bool is_adaptive_playback_;
InitCobaltHelper init_cobalt_helper_;
MediaSandbox media_sandbox_;
std::unique_ptr<WebMediaPlayerHelper> player_helper_;
ChunkDemuxer* chunk_demuxer_ = NULL;
WebMediaPlayer* player_ = NULL;
std::unique_ptr<ScopedFile> audio_file_;
std::unique_ptr<ScopedFile> video_file_;
int64 audio_offset_ = 0;
int64 video_offset_ = 0;
bool eos_appended_ = false;
SbEventId timer_event_id_ = kSbEventIdInvalid;
};
} // namespace
} // namespace sandbox
} // namespace media
} // namespace cobalt
void SbEventHandle(const SbEvent* event) {
using cobalt::media::sandbox::Application;
static Application* s_application;
switch (event->type) {
case kSbEventTypeStart: {
SbEventStartData* data = static_cast<SbEventStartData*>(event->data);
DCHECK(!s_application);
if (data->argument_count == 1) {
cobalt::media::sandbox::PrintUsage(data->argument_values[0]);
SbSystemRequestStop(0);
return;
}
s_application =
new Application(data->argument_count, data->argument_values);
break;
}
case kSbEventTypeStop: {
delete s_application;
s_application = NULL;
break;
}
default: { break; }
}
}