blob: 2514dae6a06c5d2d1ac786beebbb9d24e6571db6 [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 <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/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/threading/platform_thread.h"
#include "cobalt/base/wrap_main.h"
#include "cobalt/media/sandbox/media_sandbox.h"
#include "cobalt/media/sandbox/web_media_player_helper.h"
#include "cobalt/render_tree/image.h"
#if !defined(COBALT_MEDIA_SOURCE_2016)
#include "media/base/video_frame.h"
#endif // !defined(COBALT_MEDIA_SOURCE_2016)
#include "net/base/net_util.h"
#include "starboard/event.h"
#include "starboard/file.h"
#include "starboard/system.h"
namespace cobalt {
namespace media {
namespace sandbox {
namespace {
#if !defined(COBALT_MEDIA_SOURCE_2016)
using ::media::ChunkDemuxer;
using ::media::PIPELINE_OK;
using ::media::Ranges;
using ::media::VideoFrame;
using ::media::WebMediaPlayer;
#endif // !defined(COBALT_MEDIA_SOURCE_2016)
using base::TimeDelta;
using render_tree::Image;
using starboard::ScopedFile;
FilePath ResolvePath(const char* path) {
FilePath result(path);
if (!result.IsAbsolute()) {
FilePath content_path;
PathService::Get(base::DIR_TEST_DATA, &content_path);
CHECK(content_path.IsAbsolute());
result = content_path.Append(result);
}
if (SbFileCanOpen(result.value().c_str(), kSbFileOpenOnly | kSbFileRead)) {
return result;
}
return FilePath();
}
GURL ResolveUrl(const char* url) {
GURL gurl(url);
if (gurl.is_valid()) {
return gurl;
}
// Assume the input is a path. Try to figure out the path to this file and
// convert it to a URL.
FilePath path = ResolvePath(url);
if (path.empty()) {
return GURL();
}
return net::FilePathToFileURL(path);
}
void PrintUsage(const char* executable_path_name) {
std::string executable_file_name =
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";
LOG(ERROR) << "\n\n\n" // Extra empty lines to separate from other messages
<< "Usage: " << 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 video or progressive video\n\n"
<< "For example:\n"
<< executable_file_name << " " << kExampleAdaptiveAudioPathName
<< " " << kExampleAdaptiveVideoPathName << "\n"
<< executable_file_name << " " << kExampleProgressiveUrl << "\n\n";
}
#if defined(COBALT_MEDIA_SOURCE_2016)
std::string MakeCodecParameter(const char* string) { return string; }
void OnInitSegmentReceived(scoped_ptr<MediaTracks> tracks) {
UNREFERENCED_PARAMETER(tracks);
}
#else // defined(COBALT_MEDIA_SOURCE_2016)
std::vector<std::string> MakeCodecParameter(const char* string) {
std::vector<std::string> result;
result.push_back(string);
return result;
}
#endif // defined(COBALT_MEDIA_SOURCE_2016)
bool IsWebM(const std::string& path) {
return path.size() >= 5 && path.substr(path.size() - 5) == ".webm";
}
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,
FilePath(FILE_PATH_LITERAL("media_source_sandbox_trace.json"))) {
if (argc > 2) {
FilePath audio_path = ResolvePath(argv[argc - 2]);
FilePath video_path = ResolvePath(argv[argc - 1]);
if (!audio_path.empty() && !video_path.empty()) {
is_adaptive_playback_ = true;
InitializeAdaptivePlayback(audio_path.value(), video_path.value());
return;
}
}
if (argc > 1) {
GURL video_url = ResolveUrl(argv[argc - 1]);
if (video_url.is_valid()) {
is_adaptive_playback_ = false;
InitializeProgressivePlayback(video_url);
return;
}
}
PrintUsage(argv[0]);
SbSystemRequestStop(0);
}
private:
void InitializeAdaptivePlayback(const std::string& audio_path,
const std::string& video_path) {
audio_file_.reset(
new ScopedFile(audio_path.c_str(), kSbFileOpenOnly | kSbFileRead));
video_file_.reset(
new ScopedFile(video_path.c_str(), kSbFileOpenOnly | kSbFileRead));
if (!audio_file_->IsValid()) {
LOG(ERROR) << "Failed to open audio file: " << audio_path;
SbSystemRequestStop(0);
return;
}
if (!video_file_->IsValid()) {
LOG(ERROR) << "Failed to open video file: " << video_path;
SbSystemRequestStop(0);
return;
}
player_helper_.reset(
new WebMediaPlayerHelper(media_sandbox_.GetMediaModule(),
base::Bind(&Application::OnChunkDemuxerOpened,
base::Unretained(this))));
// |chunk_demuxer_| will be set inside OnChunkDemuxerOpened()
// asynchronously during initialization of |player_helper_|. Wait until
// it is set before proceed.
while (!chunk_demuxer_) {
MessageLoop::current()->RunUntilIdle();
}
LOG(INFO) << "Playing " << audio_path << " and " << video_path;
AddSourceBuffers(IsWebM(audio_path), IsWebM(video_path));
#if defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->SetTracksWatcher(kAudioId,
base::Bind(OnInitSegmentReceived));
chunk_demuxer_->SetTracksWatcher(kVideoId,
base::Bind(OnInitSegmentReceived));
#endif // defined(COBALT_MEDIA_SOURCE_2016)
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 GURL& video_url) {
LOG(INFO) << "Playing " << video_url;
player_helper_.reset(new WebMediaPlayerHelper(
media_sandbox_.GetMediaModule(), media_sandbox_.GetFetcherFactory(),
video_url));
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_) {
AppendData(kAudioId, audio_file_.get(), &audio_offset_);
AppendData(kVideoId, video_file_.get(), &video_offset_);
if (audio_offset_ == audio_file_->GetSize() &&
video_offset_ == video_file_->GetSize()) {
#if defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->MarkEndOfStream(PIPELINE_OK);
#else // defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->EndOfStream(PIPELINE_OK);
#endif // defined(COBALT_MEDIA_SOURCE_2016)
eos_appended_ = true;
}
}
MessageLoop::current()->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 AddSourceBuffers(bool is_audio_webm, bool is_video_webm) {
const char kAACMime[] = "audio/mp4";
const char kAACCodecs[] = "mp4a.40.2";
const char kAVCMime[] = "video/mp4";
const char kAVCCodecs[] = "avc1.640028";
const char kOpusMime[] = "audio/webm";
const char kOpusCodecs[] = "opus";
const char kVp9Mime[] = "video/webm";
const char kVp9Codecs[] = "vp9";
auto audio_codecs =
MakeCodecParameter(is_audio_webm ? kOpusCodecs : kAACCodecs);
auto status = chunk_demuxer_->AddId(
kAudioId, is_audio_webm ? kOpusMime : kAACMime, audio_codecs);
CHECK_EQ(status, ChunkDemuxer::kOk);
auto video_codecs =
MakeCodecParameter(is_video_webm ? kVp9Codecs : kAVCCodecs);
status = chunk_demuxer_->AddId(
kVideoId, is_video_webm ? kVp9Mime : kAVCMime, video_codecs);
CHECK_EQ(status, ChunkDemuxer::kOk);
}
void AppendData(const std::string& id, ScopedFile* file, int64* offset) {
const float kLowWaterMarkInSeconds = 5.f;
const int64 kMaxBytesToAppend = 1024 * 1024;
char 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);
file->Read(buffer, bytes_to_append);
#if defined(COBALT_MEDIA_SOURCE_2016)
base::TimeDelta timestamp_offset;
chunk_demuxer_->AppendData(id, reinterpret_cast<uint8*>(buffer),
bytes_to_append, base::TimeDelta(),
media::kInfiniteDuration, &timestamp_offset);
#else // defined(COBALT_MEDIA_SOURCE_2016)
chunk_demuxer_->AppendData(id, reinterpret_cast<uint8*>(buffer),
bytes_to_append);
#endif // defined(COBALT_MEDIA_SOURCE_2016)
*offset += bytes_to_append;
}
}
scoped_refptr<Image> FrameCB(const base::TimeDelta& time) {
UNREFERENCED_PARAMETER(time);
#if SB_HAS(GRAPHICS)
SbDecodeTarget decode_target = player_helper_->GetCurrentDecodeTarget();
if (SbDecodeTargetIsValid(decode_target)) {
return media_sandbox_.resource_provider()->CreateImageFromSbDecodeTarget(
decode_target);
}
#if !defined(COBALT_MEDIA_SOURCE_2016)
scoped_refptr<VideoFrame> frame = player_helper_->GetCurrentFrame();
return frame ? reinterpret_cast<Image*>(frame->texture_id()) : NULL;
#else // !defined(COBALT_MEDIA_SOURCE_2016)
return NULL;
#endif // !defined(COBALT_MEDIA_SOURCE_2016)
#else // SB_HAS(GRAPHICS)
return NULL;
#endif // SB_HAS(GRAPHICS)
}
const std::string kAudioId = "audio";
const std::string kVideoId = "video";
bool is_adaptive_playback_;
InitCobaltHelper init_cobalt_helper_;
MediaSandbox media_sandbox_;
scoped_ptr<WebMediaPlayerHelper> player_helper_;
ChunkDemuxer* chunk_demuxer_ = NULL;
WebMediaPlayer* player_ = NULL;
scoped_ptr<ScopedFile> audio_file_;
scoped_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);
s_application =
new Application(data->argument_count, data->argument_values);
break;
}
case kSbEventTypeStop: {
delete s_application;
s_application = NULL;
break;
}
default: { break; }
}
}