Import Cobalt 23.master.0.308671
diff --git a/.codespellignorelines b/.codespellignorelines index 0079aa8..0c7fa5f 100644 --- a/.codespellignorelines +++ b/.codespellignorelines
@@ -7,3 +7,4 @@ texture_size.height(), GrMipMapped::kNo, texture_info)); Onces represent initializations that should only ever happen once per process, + <Resource Language="TE" />
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn index c319c72..529dd2f 100644 --- a/cobalt/browser/BUILD.gn +++ b/cobalt/browser/BUILD.gn
@@ -60,8 +60,6 @@ data_deps = [ "//cobalt/dom:licenses", "//cobalt/network:copy_ssl_certificates", - "//cobalt/speech:speech_testdata", - "//cobalt/webdriver:copy_webdriver_data", "//third_party/icu:icudata", ] if (cobalt_font_package == "empty") { @@ -77,6 +75,8 @@ "//cobalt/debug/backend/content:copy_backend_web_files", "//cobalt/debug/console/content:copy_console_web_files", "//cobalt/debug/remote/content:copy_remote_web_files", + "//cobalt/speech:speech_testdata", + "//cobalt/webdriver:copy_webdriver_data", "//third_party/devtools:build_release_devtools", "//third_party/devtools:copy_devtools_modules", "//third_party/devtools:copy_inspector_images",
diff --git a/cobalt/demos/content/watchdog-demo/index.html b/cobalt/demos/content/watchdog-demo/index.html index 3354881..4114648 100644 --- a/cobalt/demos/content/watchdog-demo/index.html +++ b/cobalt/demos/content/watchdog-demo/index.html
@@ -62,7 +62,7 @@ } else if (watchdogFunction == 'ping') { ret = h5vcc.crashLog.ping('test-name', `test-ping`); } else if (watchdogFunction == 'getWatchdogViolations') { - ret = h5vcc.crashLog.getWatchdogViolations(true); + ret = h5vcc.crashLog.getWatchdogViolations(); } else if (watchdogFunction == 'getPersistentSettingWatchdogCrash') { ret = h5vcc.crashLog.getPersistentSettingWatchdogCrash(); } else if (watchdogFunction == 'setPersistentSettingWatchdogCrashTrue') {
diff --git a/cobalt/evergreen_tests/evergreen_tests.py b/cobalt/evergreen_tests/evergreen_tests.py index b0e2385..74eafb0 100644 --- a/cobalt/evergreen_tests/evergreen_tests.py +++ b/cobalt/evergreen_tests/evergreen_tests.py
@@ -28,7 +28,6 @@ from starboard.tools.paths import REPOSITORY_ROOT _DEFAULT_PLATFORM_UNDER_TEST = 'linux' -_DEFAULT_TEST_TYPE = 'functional' def _Exec(cmd, env=None): @@ -58,10 +57,6 @@ '--platform_under_test', default=_DEFAULT_PLATFORM_UNDER_TEST, help='The platform to run the tests on (e.g., linux or raspi).') - arg_parser.add_argument( - '--test_type', - default=_DEFAULT_TEST_TYPE, - help='The type of tests to run: functional or performance.') authentication_method = arg_parser.add_mutually_exclusive_group() authentication_method.add_argument( '--public-key-auth', @@ -123,9 +118,6 @@ command.append('-a') command.append('password') - command.append('-t') - command.append(args.test_type) - command.append(args.platform_under_test) return _Exec(command, env)
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc index f9304e9..d61dca0 100644 --- a/cobalt/h5vcc/h5vcc_crash_log.cc +++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -180,9 +180,9 @@ return false; } -std::string H5vccCrashLog::GetWatchdogViolations(bool current) { +std::string H5vccCrashLog::GetWatchdogViolations() { watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance(); - if (watchdog) return watchdog->GetWatchdogViolations(current); + if (watchdog) return watchdog->GetWatchdogViolations(); return ""; }
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h index c39f4bd..5b1557b 100644 --- a/cobalt/h5vcc/h5vcc_crash_log.h +++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -42,7 +42,7 @@ bool Ping(const std::string& name, const std::string& ping_info); - std::string GetWatchdogViolations(bool current); + std::string GetWatchdogViolations(); bool GetPersistentSettingWatchdogCrash();
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl index d1e9e1a..5c91aa0 100644 --- a/cobalt/h5vcc/h5vcc_crash_log.idl +++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -33,10 +33,12 @@ // name, Watchdog client to register. // description, information on the Watchdog client. // monitor_state, application state up to which the client is monitored. + // Inclusive. // time_interval, maximum number of microseconds allowed between pings // before triggering a Watchdog violation. // time_wait, number of microseconds to initially wait before Watchdog - // violations can be triggered. + // violations can be triggered. Reapplies after client resumes from idle + // state due to application state changes. // replace, behavior with previously registered Watchdog clients of the // same name. boolean register(DOMString name, DOMString description, @@ -52,11 +54,9 @@ // metadata. boolean ping(DOMString name, DOMString ping_info); - // Returns a json string containing the Watchdog violations. Current boolean - // determines whether the current file representing ongoing violations or the - // previous file containing violations from previous app starts and since the - // last call (up to a limit) is returned. - DOMString getWatchdogViolations(boolean current); + // Returns a json string containing the Watchdog violations since the last + // call. Clears internal cache of Watchdog violations to prevent duplicates. + DOMString getWatchdogViolations(); // Gets a persistent Watchdog setting that determines whether or not a // Watchdog violation will trigger a crash.
diff --git a/cobalt/media/base/playback_statistics.cc b/cobalt/media/base/playback_statistics.cc index ffb51f8..babd725 100644 --- a/cobalt/media/base/playback_statistics.cc +++ b/cobalt/media/base/playback_statistics.cc
@@ -33,6 +33,7 @@ volatile SbAtomic32 s_av1_played = 0; volatile SbAtomic32 s_h264_played = 0; volatile SbAtomic32 s_hevc_played = 0; +volatile SbAtomic32 s_vp8_played = 0; volatile SbAtomic32 s_vp9_played = 0; volatile SbAtomic32 s_min_video_width = 999999; volatile SbAtomic32 s_min_video_height = 999999; @@ -153,6 +154,8 @@ SbAtomicBarrier_Increment(&s_h264_played, 1); } else if (video_config.codec() == VideoCodec::kHEVC) { SbAtomicBarrier_Increment(&s_hevc_played, 1); + } else if (video_config.codec() == VideoCodec::kVP8) { + SbAtomicBarrier_Increment(&s_vp8_played, 1); } else if (video_config.codec() == VideoCodec::kVP9) { SbAtomicBarrier_Increment(&s_vp9_played, 1); } else { @@ -222,7 +225,7 @@ return starboard::FormatString( "current_codec: %s, drm: %s, width: %d, height: %d" ", active_players (max): %d (%d), av1: ~%" PRId64 ", h264: ~%" PRId64 - ", hevc: ~%" PRId64 ", vp9: ~%" PRId64 + ", hevc: ~%" PRId64 ", vp8: ~%" PRId64 ", vp9: ~%" PRId64 ", min_width: %d, min_height: %d, max_width: %d, max_height: %d" ", last_working_codec: %s, seek_time: %s" ", first_audio_time: ~%" PRId64 ", first_video_time: ~%" PRId64 @@ -234,6 +237,7 @@ RoundValue(SbAtomicNoBarrier_Load(&s_av1_played)), RoundValue(SbAtomicNoBarrier_Load(&s_h264_played)), RoundValue(SbAtomicNoBarrier_Load(&s_hevc_played)), + RoundValue(SbAtomicNoBarrier_Load(&s_vp8_played)), RoundValue(SbAtomicNoBarrier_Load(&s_vp9_played)), SbAtomicNoBarrier_Load(&s_min_video_width), SbAtomicNoBarrier_Load(&s_min_video_height),
diff --git a/cobalt/site/docs/development/setup-linux.md b/cobalt/site/docs/development/setup-linux.md index b17e8bd..b296507 100644 --- a/cobalt/site/docs/development/setup-linux.md +++ b/cobalt/site/docs/development/setup-linux.md
@@ -52,6 +52,10 @@ $ ccache --max-size=20G ``` +1. Install GN, which we use for our build system code. There are a few ways to + get the binary, follow the instructions for whichever way you prefer + [here](https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/#getting-a-binary). + 1. Clone the Cobalt code repository. The following `git` command creates a `cobalt` directory that contains the repository:
diff --git a/cobalt/site/docs/reference/starboard/modules/14/media.md b/cobalt/site/docs/reference/starboard/modules/14/media.md index 45f6400..f7ccacd 100644 --- a/cobalt/site/docs/reference/starboard/modules/14/media.md +++ b/cobalt/site/docs/reference/starboard/modules/14/media.md
@@ -30,6 +30,9 @@ * `kSbMediaAudioCodecEac3` * `kSbMediaAudioCodecOpus` * `kSbMediaAudioCodecVorbis` +* `kSbMediaAudioCodecMp3` +* `kSbMediaAudioCodecFlac` +* `kSbMediaAudioCodecPcm` ### SbMediaAudioCodingType ###
diff --git a/cobalt/site/docs/reference/starboard/modules/14/system.md b/cobalt/site/docs/reference/starboard/modules/14/system.md index 793670f..b4800a6 100644 --- a/cobalt/site/docs/reference/starboard/modules/14/system.md +++ b/cobalt/site/docs/reference/starboard/modules/14/system.md
@@ -26,22 +26,6 @@ only if) a system has this capability will SbSystemGetTotalGPUMemory() and SbSystemGetUsedGPUMemory() be valid to call. -### SbSystemConnectionType ### - -Enumeration of network connection types. - -#### Values #### - -* `kSbSystemConnectionTypeWired` - - The system is on a wired connection. -* `kSbSystemConnectionTypeWireless` - - The system is on a wireless connection. -* `kSbSystemConnectionTypeUnknown` - - The system connection type is unknown. - ### SbSystemDeviceType ### Enumeration of device types. @@ -272,16 +256,6 @@ void SbSystemClearLastError() ``` -### SbSystemGetConnectionType ### - -Returns the device's current network connection type. - -#### Declaration #### - -``` -SbSystemConnectionType SbSystemGetConnectionType() -``` - ### SbSystemGetDeviceType ### Returns the type of the device.
diff --git a/cobalt/site/docs/reference/starboard/modules/system.md b/cobalt/site/docs/reference/starboard/modules/system.md index 793670f..b4800a6 100644 --- a/cobalt/site/docs/reference/starboard/modules/system.md +++ b/cobalt/site/docs/reference/starboard/modules/system.md
@@ -26,22 +26,6 @@ only if) a system has this capability will SbSystemGetTotalGPUMemory() and SbSystemGetUsedGPUMemory() be valid to call. -### SbSystemConnectionType ### - -Enumeration of network connection types. - -#### Values #### - -* `kSbSystemConnectionTypeWired` - - The system is on a wired connection. -* `kSbSystemConnectionTypeWireless` - - The system is on a wireless connection. -* `kSbSystemConnectionTypeUnknown` - - The system connection type is unknown. - ### SbSystemDeviceType ### Enumeration of device types. @@ -272,16 +256,6 @@ void SbSystemClearLastError() ``` -### SbSystemGetConnectionType ### - -Returns the device's current network connection type. - -#### Declaration #### - -``` -SbSystemConnectionType SbSystemGetConnectionType() -``` - ### SbSystemGetDeviceType ### Returns the type of the device.
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc index 49349d1..3b6a888 100644 --- a/cobalt/watchdog/watchdog.cc +++ b/cobalt/watchdog/watchdog.cc
@@ -34,7 +34,7 @@ namespace { // The Watchdog violations json file names. -const char kWatchdogCurrentViolationsJson[] = "watchdog.json"; +const char kWatchdogViolationsJson[] = "watchdog.json"; const char kWatchdogPreviousViolationsJson[] = "watchdog_old.json"; // The default number of microseconds between each monitor loop. const int64_t kWatchdogSmallestTimeInterval = 1000000; @@ -104,21 +104,19 @@ SbThreadJoin(watchdog_thread_, nullptr); } -std::string Watchdog::GetWatchdogFilePaths(bool current) { - // Gets the current Watchdog violations file path or the previous Watchdog - // violations file path. +std::string Watchdog::GetWatchdogFilePath(bool current) { + // Gets the Watchdog violations file path or the previous Watchdog violations + // file path with lazy initialization. if (watchdog_file_ == "") { // Sets Watchdog violations file paths. std::vector<char> cache_dir(kSbFileMaxPath + 1, 0); SbSystemGetPath(kSbSystemPathCacheDirectory, cache_dir.data(), kSbFileMaxPath); watchdog_file_ = std::string(cache_dir.data()) + kSbFileSepString + - std::string(kWatchdogCurrentViolationsJson); - SB_LOG(INFO) << "Current Watchdog violations file path: " << watchdog_file_; + std::string(kWatchdogViolationsJson); + SB_LOG(INFO) << "Watchdog violations file path: " << watchdog_file_; watchdog_old_file_ = std::string(cache_dir.data()) + kSbFileSepString + std::string(kWatchdogPreviousViolationsJson); - SB_LOG(INFO) << "Previous Watchdog violations file path: " - << watchdog_old_file_; PreservePreviousWatchdogViolations(); } if (current) return watchdog_file_; @@ -126,8 +124,8 @@ } void Watchdog::PreservePreviousWatchdogViolations() { - // Copies the previous Watchdog violations file containing violations since - // last app start, if it exists, to preserve it. + // Copies the previous Watchdog violations file containing violations before + // app start, if it exists, to preserve it. starboard::ScopedFile read_file(watchdog_file_.c_str(), kSbFileOpenOnly | kSbFileRead); if (read_file.IsValid()) { @@ -137,7 +135,6 @@ starboard::ScopedFile write_file(watchdog_old_file_.c_str(), kSbFileCreateAlways | kSbFileWrite); write_file.WriteAll(&watchdog_content[0], kFileSize); - starboard::SbFileDeleteRecursive(watchdog_file_.c_str(), true); } } @@ -159,66 +156,72 @@ int64_t current_time = SbTimeToPosix(SbTimeGetNow()); SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow(); - std::string serialized_watchdog_index = ""; + std::string serialized_client_map = ""; - // Iterates through Watchdog index to monitor all registered clients. + // Iterates through client map to monitor all registered clients. bool new_watchdog_violation = false; - for (auto& it : static_cast<Watchdog*>(context)->watchdog_index_) { - // Ignores and resets clients in idle states. - if (static_cast<Watchdog*>(context)->state_ > it.second->monitor_state) { - it.second->time_registered_monotonic_microseconds = - current_monotonic_time; - it.second->time_last_pinged_microseconds = current_monotonic_time; + for (auto& it : static_cast<Watchdog*>(context)->client_map_) { + Client* client = it.second.get(); + // Ignores and resets clients in idle states, clients whose monitor_state + // is below the current application state. Resets time_wait_microseconds + // and time_interval_microseconds deltas. + if (static_cast<Watchdog*>(context)->state_ > client->monitor_state) { + client->time_registered_monotonic_microseconds = current_monotonic_time; + client->time_last_pinged_microseconds = current_monotonic_time; continue; } SbTimeMonotonic time_delta = - current_monotonic_time - it.second->time_last_pinged_microseconds; + current_monotonic_time - client->time_last_pinged_microseconds; SbTimeMonotonic time_wait = current_monotonic_time - - it.second->time_registered_monotonic_microseconds; + client->time_registered_monotonic_microseconds; // Watchdog violation - if (time_delta > it.second->time_interval_microseconds && - time_wait > it.second->time_wait_microseconds) { + if (time_delta > client->time_interval_microseconds && + time_wait > client->time_wait_microseconds) { // Reset time last pinged. - it.second->time_last_pinged_microseconds = current_monotonic_time; - // Get serialized Watchdog index. - if (serialized_watchdog_index == "") { - serialized_watchdog_index = - static_cast<Watchdog*>(context)->GetSerializedWatchdogIndex(); + client->time_last_pinged_microseconds = current_monotonic_time; + // Get serialized client map. + if (serialized_client_map == "") { + serialized_client_map = + static_cast<Watchdog*>(context)->GetSerializedClientMap(); } // Updates Watchdog violations. auto iter = (static_cast<Watchdog*>(context)->watchdog_violations_) - .find(it.second->name); + .find(client->name); bool already_violated = iter != (static_cast<Watchdog*>(context)->watchdog_violations_).end(); if (already_violated) { // Prevents excessive Watchdog violation updates. - if (iter->second->violation_count <= kWatchdogMaxViolations) + Violation* violation = iter->second.get(); + if (violation->violation_count <= kWatchdogMaxViolations) new_watchdog_violation = true; - iter->second->ping_infos = it.second->ping_infos; - iter->second->violation_time_microseconds = current_time; - iter->second->violation_delta_microseconds = time_delta; - iter->second->violation_count++; - iter->second->serialized_watchdog_index = serialized_watchdog_index; + violation->ping_infos = client->ping_infos; + violation->violation_time_microseconds = current_time; + violation->violation_delta_microseconds = time_delta; + violation->violation_count++; + violation->serialized_client_map = serialized_client_map; } else { new_watchdog_violation = true; std::unique_ptr<Violation> violation(new Violation); - *violation = *(it.second); + *violation = *client; violation->violation_time_microseconds = current_time; violation->violation_delta_microseconds = time_delta; violation->violation_count = 1; - violation->serialized_watchdog_index = serialized_watchdog_index; + violation->serialized_client_map = serialized_client_map; (static_cast<Watchdog*>(context)->watchdog_violations_) .emplace(violation->name, std::move(violation)); } } } - if (new_watchdog_violation) SerializeWatchdogViolations(context); + if (new_watchdog_violation) { + SerializeWatchdogViolations(context); + MaybeTriggerCrash(context); + } SB_CHECK(SbMutexRelease(&(static_cast<Watchdog*>(context))->mutex_)); SbThreadSleep(static_cast<Watchdog*>(context)->smallest_time_interval_); @@ -226,71 +229,79 @@ return nullptr; } -std::string Watchdog::GetSerializedWatchdogIndex() { - // Gets the current list of registered clients from the Watchdog index and +std::string Watchdog::GetSerializedClientMap() { + // Gets the current list of registered clients from the client map and // returns it as a serialized json string. - std::string serialized_watchdog_index = "["; + std::string serialized_client_map = "["; std::string comma = ""; - for (auto& it : watchdog_index_) { - serialized_watchdog_index += (comma + "\"" + it.first + "\""); + for (auto& it : client_map_) { + serialized_client_map += (comma + "\"" + it.first + "\""); comma = ", "; } - serialized_watchdog_index += "]"; - return serialized_watchdog_index; + serialized_client_map += "]"; + return serialized_client_map; } void Watchdog::SerializeWatchdogViolations(void* context) { - // Writes current Watchdog violations to persistent storage as a json file. - std::string watchdog_json = "{\n \"watchdog_violations\": [\n"; + // Writes Watchdog violations to persistent storage as a partial json file. + std::string watchdog_json = ""; std::string comma = ""; for (auto& it : static_cast<Watchdog*>(context)->watchdog_violations_) { + Violation* violation = it.second.get(); std::string ping_infos = "["; std::string inner_comma = ""; - while (it.second->ping_infos.size() > 0) { - ping_infos += (inner_comma + "\"" + it.second->ping_infos.front() + "\""); - it.second->ping_infos.pop(); + while (violation->ping_infos.size() > 0) { + ping_infos += (inner_comma + "\"" + violation->ping_infos.front() + "\""); + violation->ping_infos.pop(); inner_comma = ", "; } ping_infos += "]"; std::ostringstream ss; ss << comma << " {\n" - << " \"name\": \"" << it.second->name << "\",\n" - << " \"description\": \"" << it.second->description << "\",\n" + << " \"name\": \"" << violation->name << "\",\n" + << " \"description\": \"" << violation->description << "\",\n" << " \"ping_infos\": " << ping_infos << ",\n" << " \"monitor_state\": \"" - << std::string(GetApplicationStateString(it.second->monitor_state)) + << std::string(GetApplicationStateString(violation->monitor_state)) << "\",\n" << " \"time_interval_microseconds\": " - << it.second->time_interval_microseconds << ",\n" + << violation->time_interval_microseconds << ",\n" << " \"time_wait_microseconds\": " - << it.second->time_wait_microseconds << ",\n" + << violation->time_wait_microseconds << ",\n" << " \"time_registered_microseconds\": " - << it.second->time_registered_microseconds << ",\n" + << violation->time_registered_microseconds << ",\n" << " \"violation_time_microseconds\": " - << it.second->violation_time_microseconds << ",\n" + << violation->violation_time_microseconds << ",\n" << " \"violation_delta_microseconds\": " - << it.second->violation_delta_microseconds << ",\n" - << " \"violation_count\": " << it.second->violation_count << ",\n" - << " \"watchdog_index\": " << it.second->serialized_watchdog_index - << "\n" + << violation->violation_delta_microseconds << ",\n" + << " \"violation_count\": " << violation->violation_count << ",\n" + << " \"client_map\": " << violation->serialized_client_map << "\n" << " }"; watchdog_json += ss.str(); comma = ",\n"; } - watchdog_json += "\n ]\n}"; - SB_LOG(INFO) << "Writing Watchdog violations to: " - << static_cast<Watchdog*>(context)->GetWatchdogFilePaths(true); - SB_LOG(INFO) << watchdog_json; + // Appends previous Watchdog violations. + starboard::ScopedFile read_file( + (static_cast<Watchdog*>(context)->GetWatchdogFilePath(false)).c_str(), + kSbFileOpenOnly | kSbFileRead); + if (read_file.IsValid()) { + int64_t kFileSize = read_file.GetSize(); + std::string prev_watchdog_json(kFileSize + 1, '\0'); + read_file.ReadAll(&prev_watchdog_json[0], kFileSize); + prev_watchdog_json.erase(prev_watchdog_json.find('\0')); + watchdog_json += ",\n"; + watchdog_json += prev_watchdog_json; + } + + SB_LOG(INFO) << "Writing Watchdog violations:\n" << watchdog_json; starboard::ScopedFile watchdog_file( - (static_cast<Watchdog*>(context)->GetWatchdogFilePaths(true)).c_str(), + (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str(), kSbFileCreateAlways | kSbFileWrite); watchdog_file.WriteAll(watchdog_json.c_str(), static_cast<int>(watchdog_json.size())); - - MaybeTriggerCrash(context); } void Watchdog::MaybeTriggerCrash(void* context) { @@ -317,8 +328,8 @@ // If replace is PING or ALL, handles already registered cases. if (replace != NONE) { - auto it = watchdog_index_.find(name); - bool already_registered = it != watchdog_index_.end(); + auto it = client_map_.find(name); + bool already_registered = it != client_map_.end(); if (already_registered) { if (replace == PING) { @@ -344,7 +355,7 @@ client->time_registered_monotonic_microseconds; // Registers. - auto result = watchdog_index_.emplace(name, std::move(client)); + auto result = client_map_.emplace(name, std::move(client)); // Checks for new smallest_time_interval_. smallest_time_interval_ = std::min(smallest_time_interval_, time_interval); @@ -364,10 +375,10 @@ if (lock) SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired); // Unregisters. - auto result = watchdog_index_.erase(name); + auto result = client_map_.erase(name); // Sets new smallest_time_interval_. smallest_time_interval_ = kWatchdogSmallestTimeInterval; - for (auto& it : watchdog_index_) { + for (auto& it : client_map_) { smallest_time_interval_ = std::min(smallest_time_interval_, it.second->time_interval_microseconds); } @@ -388,8 +399,8 @@ if (is_stub_) return true; SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired); - auto it = watchdog_index_.find(name); - bool client_exists = it != watchdog_index_.end(); + auto it = client_map_.find(name); + bool client_exists = it != client_map_.end(); if (client_exists) { // Updates last ping. @@ -408,29 +419,36 @@ return client_exists; } -std::string Watchdog::GetWatchdogViolations(bool current) { - // Gets the current Watchdog violations file representing ongoing violations - // or gets the previous Watchdog violations file containing violations from - // previous app starts and since the last call (up to a limit). +std::string Watchdog::GetWatchdogViolations() { + // Gets a json string containing the Watchdog violations since the last + // call (up to a limit). // Watchdog stub if (is_stub_) return ""; + std::string watchdog_json = ""; SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired); - starboard::ScopedFile read_file(GetWatchdogFilePaths(current).c_str(), + starboard::ScopedFile read_file(GetWatchdogFilePath().c_str(), kSbFileOpenOnly | kSbFileRead); if (read_file.IsValid()) { int64_t kFileSize = read_file.GetSize(); std::string watchdog_content(kFileSize + 1, '\0'); read_file.ReadAll(&watchdog_content[0], kFileSize); - SB_CHECK(SbMutexRelease(&mutex_)); - SB_LOG(INFO) << "Reading Watchdog violations:\n" << watchdog_content; - return watchdog_content; + watchdog_content.erase(watchdog_content.find('\0')); + watchdog_json = "{\n \"watchdog_violations\": [\n"; + watchdog_json += watchdog_content; + watchdog_json += "\n ]\n}"; + + // Removes all Watchdog violations. + watchdog_violations_.clear(); + starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true); + starboard::SbFileDeleteRecursive(GetWatchdogFilePath(false).c_str(), true); + SB_LOG(INFO) << "Reading Watchdog violations:\n" << watchdog_json; } else { - SB_CHECK(SbMutexRelease(&mutex_)); SB_LOG(INFO) << "No Watchdog Violations."; - return ""; } + SB_CHECK(SbMutexRelease(&mutex_)); + return watchdog_json; } bool Watchdog::GetPersistentSettingWatchdogCrash() {
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h index d95f1ca..ed14db0 100644 --- a/cobalt/watchdog/watchdog.h +++ b/cobalt/watchdog/watchdog.h
@@ -39,7 +39,12 @@ std::queue<std::string> ping_infos; // Application state to continue monitoring client up to. base::ApplicationState monitor_state; + // Maximum number of microseconds allowed between pings before triggering a + // Watchdog violation. int64_t time_interval_microseconds; + // Number of microseconds to initially wait before Watchdog violations can be + // triggered. Reapplies after client resumes from idle state due to + // application state changes. int64_t time_wait_microseconds; int64_t time_registered_microseconds; // since epoch SbTimeMonotonic time_registered_monotonic_microseconds; // since (relative) @@ -52,16 +57,21 @@ std::string description; // List of strings optionally provided with each Ping. std::queue<std::string> ping_infos; - // Application state to continue monitoring client up to. + // Application state to continue monitoring client up to. Inclusive. base::ApplicationState monitor_state; + // Maximum number of microseconds allowed between pings before triggering a + // Watchdog violation. int64_t time_interval_microseconds; + // Number of microseconds to initially wait before Watchdog violations can be + // triggered. Reapplies after client resumes from idle state due to + // application state changes. int64_t time_wait_microseconds; int64_t time_registered_microseconds; // since epoch int64_t violation_time_microseconds; // since epoch int64_t violation_delta_microseconds; // over time_interval int64_t violation_count; - // Watchdog index as a serialized json string - std::string serialized_watchdog_index; + // Client map as a serialized json string + std::string serialized_client_map; void operator=(const Client& c) { name = c.name; @@ -96,7 +106,7 @@ bool Unregister(const std::string& name, bool lock = true); bool Ping(const std::string& name); bool Ping(const std::string& name, const std::string& info); - std::string GetWatchdogViolations(bool current = false); + std::string GetWatchdogViolations(); bool GetPersistentSettingWatchdogCrash(); void SetPersistentSettingWatchdogCrash(bool can_trigger_crash); @@ -106,22 +116,21 @@ #endif // defined(_DEBUG) private: - std::string GetWatchdogFilePaths(bool current); + std::string GetWatchdogFilePath(bool current = true); void PreservePreviousWatchdogViolations(); static void* Monitor(void* context); - std::string GetSerializedWatchdogIndex(); + std::string GetSerializedClientMap(); static void SerializeWatchdogViolations(void* context); static void MaybeTriggerCrash(void* context); - // Current Watchdog violations file path. + // Watchdog violations file paths. std::string watchdog_file_; - // Previous Watchdog violations file path. std::string watchdog_old_file_; // Creates a lock which ensures that each loop of monitor is atomic in that // modifications to is_monitoring_, state_, smallest_time_interval_, and most - // importantly to the dictionaries containing Watchdog clients, - // watchdog_index_ and watchdog_violations_, only occur in between loops of - // monitor. API functions like Register(), Unregister(), Ping(), and + // importantly to the dictionaries containing Watchdog clients, client_map_ + // and watchdog_violations_, only occur in between loops of monitor. API + // functions like Register(), Unregister(), Ping(), and // GetWatchdogViolations() will be called by various threads and interact // with these class variables. SbMutex mutex_; @@ -134,7 +143,7 @@ // Tracks application state. base::ApplicationState state_ = base::kApplicationStateStarted; // Dictionary of registered Watchdog clients. - std::unordered_map<std::string, std::unique_ptr<Client>> watchdog_index_; + std::unordered_map<std::string, std::unique_ptr<Client>> client_map_; // Dictionary of Watchdog violations. std::unordered_map<std::string, std::unique_ptr<Violation>> watchdog_violations_;
diff --git a/docker/windows/base/build/Dockerfile b/docker/windows/base/build/Dockerfile index 658ec30..ec4ebaa 100644 --- a/docker/windows/base/build/Dockerfile +++ b/docker/windows/base/build/Dockerfile
@@ -29,15 +29,10 @@ # of the execution, i.e. the full invocation string. COPY ./list_python_processes.py /list_python_processes.py -# Helper file to ensure python2 is invoked correctly from command line. -COPY ./python2.bat /python2/python2.bat - # Install deps via chocolatey. RUN iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'));` mkdir C:\choco-cache;` - # Install Python 3 before Python 2, so the default on the path is python2. choco install -y -c C:\choco-cache python3 -ia '/quiet InstallAllUsers=1 PrependPath=1 TargetDir="C:\Python3"';` - choco install -y -c C:\choco-cache python2 --params '/InstallDir:C:\python2';` choco install -y -c C:\choco-cache winflexbison3 --params '/InstallDir:C:\bison';` choco install -y -c C:\choco-cache ninja;` choco install -y -c C:\choco-cache nodejs-lts;` @@ -47,16 +42,10 @@ Remove-Item -Force -Recurse $env:TEMP\*;` C:\fast-win-rmdir.cmd C:\choco-cache;` # Create version specific copy of each python executable. - Copy-Item C:\Python3\python.exe C:\Python3\python3.exe;` - Copy-Item C:\python2\python.exe C:\python2\python2.exe + Copy-Item C:\Python3\python.exe C:\Python3\python3.exe -# Install python2 packages via PIP. -# Additionally do the same for python3, and set various configurations. +# Install python3 packages via PIP and set various configurations. RUN mkdir C:\pip-cache;` - python2 -m pip install pypiwin32 six --cache-dir C:\pip-cache;` - C:\fast-win-rmdir.cmd C:\pip-cache;` - # Install python3 packages via PIP. - mkdir C:\pip-cache;` python3 -m pip install six --cache-dir C:\pip-cache;` C:\fast-win-rmdir.cmd C:\pip-cache;` # Configure git global settings.
diff --git a/docker/windows/base/build/python2.bat b/docker/windows/base/build/python2.bat deleted file mode 100644 index 9e2101e..0000000 --- a/docker/windows/base/build/python2.bat +++ /dev/null
@@ -1,3 +0,0 @@ -@echo off -setlocal -c:\python2\python.exe %*
diff --git a/docker/windows/win32/unittest/Dockerfile b/docker/windows/win32/unittest/Dockerfile deleted file mode 100644 index 780c58c..0000000 --- a/docker/windows/win32/unittest/Dockerfile +++ /dev/null
@@ -1,40 +0,0 @@ -# escape=` - -# Copyright 2021 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. -FROM mcr.microsoft.com/windows:1809 - -# Restore the default Windows shell for correct batch processing. -SHELL ["cmd", "/S", "/C"] - -RUN powershell -ExecutionPolicy unrestricted -Command ` - iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) - -# Needed to extract the app launcher scripts for running tests. -RUN choco install -f -y unzip - -# Install python2 -RUN choco install -f -y python2 --params '/InstallDir:C:\python2' -RUN pip install pypiwin32 -RUN setx path "%path%;C:\Python2" -RUN setx python2 "C:\Python2\python.exe" -ADD ./python2.bat /python2/python2.bat - -# Install visual C runtime redistributables -ADD https://aka.ms/vs/16/release/vc_redist.x64.exe C:/vcredist_x64.exe -RUN C:/vcredist_x64.exe /install /passive /norestart /log out.txt - -ADD ./unittest.cmd /unittest.cmd - -CMD [ "/unittest.cmd", "${PLATFORM}", "${CONFIG}", "${TEST}"]
diff --git a/docker/windows/win32/unittest/python2.bat b/docker/windows/win32/unittest/python2.bat deleted file mode 100644 index 9e2101e..0000000 --- a/docker/windows/win32/unittest/python2.bat +++ /dev/null
@@ -1,3 +0,0 @@ -@echo off -setlocal -c:\python2\python.exe %*
diff --git a/docker/windows/win32/unittest/unittest.cmd b/docker/windows/win32/unittest/unittest.cmd deleted file mode 100644 index 36b1426..0000000 --- a/docker/windows/win32/unittest/unittest.cmd +++ /dev/null
@@ -1,7 +0,0 @@ -if "%1" == "" set "PLATFORM=win-win32" -if "%2" == "" set "CONFIG=devel" -if "%3" == "" set "TEST=eztime_test" - -cd c:\code\out\%PLATFORM%_%CONFIG%\ -unzip app_launcher.zip -d c:\app_launcher_out\ -%python2% c:\app_launcher_out\starboard\tools\testing\test_runner.py --run --platform %PLATFORM% --config %CONFIG% -o c:\code\out\%PLATFORM%_%CONFIG%
diff --git a/glimp/egl/display.cc b/glimp/egl/display.cc index 3747d9c..8cfac3c 100644 --- a/glimp/egl/display.cc +++ b/glimp/egl/display.cc
@@ -27,12 +27,6 @@ namespace glimp { namespace egl { -// sce::Gnm::submitDone() is expected to be called once every 5 secs: -// https://ps4.siedev.net/resources/documents/SDK/9.500/Programming-Startup_Guide/0006.html#__document_toc_00000034 -// sce::Gnm::submitDone() gets called regularly in the rasterizer thread and -// should be called from the main thread when the app is in suspended state. -// kSubmitDoneDelay is set to 1/60sec (same scheduling frequency as -// the app in foreground) const SbTime kSubmitDoneDelay = kSbTimeSecond / 60; // Don't repeat the submitDone callback during suspension
diff --git a/glimp/egl/display_impl.h b/glimp/egl/display_impl.h index caf3c3a..71ac0d1 100644 --- a/glimp/egl/display_impl.h +++ b/glimp/egl/display_impl.h
@@ -53,8 +53,7 @@ // Creates and returns a new DisplayImpl object. // To be implemented by each implementing platform. static nb::scoped_ptr<DisplayImpl> Create(EGLNativeDisplayType display_id); - // This method is declared here and defined in ps4/egl/display_impl_ps4.cc - // and ps5/egl/display_impl_ps5.cc respectively. + // Submit done call. static void CallSubmitDone(); // Returns the EGL major and minor versions, if they are not NULL.
diff --git a/starboard/android/apk/app/CMakeLists.txt b/starboard/android/apk/app/CMakeLists.txt index b3f36b7..d5d598f 100644 --- a/starboard/android/apk/app/CMakeLists.txt +++ b/starboard/android/apk/app/CMakeLists.txt
@@ -78,7 +78,7 @@ DEPENDS coat_lib COMMAND ${CMAKE_COMMAND} -E create_symlink ${COBALT_CONTENT_DIR} - ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../../cobalt_content + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../../../../cobalt_content ) # We need a target (not a file) for the phony native dependency below.
diff --git a/starboard/android/apk/app/build.gradle b/starboard/android/apk/app/build.gradle index 568d689..2712f25 100644 --- a/starboard/android/apk/app/build.gradle +++ b/starboard/android/apk/app/build.gradle
@@ -42,8 +42,8 @@ println "TARGET: ${cobaltTarget}" android { - compileSdkVersion 'android-30' - buildToolsVersion '30.0.0' + compileSdkVersion 'android-31' + buildToolsVersion '31.0.0' ndkVersion NDK_VERSION compileOptions { @@ -72,8 +72,8 @@ } defaultConfig { applicationId "dev.cobalt.coat" - minSdkVersion 21 - targetSdkVersion 30 + minSdkVersion 24 + targetSdkVersion 31 versionCode 1 versionName "${buildId}" manifestPlaceholders = [applicationName: "CoAT: ${cobaltTarget}"] @@ -140,16 +140,16 @@ } // Add the directories symlinked by the CMake "cobalt_content" custom command. debug { - assets.srcDir "${buildDir}/intermediates/cmake/debug/cobalt_content" + assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content" } devel { - assets.srcDir "${buildDir}/intermediates/cmake/devel/cobalt_content" + assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content" } qa { - assets.srcDir "${buildDir}/intermediates/cmake/qa/cobalt_content" + assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content" } release { - assets.srcDir "${buildDir}/intermediates/cmake/release/cobalt_content" + assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content" } } externalNativeBuild {
diff --git a/starboard/android/apk/app/src/app/AndroidManifest.xml b/starboard/android/apk/app/src/app/AndroidManifest.xml index 78b3400..42d8bc0 100644 --- a/starboard/android/apk/app/src/app/AndroidManifest.xml +++ b/starboard/android/apk/app/src/app/AndroidManifest.xml
@@ -42,6 +42,7 @@ android:label="${applicationName}"> <activity android:name="dev.cobalt.app.MainActivity" + android:exported="true" android:launchMode="singleTask" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:screenOrientation="sensorLandscape"
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java index 902ca00..f9ceeee 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -58,9 +58,7 @@ import java.util.Calendar; import java.util.Enumeration; import java.util.HashMap; -import java.util.HashSet; import java.util.Locale; -import java.util.Set; import java.util.TimeZone; /** Implementation of the required JNI methods called by the Starboard C++ code. */ @@ -111,10 +109,6 @@ private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("America/Los_Angeles"); private final long timeNanosecondsPerMicrosecond = 1000; - private Set<Integer> supportedHdrTypesSet = new HashSet<Integer>(); - private long supportedHdrTypesSetUpdatedAt = 0; - private final long supportedHdrTypesCacheTtlMs = 1000; - public StarboardBridge( Context appContext, Holder<Activity> activityHolder, @@ -747,53 +741,22 @@ } } - long supportedHdrTypesSetUpdatedAtNs; - - private void refreshHdrTypesCacheIfNecessary() { - if (System.currentTimeMillis() - supportedHdrTypesSetUpdatedAt < supportedHdrTypesCacheTtlMs) { - // Cache is up to date. - return; - } - supportedHdrTypesSet.clear(); - supportedHdrTypesSetUpdatedAt = System.currentTimeMillis(); - + /** Return supported hdr types. */ + @RequiresApi(24) + @SuppressWarnings("unused") + @UsedByNative + public int[] getSupportedHdrTypes() { Display defaultDisplay = DisplayUtil.getDefaultDisplay(); if (defaultDisplay == null) { - return; + return null; } Display.HdrCapabilities hdrCapabilities = defaultDisplay.getHdrCapabilities(); if (hdrCapabilities == null) { - return; + return null; } - int[] supportedHdrTypes = hdrCapabilities.getSupportedHdrTypes(); - if (supportedHdrTypes == null) { - return; - } - - for (int supportedType : supportedHdrTypes) { - supportedHdrTypesSet.add(supportedType); - } - } - - /** - * Check if hdrType is supported by the current default display. See - * https://developer.android.com/reference/android/view/Display.HdrCapabilities.html for valid - * values. - */ - @RequiresApi(24) - @SuppressWarnings("unused") - @UsedByNative - public boolean isHdrTypeSupported(int hdrType) { - if (android.os.Build.VERSION.SDK_INT < 24) { - return false; - } - - synchronized (this) { - refreshHdrTypesCacheIfNecessary(); - return supportedHdrTypesSet.contains(hdrType); - } + return hdrCapabilities.getSupportedHdrTypes(); } /** Return the CobaltMediaSession. */
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java index 69c2e78..4c11399 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -386,4 +386,22 @@ } return audioTrack.getUnderrunCount(); } + + @SuppressWarnings("unused") + @UsedByNative + private int getStartThresholdInFrames() { + if (Build.VERSION.SDK_INT >= 31) { + return getStartThresholdInFramesV31(); + } + return 0; + } + + @RequiresApi(31) + private int getStartThresholdInFramesV31() { + if (audioTrack == null) { + Log.e(TAG, "Unable to call getStartThresholdInFrames() with NULL audio track."); + return 0; + } + return audioTrack.getStartThresholdInFrames(); + } }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java index 9012df2..c5c66cd 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -24,6 +24,7 @@ import android.media.MediaCodec; import android.media.MediaCodec.CryptoInfo; import android.media.MediaCodec.CryptoInfo.Pattern; +import android.media.MediaCodecInfo; import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.VideoCapabilities; import android.media.MediaCrypto; @@ -525,22 +526,24 @@ public static MediaCodecBridge createAudioMediaCodecBridge( long nativeMediaCodecBridge, String mime, + String decoderName, int sampleRate, int channelCount, MediaCrypto crypto, @Nullable byte[] configurationData) { + if (decoderName.equals("")) { + Log.e(TAG, "Invalid decoder name."); + return null; + } MediaCodec mediaCodec = null; try { - String decoderName = - MediaCodecUtil.findAudioDecoder(mime, 0, false /* mustSupportTunnelMode */); - if (decoderName.equals("")) { - Log.e(TAG, String.format("Failed to find decoder: %s", mime)); - return null; - } Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName)); mediaCodec = MediaCodec.createByCodecName(decoderName); } catch (Exception e) { - Log.e(TAG, String.format("Failed to create MediaCodec: %s, ", mime), e); + Log.e( + TAG, + String.format("Failed to create MediaCodec: %s, DecoderName: %s", mime, decoderName), + e); return null; } if (mediaCodec == null) { @@ -586,9 +589,7 @@ public static void createVideoMediaCodecBridge( long nativeMediaCodecBridge, String mime, - boolean mustSupportSecure, - boolean mustSupportSoftwareCodec, - boolean forceImprovedSupportCheck, + String decoderName, int width, int height, int fps, @@ -600,54 +601,21 @@ MediaCodec mediaCodec = null; outCreateMediaCodecBridgeResult.mMediaCodecBridge = null; - boolean mustSupportHdr = android.os.Build.VERSION.SDK_INT >= 24 && colorInfo != null; - boolean mustSupportTunneled = tunnelModeAudioSessionId != -1; - // On first pass, try to find a decoder with HDR if the color info is non-null. - MediaCodecUtil.FindVideoDecoderResult findVideoDecoderResult = - MediaCodecUtil.findVideoDecoder( - mime, - mustSupportSecure, - mustSupportHdr, - mustSupportSoftwareCodec, - mustSupportTunneled, - forceImprovedSupportCheck, - -1 /* decoderCacheTtlMs */, - 0 /* frameWidth */, - 0 /* frameHeight */, - 0 /* bitrate */, - 0 /* fps */); - if (findVideoDecoderResult.name.equals("") && mustSupportHdr) { - // On second pass, forget HDR. - findVideoDecoderResult = - MediaCodecUtil.findVideoDecoder( - mime, - mustSupportSecure, - false /* mustSupportHdr */, - mustSupportSoftwareCodec, - mustSupportTunneled, - forceImprovedSupportCheck, - -1 /* decoderCacheTtlMs */, - 0 /* frameWidth */, - 0 /* frameHeight */, - 0 /* bitrate */, - 0 /* fps */); + if (decoderName.equals("")) { + String message = "Invalid decoder name."; + Log.e(TAG, message); + outCreateMediaCodecBridgeResult.mErrorMessage = message; + return; } + try { - String decoderName = findVideoDecoderResult.name; - if (decoderName.equals("") || findVideoDecoderResult.videoCapabilities == null) { - String message = - String.format( - "Failed to find decoder: %s, mustSupportSecure: %s", mime, mustSupportSecure); - Log.e(TAG, message); - outCreateMediaCodecBridgeResult.mErrorMessage = message; - return; - } Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName)); mediaCodec = MediaCodec.createByCodecName(decoderName); } catch (Exception e) { String message = String.format( - "Failed to create MediaCodec: %s, mustSupportSecure: %s", mime, mustSupportSecure); + "Failed to create MediaCodec: %s, mustSupportSecure: %s," + " DecoderName: %s", + mime, crypto != null, decoderName); Log.e(TAG, message, e); outCreateMediaCodecBridgeResult.mErrorMessage = message; return; @@ -656,6 +624,23 @@ outCreateMediaCodecBridgeResult.mErrorMessage = "mediaCodec is null"; return; } + + MediaCodecInfo codecInfo = mediaCodec.getCodecInfo(); + if (codecInfo == null) { + outCreateMediaCodecBridgeResult.mErrorMessage = "codecInfo is null"; + return; + } + CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(mime); + if (codecCapabilities == null) { + outCreateMediaCodecBridgeResult.mErrorMessage = "codecCapabilities is null"; + return; + } + VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); + if (videoCapabilities == null) { + outCreateMediaCodecBridgeResult.mErrorMessage = "videoCapabilities is null"; + return; + } + MediaCodecBridge bridge = new MediaCodecBridge( nativeMediaCodecBridge, @@ -664,13 +649,10 @@ true, BitrateAdjustmentTypes.NO_ADJUSTMENT, tunnelModeAudioSessionId); - MediaFormat mediaFormat = - createVideoDecoderFormat(mime, width, height, findVideoDecoderResult.videoCapabilities); + MediaFormat mediaFormat = createVideoDecoderFormat(mime, width, height, videoCapabilities); boolean shouldConfigureHdr = - android.os.Build.VERSION.SDK_INT >= 24 - && colorInfo != null - && MediaCodecUtil.isHdrCapableVideoDecoder(mime, findVideoDecoderResult); + colorInfo != null && MediaCodecUtil.isHdrCapableVideoDecoder(mime, codecCapabilities); if (shouldConfigureHdr) { Log.d(TAG, "Setting HDR info."); mediaFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer); @@ -688,7 +670,6 @@ Log.d(TAG, "Enabled tunnel mode playback on audio session " + tunnelModeAudioSessionId); } - VideoCapabilities videoCapabilities = findVideoDecoderResult.videoCapabilities; int maxWidth = videoCapabilities.getSupportedWidths().getUpper(); int maxHeight = videoCapabilities.getSupportedHeights().getUpper(); if (fps > 0) {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java index bd1461e..2d023be 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -31,6 +31,8 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -41,8 +43,6 @@ // A high priority allow list of brands/model that should always attempt to // play vp9. private static final Map<String, Set<String>> vp9AllowList = new HashMap<>(); - // An allow list of software codec names that can be used. - private static final Set<String> softwareCodecAllowList = new HashSet<>(); // Whether we should report vp9 codecs as supported or not. Will be set // based on whether vp9AllowList contains our brand/model. If this is set // to true, then videoCodecDenyList will be ignored. @@ -51,23 +51,6 @@ private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9"; private static final String AV1_MIME_TYPE = "video/av01"; - /** - * A simple "struct" to bundle up the results from findVideoDecoder, as its clients may require - * the max supported width and height in addition to just the decoder name. - */ - public static final class FindVideoDecoderResult { - public String name; - public VideoCapabilities videoCapabilities; - public CodecCapabilities codecCapabilities; - - public FindVideoDecoderResult( - String name, VideoCapabilities videoCapabilities, CodecCapabilities codecCapabilities) { - this.name = name; - this.videoCapabilities = videoCapabilities; - this.codecCapabilities = codecCapabilities; - } - } - static { if (Build.VERSION.SDK_INT >= 24 && Build.BRAND.equals("google")) { videoCodecDenyList.add("OMX.Nvidia.vp9.decode"); @@ -377,61 +360,108 @@ isVp9AllowListed = vp9AllowList.containsKey(Build.BRAND) && vp9AllowList.get(Build.BRAND).contains(Build.MODEL); - - softwareCodecAllowList.add("OMX.google.h264.decoder"); } private MediaCodecUtil() {} - /** - * Returns whether a given combination of (frame width x frame height) frames at bitrate and fps - * has a decoder with mime type. - * - * <p>Setting any of the int parameters to 0 indicates that they shouldn't be considered. - */ - @SuppressWarnings("unused") - @UsedByNative - public static boolean hasVideoDecoderFor( - String mimeType, - boolean mustSupportSecure, - boolean mustSupportHdr, - boolean mustSupportTunnelMode, - boolean forceImprovedSupportCheck, - int decoderCacheTtlMs, - int frameWidth, - int frameHeight, - int bitrate, - int fps) { - FindVideoDecoderResult findVideoDecoderResult = - findVideoDecoder( - mimeType, - mustSupportSecure, - mustSupportHdr, - false /* mustSupportSoftwareCodec */, - mustSupportTunnelMode, - forceImprovedSupportCheck, - decoderCacheTtlMs, - frameWidth, - frameHeight, - bitrate, - fps); - return !findVideoDecoderResult.name.isEmpty(); + /** A wrapper class of codec capability infos. */ + public static class CodecCapabilityInfo { + CodecCapabilityInfo(MediaCodecInfo codecInfo, String mimeType) { + this.codecInfo = codecInfo; + this.mimeType = mimeType; + this.decoderName = codecInfo.getName(); + this.codecCapabilities = codecInfo.getCapabilitiesForType(mimeType); + this.audioCapabilities = this.codecCapabilities.getAudioCapabilities(); + this.videoCapabilities = this.codecCapabilities.getVideoCapabilities(); + } + + public MediaCodecInfo codecInfo; + public String mimeType; + public String decoderName; + public CodecCapabilities codecCapabilities; + public AudioCapabilities audioCapabilities; + public VideoCapabilities videoCapabilities; + + public boolean isSecureRequired() { + // MediaCodecList is supposed to feed us names of decoders that do NOT end in ".secure". We + // are then supposed to check if FEATURE_SecurePlayback is supported, and if it is and we + // want a secure codec, we append ".secure" ourselves, and then pass that to + // MediaCodec.createDecoderByName. Some devices, do not follow this spec, and show us + // decoders that end in ".secure". Empirically, FEATURE_SecurePlayback has still been + // correct when this happens. + if (this.decoderName.endsWith(SECURE_DECODER_SUFFIX)) { + // If a decoder name ends with ".secure", then we don't want to use it for clear content. + return true; + } + return this.codecCapabilities.isFeatureRequired( + MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); + } + + public boolean isSecureSupported() { + return this.codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); + } + + public boolean isTunnelModeRequired() { + return this.codecCapabilities.isFeatureRequired( + MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback); + } + + public boolean isTunnelModeSupported() { + return this.codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback); + } + + public boolean isSoftware() { + return isSoftwareDecoder(this.codecInfo); + } + + public boolean isHdrCapable() { + return isHdrCapableVideoDecoder(this.mimeType, this.codecCapabilities); + } } - /** - * Returns whether an audio decoder that supports mimeType at bitrate. Setting bitrate to 0 - * indicates that it should not be considered. - */ + /** Returns an array of CodecCapabilityInfo for all available decoder. */ @SuppressWarnings("unused") @UsedByNative - public static boolean hasAudioDecoderFor( - String mimeType, int bitrate, boolean mustSupportTunnelMode) { - return !findAudioDecoder(mimeType, bitrate, mustSupportTunnelMode).equals(""); + public static CodecCapabilityInfo[] getAllCodecCapabilityInfos() { + List<CodecCapabilityInfo> codecCapabilityInfos = new ArrayList<>(); + + for (MediaCodecInfo codecInfo : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) { + // We don't use encoder. + if (codecInfo.isEncoder()) { + continue; + } + + // Filter blacklisted video decoders. + String name = codecInfo.getName(); + if (!isVp9AllowListed && videoCodecDenyList.contains(name)) { + Log.v(TAG, String.format("Rejecting %s, reason: codec is on deny list", name)); + continue; + } + if (name.endsWith(SECURE_DECODER_SUFFIX)) { + // If we want a secure decoder, then make sure the version without ".secure" isn't + // denylisted. + String nameWithoutSecureSuffix = + name.substring(0, name.length() - SECURE_DECODER_SUFFIX.length()); + if (!isVp9AllowListed && videoCodecDenyList.contains(nameWithoutSecureSuffix)) { + String format = "Rejecting %s, reason: offpsec denylisted secure decoder"; + Log.v(TAG, String.format(format, name)); + continue; + } + } + + for (String mimeType : codecInfo.getSupportedTypes()) { + codecCapabilityInfos.add(new CodecCapabilityInfo(codecInfo, mimeType)); + } + } + CodecCapabilityInfo[] array = new CodecCapabilityInfo[codecCapabilityInfos.size()]; + return codecCapabilityInfos.toArray(array); } - /** Determine whether findVideoDecoderResult is capable of playing HDR. */ + /** Determine whether codecCapabilities is capable of playing HDR. */ public static boolean isHdrCapableVideoDecoder( - String mimeType, FindVideoDecoderResult findVideoDecoderResult) { + String mimeType, CodecCapabilities codecCapabilities) { // VP9Profile* values were not added until API level 24. See // https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel.html. if (Build.VERSION.SDK_INT < 24) { @@ -443,7 +473,6 @@ return false; } - CodecCapabilities codecCapabilities = findVideoDecoderResult.codecCapabilities; if (codecCapabilities == null) { return false; } @@ -467,14 +496,34 @@ return false; } + /** Return true if and only if info belongs to a software decoder. */ + public static boolean isSoftwareDecoder(MediaCodecInfo codecInfo) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return !codecInfo.isHardwareAccelerated(); + } + String name = codecInfo.getName().toLowerCase(Locale.ROOT); + // This is taken from libstagefright/OMXCodec.cpp for pre codec2. + if (name.startsWith("omx.google.")) { + return true; + } + + // Codec2 names sw decoders this way. + // See hardware/google/av/codec2/vndk/C2Store.cpp. + if (name.startsWith("c2.google.") || name.startsWith("c2.android.")) { + return true; + } + + return false; + } + /** - * The same as hasVideoDecoderFor, returns a FindVideoDecoderResult constructed from the video - * decoder if it is found, or an empty instance otherwise. + * The same as hasVideoDecoderFor, returns the name of the video decoder if it is found, or "" + * otherwise. * * <p>NOTE: This code path is called repeatedly by the player to determine the decoding * capabilities of the device. To ensure speedy playback the code below should be kept performant. */ - public static FindVideoDecoderResult findVideoDecoder( + public static String findVideoDecoder( String mimeType, boolean mustSupportSecure, boolean mustSupportHdr, @@ -516,15 +565,16 @@ for (VideoDecoderCache.CachedDecoder decoder : VideoDecoderCache.getCachedDecoders(mimeType, decoderCacheTtlMs)) { String name = decoder.info.getName(); - if (mustSupportSoftwareCodec && !softwareCodecAllowList.contains(name)) { - Log.v(TAG, "Rejecting " + name + ", reason: require software codec"); - continue; - } if (!isVp9AllowListed && videoCodecDenyList.contains(name)) { Log.v(TAG, "Rejecting " + name + ", reason: codec is on deny list"); continue; } + if (mustSupportSoftwareCodec && !isSoftwareDecoder(decoder.info)) { + Log.v(TAG, "Rejecting " + name + ", reason: require software codec"); + continue; + } + // MediaCodecList is supposed to feed us names of decoders that do NOT end in ".secure". We // are then supposed to check if FEATURE_SecurePlayback is supported, and if it is and we // want a secure codec, we append ".secure" ourselves, and then pass that to @@ -664,20 +714,19 @@ } } + if (mustSupportHdr && !isHdrCapableVideoDecoder(mimeType, decoder.codecCapabilities)) { + Log.v(TAG, "Rejecting " + name + ", reason: codec does not support HDR"); + continue; + } + String resultName = (mustSupportSecure && !name.endsWith(SECURE_DECODER_SUFFIX)) ? (name + SECURE_DECODER_SUFFIX) : name; - FindVideoDecoderResult findVideoDecoderResult = - new FindVideoDecoderResult(resultName, videoCapabilities, decoder.codecCapabilities); - if (mustSupportHdr && !isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult)) { - Log.v(TAG, "Rejecting " + name + ", reason: codec does not support HDR"); - continue; - } Log.v(TAG, "Found suitable decoder, " + name); - return findVideoDecoderResult; + return resultName; } - return new FindVideoDecoderResult("", null, null); + return ""; } /** @@ -841,10 +890,8 @@ && !name.endsWith(SECURE_DECODER_SUFFIX)) ? (name + SECURE_DECODER_SUFFIX) : name; - FindVideoDecoderResult findVideoDecoderResult = - new FindVideoDecoderResult(resultName, videoCapabilities, codecCapabilities); boolean isHdrCapable = - isHdrCapableVideoDecoder(codecCapabilities.getMimeType(), findVideoDecoderResult); + isHdrCapableVideoDecoder(codecCapabilities.getMimeType(), codecCapabilities); if (videoCapabilities != null) { String frameRateAndResolutionString = getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable);
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java index b712376..1d3428e 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -170,14 +170,22 @@ * @return true if the container and the crypto scheme is supported, or false otherwise. */ @UsedByNative - static boolean isWidevineCryptoSchemeSupported(boolean usesCbcs) { - if (Build.VERSION.SDK_INT < 24 && usesCbcs) { - Log.e(TAG, "Encryption scheme 'cbcs' is not supported on this platform."); - return false; - } + static boolean isWidevineCryptoSchemeSupported() { return MediaDrm.isCryptoSchemeSupported(WIDEVINE_UUID); } + /** + * Check whether `cbcs` scheme is supported. + * + * @return true if the `cbcs` encryption is supported, or false otherwise. + */ + @UsedByNative + static boolean isCbcsSchemeSupported() { + // While 'cbcs' scheme was originally implemented in N, there was a bug (in the + // DRM code) which means that it didn't really work properly until N-MR1). + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1; + } + /** Destroy the MediaDrmBridge object. */ @UsedByNative void destroy() {
diff --git a/starboard/android/apk/build.gradle b/starboard/android/apk/build.gradle index 302652a..3db6ad6 100644 --- a/starboard/android/apk/build.gradle +++ b/starboard/android/apk/build.gradle
@@ -20,7 +20,7 @@ jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.0.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files
diff --git a/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties b/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties index 186b715..29e4134 100644 --- a/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties +++ b/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists
diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn index 599bd7d..5efe78e 100644 --- a/starboard/android/shared/BUILD.gn +++ b/starboard/android/shared/BUILD.gn
@@ -334,6 +334,8 @@ "log_is_tty.cc", "log_raw.cc", "main.cc", + "media_capabilities_cache.cc", + "media_capabilities_cache.h", "media_codec_bridge.cc", "media_codec_bridge.h", "media_common.h", @@ -446,6 +448,18 @@ "player_components_factory.cc", ] } + + if (sb_is_evergreen_compatible) { + public_deps += [ "//starboard/elf_loader:evergreen_config" ] + + if (!sb_evergreen_compatible_enable_lite) { + public_deps += [ "//starboard/loader_app:pending_restart" ] + } + } + + if (sb_evergreen_compatible_use_libunwind) { + deps += [ "//third_party/llvm-project/libunwind:unwind_starboard" ] + } } static_library("starboard_base_symbolize") {
diff --git a/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/starboard/android/shared/audio_sink_min_required_frames_tester.cc index 80656e2..92ca9e4 100644 --- a/starboard/android/shared/audio_sink_min_required_frames_tester.cc +++ b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -131,16 +131,30 @@ wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5); } - if (wait_timeout) { - SB_LOG(ERROR) << "Audio sink min required frames tester timeout."; - } + // Get start threshold before release the audio sink. + int start_threshold = audio_sink_->GetStartThresholdInFrames(); + // |min_required_frames_| is shared between two threads. Release audio sink + // to end audio sink thread before access |min_required_frames_| on this + // thread. delete audio_sink_; audio_sink_ = nullptr; - // Call |received_cb_| after audio sink thread is ended. - // |min_required_frames_| is shared between two threads. - if (!destroying_.load() && !wait_timeout) { + if (wait_timeout) { + SB_LOG(ERROR) << "Audio sink min required frames tester timeout."; + // Overwrite |min_required_frames_| if failed to get a stable result. + min_required_frames_ = max_required_frames_; + } + + if (start_threshold > min_required_frames_) { + SB_LOG(INFO) << "Audio sink min required frames is overwritten from " + << min_required_frames_ << " to audio track start threshold " + << start_threshold << "."; + // Overwrite |min_required_frames_| to match |start_threshold|. + min_required_frames_ = start_threshold; + } + + if (!destroying_.load()) { task.received_cb(task.number_of_channels, task.sample_type, task.sample_rate, min_required_frames_); }
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc index 45d90b0..d055a29 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.cc +++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -389,6 +389,10 @@ return bridge_.GetUnderrunCount(); } +int AudioTrackAudioSink::GetStartThresholdInFrames() { + return bridge_.GetStartThresholdInFrames(); +} + // static int AudioTrackAudioSinkType::GetMinBufferSizeInFrames( int channels,
diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h index 035b093..38c7014 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.h +++ b/starboard/android/shared/audio_track_audio_sink_type.h
@@ -125,6 +125,7 @@ void SetVolume(double volume) override; int GetUnderrunCount(); + int GetStartThresholdInFrames(); private: static void* ThreadEntryPoint(void* context);
diff --git a/starboard/android/shared/audio_track_bridge.cc b/starboard/android/shared/audio_track_bridge.cc index aad3562..629a96e 100644 --- a/starboard/android/shared/audio_track_bridge.cc +++ b/starboard/android/shared/audio_track_bridge.cc
@@ -296,6 +296,15 @@ "()I"); } +int AudioTrackBridge::GetStartThresholdInFrames( + JniEnvExt* env /*= JniEnvExt::Get()*/) { + SB_DCHECK(env); + SB_DCHECK(is_valid()); + + return env->CallIntMethodOrAbort(j_audio_track_bridge_, + "getStartThresholdInFrames", "()I"); +} + } // namespace shared } // namespace android } // namespace starboard
diff --git a/starboard/android/shared/audio_track_bridge.h b/starboard/android/shared/audio_track_bridge.h index 9ddb4f1..1844151 100644 --- a/starboard/android/shared/audio_track_bridge.h +++ b/starboard/android/shared/audio_track_bridge.h
@@ -83,6 +83,7 @@ JniEnvExt* env = JniEnvExt::Get()); bool GetAndResetHasAudioDeviceChanged(JniEnvExt* env = JniEnvExt::Get()); int GetUnderrunCount(JniEnvExt* env = JniEnvExt::Get()); + int GetStartThresholdInFrames(JniEnvExt* env = JniEnvExt::Get()); private: int max_samples_per_write_;
diff --git a/starboard/android/shared/download_sdk.sh b/starboard/android/shared/download_sdk.sh index d968f8b..e05cef4 100755 --- a/starboard/android/shared/download_sdk.sh +++ b/starboard/android/shared/download_sdk.sh
@@ -44,14 +44,14 @@ # Update the installation ${SDK_MANAGER_TOOL} --sdk_root=${ANDROID_SDK_ROOT} \ - "build-tools;30.0.0" \ + "build-tools;31.0.0" \ "cmake;3.10.2.4988404" \ "cmdline-tools;1.0" \ "extras;android;m2repository" \ "extras;google;m2repository" \ "ndk;21.1.6352462" \ "patcher;v4" \ - "platforms;android-30" \ + "platforms;android-31" \ "platform-tools" echo "Android SDK updated."
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc new file mode 100644 index 0000000..53360ea --- /dev/null +++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -0,0 +1,531 @@ +// Copyright 2022 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 "starboard/android/shared/media_capabilities_cache.h" + +#include <utility> + +#include "starboard/android/shared/jni_utils.h" +#include "starboard/android/shared/media_common.h" +#include "starboard/common/log.h" +#include "starboard/once.h" + +namespace starboard { +namespace android { +namespace shared { +namespace { + +// https://developer.android.com/reference/android/view/Display.HdrCapabilities.html#HDR_TYPE_HDR10 +const jint HDR_TYPE_DOLBY_VISION = 1; +const jint HDR_TYPE_HDR10 = 2; +const jint HDR_TYPE_HLG = 3; +const jint HDR_TYPE_HDR10_PLUS = 4; + +const char SECURE_DECODER_SUFFIX[] = ".secure"; + +bool EndsWith(const std::string& str, const std::string& suffix) { + if (str.size() < suffix.size()) { + return false; + } + return strcmp(str.c_str() + (str.size() - suffix.size()), suffix.c_str()) == + 0; +} + +Range ConvertJavaRangeToRange(JniEnvExt* env, jobject j_range) { + jobject j_upper_comparable = env->CallObjectMethodOrAbort( + j_range, "getUpper", "()Ljava/lang/Comparable;"); + jint j_upper_int = + env->CallIntMethodOrAbort(j_upper_comparable, "intValue", "()I"); + + jobject j_lower_comparable = env->CallObjectMethodOrAbort( + j_range, "getLower", "()Ljava/lang/Comparable;"); + jint j_lower_int = + env->CallIntMethodOrAbort(j_lower_comparable, "intValue", "()I"); + return Range(j_lower_int, j_upper_int); +} + +void ConvertStringToLowerCase(std::string* str) { + for (int i = 0; i < str->length(); i++) { + (*str)[i] = std::tolower((*str)[i]); + } +} + +bool GetIsWidevineSupported() { + return JniEnvExt::Get()->CallStaticBooleanMethodOrAbort( + "dev/cobalt/media/MediaDrmBridge", + "isWidevineCryptoSchemeSupported", "()Z") == JNI_TRUE; +} + +bool GetIsCbcsSupported() { + return JniEnvExt::Get()->CallStaticBooleanMethodOrAbort( + "dev/cobalt/media/MediaDrmBridge", "isCbcsSchemeSupported", + "()Z") == JNI_TRUE; +} + +std::set<SbMediaTransferId> GetSupportedHdrTypes() { + std::set<SbMediaTransferId> supported_transfer_ids; + + JniEnvExt* env = JniEnvExt::Get(); + jintArray j_supported_hdr_types = static_cast<jintArray>( + env->CallStarboardObjectMethodOrAbort("getSupportedHdrTypes", "()[I")); + jsize length = env->GetArrayLength(j_supported_hdr_types); + jint* numbers = env->GetIntArrayElements(j_supported_hdr_types, 0); + for (int i = 0; i < length; i++) { + switch (numbers[i]) { + case HDR_TYPE_DOLBY_VISION: + continue; + case HDR_TYPE_HDR10: + supported_transfer_ids.insert(kSbMediaTransferIdSmpteSt2084); + continue; + case HDR_TYPE_HLG: + supported_transfer_ids.insert(kSbMediaTransferIdAribStdB67); + continue; + case HDR_TYPE_HDR10_PLUS: + continue; + } + } + env->ReleaseIntArrayElements(j_supported_hdr_types, numbers, 0); + + return supported_transfer_ids; +} + +bool GetIsPassthroughSupported(SbMediaAudioCodec codec) { + SbMediaAudioCodingType coding_type; + switch (codec) { + case kSbMediaAudioCodecAc3: + coding_type = kSbMediaAudioCodingTypeAc3; + break; + case kSbMediaAudioCodecEac3: + coding_type = kSbMediaAudioCodingTypeDolbyDigitalPlus; + break; + default: + return false; + } + int encoding = GetAudioFormatSampleType(coding_type); + JniEnvExt* env = JniEnvExt::Get(); + ScopedLocalJavaRef<jobject> j_audio_output_manager( + env->CallStarboardObjectMethodOrAbort( + "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;")); + return env->CallBooleanMethodOrAbort(j_audio_output_manager.Get(), + "hasPassthroughSupportFor", "(I)Z", + encoding) == JNI_TRUE; +} + +int GetMaxAudioOutputChannels() { + JniEnvExt* env = JniEnvExt::Get(); + ScopedLocalJavaRef<jobject> j_audio_output_manager( + env->CallStarboardObjectMethodOrAbort( + "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;")); + return static_cast<int>(env->CallIntMethodOrAbort( + j_audio_output_manager.Get(), "getMaxChannels", "()I")); +} + +} // namespace + +CodecCapability::CodecCapability(JniEnvExt* env, jobject j_codec_info) { + SB_DCHECK(env); + SB_DCHECK(j_codec_info); + + name_ = env->GetStringStandardUTFOrAbort( + env->GetStringFieldOrAbort(j_codec_info, "decoderName")); + + is_secure_required_ = + env->CallBooleanMethodOrAbort(j_codec_info, "isSecureRequired", "()Z") == + JNI_TRUE; + is_secure_supported_ = + env->CallBooleanMethodOrAbort(j_codec_info, "isSecureSupported", "()Z") == + JNI_TRUE; + is_tunnel_mode_required_ = + env->CallBooleanMethodOrAbort(j_codec_info, "isTunnelModeRequired", + "()Z") == JNI_TRUE; + is_tunnel_mode_supported_ = + env->CallBooleanMethodOrAbort(j_codec_info, "isTunnelModeSupported", + "()Z") == JNI_TRUE; +} + +AudioCodecCapability::AudioCodecCapability(JniEnvExt* env, + jobject j_codec_info, + jobject j_audio_capabilities) + : CodecCapability(env, j_codec_info) { + SB_DCHECK(env); + SB_DCHECK(j_codec_info); + SB_DCHECK(j_audio_capabilities); + + jobject j_bitrate_range = env->CallObjectMethodOrAbort( + j_audio_capabilities, "getBitrateRange", "()Landroid/util/Range;"); + supported_bitrates_ = ConvertJavaRangeToRange(env, j_bitrate_range); + + // Overwrite the lower bound to 0. + supported_bitrates_.minimum = 0; +} + +bool AudioCodecCapability::IsBitrateSupported(int bitrate) const { + return supported_bitrates_.Contains(bitrate); +} + +VideoCodecCapability::VideoCodecCapability(JniEnvExt* env, + jobject j_codec_info, + jobject j_video_capabilities) + : CodecCapability(env, j_codec_info) { + SB_DCHECK(env); + SB_DCHECK(j_codec_info); + SB_DCHECK(j_video_capabilities); + + is_software_decoder_ = env->CallBooleanMethodOrAbort( + j_codec_info, "isSoftware", "()Z") == JNI_TRUE; + + is_hdr_capable_ = env->CallBooleanMethodOrAbort(j_codec_info, "isHdrCapable", + "()Z") == JNI_TRUE; + + j_video_capabilities_ = env->ConvertLocalRefToGlobalRef(j_video_capabilities); + + jobject j_width_range = env->CallObjectMethodOrAbort( + j_video_capabilities_, "getSupportedWidths", "()Landroid/util/Range;"); + supported_widths_ = ConvertJavaRangeToRange(env, j_width_range); + + jobject j_height_range = env->CallObjectMethodOrAbort( + j_video_capabilities_, "getSupportedHeights", "()Landroid/util/Range;"); + supported_heights_ = ConvertJavaRangeToRange(env, j_height_range); + + jobject j_bitrate_range = env->CallObjectMethodOrAbort( + j_video_capabilities_, "getBitrateRange", "()Landroid/util/Range;"); + supported_bitrates_ = ConvertJavaRangeToRange(env, j_bitrate_range); + + jobject j_frame_rate_range = env->CallObjectMethodOrAbort( + j_video_capabilities_, "getSupportedFrameRates", + "()Landroid/util/Range;"); + supported_frame_rates_ = ConvertJavaRangeToRange(env, j_frame_rate_range); +} + +VideoCodecCapability::~VideoCodecCapability() { + JniEnvExt* env = JniEnvExt::Get(); + env->DeleteGlobalRef(j_video_capabilities_); +} + +bool VideoCodecCapability::IsBitrateSupported(int bitrate) const { + return supported_bitrates_.Contains(bitrate); +} + +bool VideoCodecCapability::AreResolutionAndRateSupported( + bool force_improved_support_check, + int frame_width, + int frame_height, + int fps) { + if (force_improved_support_check) { + if (frame_width != 0 && frame_height != 0 && fps != 0) { + return JniEnvExt::Get()->CallBooleanMethodOrAbort( + j_video_capabilities_, "areSizeAndRateSupported", "(IID)Z", + frame_width, frame_height, + static_cast<jdouble>(fps)) == JNI_TRUE; + } else if (frame_width != 0 && frame_height != 0) { + return JniEnvExt::Get()->CallBooleanMethodOrAbort( + j_video_capabilities_, "isSizeSupported", "(II)Z", frame_width, + frame_height) == JNI_TRUE; + } + } + if (frame_width != 0 && !supported_widths_.Contains(frame_width)) { + return false; + } + if (frame_height != 0 && !supported_heights_.Contains(frame_height)) { + return false; + } + if (fps != 0 && !supported_frame_rates_.Contains(fps)) { + return false; + } + return true; +} + +// static +SB_ONCE_INITIALIZE_FUNCTION(MediaCapabilitiesCache, + MediaCapabilitiesCache::GetInstance); + +bool MediaCapabilitiesCache::IsWidevineSupported() { + if (!is_enabled_) { + return GetIsWidevineSupported(); + } + ScopedLock scoped_lock(mutex_); + LazyInitialize_Locked(); + return is_widevine_supported_; +} + +bool MediaCapabilitiesCache::IsCbcsSchemeSupported() { + if (!is_enabled_) { + return GetIsCbcsSupported(); + } + ScopedLock scoped_lock(mutex_); + LazyInitialize_Locked(); + return is_cbcs_supported_; +} + +bool MediaCapabilitiesCache::IsHDRTransferCharacteristicsSupported( + SbMediaTransferId transfer_id) { + if (!is_enabled_) { + std::set<SbMediaTransferId> supported_transfer_ids = GetSupportedHdrTypes(); + return supported_transfer_ids.find(transfer_id) != + supported_transfer_ids.end(); + } + ScopedLock scoped_lock(mutex_); + LazyInitialize_Locked(); + return supported_transfer_ids_.find(transfer_id) != + supported_transfer_ids_.end(); +} + +bool MediaCapabilitiesCache::IsPassthroughSupported(SbMediaAudioCodec codec) { + if (!is_enabled_) { + return GetIsPassthroughSupported(codec); + } + // IsPassthroughSupported() caches the results of previous quiries, and does + // not rely on LazyInitialize(), which is different from other functions. + ScopedLock scoped_lock(mutex_); + auto iter = passthrough_supportabilities_.find(codec); + if (iter != passthrough_supportabilities_.end()) { + return iter->second; + } + bool supported = GetIsPassthroughSupported(codec); + passthrough_supportabilities_[codec] = supported; + return supported; +} + +int MediaCapabilitiesCache::GetMaxAudioOutputChannels() { + if (!is_enabled_) { + return ::starboard::android::shared::GetMaxAudioOutputChannels(); + } + + ScopedLock scoped_lock(mutex_); + LazyInitialize_Locked(); + return max_audio_output_channels_; +} + +bool MediaCapabilitiesCache::HasAudioDecoderFor(const std::string& mime_type, + int bitrate, + bool must_support_tunnel_mode) { + return !FindAudioDecoder(mime_type, bitrate, must_support_tunnel_mode) + .empty(); +} + +bool MediaCapabilitiesCache::HasVideoDecoderFor( + const std::string& mime_type, + bool must_support_secure, + bool must_support_hdr, + bool must_support_tunnel_mode, + bool force_improved_support_check, + int frame_width, + int frame_height, + int bitrate, + int fps) { + return !FindVideoDecoder(mime_type, must_support_secure, must_support_hdr, + false, must_support_tunnel_mode, + force_improved_support_check, frame_width, + frame_height, bitrate, fps) + .empty(); +} + +std::string MediaCapabilitiesCache::FindAudioDecoder( + const std::string& mime_type, + int bitrate, + bool must_support_tunnel_mode) { + if (!is_enabled_) { + JniEnvExt* env = JniEnvExt::Get(); + ScopedLocalJavaRef<jstring> j_mime( + env->NewStringStandardUTFOrAbort(mime_type.c_str())); + jobject j_decoder_name = env->CallStaticObjectMethodOrAbort( + "dev/cobalt/media/MediaCodecUtil", "findAudioDecoder", + "(Ljava/lang/String;IZ)Ljava/lang/String;", j_mime.Get(), bitrate, + must_support_tunnel_mode); + return env->GetStringStandardUTFOrAbort( + static_cast<jstring>(j_decoder_name)); + } + + ScopedLock scoped_lock(mutex_); + LazyInitialize_Locked(); + + for (auto& audio_capability : audio_codec_capabilities_map_[mime_type]) { + // Reject if tunnel mode is required but codec doesn't support it. + if (must_support_tunnel_mode && + !audio_capability->is_tunnel_mode_supported()) { + continue; + } + // Reject if bitrate is not supported. + if (!audio_capability->IsBitrateSupported(bitrate)) { + continue; + } + return audio_capability->name(); + } + + return ""; +} + +std::string MediaCapabilitiesCache::FindVideoDecoder( + const std::string& mime_type, + bool must_support_secure, + bool must_support_hdr, + bool require_software_codec, + bool must_support_tunnel_mode, + bool force_improved_support_check, + int frame_width, + int frame_height, + int bitrate, + int fps) { + if (!is_enabled_) { + JniEnvExt* env = JniEnvExt::Get(); + ScopedLocalJavaRef<jstring> j_mime( + env->NewStringStandardUTFOrAbort(mime_type.c_str())); + jobject j_decoder_name = env->CallStaticObjectMethodOrAbort( + "dev/cobalt/media/MediaCodecUtil", "findVideoDecoder", + "(Ljava/lang/String;ZZZZZIIIII)Ljava/lang/String;", j_mime.Get(), + must_support_secure, must_support_hdr, + false, /* mustSupportSoftwareCodec */ + must_support_tunnel_mode, force_improved_support_check, + -1, /* decoderCacheTtlMs */ + frame_width, frame_height, bitrate, fps); + return env->GetStringStandardUTFOrAbort( + static_cast<jstring>(j_decoder_name)); + } + + ScopedLock scoped_lock(mutex_); + LazyInitialize_Locked(); + + for (auto& video_capability : video_codec_capabilities_map_[mime_type]) { + // Reject if secure decoder is required but codec doesn't support it. + if (must_support_secure && !video_capability->is_secure_supported()) { + continue; + } + // Reject if non secure decoder is required but codec doesn't support it. + if (!must_support_secure && video_capability->is_secure_required()) { + continue; + } + // Reject if tunnel mode is required but codec doesn't support it. + if (must_support_tunnel_mode && + !video_capability->is_tunnel_mode_supported()) { + continue; + } + // Reject if non tunnel mode is required but codec doesn't support it. + if (!must_support_tunnel_mode && + video_capability->is_tunnel_mode_required()) { + continue; + } + // Reject if software codec is required but codec is not. + if (require_software_codec && !video_capability->is_software_decoder()) { + continue; + } + // Reject if hdr is required but codec doesn't support it. + if (must_support_hdr && !video_capability->is_hdr_capable()) { + continue; + } + + bool enable_improved_support_check = + force_improved_support_check || + (frame_width > 3840 || frame_height > 2160); + // Reject if resolution or frame rate is not supported. + if (!video_capability->AreResolutionAndRateSupported( + enable_improved_support_check, frame_width, frame_height, fps)) { + continue; + } + + // Reject if bitrate is not supported. + if (bitrate != 0 && !video_capability->IsBitrateSupported(bitrate)) { + continue; + } + + // Append ".secure" for secure decoder if not represents. + if (must_support_secure && + !EndsWith(video_capability->name(), SECURE_DECODER_SUFFIX)) { + return video_capability->name() + SECURE_DECODER_SUFFIX; + } + return video_capability->name(); + } + + return ""; +} + +void MediaCapabilitiesCache::ClearCache() { + ScopedLock scoped_lock(mutex_); + is_initialized_ = false; + is_widevine_supported_ = false; + is_cbcs_supported_ = false; + supported_transfer_ids_.clear(); + passthrough_supportabilities_.clear(); + audio_codec_capabilities_map_.clear(); + video_codec_capabilities_map_.clear(); + max_audio_output_channels_ = -1; +} + +void MediaCapabilitiesCache::LazyInitialize_Locked() { + mutex_.DCheckAcquired(); + + if (is_initialized_) { + return; + } + + is_widevine_supported_ = GetIsWidevineSupported(); + is_cbcs_supported_ = GetIsCbcsSupported(); + supported_transfer_ids_ = GetSupportedHdrTypes(); + max_audio_output_channels_ = + ::starboard::android::shared::GetMaxAudioOutputChannels(); + + LoadCodecInfos_Locked(); + + is_initialized_ = true; +} + +void MediaCapabilitiesCache::LoadCodecInfos_Locked() { + SB_DCHECK(audio_codec_capabilities_map_.empty()); + SB_DCHECK(video_codec_capabilities_map_.empty()); + mutex_.DCheckAcquired(); + + JniEnvExt* env = JniEnvExt::Get(); + jobjectArray j_codec_infos = + static_cast<jobjectArray>(env->CallStaticObjectMethodOrAbort( + "dev/cobalt/media/MediaCodecUtil", "getAllCodecCapabilityInfos", + "()[Ldev/cobalt/media/MediaCodecUtil$CodecCapabilityInfo;")); + jsize length = env->GetArrayLength(j_codec_infos); + // Note: Codec infos are sorted by the framework such that the best + // decoders come first. + // This order is maintained in the cache. + for (int i = 0; i < length; i++) { + jobject j_codec_info = env->GetObjectArrayElementOrAbort(j_codec_infos, i); + + std::string mime_type = env->GetStringStandardUTFOrAbort( + env->GetStringFieldOrAbort(j_codec_info, "mimeType")); + + // Convert the mime type to lower case. + ConvertStringToLowerCase(&mime_type); + + jobject j_audio_capabilities = env->GetObjectFieldOrAbort( + j_codec_info, "audioCapabilities", + "Landroid/media/MediaCodecInfo$AudioCapabilities;"); + if (j_audio_capabilities) { + // Found an audio decoder. + std::unique_ptr<AudioCodecCapability> audio_codec_capabilities( + new AudioCodecCapability(env, j_codec_info, j_audio_capabilities)); + audio_codec_capabilities_map_[mime_type].push_back( + std::move(audio_codec_capabilities)); + continue; + } + jobject j_video_capabilities = env->GetObjectFieldOrAbort( + j_codec_info, "videoCapabilities", + "Landroid/media/MediaCodecInfo$VideoCapabilities;"); + if (j_video_capabilities) { + // Found a video decoder. + std::unique_ptr<VideoCodecCapability> video_codec_capabilities( + new VideoCodecCapability(env, j_codec_info, j_video_capabilities)); + video_codec_capabilities_map_[mime_type].push_back( + std::move(video_codec_capabilities)); + } + } +} + +} // namespace shared +} // namespace android +} // namespace starboard
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h new file mode 100644 index 0000000..46da91a --- /dev/null +++ b/starboard/android/shared/media_capabilities_cache.h
@@ -0,0 +1,197 @@ +// Copyright 2022 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. + +#ifndef STARBOARD_ANDROID_SHARED_MEDIA_CAPABILITIES_CACHE_H_ +#define STARBOARD_ANDROID_SHARED_MEDIA_CAPABILITIES_CACHE_H_ + +#include <jni.h> +#include <atomic> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "starboard/android/shared/jni_env_ext.h" +#include "starboard/common/mutex.h" +#include "starboard/media.h" +#include "starboard/shared/internal_only.h" + +namespace starboard { +namespace android { +namespace shared { + +// TODO: encapsulate a common Range class. +struct Range { + Range() : minimum(0), maximum(0) {} + Range(int min, int max) : minimum(min), maximum(max) {} + int minimum; + int maximum; + + bool Contains(int val) const { return val >= minimum && val <= maximum; } +}; + +class CodecCapability { + public: + CodecCapability(JniEnvExt* env, jobject j_codec_info); + virtual ~CodecCapability() {} + + const std::string& name() const { return name_; } + bool is_secure_required() const { return is_secure_required_; } + bool is_secure_supported() const { return is_secure_supported_; } + bool is_tunnel_mode_required() const { return is_tunnel_mode_required_; } + bool is_tunnel_mode_supported() const { return is_tunnel_mode_supported_; } + + private: + CodecCapability(const CodecCapability&) = delete; + CodecCapability& operator=(const CodecCapability&) = delete; + + std::string name_; + bool is_secure_required_; + bool is_secure_supported_; + bool is_tunnel_mode_required_; + bool is_tunnel_mode_supported_; +}; + +class AudioCodecCapability : public CodecCapability { + public: + AudioCodecCapability(JniEnvExt* env, + jobject j_codec_info, + jobject j_audio_capabilities); + ~AudioCodecCapability() override {} + + bool IsBitrateSupported(int bitrate) const; + + private: + AudioCodecCapability(const AudioCodecCapability&) = delete; + AudioCodecCapability& operator=(const AudioCodecCapability&) = delete; + + Range supported_bitrates_; +}; + +class VideoCodecCapability : public CodecCapability { + public: + VideoCodecCapability(JniEnvExt* env, + jobject j_codec_info, + jobject j_video_capabilities); + ~VideoCodecCapability() override; + + bool is_software_decoder() const { return is_software_decoder_; } + bool is_hdr_capable() const { return is_hdr_capable_; } + + bool IsBitrateSupported(int bitrate) const; + // VideoCodecCapability caches java object MediaCodecInfo.VideoCapabilities. + // If improved support check is used, + // VideoCapabilities.areSizeAndRateSupported() or + // VideoCapabilities.isSizeSupported() will be used to check the + // supportability. + bool AreResolutionAndRateSupported(bool force_improved_support_check, + int frame_width, + int frame_height, + int fps); + + private: + VideoCodecCapability(const VideoCodecCapability&) = delete; + VideoCodecCapability& operator=(const VideoCodecCapability&) = delete; + + bool is_software_decoder_; + bool is_hdr_capable_; + jobject j_video_capabilities_; + Range supported_widths_; + Range supported_heights_; + Range supported_bitrates_; + Range supported_frame_rates_; +}; + +class MediaCapabilitiesCache { + public: + static MediaCapabilitiesCache* GetInstance(); + + bool IsWidevineSupported(); + bool IsCbcsSchemeSupported(); + + bool IsHDRTransferCharacteristicsSupported(SbMediaTransferId transfer_id); + + bool IsPassthroughSupported(SbMediaAudioCodec codec); + + int GetMaxAudioOutputChannels(); + + bool HasAudioDecoderFor(const std::string& mime_type, + int bitrate, + bool must_support_tunnel_mode); + + bool HasVideoDecoderFor(const std::string& mime_type, + bool must_support_secure, + bool must_support_hdr, + bool must_support_tunnel_mode, + bool force_improved_support_check, + int frame_width, + int frame_height, + int bitrate, + int fps); + + std::string FindAudioDecoder(const std::string& mime_type, + int bitrate, + bool must_support_tunnel_mode); + + std::string FindVideoDecoder(const std::string& mime_type, + bool must_support_secure, + bool must_support_hdr, + bool require_software_codec, + bool must_support_tunnel_mode, + bool force_improved_support_check, + int frame_width, + int frame_height, + int bitrate, + int fps); + + bool IsEnabled() const { return is_enabled_; } + void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } + void ClearCache(); + + private: + MediaCapabilitiesCache() {} + ~MediaCapabilitiesCache() {} + + MediaCapabilitiesCache(const MediaCapabilitiesCache&) = delete; + MediaCapabilitiesCache& operator=(const MediaCapabilitiesCache&) = delete; + + void LazyInitialize_Locked(); + void LoadCodecInfos_Locked(); + + Mutex mutex_; + + std::set<SbMediaTransferId> supported_transfer_ids_; + std::map<SbMediaAudioCodec, bool> passthrough_supportabilities_; + + typedef std::vector<std::unique_ptr<AudioCodecCapability>> + AudioCodecCapabilities; + typedef std::vector<std::unique_ptr<VideoCodecCapability>> + VideoCodecCapabilities; + + std::map<std::string, AudioCodecCapabilities> audio_codec_capabilities_map_; + std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_; + + std::atomic_bool is_enabled_{false}; + bool is_initialized_ = false; + bool is_widevine_supported_ = false; + bool is_cbcs_supported_ = false; + int max_audio_output_channels_ = -1; +}; + +} // namespace shared +} // namespace android +} // namespace starboard + +#endif // STARBOARD_ANDROID_SHARED_MEDIA_CAPABILITIES_CACHE_H_
diff --git a/starboard/android/shared/media_codec_bridge.cc b/starboard/android/shared/media_codec_bridge.cc index d663f08..2e2cd1a 100644 --- a/starboard/android/shared/media_codec_bridge.cc +++ b/starboard/android/shared/media_codec_bridge.cc
@@ -14,6 +14,7 @@ #include "starboard/android/shared/media_codec_bridge.h" +#include "starboard/android/shared/media_capabilities_cache.h" #include "starboard/common/string.h" namespace starboard { @@ -163,8 +164,20 @@ const char* mime = SupportedAudioCodecToMimeType(audio_codec, &is_passthrough); if (!mime) { + SB_LOG(ERROR) << "Unsupported codec " << audio_codec << "."; return scoped_ptr<MediaCodecBridge>(NULL); } + + std::string decoder_name = + MediaCapabilitiesCache::GetInstance()->FindAudioDecoder( + mime, 0, /* bitrate */ + false /* must_support_tunnel_mode */); + + if (decoder_name.empty()) { + SB_LOG(ERROR) << "Failed to find decoder for " << audio_codec << "."; + return scoped_ptr<MediaCodecBridge>(NULL); + } + JniEnvExt* env = JniEnvExt::Get(); ScopedLocalJavaRef<jbyteArray> configuration_data; if (audio_codec == kSbMediaAudioCodecOpus && @@ -174,18 +187,21 @@ audio_sample_info.audio_specific_config_size)); } ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime)); + ScopedLocalJavaRef<jstring> j_decoder_name( + env->NewStringStandardUTFOrAbort(decoder_name.c_str())); scoped_ptr<MediaCodecBridge> native_media_codec_bridge( new MediaCodecBridge(handler)); jobject j_media_codec_bridge = env->CallStaticObjectMethodOrAbort( "dev/cobalt/media/MediaCodecBridge", "createAudioMediaCodecBridge", - "(JLjava/lang/String;IILandroid/media/MediaCrypto;[B)Ldev/cobalt/media/" - "MediaCodecBridge;", + "(JLjava/lang/String;Ljava/lang/String;IILandroid/media/MediaCrypto;" + "[B)Ldev/cobalt/media/MediaCodecBridge;", reinterpret_cast<jlong>(native_media_codec_bridge.get()), j_mime.Get(), - audio_sample_info.samples_per_second, + j_decoder_name.Get(), audio_sample_info.samples_per_second, audio_sample_info.number_of_channels, j_media_crypto, configuration_data.Get()); if (!j_media_codec_bridge) { + SB_LOG(ERROR) << "Failed to create codec bridge for " << audio_codec << "."; return scoped_ptr<MediaCodecBridge>(NULL); } @@ -216,8 +232,48 @@ *error_message = FormatString("Unsupported mime for codec %d", video_codec); return scoped_ptr<MediaCodecBridge>(NULL); } + + const bool must_support_secure = !!j_media_crypto; + const bool must_support_hdr = color_metadata; + const bool must_support_tunnel_mode = tunnel_mode_audio_session_id != -1; + // On first pass, try to find a decoder with HDR if the color info is + // non-null. + std::string decoder_name = + MediaCapabilitiesCache::GetInstance()->FindVideoDecoder( + mime, must_support_secure, /* must_support_secure */ + must_support_hdr, /* must_support_hdr */ + require_software_codec, /* is_software_codec */ + must_support_tunnel_mode, /* must_support_tunnel_mode */ + force_improved_support_check, /* force_improved_support_check */ + 0, /* frame_width */ + 0, /* frame_height */ + 0, /* bitrate */ + 0 /* fps */); + if (decoder_name.empty() && color_metadata) { + // On second pass, forget HDR. + decoder_name = MediaCapabilitiesCache::GetInstance()->FindVideoDecoder( + mime, must_support_secure, /* must_support_secure */ + false, /* must_support_hdr */ + require_software_codec, /* is_software_codec */ + must_support_tunnel_mode, /* must_support_tunnel_mode */ + force_improved_support_check, /* force_improved_support_check */ + 0, /* frame_width */ + 0, /* frame_height */ + 0, /* bitrate */ + 0 /* fps */); + } + + if (decoder_name.empty()) { + *error_message = + FormatString("Failed to find decoder: %s, mustSupportSecure: %d.", mime, + !!j_media_crypto); + return scoped_ptr<MediaCodecBridge>(NULL); + } + JniEnvExt* env = JniEnvExt::Get(); ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime)); + ScopedLocalJavaRef<jstring> j_decoder_name( + env->NewStringStandardUTFOrAbort(decoder_name.c_str())); ScopedLocalJavaRef<jobject> j_color_info(nullptr); if (color_metadata) { @@ -257,16 +313,16 @@ new MediaCodecBridge(handler)); env->CallStaticVoidMethodOrAbort( "dev/cobalt/media/MediaCodecBridge", "createVideoMediaCodecBridge", - "(JLjava/lang/String;ZZZIIILandroid/view/Surface;" + "(JLjava/lang/String;Ljava/lang/String;IIILandroid/view/Surface;" "Landroid/media/MediaCrypto;" "Ldev/cobalt/media/MediaCodecBridge$ColorInfo;" "I" "Ldev/cobalt/media/MediaCodecBridge$CreateMediaCodecBridgeResult;)" "V", reinterpret_cast<jlong>(native_media_codec_bridge.get()), j_mime.Get(), - !!j_media_crypto, require_software_codec, force_improved_support_check, - width, height, fps, j_surface, j_media_crypto, j_color_info.Get(), - tunnel_mode_audio_session_id, j_create_media_codec_bridge_result.Get()); + j_decoder_name.Get(), width, height, fps, j_surface, j_media_crypto, + j_color_info.Get(), tunnel_mode_audio_session_id, + j_create_media_codec_bridge_result.Get()); jobject j_media_codec_bridge = env->CallObjectMethodOrAbort( j_create_media_codec_bridge_result.Get(), "mediaCodecBridge",
diff --git a/starboard/android/shared/media_get_audio_configuration.cc b/starboard/android/shared/media_get_audio_configuration.cc index ef59d08..bd70438 100644 --- a/starboard/android/shared/media_get_audio_configuration.cc +++ b/starboard/android/shared/media_get_audio_configuration.cc
@@ -14,11 +14,9 @@ #include "starboard/media.h" -#include "starboard/android/shared/jni_env_ext.h" -#include "starboard/android/shared/jni_utils.h" +#include "starboard/android/shared/media_capabilities_cache.h" -using starboard::android::shared::JniEnvExt; -using starboard::android::shared::ScopedLocalJavaRef; +using starboard::android::shared::MediaCapabilitiesCache; bool SbMediaGetAudioConfiguration( int output_index, @@ -32,12 +30,8 @@ out_configuration->latency = 0; out_configuration->coding_type = kSbMediaAudioCodingTypePcm; - JniEnvExt* env = JniEnvExt::Get(); - ScopedLocalJavaRef<jobject> j_audio_output_manager( - env->CallStarboardObjectMethodOrAbort( - "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;")); - int channels = static_cast<int>(env->CallIntMethodOrAbort( - j_audio_output_manager.Get(), "getMaxChannels", "()I")); + int channels = + MediaCapabilitiesCache::GetInstance()->GetMaxAudioOutputChannels(); if (channels < 2) { SB_DLOG(WARNING) << "The supported channels from output device is smaller than 2. "
diff --git a/starboard/android/shared/media_is_audio_supported.cc b/starboard/android/shared/media_is_audio_supported.cc index d9efe42..46b73e5 100644 --- a/starboard/android/shared/media_is_audio_supported.cc +++ b/starboard/android/shared/media_is_audio_supported.cc
@@ -15,20 +15,19 @@ #include "starboard/shared/starboard/media/media_support_internal.h" #include "starboard/android/shared/jni_utils.h" +#include "starboard/android/shared/media_capabilities_cache.h" #include "starboard/android/shared/media_common.h" #include "starboard/audio_sink.h" #include "starboard/configuration.h" #include "starboard/configuration_constants.h" #include "starboard/media.h" -#include "starboard/shared/starboard/media/mime_type.h" -using starboard::android::shared::JniEnvExt; -using starboard::android::shared::ScopedLocalJavaRef; +using starboard::android::shared::MediaCapabilitiesCache; using starboard::android::shared::SupportedAudioCodecToMimeType; using starboard::shared::starboard::media::MimeType; bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { if (bitrate >= kSbMediaMaxAudioBitrateInBitsPerSecond) { return false; @@ -41,28 +40,41 @@ return false; } - MimeType mime_type(content_type); - if (strlen(content_type) > 0) { + bool enable_tunnel_mode = false; + bool enable_audio_passthrough = true; + if (mime_type) { + if (!mime_type->is_valid()) { + return false; + } // Allows for disabling the use of the AudioDeviceCallback API to detect // when audio peripherals are connected. Enabled by default. // (https://developer.android.com/reference/android/media/AudioDeviceCallback) - mime_type.RegisterBoolParameter("enableaudiodevicecallback"); + if (!mime_type->ValidateBoolParameter("enableaudiodevicecallback")) { + return false; + } + // Allows for enabling tunneled playback. Disabled by default. // (https://source.android.com/devices/tv/multimedia-tunneling) - mime_type.RegisterBoolParameter("tunnelmode"); + if (!mime_type->ValidateBoolParameter("tunnelmode")) { + return false; + } + enable_tunnel_mode = mime_type->GetParamBoolValue("tunnelmode", false); + // Enables audio passthrough if the codec supports it. - mime_type.RegisterBoolParameter("audiopassthrough"); + if (!mime_type->ValidateBoolParameter("audiopassthrough")) { + return false; + } + enable_audio_passthrough = + mime_type->GetParamBoolValue("audiopassthrough", true); + // Allows for disabling the CONTENT_TYPE_MOVIE AudioAttribute for // non-tunneled playbacks with PCM audio. Enabled by default. // (https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE) - mime_type.RegisterBoolParameter("enablepcmcontenttypemovie"); - - if (!mime_type.is_valid()) { + if (!mime_type->ValidateBoolParameter("enablepcmcontenttypemovie")) { return false; } } - bool enable_tunnel_mode = mime_type.GetParamBoolValue("tunnelmode", false); if (enable_tunnel_mode && !SbAudioSinkIsAudioSampleTypeSupported( kSbMediaAudioSampleTypeInt16Deprecated)) { SB_LOG(WARNING) @@ -77,13 +89,10 @@ return true; } - JniEnvExt* env = JniEnvExt::Get(); - ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime)); - auto media_codec_supported = - env->CallStaticBooleanMethodOrAbort( - "dev/cobalt/media/MediaCodecUtil", "hasAudioDecoderFor", - "(Ljava/lang/String;IZ)Z", j_mime.Get(), static_cast<jint>(bitrate), - enable_tunnel_mode) == JNI_TRUE; + bool media_codec_supported = + MediaCapabilitiesCache::GetInstance()->HasAudioDecoderFor( + mime, bitrate, enable_tunnel_mode); + if (!media_codec_supported) { return false; } @@ -92,29 +101,12 @@ return true; } - if (!mime_type.GetParamBoolValue("audiopassthrough", true)) { + if (!enable_audio_passthrough) { SB_LOG(INFO) << "Passthrough codec is rejected because passthrough is " "disabled through mime param."; return false; } - SbMediaAudioCodingType coding_type; - switch (audio_codec) { - case kSbMediaAudioCodecAc3: - coding_type = kSbMediaAudioCodingTypeAc3; - break; - case kSbMediaAudioCodecEac3: - coding_type = kSbMediaAudioCodingTypeDolbyDigitalPlus; - break; - default: - return false; - } - int encoding = - ::starboard::android::shared::GetAudioFormatSampleType(coding_type); - ScopedLocalJavaRef<jobject> j_audio_output_manager( - env->CallStarboardObjectMethodOrAbort( - "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;")); - return env->CallBooleanMethodOrAbort(j_audio_output_manager.Get(), - "hasPassthroughSupportFor", "(I)Z", - encoding) == JNI_TRUE; + return MediaCapabilitiesCache::GetInstance()->IsPassthroughSupported( + audio_codec); }
diff --git a/starboard/android/shared/media_is_supported.cc b/starboard/android/shared/media_is_supported.cc index f6cfe75..fcd6806 100644 --- a/starboard/android/shared/media_is_supported.cc +++ b/starboard/android/shared/media_is_supported.cc
@@ -16,7 +16,7 @@ #include "starboard/shared/starboard/media/media_support_internal.h" -#include "starboard/android/shared/jni_env_ext.h" +#include "starboard/android/shared/media_capabilities_cache.h" #include "starboard/android/shared/media_common.h" #include "starboard/media.h" #include "starboard/shared/starboard/media/mime_type.h" @@ -26,15 +26,15 @@ SbMediaAudioCodec audio_codec, const char* key_system) { using starboard::android::shared::IsWidevineL1; - using starboard::android::shared::JniEnvExt; + using starboard::android::shared::MediaCapabilitiesCache; using starboard::shared::starboard::media::MimeType; // It is possible that the |key_system| comes with extra attributes, like // `com.widevine.alpha; encryptionscheme="cenc"`. We prepend "key_system/" // to it, so it can be parsed by MimeType. MimeType mime_type(std::string("key_system/") + key_system); - mime_type.RegisterStringParameter("encryptionscheme", "cenc|cbcs|cbcs-1-9"); - if (!mime_type.is_valid()) { + if (!mime_type.is_valid() || !mime_type.ValidateStringParameter( + "encryptionscheme", "cenc|cbcs|cbcs-1-9")) { return false; } @@ -51,11 +51,16 @@ if (!IsWidevineL1(key_system_type)) { return false; } + + if (!MediaCapabilitiesCache::GetInstance()->IsWidevineSupported()) { + return false; + } + std::string encryption_scheme = mime_type.GetParamStringValue("encryptionscheme", ""); - bool uses_cbcs = - encryption_scheme == "cbcs" || encryption_scheme == "cbcs-1-9"; - return JniEnvExt::Get()->CallStaticBooleanMethodOrAbort( - "dev/cobalt/media/MediaDrmBridge", - "isWidevineCryptoSchemeSupported", "(Z)Z", uses_cbcs) == JNI_TRUE; + if (encryption_scheme == "cbcs" || encryption_scheme == "cbcs-1-9") { + return MediaCapabilitiesCache::GetInstance()->IsCbcsSchemeSupported(); + } + + return true; }
diff --git a/starboard/android/shared/media_is_video_supported.cc b/starboard/android/shared/media_is_video_supported.cc index ff0acdf..1f5c851 100644 --- a/starboard/android/shared/media_is_video_supported.cc +++ b/starboard/android/shared/media_is_video_supported.cc
@@ -14,47 +14,19 @@ #include "starboard/shared/starboard/media/media_support_internal.h" -#include "starboard/android/shared/jni_env_ext.h" -#include "starboard/android/shared/jni_utils.h" +#include "starboard/android/shared/media_capabilities_cache.h" #include "starboard/android/shared/media_common.h" #include "starboard/configuration.h" #include "starboard/media.h" #include "starboard/shared/starboard/media/media_util.h" -#include "starboard/shared/starboard/media/mime_type.h" -using starboard::android::shared::JniEnvExt; -using starboard::android::shared::ScopedLocalJavaRef; +using starboard::android::shared::MediaCapabilitiesCache; using starboard::android::shared::SupportedVideoCodecToMimeType; using starboard::shared::starboard::media::IsSDRVideo; using starboard::shared::starboard::media::MimeType; -namespace { - -// https://developer.android.com/reference/android/view/Display.HdrCapabilities.html#HDR_TYPE_HDR10 -const jint HDR_TYPE_DOLBY_VISION = 1; -const jint HDR_TYPE_HDR10 = 2; -const jint HDR_TYPE_HLG = 3; - -bool IsHDRTransferCharacteristicsSupported(SbMediaTransferId transfer_id) { - jint hdr_type; - if (transfer_id == kSbMediaTransferIdSmpteSt2084) { - hdr_type = HDR_TYPE_HDR10; - } else if (transfer_id == kSbMediaTransferIdAribStdB67) { - hdr_type = HDR_TYPE_HLG; - } else { - // No other transfer functions are supported, see - // https://source.android.com/devices/tech/display/hdr. - return false; - } - - return JniEnvExt::Get()->CallStarboardBooleanMethodOrAbort( - "isHdrTypeSupported", "(I)Z", hdr_type) == JNI_TRUE; -} - -} // namespace - bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth, @@ -68,36 +40,48 @@ bool decode_to_texture_required) { const bool must_support_hdr = !IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id); - if (must_support_hdr && !IsHDRTransferCharacteristicsSupported(transfer_id)) { + if (must_support_hdr && + !MediaCapabilitiesCache::GetInstance() + ->IsHDRTransferCharacteristicsSupported(transfer_id)) { return false; } // While not necessarily true, for now we assume that all Android devices // can play decode-to-texture video just as well as normal video. - // Check extended parameters for correctness and return false if any invalid - // invalid params are found. - MimeType mime_type(content_type); - if (strlen(content_type) > 0) { - // Allows for enabling tunneled playback. Disabled by default. - // https://source.android.com/devices/tv/multimedia-tunneling - mime_type.RegisterBoolParameter("tunnelmode"); - // Override endianness on HDR Info header. Defaults to little. - mime_type.RegisterStringParameter("hdrinfoendianness", "big|little"); - // Forces the use of specific Android APIs (isSizeSupported() and - // areSizeAndRateSupported()) to determine format support. - mime_type.RegisterBoolParameter("forceimprovedsupportcheck"); - // Override the default decoder cache TTL to the specified value. - // The cache will be disabled if the value is non-positive. - // TODO(b/227356434): RegisterIntParameter requires API review. - // mime_type.RegisterIntParameter("decoder_cache_ttl_ms"); - - if (!mime_type.is_valid()) { + bool must_support_tunnel_mode = false; + bool force_improved_support_check = true; + int decoder_cache_ttl_ms = -1; + if (mime_type) { + if (!mime_type->is_valid()) { return false; } + + // Allows for enabling tunneled playback. Disabled by default. + // https://source.android.com/devices/tv/multimedia-tunneling + if (!mime_type->ValidateBoolParameter("tunnelmode")) { + return false; + } + must_support_tunnel_mode = + mime_type->GetParamBoolValue("tunnelmode", false); + + // Override endianness on HDR Info header. Defaults to little. + if (!mime_type->ValidateStringParameter("hdrinfoendianness", + "big|little")) { + return false; + } + + // Forces the use of specific Android APIs (isSizeSupported() and + // areSizeAndRateSupported()) to determine format support. + if (!mime_type->ValidateBoolParameter("forceimprovedsupportcheck")) { + return false; + } + force_improved_support_check = + mime_type->GetParamBoolValue("forceimprovedsupportcheck", true); + + decoder_cache_ttl_ms = + mime_type->GetParamIntValue("decoder_cache_ttl_ms", -1); } - bool must_support_tunnel_mode = - mime_type.GetParamBoolValue("tunnelmode", false); if (must_support_tunnel_mode && decode_to_texture_required) { SB_LOG(WARNING) << "Tunnel mode is rejected because output mode decode to " "texture is required but not supported."; @@ -108,24 +92,14 @@ if (!mime) { return false; } - JniEnvExt* env = JniEnvExt::Get(); - ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime)); // We assume that if a device supports a format for clear playback, it will // also support it for encrypted playback. However, some devices require // tunneled playback to be encrypted, so we must align the tunnel mode // requirement with the secure playback requirement. const bool require_secure_playback = must_support_tunnel_mode; - const bool force_improved_support_check = - mime_type.GetParamBoolValue("forceimprovedsupportcheck", true); - const int decoder_cache_ttl_ms = - mime_type.GetParamIntValue("decoder_cache_ttl_ms", -1); - return env->CallStaticBooleanMethodOrAbort( - "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor", - "(Ljava/lang/String;ZZZZIIIII)Z", j_mime.Get(), - require_secure_playback, must_support_hdr, - must_support_tunnel_mode, force_improved_support_check, - decoder_cache_ttl_ms, frame_width, frame_height, - static_cast<jint>(bitrate), fps) == JNI_TRUE; + return MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor( + mime, require_secure_playback, must_support_hdr, must_support_tunnel_mode, + force_improved_support_check, frame_width, frame_height, bitrate, fps); }
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h index 3b683fb..6d26909 100644 --- a/starboard/android/shared/player_components_factory.h +++ b/starboard/android/shared/player_components_factory.h
@@ -24,6 +24,7 @@ #include "starboard/android/shared/drm_system.h" #include "starboard/android/shared/jni_env_ext.h" #include "starboard/android/shared/jni_utils.h" +#include "starboard/android/shared/media_capabilities_cache.h" #include "starboard/android/shared/media_common.h" #include "starboard/android/shared/video_decoder.h" #include "starboard/atomic.h" @@ -206,25 +207,26 @@ error_message); } - MimeType audio_mime_type(creation_parameters.audio_mime()); + bool enable_audio_device_callback = true; if (strlen(creation_parameters.audio_mime()) > 0) { - audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback"); - audio_mime_type.RegisterBoolParameter("audiopassthrough"); - if (!audio_mime_type.is_valid()) { + MimeType audio_mime_type(creation_parameters.audio_mime()); + if (!audio_mime_type.is_valid() || + !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") || + !audio_mime_type.ValidateBoolParameter("audiopassthrough")) { return scoped_ptr<PlayerComponents>(); } - } - bool enable_audio_device_callback = - audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true); - SB_LOG(INFO) << "AudioDeviceCallback is " - << (enable_audio_device_callback ? "enabled." : "disabled."); + enable_audio_device_callback = + audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true); + SB_LOG(INFO) << "AudioDeviceCallback is " + << (enable_audio_device_callback ? "enabled." : "disabled."); - if (!audio_mime_type.GetParamBoolValue("audiopassthrough", true)) { - SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: " - "false. Passthrough is disabled."; - return scoped_ptr<PlayerComponents>(); + if (!audio_mime_type.GetParamBoolValue("audiopassthrough", true)) { + SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: " + "false. Passthrough is disabled."; + return scoped_ptr<PlayerComponents>(); + } } SB_LOG(INFO) << "Creating passthrough components."; @@ -244,15 +246,20 @@ constexpr int kTunnelModeAudioSessionId = -1; constexpr bool kForceSecurePipelineUnderTunnelMode = false; - MimeType video_mime_type(creation_parameters.video_mime()); - video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck"); - if (!video_mime_type.is_valid()) { - return scoped_ptr<PlayerComponents>(); + bool force_improved_support_check = true; + + if (strlen(creation_parameters.video_mime()) > 0) { + MimeType video_mime_type(creation_parameters.video_mime()); + if (!video_mime_type.is_valid() || + !video_mime_type.ValidateBoolParameter( + "forceimprovedsupportcheck")) { + return scoped_ptr<PlayerComponents>(); + } + force_improved_support_check = video_mime_type.GetParamBoolValue( + "forceimprovedsupportcheck", true); + SB_LOG_IF(INFO, !force_improved_support_check) + << "Improved support check is disabled for queries under 4K."; } - const bool force_improved_support_check = - video_mime_type.GetParamBoolValue("forceimprovedsupportcheck", true); - SB_LOG_IF(INFO, !force_improved_support_check) - << "Improved support check is disabled for queries under 4K."; scoped_ptr<VideoDecoder> video_decoder = CreateVideoDecoder(creation_parameters, kTunnelModeAudioSessionId, @@ -292,13 +299,11 @@ ? creation_parameters.audio_mime() : ""; MimeType audio_mime_type(audio_mime); - if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone && - strlen(creation_parameters.audio_mime()) > 0) { - audio_mime_type.RegisterBoolParameter("tunnelmode"); - audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback"); - audio_mime_type.RegisterBoolParameter("enablepcmcontenttypemovie"); - - if (!audio_mime_type.is_valid()) { + if (strlen(audio_mime) > 0) { + if (!audio_mime_type.is_valid() || + !audio_mime_type.ValidateBoolParameter("tunnelmode") || + !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") || + !audio_mime_type.ValidateBoolParameter("enablepcmcontenttypemovie")) { *error_message = "Invalid audio MIME: '" + std::string(audio_mime) + "'"; return false; @@ -310,12 +315,10 @@ ? creation_parameters.video_mime() : ""; MimeType video_mime_type(video_mime); - if (creation_parameters.video_codec() != kSbMediaVideoCodecNone && - strlen(creation_parameters.video_mime()) > 0) { - video_mime_type.RegisterBoolParameter("tunnelmode"); - video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck"); - - if (!video_mime_type.is_valid()) { + if (strlen(video_mime) > 0) { + if (!video_mime_type.is_valid() || + !video_mime_type.ValidateBoolParameter("tunnelmode") || + !video_mime_type.ValidateBoolParameter("forceimprovedsupportcheck")) { *error_message = "Invalid video MIME: '" + std::string(video_mime) + "'"; return false; @@ -496,18 +499,18 @@ bool force_secure_pipeline_under_tunnel_mode, bool force_improved_support_check, std::string* error_message) { - // Use mime param to determine endianness of HDR metadata. If param is - // missing or invalid it defaults to Little Endian. - MimeType video_mime_type(creation_parameters.video_mime()); - + bool force_big_endian_hdr_metadata = false; if (strlen(creation_parameters.video_mime()) > 0) { - video_mime_type.RegisterStringParameter("hdrinfoendianness", + // Use mime param to determine endianness of HDR metadata. If param is + // missing or invalid it defaults to Little Endian. + MimeType video_mime_type(creation_parameters.video_mime()); + video_mime_type.ValidateStringParameter("hdrinfoendianness", "big|little"); + const std::string& hdr_info_endianness = + video_mime_type.GetParamStringValue("hdrinfoendianness", + /*default=*/"little"); + force_big_endian_hdr_metadata = hdr_info_endianness == "big"; } - const std::string& hdr_info_endianness = - video_mime_type.GetParamStringValue("hdrinfoendianness", - /*default=*/"little"); - bool force_big_endian_hdr_metadata = hdr_info_endianness == "big"; scoped_ptr<VideoDecoder> video_decoder(new VideoDecoder( creation_parameters.video_codec(), @@ -563,29 +566,24 @@ << creation_parameters.video_codec() << " is not supported."; return false; } - JniEnvExt* env = JniEnvExt::Get(); - ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime)); DrmSystem* drm_system_ptr = static_cast<DrmSystem*>(creation_parameters.drm_system()); jobject j_media_crypto = drm_system_ptr ? drm_system_ptr->GetMediaCrypto() : NULL; bool is_encrypted = !!j_media_crypto; - if (env->CallStaticBooleanMethodOrAbort( - "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor", - "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(), is_encrypted, false, - true, force_improved_support_check, 0, 0, 0, 0) == JNI_TRUE) { + if (MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor( + mime, is_encrypted, false, true, force_improved_support_check, 0, 0, + 0, 0)) { return true; } if (kForceSecurePipelineInTunnelModeWhenRequired && !is_encrypted) { const bool kIsEncrypted = true; auto support_tunnel_mode_under_secure_pipeline = - env->CallStaticBooleanMethodOrAbort( - "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor", - "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(), kIsEncrypted, - false, true, force_improved_support_check, 0, 0, 0, - 0) == JNI_TRUE; + MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor( + mime, kIsEncrypted, false, true, force_improved_support_check, 0, + 0, 0, 0); if (support_tunnel_mode_under_secure_pipeline) { *force_secure_pipeline_under_tunnel_mode = true; return true;
diff --git a/starboard/elf_loader/BUILD.gn b/starboard/elf_loader/BUILD.gn index 4a9f9a6..07974b0 100644 --- a/starboard/elf_loader/BUILD.gn +++ b/starboard/elf_loader/BUILD.gn
@@ -108,9 +108,6 @@ "//starboard", ] - # TODO: Remove this dependency once MediaSession is migrated to use CobaltExtensions. - deps += cobalt_platform_dependencies - if (!sb_is_evergreen_compatible) { deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ] } @@ -173,9 +170,6 @@ ":elf_loader", ] - # TODO: Remove this dependency once MediaSession is migrated to use CobaltExtensions. - deps += cobalt_platform_dependencies - data_deps = [ ":copy_elf_loader_testdata" ] } }
diff --git a/starboard/evergreen/testing/performance_tests/baseline_update_test.sh b/starboard/evergreen/testing/performance_tests/baseline_update_test.sh deleted file mode 100644 index de3a2f6..0000000 --- a/starboard/evergreen/testing/performance_tests/baseline_update_test.sh +++ /dev/null
@@ -1,36 +0,0 @@ -#!/bin/bash -# -# Copyright 2022 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. - -# Unset the previous test's name and runner function. -unset TEST_NAME -unset TEST_FILE -unset -f run_test - -TEST_NAME="BaselineUpdatePerformance" - -function run_test() { - source $(dirname "$0")/performance_tests/run_update_trial.sh - - for i in {1..3}; do - run_update_trial "$i"; - result=$? - if [[ "${result}" -ne 0 ]]; then - return 1 - fi - done - - return 0 -}
diff --git a/starboard/evergreen/testing/performance_tests/compression_update_test.sh b/starboard/evergreen/testing/performance_tests/compression_update_test.sh deleted file mode 100644 index 4b1d4ef..0000000 --- a/starboard/evergreen/testing/performance_tests/compression_update_test.sh +++ /dev/null
@@ -1,36 +0,0 @@ -#!/bin/bash -# -# Copyright 2022 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. - -# Unset the previous test's name and runner function. -unset TEST_NAME -unset TEST_FILE -unset -f run_test - -TEST_NAME="CompressionUpdatePerformance" - -function run_test() { - source $(dirname "$0")/performance_tests/run_update_trial.sh - - for i in {1..3}; do - run_update_trial "$i" "--compress_update" "--loader_use_compression"; - result=$? - if [[ "${result}" -ne 0 ]]; then - return 1 - fi - done - - return 0 -}
diff --git a/starboard/evergreen/testing/performance_tests/run_update_trial.sh b/starboard/evergreen/testing/performance_tests/run_update_trial.sh deleted file mode 100644 index 7188c4a..0000000 --- a/starboard/evergreen/testing/performance_tests/run_update_trial.sh +++ /dev/null
@@ -1,45 +0,0 @@ -#!/bin/bash -# -# Copyright 2022 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. - -# Runs an update cycle to generate performance logs. -# -# The size of the update is reported by this function directly, while memory -# usage and shared library load time are logged by the loader app process. -# -# Args: -# Trial number, extra arguments for the loader app run to install an update, -# extra arguments for the loader app run to load the update. -function run_update_trial() { - clear_storage - - log "info" "Running trial ${1}" - # Run the loader app once until an update has been installed. - cycle_cobalt "https://www.youtube.com/tv" "${TEST_NAME}.0.log" "PingSender::SendPingComplete" "--loader_track_memory=100 ${2}" - - run_command "ls -l \"${STORAGE_DIR}/installation_1/lib\"" - - # And run the loader app once more until it loads the installed update. The - # memory tracker is configured with a shorter period to capture more data - # during loading of the shared library. - cycle_cobalt "https://www.youtube.com/tv" "${TEST_NAME}.1.log" "Loading took" "--loader_track_memory=10 ${3}" - - if grep -Eq "content/app/cobalt/lib/" "${LOG_PATH}/${TEST_NAME}.1.log"; then - log "error" "The system image was loaded instead of the update, which must have failed" - return 1 - fi - - return 0 -}
diff --git a/starboard/evergreen/testing/run_all_tests.sh b/starboard/evergreen/testing/run_all_tests.sh index 2349c2e..efb9d0b 100755 --- a/starboard/evergreen/testing/run_all_tests.sh +++ b/starboard/evergreen/testing/run_all_tests.sh
@@ -21,8 +21,7 @@ DIR="$(dirname "${0}")" AUTH_METHOD="public-key" -TEST_TYPE="functional" -while getopts "d:a:t:" o; do +while getopts "d:a:" o; do case "${o}" in d) DEVICE_ID=${OPTARG} @@ -30,9 +29,6 @@ a) AUTH_METHOD=${OPTARG} ;; - t) - TEST_TYPE=${OPTARG} - ;; esac done shift $((OPTIND-1)) @@ -45,14 +41,8 @@ source $DIR/setup.sh # Find all of the test files within the 'test' subdirectory. -if [[ "${TEST_TYPE}" == "functional" ]]; then - TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'")) -elif [[ "${TEST_TYPE}" == "performance" ]]; then - TESTS=($(eval "find ${DIR}/performance_tests -maxdepth 1 -name '*_test.sh'")) -else - echo "Only functional and performance tests are supported" - exit 1 -fi +TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'")) + COUNT=0 RETRIED=() FAILED=()
diff --git a/starboard/linux/shared/media_is_audio_supported.cc b/starboard/linux/shared/media_is_audio_supported.cc index 1780684..bedb0c7 100644 --- a/starboard/linux/shared/media_is_audio_supported.cc +++ b/starboard/linux/shared/media_is_audio_supported.cc
@@ -19,14 +19,11 @@ #include "starboard/configuration_constants.h" #include "starboard/media.h" -bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, - int64_t bitrate) { - if (!content_type) { - SB_LOG(WARNING) << "|content_type| cannot be nullptr."; - return false; - } +using ::starboard::shared::starboard::media::MimeType; +bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, + const MimeType* mime_type, + int64_t bitrate) { if (audio_codec == kSbMediaAudioCodecAac) { return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond; } @@ -42,5 +39,14 @@ } } + if (audio_codec == kSbMediaAudioCodecVorbis) { + return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond; + } +#if SB_API_VERSION >= 14 + if (audio_codec == kSbMediaAudioCodecMp3) { + return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond; + } +#endif // SB_API_VERSION >= 14 + return false; }
diff --git a/starboard/linux/shared/media_is_video_supported.cc b/starboard/linux/shared/media_is_video_supported.cc index 98ccacd..e932cb1 100644 --- a/starboard/linux/shared/media_is_video_supported.cc +++ b/starboard/linux/shared/media_is_video_supported.cc
@@ -22,11 +22,12 @@ #include "starboard/shared/libde265/de265_library_loader.h" #include "starboard/shared/starboard/media/media_util.h" -using starboard::shared::de265::is_de265_supported; -using starboard::shared::starboard::media::IsSDRVideo; +using ::starboard::shared::de265::is_de265_supported; +using ::starboard::shared::starboard::media::IsSDRVideo; +using ::starboard::shared::starboard::media::MimeType; bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth, @@ -38,11 +39,6 @@ int64_t bitrate, int fps, bool decode_to_texture_required) { - if (!content_type) { - SB_LOG(WARNING) << "|content_type| cannot be nullptr."; - return false; - } - if (!IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id)) { if (bit_depth != 10 && bit_depth != 12) { return false; @@ -67,6 +63,7 @@ return (video_codec == kSbMediaVideoCodecAv1 || video_codec == kSbMediaVideoCodecH264 || (video_codec == kSbMediaVideoCodecH265 && is_de265_supported()) || + (video_codec == kSbMediaVideoCodecVp8) || (video_codec == kSbMediaVideoCodecVp9)) && frame_width <= 1920 && frame_height <= 1080 && bitrate <= kSbMediaMaxVideoBitrateInBitsPerSecond && fps <= 60;
diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn index 951827f..3ef98fd 100644 --- a/starboard/loader_app/BUILD.gn +++ b/starboard/loader_app/BUILD.gn
@@ -47,7 +47,6 @@ "//cobalt/content/fonts:copy_font_data", "//starboard/elf_loader", ] - deps += cobalt_platform_dependencies } } @@ -68,7 +67,6 @@ "//cobalt/content/fonts:copy_font_data", "//starboard/elf_loader:elf_loader_sys", ] - deps += cobalt_platform_dependencies } } }
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/starboard/nplb/media_can_play_mime_and_key_system_test.cc index beea8ac..af93b99 100644 --- a/starboard/nplb/media_can_play_mime_and_key_system_test.cc +++ b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -74,6 +74,10 @@ result = SbMediaCanPlayMimeAndKeySystem( "audio/webm; codecs=\"opus\"; channels=2", ""); ASSERT_EQ(result, kSbMediaSupportTypeProbably); + // Two codecs + result = SbMediaCanPlayMimeAndKeySystem( + "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", ""); + ASSERT_EQ(result, kSbMediaSupportTypeProbably); } TEST(SbMediaCanPlayMimeAndKeySystem, Invalid) {
diff --git a/starboard/nplb/media_set_audio_write_duration_test.cc b/starboard/nplb/media_set_audio_write_duration_test.cc index 237fe58..84ff9b2 100644 --- a/starboard/nplb/media_set_audio_write_duration_test.cc +++ b/starboard/nplb/media_set_audio_write_duration_test.cc
@@ -31,7 +31,7 @@ namespace { using ::starboard::testing::FakeGraphicsContextProvider; -using shared::starboard::player::video_dmp::VideoDmpReader; +using ::shared::starboard::player::video_dmp::VideoDmpReader; using ::testing::ValuesIn; const SbTime kDuration = kSbTimeSecond / 2; @@ -261,8 +261,7 @@ const SbMediaAudioSampleInfo* audio_sample_info = &dmp_reader.audio_sample_info(); - if (SbMediaIsAudioSupported(dmp_reader.audio_codec(), - "", // content_type + if (SbMediaIsAudioSupported(dmp_reader.audio_codec(), nullptr, dmp_reader.audio_bitrate())) { test_params.push_back(filename); }
diff --git a/starboard/raspi/shared/media_is_video_supported.cc b/starboard/raspi/shared/media_is_video_supported.cc index 704faea..16a7088 100644 --- a/starboard/raspi/shared/media_is_video_supported.cc +++ b/starboard/raspi/shared/media_is_video_supported.cc
@@ -19,8 +19,11 @@ #include "starboard/media.h" #include "starboard/shared/starboard/media/media_util.h" +using ::starboard::shared::starboard::media::IsSDRVideo; +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth, @@ -32,8 +35,6 @@ int64_t bitrate, int fps, bool decode_to_texture_required) { - using starboard::shared::starboard::media::IsSDRVideo; - if (!IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id)) { return false; }
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc index 34d66fd..50cb9bd 100644 --- a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc +++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
@@ -46,6 +46,12 @@ return kSbHasAc3Audio ? AV_CODEC_ID_EAC3 : AV_CODEC_ID_NONE; case kSbMediaAudioCodecOpus: return AV_CODEC_ID_OPUS; + case kSbMediaAudioCodecVorbis: + return AV_CODEC_ID_VORBIS; +#if SB_API_VERSION >= 14 + case kSbMediaAudioCodecMp3: + return AV_CODEC_ID_MP3; +#endif // SB_API_VERSION >= 14 default: return AV_CODEC_ID_NONE; } @@ -272,7 +278,8 @@ codec_context_->extradata = NULL; codec_context_->extradata_size = 0; - if (codec_context_->codec_id == AV_CODEC_ID_OPUS && + if ((codec_context_->codec_id == AV_CODEC_ID_OPUS || + codec_context_->codec_id == AV_CODEC_ID_VORBIS) && audio_sample_info_.audio_specific_config_size > 0) { // AV_INPUT_BUFFER_PADDING_SIZE is not defined in ancient avcodec.h. Use a // large enough padding here explicitly.
diff --git a/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc index 1a6678e..d8605a6 100644 --- a/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc +++ b/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc
@@ -74,6 +74,22 @@ } #endif // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8 +AVCodecID GetFfmpegCodecIdByMediaCodec(SbMediaVideoCodec video_codec) { + // Note: although SbMediaVideoCodec values exist for them, MPEG2VIDEO, THEORA, + // and VC1 are not included here since we do not officially support them. + // Also, note that VP9 and HEVC use different decoders (not FFmpeg). + switch (video_codec) { + case kSbMediaVideoCodecH264: + return AV_CODEC_ID_H264; + case kSbMediaVideoCodecVp8: + return AV_CODEC_ID_VP8; + default: + SB_DLOG(WARNING) << "FFmpeg decoder does not support SbMediaVideoCodec " + << video_codec; + } + return AV_CODEC_ID_NONE; +} + const bool g_registered = FFMPEGDispatch::RegisterSpecialization(FFMPEG, LIBAVCODEC_VERSION_MAJOR, @@ -320,7 +336,7 @@ } codec_context_->codec_type = AVMEDIA_TYPE_VIDEO; - codec_context_->codec_id = AV_CODEC_ID_H264; + codec_context_->codec_id = GetFfmpegCodecIdByMediaCodec(video_codec_); codec_context_->profile = FF_PROFILE_UNKNOWN; codec_context_->coded_width = 0; codec_context_->coded_height = 0;
diff --git a/starboard/shared/starboard/media/BUILD.gn b/starboard/shared/starboard/media/BUILD.gn index 1e7355b..28ba606 100644 --- a/starboard/shared/starboard/media/BUILD.gn +++ b/starboard/shared/starboard/media/BUILD.gn
@@ -17,10 +17,20 @@ sources = [ "//starboard/shared/starboard/media/avc_util.cc", "//starboard/shared/starboard/media/avc_util.h", + "//starboard/shared/starboard/media/bitrate_supportability_cache.cc", + "//starboard/shared/starboard/media/bitrate_supportability_cache.h", "//starboard/shared/starboard/media/codec_util.cc", "//starboard/shared/starboard/media/codec_util.h", + "//starboard/shared/starboard/media/key_system_supportability_cache.cc", + "//starboard/shared/starboard/media/key_system_supportability_cache.h", "//starboard/shared/starboard/media/media_util.cc", "//starboard/shared/starboard/media/media_util.h", + "//starboard/shared/starboard/media/mime_supportability_cache.cc", + "//starboard/shared/starboard/media/mime_supportability_cache.h", + "//starboard/shared/starboard/media/mime_util.cc", + "//starboard/shared/starboard/media/mime_util.h", + "//starboard/shared/starboard/media/parsed_mime_info.cc", + "//starboard/shared/starboard/media/parsed_mime_info.h", "//starboard/shared/starboard/media/video_capabilities.cc", "//starboard/shared/starboard/media/video_capabilities.h", "//starboard/shared/starboard/media/vp9_util.cc",
diff --git a/starboard/shared/starboard/media/bitrate_supportability_cache.cc b/starboard/shared/starboard/media/bitrate_supportability_cache.cc new file mode 100644 index 0000000..01c2917 --- /dev/null +++ b/starboard/shared/starboard/media/bitrate_supportability_cache.cc
@@ -0,0 +1,150 @@ +// Copyright 2022 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 "starboard/shared/starboard/media/bitrate_supportability_cache.h" + +#include <map> + +#include "starboard/common/log.h" +#include "starboard/common/mutex.h" +#include "starboard/log.h" +#include "starboard/media.h" +#include "starboard/once.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +template <typename T> +class BitrateSupportabilityContainer { + public: + Supportability GetSupportability(T codec, int bitrate) { + // Reject invalid parameters. + if (bitrate < 0) { + return kSupportabilityNotSupported; + } + // Bitrate 0 is always supported. + if (bitrate == 0) { + return kSupportabilitySupported; + } + + ScopedLock scoped_lock(mutex_); + auto iter = supported_bitrate_ranges_.find(codec); + if (iter == supported_bitrate_ranges_.end()) { + return kSupportabilityUnknown; + } + const Range& range = iter->second; + if (bitrate < range.minimum || bitrate > range.maximum) { + return kSupportabilityNotSupported; + } + return kSupportabilitySupported; + } + + void SetSupportedBitrate(T codec, int min, int max) { + SB_DCHECK(min >= 0 && max >= min); + + ScopedLock scoped_lock(mutex_); + supported_bitrate_ranges_[codec] = Range(min, max); + } + void ClearContainer() { + ScopedLock scoped_lock(mutex_); + supported_bitrate_ranges_.clear(); + } + + private: + struct Range { + Range() : minimum(0), maximum(0) {} + Range(int min, int max) : minimum(min), maximum(max) {} + int minimum; + int maximum; + }; + + Mutex mutex_; + std::map<T, Range> supported_bitrate_ranges_; +}; + +template <typename T> +SB_ONCE_INITIALIZE_FUNCTION(BitrateSupportabilityContainer<T>, GetContainer); + +} // namespace + +// static +SB_ONCE_INITIALIZE_FUNCTION(BitrateSupportabilityCache, + BitrateSupportabilityCache::GetInstance); + +Supportability BitrateSupportabilityCache::GetBitrateSupportability( + const ParsedMimeInfo& mime_info) { + SB_DCHECK(mime_info.is_valid()); + + if (!is_enabled_) { + return kSupportabilityUnknown; + } + + Supportability audio_supportability = kSupportabilitySupported; + if (mime_info.has_audio_info()) { + audio_supportability = GetContainer<SbMediaAudioCodec>()->GetSupportability( + mime_info.audio_info().codec, mime_info.audio_info().bitrate); + } + + Supportability video_supportability = kSupportabilitySupported; + if (mime_info.has_video_info()) { + video_supportability = GetContainer<SbMediaVideoCodec>()->GetSupportability( + mime_info.video_info().codec, mime_info.video_info().bitrate); + } + + if (audio_supportability == kSupportabilityNotSupported || + video_supportability == kSupportabilityNotSupported) { + return kSupportabilityNotSupported; + } + if (audio_supportability == kSupportabilityUnknown || + video_supportability == kSupportabilityUnknown) { + return kSupportabilityUnknown; + } + return kSupportabilitySupported; +} + +void BitrateSupportabilityCache::SetSupportedBitrate(SbMediaAudioCodec codec, + int min, + int max) { + SB_DCHECK(min >= 0 && min <= max) << "Invalid bitrate range."; + + if (!is_enabled_) { + return; + } + GetContainer<SbMediaAudioCodec>()->SetSupportedBitrate(codec, min, max); +} + +void BitrateSupportabilityCache::SetSupportedBitrate(SbMediaVideoCodec codec, + int min, + int max) { + SB_DCHECK(min >= 0 && min <= max) << "Invalid bitrate range."; + + if (!is_enabled_) { + return; + } + GetContainer<SbMediaVideoCodec>()->SetSupportedBitrate(codec, min, max); +} + +void BitrateSupportabilityCache::ClearCache() { + GetContainer<SbMediaAudioCodec>()->ClearContainer(); + GetContainer<SbMediaVideoCodec>()->ClearContainer(); +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/bitrate_supportability_cache.h b/starboard/shared/starboard/media/bitrate_supportability_cache.h new file mode 100644 index 0000000..fc1f553 --- /dev/null +++ b/starboard/shared/starboard/media/bitrate_supportability_cache.h
@@ -0,0 +1,66 @@ +// Copyright 2022 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. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_ + +#include "starboard/media.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/mime_supportability_cache.h" +#include "starboard/shared/starboard/media/parsed_mime_info.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +// TODO: add unit tests for BitrateSupportabilityCache +class BitrateSupportabilityCache { + public: + static BitrateSupportabilityCache* GetInstance(); + + // When cache is not enabled, GetBitrateSupportability() will always return + // kSupportabilityUnknown, and SetSupportedBitrate() will do nothing. + bool IsEnabled() const { return is_enabled_; } + void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } + + // Get bitrate supportability. + Supportability GetBitrateSupportability(const ParsedMimeInfo& mime_info); + // Set supported bitrate range for the |codec|. Note that if supported bitrate + // range is not set, MimeSupportabilityCache::GetMimeSupportability() will + // always return kSupportabilityUnknown. + void SetSupportedBitrate(SbMediaAudioCodec codec, int min, int max); + void SetSupportedBitrate(SbMediaVideoCodec codec, int min, int max); + + // Clear all cached supported bitrate ranges. + void ClearCache(); + + private: + // Class can only be instanced via the singleton + BitrateSupportabilityCache() {} + ~BitrateSupportabilityCache() {} + + BitrateSupportabilityCache(const BitrateSupportabilityCache&) = delete; + BitrateSupportabilityCache& operator=(const BitrateSupportabilityCache&) = + delete; + + std::atomic_bool is_enabled_{false}; +}; + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/key_system_supportability_cache.cc b/starboard/shared/starboard/media/key_system_supportability_cache.cc new file mode 100644 index 0000000..6ee6532 --- /dev/null +++ b/starboard/shared/starboard/media/key_system_supportability_cache.cc
@@ -0,0 +1,169 @@ +// Copyright 2022 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 "starboard/shared/starboard/media/key_system_supportability_cache.h" + +#include <cstring> +#include <map> +#include <string> + +#include "starboard/common/log.h" +#include "starboard/common/mutex.h" +#include "starboard/media.h" +#include "starboard/once.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +template <typename T> +class KeySystemSupportabilityContainer { + public: + Supportability GetKeySystemSupportability(T codec, const char* key_system) { + SB_DCHECK(key_system); + SB_DCHECK(strlen(key_system) > 0); + + ScopedLock scoped_lock(mutex_); + auto map_iter = key_system_supportabilities_.find(codec); + if (map_iter == key_system_supportabilities_.end()) { + return kSupportabilityUnknown; + } + KeySystemToSupportabilityMap& map = map_iter->second; + auto supportability_iter = map.find(std::string(key_system)); + if (supportability_iter == map.end()) { + return kSupportabilityUnknown; + } + return supportability_iter->second; + } + + void CacheKeySystemSupportability(T codec, + const char* key_system, + Supportability supportability) { + SB_DCHECK(key_system); + SB_DCHECK(strlen(key_system) > 0); + SB_DCHECK(supportability != kSupportabilityUnknown); + + ScopedLock scoped_lock(mutex_); + key_system_supportabilities_[codec][key_system] = supportability; + } + + void ClearContainer() { + ScopedLock scoped_lock(mutex_); + key_system_supportabilities_.clear(); + } + + private: + typedef std::map<std::string, Supportability> KeySystemToSupportabilityMap; + + Mutex mutex_; + std::map<T, KeySystemToSupportabilityMap> key_system_supportabilities_; +}; + +template <typename T> +SB_ONCE_INITIALIZE_FUNCTION(KeySystemSupportabilityContainer<T>, GetContainer); + +} // namespace + +// static +SB_ONCE_INITIALIZE_FUNCTION(KeySystemSupportabilityCache, + KeySystemSupportabilityCache::GetInstance); + +Supportability KeySystemSupportabilityCache::GetKeySystemSupportability( + SbMediaAudioCodec codec, + const char* key_system) { + SB_DCHECK(key_system); + + // Empty key system is always supported. + if (strlen(key_system) == 0) { + return kSupportabilitySupported; + } + + if (!is_enabled_) { + return kSupportabilityUnknown; + } + + return GetContainer<SbMediaAudioCodec>()->GetKeySystemSupportability( + codec, key_system); +} + +Supportability KeySystemSupportabilityCache::GetKeySystemSupportability( + SbMediaVideoCodec codec, + const char* key_system) { + SB_DCHECK(key_system); + + // Empty key system is always supported. + if (strlen(key_system) == 0) { + return kSupportabilitySupported; + } + + if (!is_enabled_) { + return kSupportabilityUnknown; + } + + return GetContainer<SbMediaVideoCodec>()->GetKeySystemSupportability( + codec, key_system); +} + +void KeySystemSupportabilityCache::CacheKeySystemSupportability( + SbMediaAudioCodec codec, + const char* key_system, + Supportability supportability) { + SB_DCHECK(key_system); + SB_DCHECK(supportability != kSupportabilityUnknown); + + if (!is_enabled_) { + return; + } + + if (strlen(key_system) == 0) { + SB_LOG(WARNING) << "Rejected empty key system as it's always supported."; + } + + GetContainer<SbMediaAudioCodec>()->CacheKeySystemSupportability( + codec, key_system, supportability); +} + +void KeySystemSupportabilityCache::CacheKeySystemSupportability( + SbMediaVideoCodec codec, + const char* key_system, + Supportability supportability) { + SB_DCHECK(key_system); + SB_DCHECK(strlen(key_system) > 0); + SB_DCHECK(supportability != kSupportabilityUnknown); + + if (!is_enabled_) { + return; + } + + if (strlen(key_system) == 0) { + SB_LOG(WARNING) << "Rejected empty key system as it's always supported."; + return; + } + + GetContainer<SbMediaVideoCodec>()->CacheKeySystemSupportability( + codec, key_system, supportability); +} + +void KeySystemSupportabilityCache::ClearCache() { + GetContainer<SbMediaAudioCodec>()->ClearContainer(); + GetContainer<SbMediaVideoCodec>()->ClearContainer(); +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/key_system_supportability_cache.h b/starboard/shared/starboard/media/key_system_supportability_cache.h new file mode 100644 index 0000000..a1f2f68 --- /dev/null +++ b/starboard/shared/starboard/media/key_system_supportability_cache.h
@@ -0,0 +1,70 @@ +// Copyright 2022 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. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_ + +#include <atomic> + +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/mime_supportability_cache.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +// TODO: add unit tests for KeySystemSupportabilityCache +class KeySystemSupportabilityCache { + public: + static KeySystemSupportabilityCache* GetInstance(); + + // When cache is not enabled, GetKeySystemSupportability() will always return + // kSupportabilityUnknown, and CacheKeySystemSupportability() will do nothing. + bool IsEnabled() const { return is_enabled_; } + void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } + + // Get & cache key system supportability. + Supportability GetKeySystemSupportability(SbMediaAudioCodec codec, + const char* key_system); + Supportability GetKeySystemSupportability(SbMediaVideoCodec codec, + const char* key_system); + void CacheKeySystemSupportability(SbMediaAudioCodec codec, + const char* key_system, + Supportability supportability); + void CacheKeySystemSupportability(SbMediaVideoCodec codec, + const char* key_system, + Supportability supportability); + + // Clear all cached supportabilities. + void ClearCache(); + + private: + // Class can only be instanced via the singleton + KeySystemSupportabilityCache() {} + ~KeySystemSupportabilityCache() {} + + KeySystemSupportabilityCache(const KeySystemSupportabilityCache&) = delete; + KeySystemSupportabilityCache& operator=(const KeySystemSupportabilityCache&) = + delete; + + std::atomic_bool is_enabled_{false}; +}; + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc b/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc index b3c5723..8e5bae0 100644 --- a/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc +++ b/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
@@ -12,12 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/media.h" - #include "starboard/common/log.h" -#include "starboard/common/string.h" -#include "starboard/shared/starboard/media/media_util.h" -#include "starboard/shared/starboard/media/mime_type.h" +#include "starboard/media.h" +#include "starboard/shared/starboard/media/mime_util.h" SbMediaSupportType SbMediaCanPlayMimeAndKeySystem(const char* mime, const char* key_system) { @@ -31,11 +28,6 @@ return kSbMediaSupportTypeNotSupported; } - starboard::shared::starboard::media::MimeType mime_type(mime); - if (!mime_type.is_valid()) { - SB_DLOG(WARNING) << mime << " is not a valid mime type"; - return kSbMediaSupportTypeNotSupported; - } - - return CanPlayMimeAndKeySystem(mime_type, key_system); + return starboard::shared::starboard::media::CanPlayMimeAndKeySystem( + mime, key_system); }
diff --git a/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc b/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc index 6d2c701..e4689ca 100644 --- a/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc +++ b/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc
@@ -18,8 +18,10 @@ #include "starboard/configuration_constants.h" #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { if (audio_codec == kSbMediaAudioCodecAac) { return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
diff --git a/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc b/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc index fc72934..83dbd14 100644 --- a/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc +++ b/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc
@@ -18,8 +18,10 @@ #include "starboard/configuration_constants.h" #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { return audio_codec == kSbMediaAudioCodecAac && bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
diff --git a/starboard/shared/starboard/media/media_support_internal.h b/starboard/shared/starboard/media/media_support_internal.h index bf5fe78..6fcc832 100644 --- a/starboard/shared/starboard/media/media_support_internal.h +++ b/starboard/shared/starboard/media/media_support_internal.h
@@ -18,6 +18,7 @@ #include "starboard/configuration.h" #include "starboard/media.h" #include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/mime_type.h" #ifdef __cplusplus extern "C" { @@ -31,9 +32,9 @@ // the platform to decode any supported input formats. // // |video_codec|: The |SbMediaVideoCodec| being checked for platform -// compatibility. +// compatibility. // |audio_codec|: The |SbMediaAudioCodec| being checked for platform -// compatibility. +// compatibility. // |key_system|: The key system being checked for platform compatibility. SB_EXPORT bool SbMediaIsSupported(SbMediaVideoCodec video_codec, SbMediaAudioCodec audio_codec, @@ -46,9 +47,8 @@ // function returns |false|. // // |video_codec|: The video codec used in the media content. -// |content_type|: The full content type passed to the corresponding dom -// interface if there is any. Otherwise it will be set to "". -// It should never to set to NULL. +// |mime_type|: The parsed mime type passed to the corresponding interface. +// Note that |mime_type| can be NULL. // |profile|: The profile in the context of |video_codec|. It should be set to // -1 when it is unknown or not applicable. // |level|: The level in the context of |video_codec|. It should be set to -1 @@ -75,32 +75,33 @@ // it indicates that the fps shouldn't be considered. // |decode_to_texture_required|: Whether or not the resulting video frames can // be decoded and used as textures by the GPU. -bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, - int profile, - int level, - int bit_depth, - SbMediaPrimaryId primary_id, - SbMediaTransferId transfer_id, - SbMediaMatrixId matrix_id, - int frame_width, - int frame_height, - int64_t bitrate, - int fps, - bool decode_to_texture_required); +bool SbMediaIsVideoSupported( + SbMediaVideoCodec video_codec, + const starboard::shared::starboard::media::MimeType* mime_type, + int profile, + int level, + int bit_depth, + SbMediaPrimaryId primary_id, + SbMediaTransferId transfer_id, + SbMediaMatrixId matrix_id, + int frame_width, + int frame_height, + int64_t bitrate, + int fps, + bool decode_to_texture_required); // Indicates whether this platform supports |audio_codec| at |bitrate|. // If |audio_codec| is not supported under any condition, this function // returns |false|. // // |audio_codec|: The media's audio codec (|SbMediaAudioCodec|). -// |content_type|: The full content type passed to the corresponding dom -// interface if there is any. Otherwise it will be set to "". -// It should never to set to NULL. +// |mime_type|: The parsed mime type passed to the corresponding interface. +// Note that |mime_type| can be NULL. // |bitrate|: The media's bitrate. -bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, - int64_t bitrate); +bool SbMediaIsAudioSupported( + SbMediaAudioCodec audio_codec, + const starboard::shared::starboard::media::MimeType* mime_type, + int64_t bitrate); #ifdef __cplusplus } // extern "C"
diff --git a/starboard/shared/starboard/media/media_util.cc b/starboard/shared/starboard/media/media_util.cc index a5a135c..58bb7bf 100644 --- a/starboard/shared/starboard/media/media_util.cc +++ b/starboard/shared/starboard/media/media_util.cc
@@ -21,9 +21,7 @@ #include "starboard/common/media.h" #include "starboard/common/string.h" #include "starboard/log.h" -#include "starboard/memory.h" #include "starboard/shared/starboard/media/codec_util.h" -#include "starboard/shared/starboard/media/media_support_internal.h" #include "starboard/shared/starboard/media/mime_type.h" namespace starboard { @@ -36,165 +34,6 @@ const int64_t kDefaultBitRate = 0; const int64_t kDefaultAudioChannels = 2; -bool IsSupportedAudioCodec(const MimeType& mime_type, - const std::string& codec, - const char* key_system) { - SbMediaAudioCodec audio_codec = GetAudioCodecFromString(codec.c_str()); - if (audio_codec == kSbMediaAudioCodecNone) { - return false; - } - - // TODO: allow platform-specific rejection of a combination of codec & - // number of channels, by passing channels to SbMediaAudioIsSupported and / - // or SbMediaIsSupported. - - if (strlen(key_system) != 0) { - if (!SbMediaIsSupported(kSbMediaVideoCodecNone, audio_codec, key_system)) { - return false; - } - } - - int channels = mime_type.GetParamIntValue("channels", kDefaultAudioChannels); - if (!IsAudioOutputSupported(kSbMediaAudioCodingTypePcm, channels)) { - return false; - } - - int bitrate = mime_type.GetParamIntValue("bitrate", kDefaultBitRate); - - if (!SbMediaIsAudioSupported(audio_codec, - mime_type.raw_content_type().c_str(), bitrate)) { - return false; - } - - switch (audio_codec) { - case kSbMediaAudioCodecNone: - SB_NOTREACHED(); - return false; - case kSbMediaAudioCodecAac: - return mime_type.subtype() == "mp4"; - case kSbMediaAudioCodecAc3: - if (!kSbHasAc3Audio) { - SB_NOTREACHED() << "AC3 audio is not enabled on this platform. To " - << "enable it, set kSbHasAc3Audio to |true|."; - return false; - } - return mime_type.subtype() == "mp4"; - case kSbMediaAudioCodecEac3: - if (!kSbHasAc3Audio) { - SB_NOTREACHED() << "AC3 audio is not enabled on this platform. To " - << "enable it, set kSbHasAc3Audio to |true|."; - return false; - } - return mime_type.subtype() == "mp4"; - case kSbMediaAudioCodecOpus: - case kSbMediaAudioCodecVorbis: - return mime_type.subtype() == "webm"; -#if SB_API_VERSION >= 14 - case kSbMediaAudioCodecMp3: - return mime_type.subtype() == "mpeg" || mime_type.subtype() == "mp3" || - mime_type.subtype() == "mp4"; - case kSbMediaAudioCodecPcm: - return mime_type.subtype() == "wav" || mime_type.subtype() == "wave" || - mime_type.subtype() == "x-wav" || - mime_type.subtype() == "x-pn-wav"; - case kSbMediaAudioCodecFlac: - return mime_type.subtype() == "ogg"; -#endif // SB_API_VERSION >= 14 - } - - SB_NOTREACHED(); - return false; -} - -bool IsSupportedVideoCodec(const MimeType& mime_type, - const std::string& codec, - const char* key_system, - bool decode_to_texture_required) { - SbMediaVideoCodec video_codec; - int profile = -1; - int level = -1; - int bit_depth = 8; - SbMediaPrimaryId primary_id = kSbMediaPrimaryIdUnspecified; - SbMediaTransferId transfer_id = kSbMediaTransferIdUnspecified; - SbMediaMatrixId matrix_id = kSbMediaMatrixIdUnspecified; - - if (!ParseVideoCodec(codec.c_str(), &video_codec, &profile, &level, - &bit_depth, &primary_id, &transfer_id, &matrix_id)) { - return false; - } - SB_DCHECK(video_codec != kSbMediaVideoCodecNone); - - if (strlen(key_system) != 0) { - if (!SbMediaIsSupported(video_codec, kSbMediaAudioCodecNone, key_system)) { - return false; - } - } - - std::string eotf = mime_type.GetParamStringValue("eotf", ""); - if (!eotf.empty()) { - SbMediaTransferId transfer_id_from_eotf = GetTransferIdFromString(eotf); - // If the eotf is not known, reject immediately - without checking with - // the platform. - if (transfer_id_from_eotf == kSbMediaTransferIdUnknown) { - return false; - } - if (transfer_id != kSbMediaTransferIdUnspecified && - transfer_id != transfer_id_from_eotf) { - SB_LOG_IF(WARNING, transfer_id != kSbMediaTransferIdUnspecified) - << "transfer_id " << transfer_id << " set by the codec string \"" - << codec << "\" will be overwritten by the eotf attribute " << eotf; - } - transfer_id = transfer_id_from_eotf; - } - - std::string cryptoblockformat = - mime_type.GetParamStringValue("cryptoblockformat", ""); - if (!cryptoblockformat.empty()) { - if (mime_type.subtype() != "webm" || cryptoblockformat != "subsample") { - return false; - } - } - - int width = mime_type.GetParamIntValue("width", 0); - int height = mime_type.GetParamIntValue("height", 0); - int fps = mime_type.GetParamIntValue("framerate", 0); - - int bitrate = mime_type.GetParamIntValue("bitrate", kDefaultBitRate); - - if (width < 0 || height < 0 || fps < 0 || bitrate < 0) { - return false; - } - - if (!SbMediaIsVideoSupported( - video_codec, mime_type.raw_content_type().c_str(), profile, level, - bit_depth, primary_id, transfer_id, matrix_id, width, height, bitrate, - fps, decode_to_texture_required)) { - return false; - } - - switch (video_codec) { - case kSbMediaVideoCodecNone: - SB_NOTREACHED(); - return false; - case kSbMediaVideoCodecH264: - case kSbMediaVideoCodecH265: - return mime_type.subtype() == "mp4"; - case kSbMediaVideoCodecMpeg2: - case kSbMediaVideoCodecTheora: - return false; // No associated container in YT. - case kSbMediaVideoCodecVc1: - case kSbMediaVideoCodecAv1: - return mime_type.subtype() == "mp4"; - case kSbMediaVideoCodecVp8: - return mime_type.subtype() == "webm"; - case kSbMediaVideoCodecVp9: - return mime_type.subtype() == "mp4" || mime_type.subtype() == "webm"; - } - - SB_NOTREACHED(); - return false; -} - } // namespace AudioSampleInfo::AudioSampleInfo() { @@ -254,24 +93,6 @@ return *this; } -bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) { - int count = SbMediaGetAudioOutputCount(); - - for (int output_index = 0; output_index < count; ++output_index) { - SbMediaAudioConfiguration configuration; - if (!SbMediaGetAudioConfiguration(output_index, &configuration)) { - continue; - } - - if (configuration.coding_type == coding_type && - configuration.number_of_channels >= channels) { - return true; - } - } - - return false; -} - bool IsSDRVideo(int bit_depth, SbMediaPrimaryId primary_id, SbMediaTransferId transfer_id, @@ -346,17 +167,6 @@ return bit_depth == 8; } -SbMediaTransferId GetTransferIdFromString(const std::string& transfer_id) { - if (transfer_id == "bt709") { - return kSbMediaTransferIdBt709; - } else if (transfer_id == "smpte2084") { - return kSbMediaTransferIdSmpteSt2084; - } else if (transfer_id == "arib-std-b67") { - return kSbMediaTransferIdAribStdB67; - } - return kSbMediaTransferIdUnknown; -} - int GetBytesPerSample(SbMediaAudioSampleType sample_type) { switch (sample_type) { case kSbMediaAudioSampleTypeInt16Deprecated: @@ -369,84 +179,6 @@ return 4; } -SbMediaSupportType CanPlayMimeAndKeySystem(const MimeType& mime_type, - const char* key_system) { - SB_DCHECK(mime_type.is_valid()); - - if (mime_type.type() != "audio" && mime_type.type() != "video") { - return kSbMediaSupportTypeNotSupported; - } - - auto codecs = mime_type.GetCodecs(); - - // Pre-filter for |key_system|. - if (strlen(key_system) != 0) { - if (!SbMediaIsSupported(kSbMediaVideoCodecNone, kSbMediaAudioCodecNone, - key_system)) { - return kSbMediaSupportTypeNotSupported; - } - } - - bool decode_to_texture_required = false; - std::string decode_to_texture_value = - mime_type.GetParamStringValue("decode-to-texture", "false"); - if (decode_to_texture_value == "true") { - decode_to_texture_required = true; - } else if (decode_to_texture_value != "false") { - // If an invalid value (e.g. not "true" or "false") is passed in for - // decode-to-texture, trivially reject. - return kSbMediaSupportTypeNotSupported; - } - - if (codecs.size() == 0) { - // This happens when the H5 player is either querying for progressive - // playback support, or probing for generic mp4 support without specific - // codecs. We only support "audio/mp4" and "video/mp4" for these cases. - if ((mime_type.type() == "audio" || mime_type.type() == "video") && - mime_type.subtype() == "mp4") { - return kSbMediaSupportTypeMaybe; - } - return kSbMediaSupportTypeNotSupported; - } - - if (codecs.size() > 2) { - return kSbMediaSupportTypeNotSupported; - } - - bool has_audio_codec = false; - bool has_video_codec = false; - for (const auto& codec : codecs) { - if (IsSupportedAudioCodec(mime_type, codec, key_system)) { - if (has_audio_codec) { - // We don't support two audio codecs in one stream. - return kSbMediaSupportTypeNotSupported; - } - has_audio_codec = true; - continue; - } - if (IsSupportedVideoCodec(mime_type, codec, key_system, - decode_to_texture_required)) { - if (mime_type.type() != "video") { - // Video can only be contained in "video/*", while audio can be - // contained in both "audio/*" and "video/*". - return kSbMediaSupportTypeNotSupported; - } - if (has_video_codec) { - // We don't support two video codecs in one stream. - return kSbMediaSupportTypeNotSupported; - } - has_video_codec = true; - continue; - } - return kSbMediaSupportTypeNotSupported; - } - - if (has_audio_codec || has_video_codec) { - return kSbMediaSupportTypeProbably; - } - return kSbMediaSupportTypeNotSupported; -} - std::string GetStringRepresentation(const uint8_t* data, const int size) { std::string result;
diff --git a/starboard/shared/starboard/media/media_util.h b/starboard/shared/starboard/media/media_util.h index aef3cb8..8a97986 100644 --- a/starboard/shared/starboard/media/media_util.h +++ b/starboard/shared/starboard/media/media_util.h
@@ -21,7 +21,6 @@ #include "starboard/media.h" #include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/media/mime_type.h" namespace starboard { namespace shared { @@ -48,49 +47,14 @@ std::string max_video_capabilities_storage; }; -bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels); - bool IsSDRVideo(int bit_depth, SbMediaPrimaryId primary_id, SbMediaTransferId transfer_id, SbMediaMatrixId matrix_id); bool IsSDRVideo(const char* mime); -// Turns |eotf| into value of SbMediaTransferId. If |eotf| isn't recognized the -// function returns kSbMediaTransferIdReserved0. -// This function supports all eotfs required by YouTube TV HTML5 Technical -// Requirements (2018). -SbMediaTransferId GetTransferIdFromString(const std::string& eotf); - int GetBytesPerSample(SbMediaAudioSampleType sample_type); -// Calls to canPlayType() and isTypeSupported() are redirected to this function. -// Following are some example inputs: -// canPlayType(video/mp4) -// canPlayType(video/mp4; codecs="avc1.42001E, mp4a.40.2") -// canPlayType(video/webm) -// isTypeSupported(video/webm; codecs="vp9") -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=640) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=99999) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=360) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=99999) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=30) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=9999) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=300000) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=2000000000) -// isTypeSupported(audio/mp4; codecs="mp4a.40.2") -// isTypeSupported(audio/webm; codecs="vorbis") -// isTypeSupported(video/webm; codecs="vp9") -// isTypeSupported(video/webm; codecs="vp9") -// isTypeSupported(audio/webm; codecs="opus") -// isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=2) -// isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=99) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=true) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=false) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=invalid) -SbMediaSupportType CanPlayMimeAndKeySystem(const MimeType& mime_type, - const char* key_system); - std::string GetStringRepresentation(const uint8_t* data, const int size); std::string GetMixedRepresentation(const uint8_t* data, const int size,
diff --git a/starboard/shared/starboard/media/mime_supportability_cache.cc b/starboard/shared/starboard/media/mime_supportability_cache.cc new file mode 100644 index 0000000..34024f3 --- /dev/null +++ b/starboard/shared/starboard/media/mime_supportability_cache.cc
@@ -0,0 +1,227 @@ +// Copyright 2022 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 "starboard/shared/starboard/media/mime_supportability_cache.h" + +#include <queue> +#include <sstream> +#include <string> +#include <unordered_map> + +#include "starboard/common/log.h" +#include "starboard/common/media.h" +#include "starboard/common/mutex.h" +#include "starboard/log.h" +#include "starboard/media.h" +#include "starboard/once.h" +#include "starboard/shared/starboard/media/mime_type.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +const size_t kDefaultCacheMaxSize = 2000; + +class MimeSupportabilityContainer { + public: + struct Entry { + ParsedMimeInfo mime_info; + Supportability supportability = kSupportabilityUnknown; + + explicit Entry(const std::string& mime_string) : mime_info(mime_string) {} + }; + + // GetParsedMimeAndSupportability() will first try to find a cached Entry for + // the mime string. If no cached entry, a new Entry will be created with + // parsed mime information and supportability kSupportabilityUnknown. + // Ideally, we should decouple mime parsing and cache functionality, but + // considering that the cache is only for internal use, to avoid repeated + // lookups, we do parsing in this function. + const Entry& GetParsedMimeAndSupportability(const std::string& mime_string) { + ScopedLock scoped_lock(mutex_); + auto entry_iter = entries_.find(mime_string); + if (entry_iter != entries_.end()) { + return entry_iter->second; + } + + // We can't find anything from the cache. Parse mime string and cache + // parsed MimeType and ParsedMimeInfo. + auto insert_result = entries_.insert({mime_string, Entry(mime_string)}); + + fifo_queue_.push(insert_result.first); + while (fifo_queue_.size() > max_size_) { + entries_.erase(fifo_queue_.front()); + fifo_queue_.pop(); + } + SB_DCHECK(entries_.size() == fifo_queue_.size()); + + return insert_result.first->second; + } + + // CacheSupportability() will find the target entry and update the + // supportability. If there's no existing entry, it will parse the mime + // string and create one. + void CacheSupportability(const std::string& mime_string, + Supportability supportability) { + SB_DCHECK(!mime_string.empty()); + SB_DCHECK(supportability != kSupportabilityUnknown); + + { + ScopedLock scoped_lock(mutex_); + auto entry_iter = entries_.find(mime_string); + if (entry_iter != entries_.end()) { + entry_iter->second.supportability = supportability; + return; + } + } + + // Parse the mime string and create an entry. + GetParsedMimeAndSupportability(mime_string); + // Update the supportability again. + CacheSupportability(mime_string, supportability); + } + + // ClearCachedSupportabilities() will reset all cached |supportability|, but + // will not remove parsed mime infos. + void ClearCachedSupportabilities() { + ScopedLock scoped_lock(mutex_); + for (auto& iter : entries_) { + iter.second.supportability = kSupportabilityUnknown; + } + } + + void SetCacheMaxSize(int size) { max_size_ = size; } + + void DumpCache() { + ScopedLock scoped_lock(mutex_); + std::stringstream ss; + ss << "\n========Dumping MimeInfoCache========"; + for (const auto& entry_iter : entries_) { + const ParsedMimeInfo& mime_info = entry_iter.second.mime_info; + ss << "\nMime: " << entry_iter.first; + ss << "\n ParsedMimeInfo:"; + ss << "\n MimeType : " << mime_info.mime_type().ToString(); + if (mime_info.is_valid()) { + if (mime_info.has_audio_info()) { + const ParsedMimeInfo::AudioCodecInfo& audio_info = + mime_info.audio_info(); + ss << "\n Audio Codec : " + << GetMediaAudioCodecName(audio_info.codec); + ss << "\n Channels : " << audio_info.channels; + } + if (mime_info.has_video_info()) { + const ParsedMimeInfo::VideoCodecInfo& video_info = + mime_info.video_info(); + ss << "\n Video Codec : " + << GetMediaVideoCodecName(video_info.codec); + ss << "\n Profile : " << video_info.profile; + ss << "\n Level : " << video_info.level; + ss << "\n BitDepth : " << video_info.bit_depth; + ss << "\n PrimaryId : " + << GetMediaPrimaryIdName(video_info.primary_id); + ss << "\n TransferId : " + << GetMediaTransferIdName(video_info.transfer_id); + ss << "\n MatrixId : " + << GetMediaMatrixIdName(video_info.matrix_id); + ss << "\n Width : " << video_info.frame_width; + ss << "\n Height : " << video_info.frame_height; + ss << "\n Fps : " << video_info.fps; + ss << "\n DecodeToTexture : " + << (video_info.decode_to_texture_required ? "true" : "false"); + } + } else { + ss << "\n Mime info is not valid"; + } + + ss << "\n Supportability: "; + switch (entry_iter.second.supportability) { + case kSupportabilityUnknown: + ss << "Unknown"; + break; + case kSupportabilitySupported: + ss << "Supported"; + break; + case kSupportabilityNotSupported: + ss << "NotSupported"; + break; + } + } + ss << "\n========End of Dumping========"; + + SB_DLOG(INFO) << ss.str(); + } + + private: + typedef std::unordered_map<std::string, Entry> Entries; + + Mutex mutex_; + Entries entries_; + std::queue<Entries::iterator> fifo_queue_; + std::atomic_int max_size_{kDefaultCacheMaxSize}; +}; + +SB_ONCE_INITIALIZE_FUNCTION(MimeSupportabilityContainer, GetContainer); + +} // namespace + +// static +SB_ONCE_INITIALIZE_FUNCTION(MimeSupportabilityCache, + MimeSupportabilityCache::GetInstance); + +void MimeSupportabilityCache::SetCacheMaxSize(size_t size) { + GetContainer()->SetCacheMaxSize(size); +} + +Supportability MimeSupportabilityCache::GetMimeSupportability( + const std::string& mime, + ParsedMimeInfo* mime_info) { + // Get cached parsed mime infos and supportability. If no cache is found, + // MimeSupportabilityContainer will parse the mime string, and return a parsed + // MimeType and its parsed audio/video information. + const MimeSupportabilityContainer::Entry& entry = + GetContainer()->GetParsedMimeAndSupportability(mime); + + if (mime_info) { + // Return cached ParsedMimeInfo. + *mime_info = entry.mime_info; + } + + return is_enabled_ ? entry.supportability : kSupportabilityUnknown; +} + +void MimeSupportabilityCache::CacheMimeSupportability( + const std::string& mime, + Supportability supportability) { + if (!is_enabled_) { + return; + } + if (supportability == kSupportabilityUnknown) { + SB_LOG(WARNING) << "Rejected unknown supportability."; + return; + } + + GetContainer()->CacheSupportability(mime, supportability); +} + +void MimeSupportabilityCache::ClearCachedMimeSupportabilities() { + GetContainer()->ClearCachedSupportabilities(); +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/mime_supportability_cache.h b/starboard/shared/starboard/media/mime_supportability_cache.h new file mode 100644 index 0000000..7eb3d4f --- /dev/null +++ b/starboard/shared/starboard/media/mime_supportability_cache.h
@@ -0,0 +1,78 @@ +// Copyright 2022 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. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_ + +#include <atomic> +#include <string> + +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/parsed_mime_info.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +typedef enum Supportability { + kSupportabilityUnknown, + kSupportabilitySupported, + kSupportabilityNotSupported, +} Supportability; + +// TODO: add unit tests for MimeSupportabilityCache +class MimeSupportabilityCache { + public: + static MimeSupportabilityCache* GetInstance(); + + // When cache is not enabled, GetMimeSupportability() will always return + // kSupportabilityUnknown, and CacheMimeSupportability() will do nothing, + // but GetMimeSupportability() will still return parsed ParsedMimeInfo. + bool IsEnabled() const { return is_enabled_; } + void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } + + void SetCacheMaxSize(size_t size); + + // Get cached mime supportability. The parsed mime information would be + // returned via |mime_info| if it is not NULL. + Supportability GetMimeSupportability(const std::string& mime, + ParsedMimeInfo* mime_info); + // Cache mime supportability. If there's no cached parsed mime info and + // supportability for the mime, the function will parse the mime first and + // then update its supportability. + void CacheMimeSupportability(const std::string& mime, + Supportability supportability); + + // Clear all cached supportabilities. Note that it will not remove cached + // parsed mime infos. + void ClearCachedMimeSupportabilities(); + + private: + // Class can only be instanced via the singleton + MimeSupportabilityCache() {} + ~MimeSupportabilityCache() {} + + MimeSupportabilityCache(const MimeSupportabilityCache&) = delete; + MimeSupportabilityCache& operator=(const MimeSupportabilityCache&) = delete; + + std::atomic_bool is_enabled_{false}; +}; + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/mime_type.cc b/starboard/shared/starboard/media/mime_type.cc index 035de69..4954cee 100644 --- a/starboard/shared/starboard/media/mime_type.cc +++ b/starboard/shared/starboard/media/mime_type.cc
@@ -18,6 +18,7 @@ #include <iosfwd> #include <locale> #include <numeric> +#include <sstream> #include <string> #include <vector> @@ -33,26 +34,44 @@ typedef std::vector<std::string> Strings; -MimeType::ParamType GetParamTypeByValue(const std::string& value) { +void ParseParamTypeAndValue(const std::string& name, + const std::string& value, + MimeType::Param* param) { + SB_DCHECK(param); + + param->name = name; + if (value.size() >= 2 && value[0] == '\"' && value.back() == '\"') { + param->type = MimeType::kParamTypeString; + param->string_value = value.substr(1, value.size() - 2); + return; + } + + param->string_value = value; + int count; int i; if (SbStringScanF(value.c_str(), "%d%n", &i, &count) == 1 && count == value.size()) { - return MimeType::kParamTypeInteger; + param->type = MimeType::kParamTypeInteger; + param->int_value = i; + return; } float f; std::stringstream buffer(value); buffer.imbue(std::locale::classic()); buffer >> f; if (!buffer.fail() && buffer.rdbuf()->in_avail() == 0) { - return MimeType::kParamTypeFloat; + param->type = MimeType::kParamTypeFloat; + param->float_value = f; + return; } - if (value == "true" || value == "false") { - return MimeType::kParamTypeBoolean; + param->type = MimeType::kParamTypeBoolean; + param->bool_value = value == "true"; + return; } - return MimeType::kParamTypeString; + param->type = MimeType::kParamTypeString; } bool ContainsSpace(const std::string& str) { @@ -61,7 +80,6 @@ return true; } } - return false; } @@ -94,28 +112,29 @@ return result; } -const char* ParamTypeToString(MimeType::ParamType param_type) { - switch (param_type) { - case MimeType::kParamTypeInteger: - return "Integer"; - case MimeType::kParamTypeFloat: - return "Float"; - case MimeType::kParamTypeString: - return "String"; - case MimeType::kParamTypeBoolean: - return "Boolean"; - default: - SB_NOTREACHED(); - return "Unknown"; - } -} - } // namespace const int MimeType::kInvalidParamIndex = -1; -MimeType::MimeType(const std::string& content_type) - : raw_content_type_(content_type), is_valid_(false) { +// static +bool MimeType::ParseParamString(const std::string& param_string, Param* param) { + std::vector<std::string> name_and_value = SplitAndTrim(param_string, '='); + // The parameter must be on the format 'name=value' and neither |name| nor + // |value| can be empty. |value| must also not contain '|' and ';'. + if (name_and_value.size() != 2 || name_and_value[0].empty() || + name_and_value[1].empty() || + name_and_value[1].find('|') != std::string::npos || + name_and_value[1].find(';') != std::string::npos) { + return false; + } + + if (param) { + ParseParamTypeAndValue(name_and_value[0], name_and_value[1], param); + } + return true; +} + +MimeType::MimeType(const std::string& content_type) { Strings components = SplitAndTrim(content_type, ';'); if (components.empty()) { @@ -135,38 +154,23 @@ } type_ = type_and_container[0]; subtype_ = type_and_container[1]; + components.erase(components.begin()); // 2. Verify the parameters have valid formats, we want to be strict here. - bool has_codecs = false; for (Strings::iterator iter = components.begin(); iter != components.end(); ++iter) { - std::vector<std::string> name_and_value = SplitAndTrim(*iter, '='); - // The parameter must be on the format 'name=value' and neither |name| nor - // |value| can be empty. |value| must also not contain '|'. - if (name_and_value.size() != 2 || name_and_value[0].empty() || - name_and_value[1].empty() || - name_and_value[1].find('|') != std::string::npos) { + Param param; + if (!ParseParamString(*iter, ¶m)) { return; } - Param param; - if (name_and_value[1].size() > 2 && name_and_value[1][0] == '\"' && - *name_and_value[1].rbegin() == '\"') { - param.type = kParamTypeString; - param.value = name_and_value[1].substr(1, name_and_value[1].size() - 2); - } else { - param.type = GetParamTypeByValue(name_and_value[1]); - param.value = name_and_value[1]; - } - param.name = name_and_value[0]; + // There can only be no more than one codecs parameter and it has to be + // the first parameter if it is present. if (param.name == "codecs") { - // There can only be no more than one codecs parameter and it has to be - // the first parameter if it is present. - if (!params_.empty() || has_codecs) { + if (!params_.empty()) { return; - } else { - has_codecs = true; } + codecs_ = SplitAndTrim(param.string_value, ','); } params_.push_back(param); } @@ -174,84 +178,65 @@ is_valid_ = true; } -const std::vector<std::string>& MimeType::GetCodecs() const { - if (!codecs_.empty()) { - return codecs_; - } - int codecs_index = GetParamIndexByName("codecs"); - if (codecs_index != 0) { - return codecs_; - } - codecs_ = SplitAndTrim(params_[0].value, ','); - return codecs_; -} - int MimeType::GetParamCount() const { - SB_DCHECK(is_valid()); - return static_cast<int>(params_.size()); } MimeType::ParamType MimeType::GetParamType(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); return params_[index].type; } const std::string& MimeType::GetParamName(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); return params_[index].name; } +int MimeType::GetParamIndexByName(const char* name) const { + for (size_t i = 0; i < params_.size(); ++i) { + if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) { + return static_cast<int>(i); + } + } + return kInvalidParamIndex; +} + int MimeType::GetParamIntValue(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); - if (GetParamType(index) != kParamTypeInteger) { - return 0; + if (params_[index].type == kParamTypeInteger) { + return params_[index].int_value; } - - int i; - SbStringScanF(params_[index].value.c_str(), "%d", &i); - return i; + return 0; } float MimeType::GetParamFloatValue(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); - if (GetParamType(index) != kParamTypeInteger && - GetParamType(index) != kParamTypeFloat) { - return 0.0f; + if (params_[index].type == kParamTypeInteger) { + return params_[index].int_value; } - - float f; - std::stringstream buffer(params_[index].value.c_str()); - buffer.imbue(std::locale::classic()); - buffer >> f; - - return f; + if (params_[index].type == kParamTypeFloat) { + return params_[index].float_value; + } + return 0.0f; } const std::string& MimeType::GetParamStringValue(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); - return params_[index].value; + return params_[index].string_value; } bool MimeType::GetParamBoolValue(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); - if (GetParamType(index) != kParamTypeBoolean) { - return false; + if (params_[index].type == kParamTypeBoolean) { + return params_[index].bool_value; } - - return params_[index].value == "true"; + return false; } int MimeType::GetParamIntValue(const char* name, int default_value) const { @@ -289,23 +274,44 @@ return default_value; } -bool MimeType::RegisterBoolParameter(const char* name) { - return RegisterParameter(name, kParamTypeBoolean); -} - -bool MimeType::RegisterStringParameter(const char* name, - const std::string& pattern /* = "" */) { - if (!RegisterParameter(name, kParamTypeString)) { +bool MimeType::ValidateIntParameter(const char* name) const { + if (!is_valid()) { return false; } - int param_index = GetParamIndexByName(name); - if (param_index == kInvalidParamIndex || pattern.empty()) { + int index = GetParamIndexByName(name); + if (index == kInvalidParamIndex) { + return true; + } + return GetParamType(index) == kParamTypeInteger; +} + +bool MimeType::ValidateFloatParameter(const char* name) const { + if (!is_valid()) { + return false; + } + + int index = GetParamIndexByName(name); + if (index == kInvalidParamIndex) { + return true; + } + ParamType type = GetParamType(index); + return type == kParamTypeInteger || type == kParamTypeFloat; +} + +bool MimeType::ValidateStringParameter(const char* name, + const std::string& pattern) const { + if (!is_valid()) { + return false; + } + + int index = GetParamIndexByName(name); + if (pattern.empty() || index == kInvalidParamIndex) { return true; } - // Compare the parameter value with the provided pattern. - const std::string& param_value = GetParamStringValue(param_index); + const std::string& param_value = params_[index].string_value; + bool matches = false; size_t match_start = 0; while (!matches) { @@ -324,27 +330,10 @@ (match_end >= pattern.length() || pattern[match_end] == '|'); match_start = match_end + 1; } - - if (matches) { - return true; - } - - SB_LOG(INFO) << "Extended Parameter '" << name << "=" << param_value - << "' does not match the supplied pattern: '" << pattern << "'"; - is_valid_ = false; - return false; + return matches; } -int MimeType::GetParamIndexByName(const char* name) const { - for (size_t i = 0; i < params_.size(); ++i) { - if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) { - return static_cast<int>(i); - } - } - return kInvalidParamIndex; -} - -bool MimeType::RegisterParameter(const char* name, ParamType param_type) { +bool MimeType::ValidateBoolParameter(const char* name) const { if (!is_valid()) { return false; } @@ -353,25 +342,56 @@ if (index == kInvalidParamIndex) { return true; } + ParamType type = GetParamType(index); + return type == kParamTypeBoolean; +} - const std::string& param_value = GetParamStringValue(index); - ParamType parsed_type = GetParamType(index); - - // Check that the parameter can be returned as the requested type. - // Allowed conversions: - // Any Type -> String, Int -> Float - bool convertible = - param_type == parsed_type || param_type == kParamTypeString || - (param_type == kParamTypeFloat && parsed_type == kParamTypeInteger); - if (!convertible) { - SB_LOG(INFO) << "Extended Parameter '" << name << "=" << param_value - << "' can't be converted to " << ParamTypeToString(param_type); - is_valid_ = false; - return false; +std::string MimeType::ToString() const { + if (!is_valid()) { + return "{ InvalidMimeType }; "; } - - // All validations succeeded. - return true; + std::stringstream ss; + ss << "{ type: " << type(); + ss << ", subtype: " << subtype(); + ss << ", codecs: "; + if (codecs_.empty()) { + ss << "null"; + } else { + ss << codecs_[0]; + for (size_t i = 1; i < codecs_.size(); i++) { + ss << "|" << codecs_[i]; + } + } + ss << ", params: "; + if (params_.empty()) { + ss << "null"; + } else { + ss << "{ "; + for (size_t i = 0; i < params_.size(); i++) { + const Param& param = params_[i]; + if (i != 0) { + ss << ","; + } + ss << param.name << "="; + switch (param.type) { + case kParamTypeInteger: + ss << "(int)" << param.int_value; + break; + case kParamTypeFloat: + ss << "(float)" << param.float_value; + break; + case kParamTypeString: + ss << "(string)" << param.string_value; + break; + case kParamTypeBoolean: + ss << "(bool)" << (param.bool_value ? "true" : "false"); + break; + } + } + ss << " }"; + } + ss << " }"; + return ss.str(); } } // namespace media
diff --git a/starboard/shared/starboard/media/mime_type.h b/starboard/shared/starboard/media/mime_type.h index a05a301..5390ed0 100644 --- a/starboard/shared/starboard/media/mime_type.h +++ b/starboard/shared/starboard/media/mime_type.h
@@ -53,21 +53,36 @@ kParamTypeBoolean, }; + struct Param { + ParamType type; + std::string name; + std::string string_value; + union { + int int_value; + float float_value; + bool bool_value; + }; + }; + static const int kInvalidParamIndex; + // Expose the function as a helper function to parse a mime attribute. + static bool ParseParamString(const std::string& param_string, Param* param); + explicit MimeType(const std::string& content_type); - const std::string& raw_content_type() const { return raw_content_type_; } bool is_valid() const { return is_valid_; } const std::string& type() const { return type_; } const std::string& subtype() const { return subtype_; } - - const std::vector<std::string>& GetCodecs() const; + const std::vector<std::string>& GetCodecs() const { return codecs_; } int GetParamCount() const; ParamType GetParamType(int index) const; const std::string& GetParamName(int index) const; + // GetParamIndexByName() will return |kInvalidParamIndex| if the param name is + // not found. + int GetParamIndexByName(const char* name) const; int GetParamIntValue(int index) const; float GetParamFloatValue(int index) const; @@ -81,42 +96,29 @@ const std::string& default_value) const; bool GetParamBoolValue(const char* name, bool default_value) const; - // Pre-register a mime parameter of type boolean. - // Returns true if the mime type is valid and the value passes validation. - // If the parameter validation fails this MimeType will be marked invalid. - // NOTE: The function returns true for missing parameters. - bool RegisterBoolParameter(const char* name); - - // Pre-register a mime parameter of type string. - // Returns true if the mime type is valid and the value passes validation. + // Validate functions will return true if the param contains a valid value or + // if param name is not found. + bool ValidateIntParameter(const char* name) const; + bool ValidateFloatParameter(const char* name) const; // Allows passing a pattern on the format "value_1|...|value_n" // where the parameter value must match one of the values in the pattern in // order to be considered valid. - // If the parameter validation fails this MimeType will be marked invalid. - // NOTE: The function returns true for missing parameters. - bool RegisterStringParameter(const char* name, - const std::string& pattern = ""); + bool ValidateStringParameter(const char* name, + const std::string& pattern = "") const; + bool ValidateBoolParameter(const char* name) const; + + std::string ToString() const; private: - struct Param { - ParamType type; - std::string name; - std::string value; - }; - // Use std::vector as the number of components are usually small and we'd like // to keep the order of components. typedef std::vector<Param> Params; - int GetParamIndexByName(const char* name) const; - bool RegisterParameter(const char* name, ParamType type); - - const std::string raw_content_type_; - bool is_valid_; + bool is_valid_ = false; std::string type_; std::string subtype_; + std::vector<std::string> codecs_; Params params_; - mutable std::vector<std::string> codecs_; }; } // namespace media
diff --git a/starboard/shared/starboard/media/mime_type_test.cc b/starboard/shared/starboard/media/mime_type_test.cc index 854444d..52705f3 100644 --- a/starboard/shared/starboard/media/mime_type_test.cc +++ b/starboard/shared/starboard/media/mime_type_test.cc
@@ -22,19 +22,6 @@ namespace media { namespace { -TEST(MimeTypeTest, RawContentType) { - { - const char kContentTypeWithSpace[] = " video/mp4; name0=123; name1=123.4 "; - MimeType mime_type(kContentTypeWithSpace); - EXPECT_EQ(mime_type.raw_content_type(), kContentTypeWithSpace); - } - { - const char kInvalidContentType[] = "video /mp4"; - MimeType mime_type(kInvalidContentType); - EXPECT_EQ(mime_type.raw_content_type(), kInvalidContentType); - } -} - TEST(MimeTypeTest, EmptyString) { MimeType mime_type(""); EXPECT_FALSE(mime_type.is_valid()); @@ -343,69 +330,116 @@ EXPECT_FALSE(mime_type.GetParamBoolValue("float", false)); } -TEST(MimeTypeTest, RegisterAndValidateParamsWithPatterns) { +TEST(MimeTypeTest, ParseInvalidParamString) { + EXPECT_FALSE(MimeType::ParseParamString("", nullptr)); + EXPECT_FALSE(MimeType::ParseParamString("invalid", nullptr)); + EXPECT_FALSE(MimeType::ParseParamString("val=0;", nullptr)); + EXPECT_FALSE(MimeType::ParseParamString("val=a|b", nullptr)); + EXPECT_FALSE(MimeType::ParseParamString("val=", nullptr)); +} + +TEST(MimeTypeTest, ParseValidParamString) { + MimeType::Param result; + + EXPECT_TRUE(MimeType::ParseParamString("val=0", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeInteger); + EXPECT_EQ(result.int_value, 0); + + EXPECT_TRUE(MimeType::ParseParamString("val=1", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeInteger); + EXPECT_EQ(result.int_value, 1); + + EXPECT_TRUE(MimeType::ParseParamString("val=-1", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeInteger); + EXPECT_EQ(result.int_value, -1); + + EXPECT_TRUE(MimeType::ParseParamString("val=0.0", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeFloat); + EXPECT_EQ(result.float_value, 0.0f); + + EXPECT_TRUE(MimeType::ParseParamString("val=1.0", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeFloat); + EXPECT_EQ(result.float_value, 1.0f); + + EXPECT_TRUE(MimeType::ParseParamString("val=-1.0", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeFloat); + EXPECT_EQ(result.float_value, -1.0f); + + EXPECT_TRUE(MimeType::ParseParamString("val=true", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeBoolean); + EXPECT_EQ(result.bool_value, true); + + EXPECT_TRUE(MimeType::ParseParamString("val=false", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeBoolean); + EXPECT_EQ(result.bool_value, false); + + EXPECT_TRUE(MimeType::ParseParamString("val=\"\"", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeString); + EXPECT_EQ(result.string_value, ""); + + EXPECT_TRUE(MimeType::ParseParamString("val=\"abc\"", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeString); + EXPECT_EQ(result.string_value, "abc"); + + EXPECT_TRUE(MimeType::ParseParamString("val=abc", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeString); + EXPECT_EQ(result.string_value, "abc"); +} + +TEST(MimeTypeTest, ValidateParamsWithPatterns) { MimeType mime_type("video/mp4; string=yes"); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "yes")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "yes|no")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "no|yes|no")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "no|no|yes")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "noyes|yes")); - EXPECT_TRUE(mime_type.is_valid()); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "yes")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "yes|no")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "no|yes|no")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "no|no|yes")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "noyes|yes")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no")); } -TEST(MimeTypeTest, RegisterAndValidateParamsWithShortPatterns) { +TEST(MimeTypeTest, ValidateParamsWithShortPatterns) { MimeType mime_type("video/mp4; string=y"); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "y")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "y|n")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "n|y")); - EXPECT_TRUE(mime_type.is_valid()); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "y")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "y|n")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "n|y")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "n")); } -TEST(MimeTypeTest, RegisterAndValidateParamsWithPartialMatches) { - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "yesno|no")); - EXPECT_FALSE(mime_type.is_valid()); - } - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "noyes|no")); - EXPECT_FALSE(mime_type.is_valid()); - } - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "no|yesno")); - EXPECT_FALSE(mime_type.is_valid()); - } - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "no|noyes")); - EXPECT_FALSE(mime_type.is_valid()); - } +TEST(MimeTypeTest, ValidateParamsWithPartialMatches) { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "yesno|no")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "noyes|no")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no|yesno")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no|noyes")); } -TEST(MimeTypeTest, MissingParamReturnsTrueOnRegistration) { +TEST(MimeTypeTest, ValidateMissingParam) { MimeType mime_type("video/mp4"); - EXPECT_TRUE(mime_type.RegisterStringParameter("string")); - EXPECT_TRUE(mime_type.is_valid()); + EXPECT_TRUE(mime_type.ValidateStringParameter("string")); EXPECT_EQ(mime_type.GetParamStringValue("string", "default"), "default"); } -TEST(MimeTypeTest, RegisterAndValidateParamsWithEmptyishPattern) { - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "|")); - } - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "||")); - } +TEST(MimeTypeTest, ValidateParamsWithEmptyishPattern) { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "|")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "||")); } -TEST(MimeTypeTest, CannotRegisterParamWithInvalidMimeType) { +TEST(MimeTypeTest, ValidateParamWithInvalidMimeType) { MimeType mime_type("video/mp4; string="); ASSERT_FALSE(mime_type.is_valid()); - EXPECT_FALSE(mime_type.RegisterStringParameter("string")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string")); } } // namespace
diff --git a/starboard/shared/starboard/media/mime_util.cc b/starboard/shared/starboard/media/mime_util.cc new file mode 100644 index 0000000..518338f --- /dev/null +++ b/starboard/shared/starboard/media/mime_util.cc
@@ -0,0 +1,425 @@ +// Copyright 2022 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 "starboard/shared/starboard/media/mime_util.h" + +#include <cstring> +#include <string> +#include <vector> + +#include "starboard/common/log.h" +#include "starboard/common/media.h" +#include "starboard/log.h" +#include "starboard/shared/starboard/media/bitrate_supportability_cache.h" +#include "starboard/shared/starboard/media/key_system_supportability_cache.h" +#include "starboard/shared/starboard/media/media_support_internal.h" +#include "starboard/shared/starboard/media/mime_supportability_cache.h" +#include "starboard/shared/starboard/media/mime_type.h" +#include "starboard/shared/starboard/media/parsed_mime_info.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +// RemoveAttributeFromMime() will return a new mime string with the specified +// attribute removed. If |attribute_string| is not null, the removed attribute +// string will be returned via |attribute_string|. Following are some examples: +// mime: "video/webm; codecs=\"vp9\"; bitrate=300000" +// attribute_name: "bitrate" +// return: "video/webm; codecs=\"vp9\"" +// attribute_string: "bitrate=300000" +// +// mime: "video/webm; codecs=\"vp9\"; bitrate=300000; eotf=bt709" +// attribute_name: "bitrate" +// return: "video/webm; codecs=\"vp9\"; eotf=bt709" +// attribute_string: "bitrate=300000" +// +// mime: "bitrate=300000" +// attribute_name: "bitrate" +// return: "" +// attribute_string: "bitrate=300000" +std::string RemoveAttributeFromMime(const char* mime, + const char* attribute_name, + std::string* attribute_string) { + size_t name_length = strlen(attribute_name); + if (name_length == 0) { + return mime; + } + + std::string mime_without_attribute; + const char* start_pos = strstr(mime, attribute_name); + while (start_pos) { + if ((start_pos == mime || start_pos[-1] == ';' || isspace(start_pos[-1])) && + (start_pos[name_length] && + (start_pos[name_length] == '=' || isspace(start_pos[name_length])))) { + break; + } + start_pos += name_length; + start_pos = strstr(start_pos, attribute_name); + } + + if (!start_pos) { + // Target attribute is not found. + return std::string(mime); + } + const char* end_pos = strstr(start_pos, ";"); + if (end_pos) { + // There may be other attribute after target attribute. + if (attribute_string) { + // Returned |attribute_string| will not have a trailing ';'. + attribute_string->assign(start_pos, end_pos - start_pos); + } + + end_pos++; + // Remove leading spaces. + while (*end_pos && isspace(*end_pos)) { + end_pos++; + } + if (*end_pos) { + // Append the string after target attribute. + mime_without_attribute = std::string(mime, start_pos - mime); + mime_without_attribute.append(end_pos); + } else { + // Target attribute is the last one. Remove trailing spaces. + size_t mime_length = start_pos - mime; + while (mime_length > 0 && (isspace(mime[mime_length - 1]))) { + mime_length--; + } + mime_without_attribute = std::string(mime, mime_length); + } + } else { + // It can't find a trailing ';'. The target attribute must be the last one. + size_t mime_length = start_pos - mime; + // Remove trailing spaces. + while (mime_length > 0 && (isspace(mime[mime_length - 1]))) { + mime_length--; + } + // Remove the trailing ';'. + if (mime_length > 0 && mime[mime_length - 1] == ';') { + mime_length--; + } + mime_without_attribute = std::string(mime, mime_length); + if (attribute_string) { + *attribute_string = std::string(start_pos); + } + } + return mime_without_attribute; +} + +// Use SbMediaGetAudioConfiguration() to check if the platform can support +// |channels|. +bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) { + int count = SbMediaGetAudioOutputCount(); + + for (int output_index = 0; output_index < count; ++output_index) { + SbMediaAudioConfiguration configuration; + if (!SbMediaGetAudioConfiguration(output_index, &configuration)) { + continue; + } + + if (configuration.coding_type == coding_type && + configuration.number_of_channels >= channels) { + return true; + } + } + + return false; +} + +bool IsSupportedKeySystem(SbMediaAudioCodec codec, const char* key_system) { + SB_DCHECK(key_system); + // KeySystemSupportabilityCache() should always return supported for empty + // |key_system|, so here it should always be non empty. + SB_DCHECK(strlen(key_system) > 0); + + return SbMediaIsSupported(kSbMediaVideoCodecNone, codec, key_system); +} + +bool IsSupportedKeySystem(SbMediaVideoCodec codec, const char* key_system) { + SB_DCHECK(key_system); + // KeySystemSupportabilityCache() should always return supported for empty + // |key_system|, so here it should always be non empty. + SB_DCHECK(strlen(key_system) > 0); + + return SbMediaIsSupported(codec, kSbMediaAudioCodecNone, key_system); +} + +bool IsSupportedAudioCodec(const ParsedMimeInfo& mime_info) { + SB_DCHECK(mime_info.is_valid()); + SB_DCHECK(mime_info.mime_type().is_valid()); + SB_DCHECK(mime_info.has_audio_info()); + + const MimeType& mime_type = mime_info.mime_type(); + const ParsedMimeInfo::AudioCodecInfo& audio_info = mime_info.audio_info(); + + switch (audio_info.codec) { + case kSbMediaAudioCodecNone: + SB_NOTREACHED(); + return false; + case kSbMediaAudioCodecAac: + case kSbMediaAudioCodecAc3: + case kSbMediaAudioCodecEac3: + if (mime_type.subtype() != "mp4") { + return false; + } + break; + case kSbMediaAudioCodecOpus: + case kSbMediaAudioCodecVorbis: + if (mime_type.subtype() != "webm") { + return false; + } + break; +#if SB_API_VERSION >= 14 + case kSbMediaAudioCodecMp3: + case kSbMediaAudioCodecFlac: + case kSbMediaAudioCodecPcm: + return false; +#endif // SB_API_VERSION >= 14 + } + + if (!IsAudioOutputSupported(kSbMediaAudioCodingTypePcm, + audio_info.channels)) { + return false; + } + + return SbMediaIsAudioSupported(audio_info.codec, &mime_type, + audio_info.bitrate); +} + +bool IsSupportedVideoCodec(const ParsedMimeInfo& mime_info) { + SB_DCHECK(mime_info.is_valid()); + SB_DCHECK(mime_info.mime_type().is_valid()); + SB_DCHECK(mime_info.has_video_info()); + + const MimeType& mime_type = mime_info.mime_type(); + const ParsedMimeInfo::VideoCodecInfo& video_info = mime_info.video_info(); + + switch (video_info.codec) { + case kSbMediaVideoCodecNone: + SB_NOTREACHED(); + return false; + case kSbMediaVideoCodecH264: + case kSbMediaVideoCodecH265: + if (mime_type.subtype() != "mp4") { + return false; + } + break; + case kSbMediaVideoCodecMpeg2: + case kSbMediaVideoCodecTheora: + return false; // No associated container in YT. + case kSbMediaVideoCodecVc1: + case kSbMediaVideoCodecAv1: + if (mime_type.subtype() != "mp4") { + return false; + } + break; + case kSbMediaVideoCodecVp8: + if (mime_type.subtype() != "webm") { + return false; + } + break; + case kSbMediaVideoCodecVp9: + if (mime_type.subtype() != "mp4" && mime_type.subtype() != "webm") { + return false; + } + break; + } + + std::string cryptoblockformat = + mime_type.GetParamStringValue("cryptoblockformat", ""); + if (!cryptoblockformat.empty()) { + if (mime_type.subtype() != "webm" || cryptoblockformat != "subsample") { + return false; + } + } + + return SbMediaIsVideoSupported( + video_info.codec, &mime_type, video_info.profile, video_info.level, + video_info.bit_depth, video_info.primary_id, video_info.transfer_id, + video_info.matrix_id, video_info.frame_width, video_info.frame_height, + video_info.bitrate, video_info.fps, + video_info.decode_to_texture_required); +} + +bool ValidateAndParseBitrate(const std::string& bitrate_string, int* bitrate) { + SB_DCHECK(!bitrate_string.empty()); + + MimeType::Param param; + if (!MimeType::ParseParamString(bitrate_string, ¶m)) { + return false; + } + if (param.type != MimeType::kParamTypeInteger) { + return false; + } + if (bitrate) { + *bitrate = param.int_value; + } + return true; +} + +} // namespace + +SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime, + const char* key_system) { + SB_DCHECK(mime); + SB_DCHECK(key_system); + + // Remove bitrate from mime string and read bitrate if presents. + std::string bitrate_string; + std::string mime_without_bitrate = + RemoveAttributeFromMime(mime, "bitrate", &bitrate_string); + int bitrate = 0; + if (!bitrate_string.empty()) { + if (!ValidateAndParseBitrate(bitrate_string, &bitrate)) { + return kSbMediaSupportTypeNotSupported; + } + } + + if (bitrate < 0) { + // Reject invalid bitrate. + return kSbMediaSupportTypeNotSupported; + } + + // Get cached parsed mime infos and supportability. If it is not found in the + // cache, MimeSupportabilityCache would parse the mime string and return a + // ParsedMimeInfo. + ParsedMimeInfo mime_info; + Supportability mime_supportability = + MimeSupportabilityCache::GetInstance()->GetMimeSupportability( + mime_without_bitrate, &mime_info); + // Overwrite the bitrate. + mime_info.SetBitrate(bitrate); + + if (mime_info.disable_cache()) { + // Disable all caches if required. + mime_supportability = kSupportabilityUnknown; + MimeSupportabilityCache::GetInstance()->SetCacheEnabled(false); + KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(false); + BitrateSupportabilityCache::GetInstance()->SetCacheEnabled(false); + } + + // Reject mime if cached result is not supported. + if (mime_supportability == kSupportabilityNotSupported) { + return kSbMediaSupportTypeNotSupported; + } + + // Reject mime if parsed mime info is invalid. + if (!mime_info.is_valid()) { + return kSbMediaSupportTypeNotSupported; + } + + const MimeType& mime_type = mime_info.mime_type(); + const std::vector<std::string>& codecs = mime_type.GetCodecs(); + + // Quick check for mp4 format. + if (codecs.size() == 0) { + // This happens when the H5 player is either querying for progressive + // playback support, or probing for generic mp4 support without specific + // codecs. + if (mime_type.subtype() == "mp4") { + return kSbMediaSupportTypeMaybe; + } else { + return kSbMediaSupportTypeNotSupported; + } + } + + // Reject mime if it doesn't have any valid codec info. + if (!mime_info.has_audio_info() && !mime_info.has_video_info()) { + return kSbMediaSupportTypeNotSupported; + } + + // Get cached key system supportability. Note that we check if audio or video + // codec supports key system separately. + if (mime_info.has_audio_info()) { + Supportability key_system_supportability = + KeySystemSupportabilityCache::GetInstance()->GetKeySystemSupportability( + mime_info.audio_info().codec, key_system); + if (key_system_supportability == kSupportabilityUnknown) { + key_system_supportability = + IsSupportedKeySystem(mime_info.audio_info().codec, key_system) + ? kSupportabilitySupported + : kSupportabilityNotSupported; + KeySystemSupportabilityCache::GetInstance()->CacheKeySystemSupportability( + mime_info.audio_info().codec, key_system, key_system_supportability); + } + // Reject mime if audio codec doesn't support the key system. + if (key_system_supportability == kSupportabilityNotSupported) { + return kSbMediaSupportTypeNotSupported; + } + } + if (mime_info.has_video_info()) { + Supportability key_system_supportability = + KeySystemSupportabilityCache::GetInstance()->GetKeySystemSupportability( + mime_info.video_info().codec, key_system); + if (key_system_supportability == kSupportabilityUnknown) { + key_system_supportability = + IsSupportedKeySystem(mime_info.video_info().codec, key_system) + ? kSupportabilitySupported + : kSupportabilityNotSupported; + KeySystemSupportabilityCache::GetInstance()->CacheKeySystemSupportability( + mime_info.video_info().codec, key_system, key_system_supportability); + } + // Reject mime if video codec doesn't the key system. + if (key_system_supportability == kSupportabilityNotSupported) { + return kSbMediaSupportTypeNotSupported; + } + } + + // Get cached bitrate supportability. + Supportability bitrate_supportability = + BitrateSupportabilityCache::GetInstance()->GetBitrateSupportability( + mime_info); + + // Reject mime if bitrate is not supported. + if (bitrate_supportability == kSupportabilityNotSupported) { + return kSbMediaSupportTypeNotSupported; + } + + // Return supported if mime and bitrate are all supported. + if (mime_supportability == kSupportabilitySupported && + bitrate_supportability == kSupportabilitySupported) { + return kSbMediaSupportTypeProbably; + } + + // At this point, either mime or bitrate supportability must be unknown. + // Call platform functions to check if they are supported. + SB_DCHECK(mime_supportability == kSupportabilityUnknown || + bitrate_supportability == kSupportabilityUnknown); + if (mime_info.has_audio_info() && !IsSupportedAudioCodec(mime_info)) { + mime_supportability = kSupportabilityNotSupported; + } else if (mime_info.has_video_info() && !IsSupportedVideoCodec(mime_info)) { + mime_supportability = kSupportabilityNotSupported; + } else { + mime_supportability = kSupportabilitySupported; + } + + // Cache mime supportability when bitrate supportability is known. + if (bitrate_supportability == kSupportabilitySupported) { + MimeSupportabilityCache::GetInstance()->CacheMimeSupportability( + mime_without_bitrate, mime_supportability); + } + + SB_DCHECK(mime_supportability != kSupportabilityUnknown); + return mime_supportability == kSupportabilitySupported + ? kSbMediaSupportTypeProbably + : kSbMediaSupportTypeNotSupported; +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/mime_util.h b/starboard/shared/starboard/media/mime_util.h new file mode 100644 index 0000000..e79dac1 --- /dev/null +++ b/starboard/shared/starboard/media/mime_util.h
@@ -0,0 +1,60 @@ +// Copyright 2022 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. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_ + +#include <string> + +#include "starboard/media.h" +#include "starboard/shared/internal_only.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +// Calls to canPlayType() and isTypeSupported() are redirected to this function. +// Following are some example inputs: +// canPlayType(video/mp4) +// canPlayType(video/mp4; codecs="avc1.42001E, mp4a.40.2") +// canPlayType(video/webm) +// isTypeSupported(video/webm; codecs="vp9") +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=640) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=99999) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=360) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=99999) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=30) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=9999) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=300000) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=2000000000) +// isTypeSupported(audio/mp4; codecs="mp4a.40.2") +// isTypeSupported(audio/webm; codecs="vorbis") +// isTypeSupported(video/webm; codecs="vp9") +// isTypeSupported(video/webm; codecs="vp9") +// isTypeSupported(audio/webm; codecs="opus") +// isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=2) +// isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=99) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=true) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=false) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=invalid) +SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime, + const char* key_system); + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_
diff --git a/starboard/shared/starboard/media/parsed_mime_info.cc b/starboard/shared/starboard/media/parsed_mime_info.cc new file mode 100644 index 0000000..8894289 --- /dev/null +++ b/starboard/shared/starboard/media/parsed_mime_info.cc
@@ -0,0 +1,178 @@ +// Copyright 2022 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 "starboard/shared/starboard/media/parsed_mime_info.h" + +#include <string> + +#include "starboard/common/log.h" +#include "starboard/common/media.h" +#include "starboard/shared/starboard/media/codec_util.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +const int64_t kDefaultAudioChannels = 2; + +// Turns |eotf| into value of SbMediaTransferId. If |eotf| isn't recognized the +// function returns kSbMediaTransferIdUnknown. +// This function supports all eotfs required by YouTube TV HTML5 Technical +// Requirements. +SbMediaTransferId GetTransferIdFromString(const std::string& transfer_id) { + if (transfer_id == "bt709") { + return kSbMediaTransferIdBt709; + } else if (transfer_id == "smpte2084") { + return kSbMediaTransferIdSmpteSt2084; + } else if (transfer_id == "arib-std-b67") { + return kSbMediaTransferIdAribStdB67; + } + return kSbMediaTransferIdUnknown; +} + +} // namespace + +ParsedMimeInfo::ParsedMimeInfo(const std::string& mime_string) + : mime_type_(mime_string) { + ParseMimeInfo(); +} + +void ParsedMimeInfo::SetBitrate(int bitrate) { + audio_info_.bitrate = bitrate; + video_info_.bitrate = bitrate; +} + +void ParsedMimeInfo::ParseMimeInfo() { + if (!mime_type_.is_valid()) { + is_valid_ = false; + return; + } + + // Read "disablecache". + if (!mime_type_.ValidateBoolParameter("disablecache")) { + is_valid_ = false; + return; + } + disable_cache_ = mime_type_.GetParamBoolValue("disablecache", false); + + // We only support audio or video type. + if (mime_type_.type() != "audio" && mime_type_.type() != "video") { + is_valid_ = false; + return; + } + + auto codecs = mime_type_.GetCodecs(); + // We only support up to one audio codec and one video codec. + if (codecs.size() > 2) { + is_valid_ = false; + return; + } + + for (const auto& codec : codecs) { + if (!has_audio_info() && ParseAudioInfo(codec)) { + continue; + } + if (!has_video_info() && ParseVideoInfo(codec)) { + continue; + } + // It either has an invalid codec or has two codecs of same type. + ResetCodecInfos(); + is_valid_ = false; + return; + } +} + +bool ParsedMimeInfo::ParseAudioInfo(const std::string& codec) { + SB_DCHECK(mime_type_.is_valid()); + SB_DCHECK(!has_audio_info()); + + SbMediaAudioCodec audio_codec = GetAudioCodecFromString(codec.c_str()); + if (audio_codec == kSbMediaAudioCodecNone) { + return false; + } + if (!mime_type_.ValidateIntParameter("channels") || + !mime_type_.ValidateIntParameter("bitrate")) { + return false; + } + audio_info_.codec = audio_codec; + audio_info_.channels = + mime_type_.GetParamIntValue("channels", kDefaultAudioChannels); + audio_info_.bitrate = mime_type_.GetParamIntValue("bitrate", 0); + + return audio_info_.channels >= 0 && audio_info_.bitrate >= 0; +} + +bool ParsedMimeInfo::ParseVideoInfo(const std::string& codec) { + SB_DCHECK(mime_type_.is_valid()); + SB_DCHECK(!has_video_info()); + + if (!ParseVideoCodec(codec.c_str(), &video_info_.codec, &video_info_.profile, + &video_info_.level, &video_info_.bit_depth, + &video_info_.primary_id, &video_info_.transfer_id, + &video_info_.matrix_id)) { + return false; + } + + if (video_info_.codec == kSbMediaVideoCodecNone) { + return false; + } + + std::string eotf = mime_type_.GetParamStringValue("eotf", ""); + if (!eotf.empty()) { + SbMediaTransferId transfer_id_from_eotf = GetTransferIdFromString(eotf); + if (transfer_id_from_eotf == kSbMediaTransferIdUnknown) { + // The eotf is an unknown value, mark the codec info as invalid. + SB_LOG(WARNING) << "Unknown eotf " << eotf << "."; + return false; + } + SB_LOG_IF(WARNING, + video_info_.transfer_id != kSbMediaTransferIdUnspecified && + video_info_.transfer_id != transfer_id_from_eotf) + << "transfer_id " << video_info_.transfer_id + << " set by the codec string \"" << video_info_.codec + << "\" will be overwritten by the eotf attribute " << eotf; + video_info_.transfer_id = transfer_id_from_eotf; + } + + if (!mime_type_.ValidateIntParameter("width") || + !mime_type_.ValidateIntParameter("height") || + !mime_type_.ValidateIntParameter("framerate") || + !mime_type_.ValidateIntParameter("bitrate") || + !mime_type_.ValidateBoolParameter("decode-to-texture")) { + return false; + } + + video_info_.frame_width = mime_type_.GetParamIntValue("width", 0); + video_info_.frame_height = mime_type_.GetParamIntValue("height", 0); + video_info_.fps = mime_type_.GetParamIntValue("framerate", 0); + video_info_.bitrate = mime_type_.GetParamIntValue("bitrate", 0); + video_info_.decode_to_texture_required = + mime_type_.GetParamBoolValue("decode-to-texture", false); + + return video_info_.frame_width >= 0 && video_info_.frame_height >= 0 && + video_info_.fps >= 0 && video_info_.bitrate >= 0; +} + +void ParsedMimeInfo::ResetCodecInfos() { + audio_info_.codec = kSbMediaAudioCodecNone; + video_info_.codec = kSbMediaVideoCodecNone; +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/parsed_mime_info.h b/starboard/shared/starboard/media/parsed_mime_info.h new file mode 100644 index 0000000..5be03a5 --- /dev/null +++ b/starboard/shared/starboard/media/parsed_mime_info.h
@@ -0,0 +1,106 @@ +// Copyright 2022 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. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_ + +#include <string> + +#include "starboard/common/log.h" +#include "starboard/media.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/mime_type.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +// TODO: add unit tests for ParsedMimeInfo +class ParsedMimeInfo { + public: + struct AudioCodecInfo { + SbMediaAudioCodec codec = kSbMediaAudioCodecNone; + int channels; + int bitrate; + }; + + struct VideoCodecInfo { + SbMediaVideoCodec codec = kSbMediaVideoCodecNone; + int profile; + int level; + int bit_depth; + SbMediaPrimaryId primary_id; + SbMediaTransferId transfer_id; + SbMediaMatrixId matrix_id; + int frame_width; + int frame_height; + int fps; + int bitrate; + bool decode_to_texture_required; + }; + + ParsedMimeInfo() : mime_type_("") {} + explicit ParsedMimeInfo(const std::string& mime_string); + + const MimeType& mime_type() const { return mime_type_; } + + bool is_valid() const { return is_valid_; } + + // A switch in the mime string to disable caches. + bool disable_cache() const { return disable_cache_; } + + bool has_audio_info() const { + return audio_info_.codec != kSbMediaAudioCodecNone; + } + // Extra information for audio codec. Note that audio_info() can only be + // used when has_audio_info() returns true. + const AudioCodecInfo& audio_info() const { + SB_DCHECK(has_audio_info()); + return audio_info_; + } + + bool has_video_info() const { + return video_info_.codec != kSbMediaVideoCodecNone; + } + // Extra information for video codec. Note that video_info() can only be + // used when has_video_info() returns true. + const VideoCodecInfo& video_info() const { + SB_DCHECK(has_video_info()); + return video_info_; + } + + // Allow to overwrite the bitrate. + void SetBitrate(int bitrate); + + private: + void ParseMimeInfo(); + bool ParseAudioInfo(const std::string& codec); + bool ParseVideoInfo(const std::string& codec); + + void ResetCodecInfos(); + + MimeType mime_type_; + bool is_valid_ = true; + bool disable_cache_ = false; + AudioCodecInfo audio_info_; + VideoCodecInfo video_info_; +}; + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_
diff --git a/starboard/shared/starboard/player/filter/testing/test_util.cc b/starboard/shared/starboard/player/filter/testing/test_util.cc index 9bfdb7e..d87f71d 100644 --- a/starboard/shared/starboard/player/filter/testing/test_util.cc +++ b/starboard/shared/starboard/player/filter/testing/test_util.cc
@@ -18,6 +18,7 @@ #include "starboard/common/log.h" #include "starboard/directory.h" #include "starboard/shared/starboard/media/media_support_internal.h" +#include "starboard/shared/starboard/media/mime_type.h" #include "starboard/shared/starboard/player/filter/player_components.h" #include "starboard/shared/starboard/player/filter/stub_player_components_factory.h" #include "starboard/shared/starboard/player/filter/video_decoder_internal.h" @@ -33,6 +34,7 @@ namespace testing { namespace { +using ::starboard::shared::starboard::media::MimeType; using ::testing::AssertionFailure; using ::testing::AssertionResult; using ::testing::AssertionSuccess; @@ -155,12 +157,11 @@ } // Filter files of unsupported codec. - if (!SbMediaIsAudioSupported( - audio_file_info.audio_codec, - GetContentTypeFromAudioCodec(audio_file_info.audio_codec, - extra_mime_attributes) - .c_str(), - audio_file_info.bitrate)) { + const std::string audio_mime = GetContentTypeFromAudioCodec( + audio_file_info.audio_codec, extra_mime_attributes); + const MimeType audio_mime_type(audio_mime.c_str()); + if (!SbMediaIsAudioSupported(audio_file_info.audio_codec, &audio_mime_type, + audio_file_info.bitrate)) { continue; } @@ -203,13 +204,15 @@ const auto& video_sample_info = dmp_reader.GetPlayerSampleInfo(kSbMediaTypeVideo, 0) .video_sample_info; - + const std::string video_mime = dmp_reader.video_mime_type(); + const MimeType video_mime_type(video_mime.c_str()); if (SbMediaIsVideoSupported( - dmp_reader.video_codec(), dmp_reader.video_mime_type().c_str(), - -1, -1, 8, kSbMediaPrimaryIdUnspecified, - kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified, - video_sample_info.frame_width, video_sample_info.frame_height, - dmp_reader.video_bitrate(), dmp_reader.video_fps(), false)) { + dmp_reader.video_codec(), + video_mime.size() > 0 ? &video_mime_type : nullptr, -1, -1, 8, + kSbMediaPrimaryIdUnspecified, kSbMediaTransferIdUnspecified, + kSbMediaMatrixIdUnspecified, video_sample_info.frame_width, + video_sample_info.frame_height, dmp_reader.video_bitrate(), + dmp_reader.video_fps(), false)) { test_params.push_back(std::make_tuple(filename, output_mode)); } }
diff --git a/starboard/shared/starboard/player/player_create.cc b/starboard/shared/starboard/player/player_create.cc index c2e0a07..16bc57f 100644 --- a/starboard/shared/starboard/player/player_create.cc +++ b/starboard/shared/starboard/player/player_create.cc
@@ -30,12 +30,13 @@ #include "starboard/shared/starboard/player/video_dmp_writer.h" #endif // SB_PLAYER_ENABLE_VIDEO_DUMPER -using starboard::shared::media_session:: +using ::starboard::shared::media_session:: UpdateActiveSessionPlatformPlaybackState; -using starboard::shared::media_session::kPlaying; -using starboard::shared::starboard::player::filter:: +using ::starboard::shared::media_session::kPlaying; +using ::starboard::shared::starboard::media::MimeType; +using ::starboard::shared::starboard::player::filter:: FilterBasedPlayerWorkerHandler; -using starboard::shared::starboard::player::PlayerWorker; +using ::starboard::shared::starboard::player::PlayerWorker; SbPlayer SbPlayerCreate(SbWindow window, const SbPlayerCreationParam* creation_param, @@ -135,16 +136,19 @@ } const int64_t kDefaultBitRate = 0; - if (audio_codec != kSbMediaAudioCodecNone && - !SbMediaIsAudioSupported(audio_codec, audio_mime, kDefaultBitRate)) { - SB_LOG(ERROR) << "Unsupported audio codec " - << starboard::GetMediaAudioCodecName(audio_codec) << "."; - player_error_func( - kSbPlayerInvalid, context, kSbPlayerErrorDecode, - starboard::FormatString("Unsupported audio codec: %s", - starboard::GetMediaAudioCodecName(audio_codec)) - .c_str()); - return kSbPlayerInvalid; + if (audio_codec != kSbMediaAudioCodecNone) { + const MimeType audio_mime_type(audio_mime); + if (!SbMediaIsAudioSupported(audio_codec, &audio_mime_type, + kDefaultBitRate)) { + SB_LOG(ERROR) << "Unsupported audio codec " + << starboard::GetMediaAudioCodecName(audio_codec) << "."; + player_error_func(kSbPlayerInvalid, context, kSbPlayerErrorDecode, + starboard::FormatString( + "Unsupported audio codec: %s", + starboard::GetMediaAudioCodecName(audio_codec)) + .c_str()); + return kSbPlayerInvalid; + } } const int kDefaultProfile = -1; @@ -153,22 +157,24 @@ const int kDefaultFrameWidth = 0; const int kDefaultFrameHeight = 0; const int kDefaultFrameRate = 0; - if (video_codec != kSbMediaVideoCodecNone && - !SbMediaIsVideoSupported( - video_codec, video_mime, kDefaultProfile, kDefaultLevel, - kDefaultColorDepth, kSbMediaPrimaryIdUnspecified, - kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified, - kDefaultFrameWidth, kDefaultFrameHeight, kDefaultBitRate, - kDefaultFrameRate, - output_mode == kSbPlayerOutputModeDecodeToTexture)) { - SB_LOG(ERROR) << "Unsupported video codec " - << starboard::GetMediaVideoCodecName(video_codec) << "."; - player_error_func( - kSbPlayerInvalid, context, kSbPlayerErrorDecode, - starboard::FormatString("Unsupported video codec: %s", - starboard::GetMediaVideoCodecName(video_codec)) - .c_str()); - return kSbPlayerInvalid; + if (video_codec != kSbMediaVideoCodecNone) { + const MimeType video_mime_type(video_mime); + if (!SbMediaIsVideoSupported( + video_codec, &video_mime_type, kDefaultProfile, kDefaultLevel, + kDefaultColorDepth, kSbMediaPrimaryIdUnspecified, + kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified, + kDefaultFrameWidth, kDefaultFrameHeight, kDefaultBitRate, + kDefaultFrameRate, + output_mode == kSbPlayerOutputModeDecodeToTexture)) { + SB_LOG(ERROR) << "Unsupported video codec " + << starboard::GetMediaVideoCodecName(video_codec) << "."; + player_error_func(kSbPlayerInvalid, context, kSbPlayerErrorDecode, + starboard::FormatString( + "Unsupported video codec: %s", + starboard::GetMediaVideoCodecName(video_codec)) + .c_str()); + return kSbPlayerInvalid; + } } if (audio_codec != kSbMediaAudioCodecNone && !audio_sample_info) {
diff --git a/starboard/shared/starboard/thread_local_storage_external_access_hack_public.cc b/starboard/shared/starboard/thread_local_storage_external_access_hack_public.cc deleted file mode 100644 index c841163..0000000 --- a/starboard/shared/starboard/thread_local_storage_external_access_hack_public.cc +++ /dev/null
@@ -1,25 +0,0 @@ -// 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 "starboard/shared/starboard/thread_local_storage_external_access_hack_public.h" - -#include "starboard/shared/starboard/thread_local_storage_internal.h" - -void StarboardSharedTLSKeyManagerInitializeTLSForThread() { - starboard::shared::TLSKeyManager::Get()->InitializeTLSForThread(); -} - -void StarboardSharedTLSKeyManagerShutdownTLSForThread() { - starboard::shared::TLSKeyManager::Get()->ShutdownTLSForThread(); -}
diff --git a/starboard/shared/starboard/thread_local_storage_external_access_hack_public.h b/starboard/shared/starboard/thread_local_storage_external_access_hack_public.h deleted file mode 100644 index 003869b..0000000 --- a/starboard/shared/starboard/thread_local_storage_external_access_hack_public.h +++ /dev/null
@@ -1,40 +0,0 @@ -// 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. - -#ifndef STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_EXTERNAL_ACCESS_HACK_PUBLIC_H_ -#define STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_EXTERNAL_ACCESS_HACK_PUBLIC_H_ - -// This file defines "backdoor" functions that allow external libraries access -// to internal functions. In particular, this is currently being used by Cobalt -// on some non-Starboard platforms to gain access to thread local storage thread -// initialization and shutdown functions since some platforms are only "kinda" -// using Starboard right now, via glimp. Since most of non-Starboard Cobalt -// creates threads through pthreads, not Starboard, we need to call these -// functions on newly created pthread-created threads. - -// TODO: This file should ABSOLUTELY be removed as soon as all platforms are -// fully up and running on Starboard. - -#ifdef __cplusplus -extern "C" { -#endif - -void StarboardSharedTLSKeyManagerInitializeTLSForThread(); -void StarboardSharedTLSKeyManagerShutdownTLSForThread(); - -#ifdef __cplusplus -} -#endif - -#endif // STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_EXTERNAL_ACCESS_HACK_PUBLIC_H_
diff --git a/starboard/shared/starboard/thread_local_storage_internal.cc b/starboard/shared/starboard/thread_local_storage_internal.cc index 02a1763..55bfb03 100644 --- a/starboard/shared/starboard/thread_local_storage_internal.cc +++ b/starboard/shared/starboard/thread_local_storage_internal.cc
@@ -284,54 +284,6 @@ return data_->key_table_[key->index].values[current_thread_id]; } -void TLSKeyManager::InitializeTLSForThread() { - int current_thread_id = GetCurrentThreadId(); - - ScopedLock lock(mutex_); - - const size_t table_size = data_->key_table_.size(); - for (int i = 0; i < table_size; ++i) { - KeyRecord* key_record = &data_->key_table_[i]; - if (key_record->valid) { - key_record->values[current_thread_id] = NULL; - } - } -} - -void TLSKeyManager::ShutdownTLSForThread() { - int current_thread_id = GetCurrentThreadId(); - - ScopedLock lock(mutex_); - - // Apply the destructors multiple times (4 is the minimum value - // according to the specifications). This is necessary if one of - // the destructors adds new values to the key map. - for (int d = 0; d < 4; ++d) { - // Move the map into a new temporary map so that we can iterate - // through that while the original s_tls_thread_keys may have more - // values added to it via destructor calls. - const size_t table_size = data_->key_table_.size(); - - for (int i = 0; i < table_size; ++i) { - KeyRecord* key_record = &data_->key_table_[i]; - if (key_record->valid) { - void* value = key_record->values[current_thread_id]; - key_record->values[current_thread_id] = NULL; - SbThreadLocalDestructor destructor = key_record->destructor; - - if (value && destructor) { - mutex_.Release(); - destructor(value); - mutex_.Acquire(); - } - } - } - } - - data_->thread_id_map_.Erase(SbThreadGetId()); - data_->available_thread_ids_.push_back(current_thread_id); -} - bool TLSKeyManager::IsKeyActive(SbThreadLocalKey key) { return data_->key_table_[key->index].valid; }
diff --git a/starboard/shared/starboard/thread_local_storage_internal.h b/starboard/shared/starboard/thread_local_storage_internal.h index ffa225a..13e988a 100644 --- a/starboard/shared/starboard/thread_local_storage_internal.h +++ b/starboard/shared/starboard/thread_local_storage_internal.h
@@ -46,12 +46,6 @@ // Returns the thread local value for the given key. void* GetLocalValue(SbThreadLocalKey key); - // Called whenever a thread is created. - void InitializeTLSForThread(); - - // Called whenever a thread is destroyed. - void ShutdownTLSForThread(); - private: // We add 1 to kSbMaxThreads here to account for the main thread. static const int kMaxThreads = kSbMaxThreads + 1;
diff --git a/starboard/shared/stub/media_is_audio_supported.cc b/starboard/shared/stub/media_is_audio_supported.cc index d9c70c5..853eca0 100644 --- a/starboard/shared/stub/media_is_audio_supported.cc +++ b/starboard/shared/stub/media_is_audio_supported.cc
@@ -16,8 +16,10 @@ #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { return false; }
diff --git a/starboard/shared/stub/media_is_video_supported.cc b/starboard/shared/stub/media_is_video_supported.cc index e16ee53..1049791 100644 --- a/starboard/shared/stub/media_is_video_supported.cc +++ b/starboard/shared/stub/media_is_video_supported.cc
@@ -16,8 +16,10 @@ #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth,
diff --git a/starboard/shared/win32/media_is_audio_supported.cc b/starboard/shared/win32/media_is_audio_supported.cc index b1c99f3..f838ac3 100644 --- a/starboard/shared/win32/media_is_audio_supported.cc +++ b/starboard/shared/win32/media_is_audio_supported.cc
@@ -18,8 +18,10 @@ #include "starboard/configuration_constants.h" #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { if (audio_codec != kSbMediaAudioCodecAac && audio_codec != kSbMediaAudioCodecOpus) {
diff --git a/starboard/shared/win32/media_is_video_supported.cc b/starboard/shared/win32/media_is_video_supported.cc index 4b49b55..b7af5ce 100644 --- a/starboard/shared/win32/media_is_video_supported.cc +++ b/starboard/shared/win32/media_is_video_supported.cc
@@ -22,16 +22,14 @@ #include "starboard/configuration_constants.h" #include "starboard/shared/starboard/media/media_util.h" +using ::starboard::shared::starboard::media::MimeType; + namespace { #if SB_API_VERSION >= SB_RUNTIME_CONFIGS_VERSION || \ defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT) // Cache the VP9 support status since the check may be expensive. -enum Vp9Support { - kVp9SupportUnknown, - kVp9SupportYes, - kVp9SupportNo -}; +enum Vp9Support { kVp9SupportUnknown, kVp9SupportYes, kVp9SupportNo }; Vp9Support s_vp9_support = kVp9SupportUnknown; // Check for VP9 support. Since this is used by a starboard function, it @@ -76,7 +74,7 @@ return s_vp9_support == kVp9SupportYes; } #else // SB_API_VERSION >= SB_RUNTIME_CONFIGS_VERSION || - // defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT) +// defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT) bool IsVp9Supported() { return false; } @@ -86,7 +84,7 @@ } // namespace bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth, @@ -105,18 +103,18 @@ int max_height = 1080; if (video_codec == kSbMediaVideoCodecVp9) { - // Vp9 supports 8k only in whitelisted platforms, up to 4k in the others. +// Vp9 supports 8k only in whitelisted platforms, up to 4k in the others. #ifdef ENABLE_VP9_8K_SUPPORT max_width = 7680; max_height = 4320; -#else // ENABLE_VP9_8K_SUPPORT +#else // ENABLE_VP9_8K_SUPPORT max_width = 3840; max_height = 2160; #endif // ENABLE_VP9_8K_SUPPORT } else if (video_codec == kSbMediaVideoCodecH264) { - // Not all devices can support 4k H264; some (e.g. xb1) may crash in - // the decoder if provided too high of a resolution. Therefore - // platforms must explicitly opt-in to support 4k H264. +// Not all devices can support 4k H264; some (e.g. xb1) may crash in +// the decoder if provided too high of a resolution. Therefore +// platforms must explicitly opt-in to support 4k H264. #ifdef ENABLE_H264_4K_SUPPORT max_width = 3840; max_height = 2160;
diff --git a/third_party/crashpad/handler/BUILD.gn b/third_party/crashpad/handler/BUILD.gn index 9442c21..10582a8 100644 --- a/third_party/crashpad/handler/BUILD.gn +++ b/third_party/crashpad/handler/BUILD.gn
@@ -170,6 +170,7 @@ crashpad_executable("crashpad_handler") { if (crashpad_is_in_starboard) { + install_target = !crashpad_is_android check_includes = false data_deps = [ "//third_party/icu:icudata" ] }
diff --git a/third_party/libvpx/.gitignore b/third_party/libvpx/.gitignore index 5f26835..aeb6d58 100644 --- a/third_party/libvpx/.gitignore +++ b/third_party/libvpx/.gitignore
@@ -1,5 +1,6 @@ *.S *.a +!/platforms/*/libvpx.a *.asm.s *.d *.gcda
diff --git a/third_party/opus/.gitignore b/third_party/opus/.gitignore index eccd0a2..8cf3ed1 100644 --- a/third_party/opus/.gitignore +++ b/third_party/opus/.gitignore
@@ -9,6 +9,7 @@ compile config.guess config.h +!/*/config.h config.h.in config.log config.status @@ -67,6 +68,7 @@ doc/man package_version version.h +!/*/version.h celt/Debug celt/Release celt/x64