| // 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 <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/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 "cobalt/media/base/shell_video_frame_provider.h" |
| #else // defined(COBALT_MEDIA_SOURCE_2016) |
| #include "media/base/video_frame.h" |
| #endif // defined(COBALT_MEDIA_SOURCE_2016) |
| #include "starboard/file.h" |
| |
| namespace cobalt { |
| namespace media { |
| namespace sandbox { |
| namespace { |
| |
| #if defined(COBALT_MEDIA_SOURCE_2016) |
| typedef media::WebMediaPlayer::AddIdStatus AddIdStatus; |
| #else // defined(COBALT_MEDIA_SOURCE_2016) |
| typedef ::media::WebMediaPlayer::AddIdStatus AddIdStatus; |
| using ::media::Ranges; |
| using ::media::VideoFrame; |
| using ::media::WebMediaPlayer; |
| #endif // defined(WebMediaPlayerDelegate) |
| |
| 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_SOURCE_ROOT, &content_path); |
| DCHECK(content_path.IsAbsolute()); |
| result = content_path.Append(result); |
| } |
| return result; |
| } |
| |
| std::vector<std::string> MakeStringVector(const char* string) { |
| std::vector<std::string> result; |
| result.push_back(string); |
| return result; |
| } |
| |
| void AddSourceBuffers(const std::string audio_id, const std::string video_id, |
| bool is_webm, WebMediaPlayer* player) { |
| const char kAACMime[] = "audio/mp4"; |
| const char kAACCodecs[] = "mp4a.40.2"; |
| const char kAVCMime[] = "video/mp4"; |
| const char kAVCCodecs[] = "avc1.640028"; |
| const char kWebMMime[] = "video/webm"; |
| const char kVp9Codecs[] = "vp9"; |
| |
| AddIdStatus status = |
| player->SourceAddId(audio_id, kAACMime, MakeStringVector(kAACCodecs)); |
| CHECK_EQ(status, WebMediaPlayer::kAddIdStatusOk); |
| status = |
| player->SourceAddId(video_id, is_webm ? kWebMMime : kAVCMime, |
| MakeStringVector(is_webm ? kVp9Codecs : kAVCCodecs)); |
| CHECK_EQ(status, WebMediaPlayer::kAddIdStatusOk); |
| } |
| |
| bool IsWebM(const FilePath& path) { |
| std::string filename = path.value(); |
| return filename.size() >= 5 && |
| filename.substr(filename.size() - 5) == ".webm"; |
| } |
| |
| void AppendData(WebMediaPlayer* player, 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 = player->SourceBuffered(id); |
| float end_of_buffer = |
| ranges.size() == 0 ? 0.f : ranges.end(ranges.size() - 1).InSecondsF(); |
| float media_time = player->GetCurrentTime(); |
| if (end_of_buffer - media_time > kLowWaterMarkInSeconds) { |
| break; |
| } |
| int64 bytes_to_append = |
| std::min(kMaxBytesToAppend, file->GetSize() - *offset); |
| file->Read(buffer, bytes_to_append); |
| player->SourceAppend(id, reinterpret_cast<uint8*>(buffer), bytes_to_append); |
| *offset += bytes_to_append; |
| } |
| } |
| |
| scoped_refptr<Image> FrameCB(WebMediaPlayerHelper* player_helper, |
| const base::TimeDelta& time) { |
| UNREFERENCED_PARAMETER(time); |
| |
| scoped_refptr<VideoFrame> frame = player_helper->GetCurrentFrame(); |
| return frame ? reinterpret_cast<Image*>(frame->texture_id()) : NULL; |
| } |
| |
| int SandboxMain(int argc, char** argv) { |
| if (argc != 3 && argc != 4) { |
| LOG(ERROR) << "Usage: " << argv[0] |
| << " [--null_audio_streamer] <audio file path> " |
| << "<video file path>"; |
| return 1; |
| } |
| MediaSandbox media_sandbox( |
| argc, argv, |
| FilePath(FILE_PATH_LITERAL("web_media_player_sandbox_trace.json"))); |
| WebMediaPlayerHelper player_helper(media_sandbox.GetMediaModule()); |
| |
| // Note that we can't access PathService until MediaSandbox is initialized. |
| FilePath audio_path = ResolvePath(argv[argc - 2]); |
| FilePath video_path = ResolvePath(argv[argc - 1]); |
| |
| ScopedFile audio_file(audio_path.value().c_str(), |
| kSbFileOpenOnly | kSbFileRead); |
| ScopedFile video_file(video_path.value().c_str(), |
| kSbFileOpenOnly | kSbFileRead); |
| |
| if (!audio_file.IsValid()) { |
| LOG(ERROR) << "Failed to open audio file: " << audio_path.value(); |
| return 1; |
| } |
| |
| if (!video_file.IsValid()) { |
| LOG(ERROR) << "Failed to open video file: " << video_path.value(); |
| return 1; |
| } |
| |
| LOG(INFO) << "Playing " << audio_path.value() << " and " |
| << video_path.value(); |
| |
| WebMediaPlayer* player = player_helper.player(); |
| |
| const std::string kAudioId = "audio"; |
| const std::string kVideoId = "video"; |
| |
| AddSourceBuffers(kAudioId, kVideoId, IsWebM(video_path), player); |
| |
| int64 audio_offset = 0; |
| int64 video_offset = 0; |
| bool eos_appended = false; |
| |
| scoped_refptr<VideoFrame> last_frame; |
| |
| media_sandbox.RegisterFrameCB( |
| base::Bind(FrameCB, base::Unretained(&player_helper))); |
| |
| for (;;) { |
| if (!eos_appended) { |
| AppendData(player, kAudioId, &audio_file, &audio_offset); |
| AppendData(player, kVideoId, &video_file, &video_offset); |
| if (video_offset == video_file.GetSize()) { |
| player->SourceAbort(kAudioId); |
| player->SourceEndOfStream(WebMediaPlayer::kEndOfStreamStatusNoError); |
| eos_appended = true; |
| } |
| } |
| |
| if (player_helper.IsPlaybackFinished()) { |
| break; |
| } |
| scoped_refptr<VideoFrame> frame = player_helper.GetCurrentFrame(); |
| if (frame && frame != last_frame) { |
| LOG(INFO) << "showing frame " << frame->GetTimestamp().InMicroseconds(); |
| last_frame = frame; |
| } |
| MessageLoop::current()->RunUntilIdle(); |
| } |
| |
| last_frame = NULL; |
| |
| media_sandbox.RegisterFrameCB(MediaSandbox::FrameCB()); |
| LOG(INFO) << "Playback finished."; |
| |
| return 0; |
| } |
| |
| } // namespace |
| } // namespace sandbox |
| } // namespace media |
| } // namespace cobalt |
| |
| COBALT_WRAP_SIMPLE_MAIN(cobalt::media::sandbox::SandboxMain); |