Import Cobalt 24.lts.20.1032766
diff --git a/.github/config/raspi-2-skia.json b/.github/config/raspi-2-skia.json index f99fb73..e83ea1f 100644 --- a/.github/config/raspi-2-skia.json +++ b/.github/config/raspi-2-skia.json
@@ -9,7 +9,7 @@ "platform":"raspi-2-skia", "target_platform":"raspi-2-skia", "target_cpu":"target_cpu=\\\"arm\\\"", - "extra_gn_arguments": "is_clang=false" + "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false" } ] }
diff --git a/.github/config/raspi-2.json b/.github/config/raspi-2.json index ab2a1e6..9008437 100644 --- a/.github/config/raspi-2.json +++ b/.github/config/raspi-2.json
@@ -14,8 +14,6 @@ }, "platforms": [ "raspi-2", - "raspi-2-sbversion-13", - "raspi-2-sbversion-14", "raspi-2-sbversion-15" ], "includes": [ @@ -24,25 +22,7 @@ "platform":"raspi-2", "target_platform":"raspi-2", "target_cpu":"target_cpu=\\\"arm\\\"", - "extra_gn_arguments": "is_clang=false", - "dimension": "release_version=regex:10.*" - }, - { - "name":"sbversion-13", - "platform":"raspi-2-sbversion-13", - "target_platform":"raspi-2", - "target_cpu":"target_cpu=\\\"arm\\\"", - "extra_gn_arguments":"is_clang=false", - "sb_api_version": "sb_api_version=13", - "dimension": "release_version=regex:10.*" - }, - { - "name":"sbversion-14", - "platform":"raspi-2-sbversion-14", - "target_platform":"raspi-2", - "target_cpu":"target_cpu=\\\"arm\\\"", - "extra_gn_arguments":"is_clang=false", - "sb_api_version": "sb_api_version=14", + "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false", "dimension": "release_version=regex:10.*" }, { @@ -50,7 +30,7 @@ "platform":"raspi-2-sbversion-15", "target_platform":"raspi-2", "target_cpu":"target_cpu=\\\"arm\\\"", - "extra_gn_arguments":"is_clang=false", + "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false", "sb_api_version": "sb_api_version=15", "dimension": "release_version=regex:10.*" }
diff --git a/base/android/jni_generator/AndroidManifest.xml b/base/android/jni_generator/AndroidManifest.xml index 1555a81..aa0b7c5 100644 --- a/base/android/jni_generator/AndroidManifest.xml +++ b/base/android/jni_generator/AndroidManifest.xml
@@ -7,7 +7,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jni.generator"> - <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" /> + <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34" /> <application></application> </manifest>
diff --git a/base/logging.cc b/base/logging.cc index 809e141..5a159ff 100644 --- a/base/logging.cc +++ b/base/logging.cc
@@ -478,9 +478,15 @@ case LOG_ERROR: return kSbLogPriorityError; case LOG_FATAL: - case LOG_VERBOSE: return kSbLogPriorityFatal; + case LOG_VERBOSE: default: + if (level <= LOG_VERBOSE) { + // Verbose level can be any negative integer, sanity check its range to + // filter out potential errors. + DCHECK_GE(level, -256); + return kSbLogPriorityInfo; + } NOTREACHED() << "Unrecognized log level."; return kSbLogPriorityInfo; }
diff --git a/cobalt/audio/audio_device.cc b/cobalt/audio/audio_device.cc index 7aae3b4..874d1ba 100644 --- a/cobalt/audio/audio_device.cc +++ b/cobalt/audio/audio_device.cc
@@ -32,6 +32,11 @@ namespace { const int kRenderBufferSizeFrames = 1024; const int kDefaultFramesPerChannel = 8 * kRenderBufferSizeFrames; + +int AlignUp(int value, int alignment) { + int decremented_value = value - 1; + return decremented_value + alignment - (decremented_value % alignment); +} } // namespace class AudioDevice::Impl { @@ -85,11 +90,13 @@ : number_of_channels_(number_of_channels), output_sample_type_(GetPreferredOutputStarboardSampleType()), render_callback_(callback), - frames_per_channel_(std::max(SbAudioSinkGetMinBufferSizeInFrames( - number_of_channels, output_sample_type_, - kStandardOutputSampleRate) + - kRenderBufferSizeFrames * 2, - kDefaultFramesPerChannel)), + frames_per_channel_( + std::max(AlignUp(SbAudioSinkGetMinBufferSizeInFrames( + number_of_channels, output_sample_type_, + kStandardOutputSampleRate) + + kRenderBufferSizeFrames * 2, + kRenderBufferSizeFrames), + kDefaultFramesPerChannel)), input_audio_bus_(static_cast<size_t>(number_of_channels), static_cast<size_t>(kRenderBufferSizeFrames), GetPreferredOutputSampleType(), AudioBus::kPlanar), @@ -99,6 +106,7 @@ DCHECK(number_of_channels_ == 1 || number_of_channels_ == 2) << "Invalid number of channels: " << number_of_channels_; DCHECK(render_callback_); + DCHECK(frames_per_channel_ % kRenderBufferSizeFrames == 0); DCHECK(SbAudioSinkIsAudioFrameStorageTypeSupported( kSbMediaAudioFrameStorageTypeInterleaved)) << "Only interleaved frame storage is supported.";
diff --git a/cobalt/base/BUILD.gn b/cobalt/base/BUILD.gn index a619003..f2652cc 100644 --- a/cobalt/base/BUILD.gn +++ b/cobalt/base/BUILD.gn
@@ -60,6 +60,7 @@ "log_message_handler.cc", "log_message_handler.h", "message_queue.h", + "on_metric_upload_event.h", "on_screen_keyboard_hidden_event.h", "on_screen_keyboard_shown_event.h", "path_provider.cc",
diff --git a/cobalt/base/on_metric_upload_event.h b/cobalt/base/on_metric_upload_event.h new file mode 100644 index 0000000..d011f0a --- /dev/null +++ b/cobalt/base/on_metric_upload_event.h
@@ -0,0 +1,61 @@ +// Copyright 2023 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 COBALT_BASE_ON_METRIC_UPLOAD_EVENT_H_ +#define COBALT_BASE_ON_METRIC_UPLOAD_EVENT_H_ + +#include <string> +#include <utility> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/strings/string_util.h" +#include "cobalt/base/event.h" +#include "cobalt/base/polymorphic_downcast.h" +#include "cobalt/h5vcc/h5vcc_metric_type.h" + +namespace base { + +// Event sent when a metric payload is ready for upload. +class OnMetricUploadEvent : public Event { + public: + OnMetricUploadEvent(const cobalt::h5vcc::H5vccMetricType& metric_type, + const std::string& serialized_proto) + : metric_type_(metric_type), serialized_proto_(serialized_proto) {} + + explicit OnMetricUploadEvent(const Event* event) { + CHECK(event != nullptr); + const base::OnMetricUploadEvent* on_metric_upload_event = + base::polymorphic_downcast<const base::OnMetricUploadEvent*>(event); + metric_type_ = on_metric_upload_event->metric_type(); + serialized_proto_ = on_metric_upload_event->serialized_proto(); + } + + const cobalt::h5vcc::H5vccMetricType& metric_type() const { + return metric_type_; + } + + const std::string& serialized_proto() const { return serialized_proto_; } + + + BASE_EVENT_SUBCLASS(OnMetricUploadEvent); + + private: + cobalt::h5vcc::H5vccMetricType metric_type_; + std::string serialized_proto_; +}; + +} // namespace base + +#endif // COBALT_BASE_ON_METRIC_UPLOAD_EVENT_H_
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn index 25e387a..9b7b12c 100644 --- a/cobalt/browser/BUILD.gn +++ b/cobalt/browser/BUILD.gn
@@ -118,7 +118,6 @@ "client_hint_headers.h", "device_authentication.cc", "device_authentication.h", - "lifecycle_observer.h", "on_screen_keyboard_starboard_bridge.cc", "on_screen_keyboard_starboard_bridge.h", "render_tree_combiner.cc",
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc index 31cb00d..ebf95f7 100644 --- a/cobalt/browser/application.cc +++ b/cobalt/browser/application.cc
@@ -460,7 +460,12 @@ } int StringToLogLevel(const std::string& log_level) { - if (log_level == "info") { + if (log_level == "verbose") { + // The lower the verbose level is, the more messages are logged. Set it to + // a lower enough value to ensure that all known verbose messages are + // logged. + return logging::LOG_VERBOSE - 15; + } else if (log_level == "info") { return logging::LOG_INFO; } else if (log_level == "warning") { return logging::LOG_WARNING; @@ -698,9 +703,6 @@ base::kApplicationStateStarted, kWatchdogTimeInterval, kWatchdogTimeWait, watchdog::NONE); - cobalt::cache::Cache::GetInstance()->set_persistent_settings( - persistent_settings_.get()); - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); base::Optional<cssom::ViewportSize> requested_viewport_size = GetRequestedViewportSize(command_line); @@ -1025,6 +1027,8 @@ base::TimeDelta::FromSeconds(duration_in_seconds)); } #endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES + + AddCrashLogApplicationState(base::kApplicationStateStarted); } Application::~Application() { @@ -1524,6 +1528,7 @@ metrics::kMetricEnabledSettingName, false); auto metric_event_interval = persistent_settings_->GetPersistentSettingAsInt( metrics::kMetricEventIntervalSettingName, 300); + metrics_services_manager_->SetEventDispatcher(&event_dispatcher_); metrics_services_manager_->SetUploadInterval(metric_event_interval); metrics_services_manager_->ToggleMetricsEnabled(is_metrics_enabled); // Metric recording state initialization _must_ happen before we bootstrap
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc index c6f7f18..f44b2b2 100644 --- a/cobalt/browser/browser_module.cc +++ b/cobalt/browser/browser_module.cc
@@ -52,6 +52,7 @@ #include "cobalt/math/matrix3_f.h" #include "cobalt/overlay_info/overlay_info_registry.h" #include "cobalt/persistent_storage/persistent_settings.h" +#include "cobalt/trace_event/scoped_trace_to_file.h" #include "cobalt/ui_navigation/scroll_engine/scroll_engine.h" #include "cobalt/web/csp_delegate_factory.h" #include "cobalt/web/navigator_ua_data.h" @@ -161,6 +162,13 @@ "is useful when trying to target testing to certain codecs, since other " "codecs will get picked as a fallback as a result."; +const char kNavigateTimedTrace[] = "navigate_timed_trace"; +const char kNavigateTimedTraceShortHelp[] = + "Request a timed trace from the next navigation."; +const char kNavigateTimedTraceLongHelp[] = + "When this is called, a timed trace will start at the next navigation " + "and run for the given number of seconds."; + void ScreenshotCompleteCallback(const base::FilePath& output_path) { DLOG(INFO) << "Screenshot written to " << output_path.value(); } @@ -268,6 +276,11 @@ base::Unretained(this)), kDisableMediaCodecsCommandShortHelp, kDisableMediaCodecsCommandLongHelp)), + ALLOW_THIS_IN_INITIALIZER_LIST(navigate_timed_trace_command_handler_( + kNavigateTimedTrace, + base::Bind(&BrowserModule::OnNavigateTimedTrace, + base::Unretained(this)), + kNavigateTimedTraceShortHelp, kNavigateTimedTraceLongHelp)), #endif // defined(ENABLE_DEBUGGER) has_resumed_(base::WaitableEvent::ResetPolicy::MANUAL, base::WaitableEvent::InitialState::NOT_SIGNALED), @@ -289,7 +302,7 @@ platform_info_.reset(new browser::UserAgentPlatformInfo()); service_worker_registry_.reset(new ServiceWorkerRegistry( - &web_settings_, network_module, platform_info_.get(), url)); + &web_settings_, network_module, platform_info_.get())); #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this); @@ -462,7 +475,9 @@ } debug_console_.reset(); #endif + DestroySplashScreen(); + DestroyScrollEngine(); // Make sure the WebModule is destroyed before the ServiceWorkerRegistry if (web_module_) { lifecycle_observers_.RemoveObserver(web_module_.get()); @@ -478,6 +493,7 @@ DLOG(INFO) << "In BrowserModule::Navigate " << url; TRACE_EVENT1("cobalt::browser", "BrowserModule::Navigate()", "url", url.spec()); + // Reset the waitable event regardless of the thread. This ensures that the // webdriver won't incorrectly believe that the webmodule has finished loading // when it calls Navigate() and waits for the |web_module_loaded_| signal. @@ -552,6 +568,17 @@ current_main_web_module_timeline_id_ = next_timeline_id_++; main_web_module_layer_->Reset(); + +#if defined(ENABLE_DEBUGGER) + // Check to see if a timed_trace has been set, indicating that we should + // begin a timed trace upon startup. + if (navigate_timed_trace_duration_ != base::TimeDelta()) { + trace_event::TraceToFileForDuration( + base::FilePath(FILE_PATH_LITERAL("timed_trace.json")), + navigate_timed_trace_duration_); + navigate_timed_trace_duration_ = base::TimeDelta(); + } +#endif // defined(ENABLE_DEBUGGER) } void BrowserModule::NavigateResetErrorHandling() { @@ -625,7 +652,9 @@ } void BrowserModule::NavigateSetupScrollEngine() { + DestroyScrollEngine(); scroll_engine_.reset(new ui_navigation::scroll_engine::ScrollEngine()); + lifecycle_observers_.AddObserver(scroll_engine_.get()); scroll_engine_->thread()->Start(); } @@ -662,8 +691,8 @@ } options.provide_screenshot_function = - base::Bind(&ScreenShotWriter::RequestScreenshotToMemoryUnencoded, - base::Unretained(screen_shot_writer_.get())); + base::Bind(&BrowserModule::RequestScreenshotToMemoryUnencoded, + base::Unretained(this)); #if defined(ENABLE_DEBUGGER) if (base::CommandLine::ForCurrentProcess()->HasSwitch( @@ -777,6 +806,14 @@ return web_module_loaded_.TimedWait(timeout); } +void BrowserModule::EnsureScreenShotWriter() { + if (!screen_shot_writer_ && renderer_module_) { + screen_shot_writer_.reset( + new ScreenShotWriter(renderer_module_->pipeline())); + } +} + +#if defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER) void BrowserModule::RequestScreenshotToFile( const base::FilePath& path, loader::image::EncodedStaticImage::ImageFormat image_format, @@ -784,7 +821,9 @@ const base::Closure& done_callback) { TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToFile()"); DCHECK_EQ(base::MessageLoop::current(), self_message_loop_); + EnsureScreenShotWriter(); DCHECK(screen_shot_writer_); + if (!screen_shot_writer_) return; scoped_refptr<render_tree::Node> render_tree; web_module_->DoSynchronousLayoutAndGetRenderTree(&render_tree); @@ -802,7 +841,9 @@ const base::Optional<math::Rect>& clip_rect, const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready) { TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToMemory()"); + EnsureScreenShotWriter(); DCHECK(screen_shot_writer_); + if (!screen_shot_writer_) return; // Note: This does not have to be called from self_message_loop_. scoped_refptr<render_tree::Node> render_tree; @@ -815,6 +856,19 @@ screen_shot_writer_->RequestScreenshotToMemory(image_format, render_tree, clip_rect, screenshot_ready); } +#endif // defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER) + +// Request a screenshot to memory without compressing the image. +void BrowserModule::RequestScreenshotToMemoryUnencoded( + const scoped_refptr<render_tree::Node>& render_tree_root, + const base::Optional<math::Rect>& clip_rect, + const renderer::Pipeline::RasterizationCompleteCallback& callback) { + EnsureScreenShotWriter(); + DCHECK(screen_shot_writer_); + if (!screen_shot_writer_) return; + screen_shot_writer_->RequestScreenshotToMemoryUnencoded(render_tree_root, + clip_rect, callback); +} void BrowserModule::ProcessRenderTreeSubmissionQueue() { TRACE_EVENT0("cobalt::browser", @@ -1137,6 +1191,13 @@ SubmitCurrentRenderTreeToRenderer(); } +void BrowserModule::OnNavigateTimedTrace(const std::string& time) { + double duration_in_seconds = 0; + base::StringToDouble(time, &duration_in_seconds); + navigate_timed_trace_duration_ = + base::TimeDelta::FromMilliseconds(static_cast<int64_t>( + duration_in_seconds * base::Time::kMillisecondsPerSecond)); +} #endif // defined(ENABLE_DEBUGGER) void BrowserModule::OnOnScreenKeyboardInputEventProduced( @@ -1437,6 +1498,20 @@ } } +void BrowserModule::DestroyScrollEngine() { + TRACE_EVENT0("cobalt::browser", "BrowserModule::DestroyScrollEngine()"); + if (base::MessageLoop::current() != self_message_loop_) { + self_message_loop_->task_runner()->PostTask( + FROM_HERE, base::Bind(&BrowserModule::DestroyScrollEngine, weak_this_)); + return; + } + if (scroll_engine_ && + lifecycle_observers_.HasObserver(scroll_engine_.get())) { + lifecycle_observers_.RemoveObserver(scroll_engine_.get()); + } + scroll_engine_.reset(); +} + #if defined(ENABLE_WEBDRIVER) std::unique_ptr<webdriver::SessionDriver> BrowserModule::CreateSessionDriver( const webdriver::protocol::SessionId& session_id) { @@ -1744,7 +1819,7 @@ system_window_.get(), RendererModuleWithCameraOptions(options_.renderer_module_options, input_device_manager_->camera_3d()))); - screen_shot_writer_.reset(new ScreenShotWriter(renderer_module_->pipeline())); + screen_shot_writer_.reset(); } void BrowserModule::DestroyRendererModule() { @@ -2184,11 +2259,7 @@ auto url_request_context = network_module_->url_request_context(); auto http_cache = url_request_context->http_transaction_factory()->GetCache(); if (!http_cache) return; - auto cache_backend = static_cast<disk_cache::CobaltBackendImpl*>( - http_cache->GetCurrentBackend()); - if (cache_backend) { - cache_backend->ValidatePersistentSettings(); - } + network_module_->url_request_context()->ValidateCachePersistentSettings(); } } // namespace browser
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h index 2011577..abf05fd 100644 --- a/cobalt/browser/browser_module.h +++ b/cobalt/browser/browser_module.h
@@ -25,6 +25,7 @@ #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "base/time/time.h" #include "base/timer/timer.h" #include "cobalt/base/accessibility_caption_settings_changed_event.h" #include "cobalt/base/application_state.h" @@ -151,6 +152,10 @@ void AddURLHandler(const URLHandler::URLHandlerCallback& callback); void RemoveURLHandler(const URLHandler::URLHandlerCallback& callback); + // Start the ScreenShotWriter if it's not already running. + void EnsureScreenShotWriter(); + +#if defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER) // Request a screenshot to be written to the specified path. Callback will // be fired after the screenshot has been written to disk. void RequestScreenshotToFile( @@ -164,6 +169,13 @@ loader::image::EncodedStaticImage::ImageFormat image_format, const base::Optional<math::Rect>& clip_rect, const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready); +#endif // defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER) + + // Request a screenshot to memory without compressing the image. + void RequestScreenshotToMemoryUnencoded( + const scoped_refptr<render_tree::Node>& render_tree_root, + const base::Optional<math::Rect>& clip_rect, + const renderer::Pipeline::RasterizationCompleteCallback& callback); #if defined(ENABLE_WEBDRIVER) std::unique_ptr<webdriver::SessionDriver> CreateSessionDriver( @@ -361,6 +373,8 @@ // Destroys the splash screen, if currently displayed. void DestroySplashScreen(base::TimeDelta close_time = base::TimeDelta()); + void DestroyScrollEngine(); + // Called when web module has received window.close(). void OnWindowClose(base::TimeDelta close_time); @@ -383,6 +397,8 @@ const browser::WebModule::LayoutResults& layout_results); void OnDebugConsoleRenderTreeProduced( const browser::WebModule::LayoutResults& layout_results); + + void OnNavigateTimedTrace(const std::string& time); #endif // defined(ENABLE_DEBUGGER) #if defined(ENABLE_WEBDRIVER) @@ -658,6 +674,12 @@ // Saves the previous debugger state to be restored in the new WebModule. std::unique_ptr<debug::backend::DebuggerState> debugger_state_; + + // Amount of time to run a Timed Trace after Navigate + base::TimeDelta navigate_timed_trace_duration_; + + debug::console::ConsoleCommandManager::CommandHandler + navigate_timed_trace_command_handler_; #endif // defined(ENABLE_DEBUGGER) // The splash screen. The pointer wrapped here should be non-NULL iff
diff --git a/cobalt/browser/idl_files.gni b/cobalt/browser/idl_files.gni index 4663d09..41539c3 100644 --- a/cobalt/browser/idl_files.gni +++ b/cobalt/browser/idl_files.gni
@@ -171,6 +171,7 @@ "//cobalt/h5vcc/h5vcc_screen.idl", "//cobalt/h5vcc/h5vcc_system.idl", "//cobalt/h5vcc/h5vcc_trace_event.idl", + "//cobalt/h5vcc/h5vcc_net_log.idl", "//cobalt/h5vcc/h5vcc_updater.idl", "//cobalt/media_capture/blob_event.idl",
diff --git a/cobalt/browser/metrics/BUILD.gn b/cobalt/browser/metrics/BUILD.gn index 62c85fa..414d1b5 100644 --- a/cobalt/browser/metrics/BUILD.gn +++ b/cobalt/browser/metrics/BUILD.gn
@@ -24,11 +24,11 @@ "cobalt_metrics_services_manager.h", "cobalt_metrics_services_manager_client.cc", "cobalt_metrics_services_manager_client.h", - "cobalt_metrics_uploader_callback.h", ] deps = [ "//base", + "//cobalt/base", "//cobalt/browser:generated_types", "//cobalt/h5vcc:metric_event_handler_wrapper", "//components/metrics", @@ -51,8 +51,9 @@ deps = [ ":metrics", "//base", - "//cobalt//browser:test_dependencies_on_browser", + "//cobalt/base", "//cobalt/browser:generated_types", + "//cobalt/browser:test_dependencies_on_browser", "//cobalt/h5vcc", "//cobalt/h5vcc:metric_event_handler_wrapper", "//cobalt/test:run_all_unittests",
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc b/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc index 49f1c81..955ef04 100644 --- a/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc +++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc
@@ -14,9 +14,12 @@ #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h" +#include <memory> + #include "base/base64url.h" #include "base/logging.h" -#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h" +#include "cobalt/base/event_dispatcher.h" +#include "cobalt/base/on_metric_upload_event.h" #include "cobalt/h5vcc/h5vcc_metric_type.h" #include "components/metrics/log_decoder.h" #include "components/metrics/metrics_log_uploader.h" @@ -49,7 +52,7 @@ const std::string& compressed_log_data, const std::string& log_hash, const ::metrics::ReportingInfo& reporting_info) { if (service_type_ == ::metrics::MetricsLogUploader::UMA) { - if (upload_handler_ != nullptr) { + if (event_dispatcher_ != nullptr) { std::string uncompressed_serialized_proto; ::metrics::DecodeLogData(compressed_log_data, &uncompressed_serialized_proto); @@ -67,8 +70,11 @@ base::Base64UrlEncode(cobalt_uma_event.SerializeAsString(), base::Base64UrlEncodePolicy::INCLUDE_PADDING, &base64_encoded_proto); - upload_handler_->Run(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma, - base64_encoded_proto); + + event_dispatcher_->DispatchEvent( + std::unique_ptr<base::Event>(new base::OnMetricUploadEvent( + h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma, + base64_encoded_proto))); } } @@ -79,9 +85,9 @@ /*was_https*/ true); } -void CobaltMetricsLogUploader::SetOnUploadHandler( - const CobaltMetricsUploaderCallback* upload_handler) { - upload_handler_ = upload_handler; +void CobaltMetricsLogUploader::SetEventDispatcher( + const base::EventDispatcher* event_dispatcher) { + event_dispatcher_ = event_dispatcher; } } // namespace metrics
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader.h b/cobalt/browser/metrics/cobalt_metrics_log_uploader.h index fa71662..8b5b73d 100644 --- a/cobalt/browser/metrics/cobalt_metrics_log_uploader.h +++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader.h
@@ -20,7 +20,7 @@ #include "base/callback.h" #include "base/macros.h" #include "base/strings/string_piece.h" -#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h" +#include "cobalt/base/event_dispatcher.h" #include "cobalt/h5vcc/metric_event_handler_wrapper.h" #include "components/metrics/metrics_log_uploader.h" #include "third_party/metrics_proto/reporting_info.pb.h" @@ -43,6 +43,9 @@ virtual ~CobaltMetricsLogUploader() {} + // Set event dispatcher to be used to publish any metrics events (eg upload). + void SetEventDispatcher(const base::EventDispatcher* event_dispatcher); + // Uploads a log with the specified |compressed_log_data| and |log_hash|. // |log_hash| is expected to be the hex-encoded SHA1 hash of the log data // before compression. @@ -50,15 +53,10 @@ const std::string& log_hash, const ::metrics::ReportingInfo& reporting_info); - // Sets the event handler wrapper to be called when metrics are ready for - // upload. This should be the JavaScript H5vcc callback implementation. - void SetOnUploadHandler( - const CobaltMetricsUploaderCallback* metric_event_handler); - private: const ::metrics::MetricsLogUploader::MetricServiceType service_type_; const ::metrics::MetricsLogUploader::UploadCallback on_upload_complete_; - const CobaltMetricsUploaderCallback* upload_handler_ = nullptr; + const base::EventDispatcher* event_dispatcher_ = nullptr; }; } // namespace metrics
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc b/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc index 78e7d18..f6927a8 100644 --- a/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc +++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc
@@ -18,7 +18,10 @@ #include "base/base64url.h" #include "base/test/mock_callback.h" -#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h" +#include "cobalt/base/event.h" +#include "cobalt/base/event_dispatcher.h" +#include "cobalt/base/on_metric_upload_event.h" +#include "cobalt/h5vcc/h5vcc_metric_type.h" #include "cobalt/h5vcc/h5vcc_metrics.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -26,7 +29,6 @@ #include "third_party/metrics_proto/cobalt_uma_event.pb.h" #include "third_party/metrics_proto/reporting_info.pb.h" #include "third_party/zlib/google/compression_utils.h" - namespace cobalt { namespace browser { namespace metrics { @@ -43,6 +45,12 @@ class CobaltMetricsLogUploaderTest : public ::testing::Test { public: void SetUp() override { + dispatcher_ = std::make_unique<base::EventDispatcher>(); + dispatcher_->AddEventCallback( + base::OnMetricUploadEvent::TypeId(), + base::Bind(&CobaltMetricsLogUploaderTest::OnMetricUploadEventHandler, + base::Unretained(this))); + uploader_ = std::make_unique<CobaltMetricsLogUploader>( ::metrics::MetricsLogUploader::MetricServiceType::UMA, base::Bind(&CobaltMetricsLogUploaderTest::UploadCompleteCallback, @@ -51,6 +59,13 @@ void TearDown() override {} + void OnMetricUploadEventHandler(const base::Event* event) { + std::unique_ptr<base::OnMetricUploadEvent> on_metric_upload_event( + new base::OnMetricUploadEvent(event)); + last_metric_type_ = on_metric_upload_event->metric_type(); + last_serialized_proto_ = on_metric_upload_event->serialized_proto(); + } + void UploadCompleteCallback(int response_code, int error_code, bool was_https) { callback_count_++; @@ -58,13 +73,14 @@ protected: std::unique_ptr<CobaltMetricsLogUploader> uploader_; + std::unique_ptr<base::EventDispatcher> dispatcher_; int callback_count_ = 0; + cobalt::h5vcc::H5vccMetricType last_metric_type_; + std::string last_serialized_proto_ = ""; }; TEST_F(CobaltMetricsLogUploaderTest, TriggersUploadHandler) { - base::MockCallback<CobaltMetricsUploaderCallback> mock_upload_handler; - const auto cb = mock_upload_handler.Get(); - uploader_->SetOnUploadHandler(&cb); + uploader_->SetEventDispatcher(dispatcher_.get()); ::metrics::ReportingInfo dummy_reporting_info; dummy_reporting_info.set_attempt_count(33); ::metrics::ChromeUserMetricsExtension uma_log; @@ -87,12 +103,11 @@ base::Base64UrlEncode(cobalt_event.SerializeAsString(), base::Base64UrlEncodePolicy::INCLUDE_PADDING, &base64_encoded_proto); - EXPECT_CALL(mock_upload_handler, - Run(Eq(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma), - StrEq(base64_encoded_proto))) - .Times(1); uploader_->UploadLog(compressed_message, "fake_hash", dummy_reporting_info); ASSERT_EQ(callback_count_, 1); + ASSERT_EQ(last_metric_type_, + cobalt::h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma); + ASSERT_EQ(last_serialized_proto_, base64_encoded_proto); ::metrics::ChromeUserMetricsExtension uma_log2; uma_log2.set_session_id(456); @@ -108,11 +123,10 @@ base::Base64UrlEncode(cobalt_event2.SerializeAsString(), base::Base64UrlEncodePolicy::INCLUDE_PADDING, &base64_encoded_proto2); - EXPECT_CALL(mock_upload_handler, - Run(Eq(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma), - StrEq(base64_encoded_proto2))) - .Times(1); uploader_->UploadLog(compressed_message2, "fake_hash", dummy_reporting_info); + ASSERT_EQ(last_metric_type_, + cobalt::h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma); + ASSERT_EQ(last_serialized_proto_, base64_encoded_proto2); ASSERT_EQ(callback_count_, 2); } @@ -121,17 +135,15 @@ ::metrics::MetricsLogUploader::MetricServiceType::UKM, base::Bind(&CobaltMetricsLogUploaderTest::UploadCompleteCallback, base::Unretained(this)))); - base::MockCallback<CobaltMetricsUploaderCallback> mock_upload_handler; - const auto cb = mock_upload_handler.Get(); - uploader_->SetOnUploadHandler(&cb); + uploader_->SetEventDispatcher(dispatcher_.get()); ::metrics::ReportingInfo dummy_reporting_info; ::metrics::ChromeUserMetricsExtension uma_log; uma_log.set_session_id(1234); uma_log.set_client_id(1234); std::string compressed_message; compression::GzipCompress(uma_log.SerializeAsString(), &compressed_message); - EXPECT_CALL(mock_upload_handler, Run(_, _)).Times(0); uploader_->UploadLog(compressed_message, "fake_hash", dummy_reporting_info); + ASSERT_EQ(last_serialized_proto_, ""); // Even though we don't upload this log, we still need to trigger the complete // callback so the metric code can keep running. ASSERT_EQ(callback_count_, 1);
diff --git a/cobalt/browser/metrics/cobalt_metrics_service_client.cc b/cobalt/browser/metrics/cobalt_metrics_service_client.cc index 2b4d21a..c3408a9 100644 --- a/cobalt/browser/metrics/cobalt_metrics_service_client.cc +++ b/cobalt/browser/metrics/cobalt_metrics_service_client.cc
@@ -24,9 +24,9 @@ #include "base/memory/singleton.h" #include "base/strings/string16.h" #include "base/time/time.h" +#include "cobalt/base/event_dispatcher.h" #include "cobalt/browser/metrics/cobalt_enabled_state_provider.h" #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h" -#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h" #include "components/metrics/enabled_state_provider.h" #include "components/metrics/metrics_log_uploader.h" #include "components/metrics/metrics_pref_names.h" @@ -50,11 +50,11 @@ // Upload Handler. const int kStandardUploadIntervalSeconds = 5 * 60; // 5 minutes. -void CobaltMetricsServiceClient::SetOnUploadHandler( - const CobaltMetricsUploaderCallback* uploader_callback) { - upload_handler_ = uploader_callback; +void CobaltMetricsServiceClient::SetEventDispatcher( + const base::EventDispatcher* event_dispatcher) { + event_dispatcher_ = event_dispatcher; if (log_uploader_) { - log_uploader_->SetOnUploadHandler(upload_handler_); + log_uploader_->SetEventDispatcher(event_dispatcher); } } @@ -77,7 +77,8 @@ void CobaltMetricsServiceClient::SetMetricsClientId( const std::string& client_id) { - // TODO(b/286066035): What to do with client id here? + // ClientId is unnecessary within Cobalt. We expect the web client responsible + // for uploading these to have its own concept of device/client identifiers. } // TODO(b/286884542): Audit all stub implementations in this class and reaffirm @@ -153,8 +154,8 @@ auto uploader = std::make_unique<CobaltMetricsLogUploader>( service_type, on_upload_complete); log_uploader_ = uploader.get(); - if (upload_handler_ != nullptr) { - log_uploader_->SetOnUploadHandler(upload_handler_); + if (event_dispatcher_ != nullptr) { + log_uploader_->SetEventDispatcher(event_dispatcher_); } return uploader; }
diff --git a/cobalt/browser/metrics/cobalt_metrics_service_client.h b/cobalt/browser/metrics/cobalt_metrics_service_client.h index c7860f0..c8efa8f 100644 --- a/cobalt/browser/metrics/cobalt_metrics_service_client.h +++ b/cobalt/browser/metrics/cobalt_metrics_service_client.h
@@ -23,9 +23,9 @@ #include "base/callback.h" #include "base/strings/string16.h" #include "base/time/time.h" +#include "cobalt/base/event_dispatcher.h" #include "cobalt/browser/metrics/cobalt_enabled_state_provider.h" #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h" -#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h" #include "components/metrics/metrics_log_uploader.h" #include "components/metrics/metrics_reporting_default_state.h" #include "components/metrics/metrics_service.h" @@ -48,10 +48,8 @@ public: ~CobaltMetricsServiceClient() override{}; - // Sets the uploader handler to be called when metrics are ready for - // upload. - void SetOnUploadHandler( - const CobaltMetricsUploaderCallback* uploader_callback); + // Set event dispatcher to be used to publish any metrics events (eg upload). + void SetEventDispatcher(const base::EventDispatcher* event_dispatcher); // Returns the MetricsService instance that this client is associated with. // With the exception of testing contexts, the returned instance must be valid @@ -165,10 +163,10 @@ CobaltMetricsLogUploader* log_uploader_ = nullptr; - const CobaltMetricsUploaderCallback* upload_handler_ = nullptr; - uint32_t custom_upload_interval_ = UINT32_MAX; + const base::EventDispatcher* event_dispatcher_ = nullptr; + DISALLOW_COPY_AND_ASSIGN(CobaltMetricsServiceClient); };
diff --git a/cobalt/browser/metrics/cobalt_metrics_services_manager.cc b/cobalt/browser/metrics/cobalt_metrics_services_manager.cc index acb5d8b..f816858 100644 --- a/cobalt/browser/metrics/cobalt_metrics_services_manager.cc +++ b/cobalt/browser/metrics/cobalt_metrics_services_manager.cc
@@ -17,6 +17,7 @@ #include <memory> #include "base/logging.h" +#include "cobalt/base/event_dispatcher.h" #include "cobalt/browser/metrics/cobalt_metrics_service_client.h" #include "cobalt/browser/metrics/cobalt_metrics_services_manager_client.h" #include "components/metrics_services_manager/metrics_services_manager.h" @@ -41,23 +42,27 @@ return instance_; } -void CobaltMetricsServicesManager::DeleteInstance() { delete instance_; } - -void CobaltMetricsServicesManager::SetOnUploadHandler( - const CobaltMetricsUploaderCallback* uploader_callback) { - instance_->task_runner_->PostTask( - FROM_HERE, - base::Bind(&CobaltMetricsServicesManager::SetOnUploadHandlerInternal, - base::Unretained(instance_), uploader_callback)); +void CobaltMetricsServicesManager::DeleteInstance() { + delete instance_; + instance_ = nullptr; } -void CobaltMetricsServicesManager::SetOnUploadHandlerInternal( - const CobaltMetricsUploaderCallback* uploader_callback) { +void CobaltMetricsServicesManager::SetEventDispatcher( + base::EventDispatcher* event_dispatcher) { + if (instance_ != nullptr) { + instance_->task_runner_->PostTask( + FROM_HERE, + base::Bind(&CobaltMetricsServicesManager::SetEventDispatcherInternal, + base::Unretained(instance_), event_dispatcher)); + } +} + +void CobaltMetricsServicesManager::SetEventDispatcherInternal( + base::EventDispatcher* event_dispatcher) { CobaltMetricsServiceClient* client = static_cast<CobaltMetricsServiceClient*>(GetMetricsServiceClient()); DCHECK(client); - client->SetOnUploadHandler(uploader_callback); - LOG(INFO) << "New Cobalt Telemetry metric upload handler bound."; + client->SetEventDispatcher(event_dispatcher); } void CobaltMetricsServicesManager::ToggleMetricsEnabled(bool is_enabled) {
diff --git a/cobalt/browser/metrics/cobalt_metrics_services_manager.h b/cobalt/browser/metrics/cobalt_metrics_services_manager.h index da31294..20a4345 100644 --- a/cobalt/browser/metrics/cobalt_metrics_services_manager.h +++ b/cobalt/browser/metrics/cobalt_metrics_services_manager.h
@@ -20,8 +20,8 @@ #include "base//memory/scoped_refptr.h" #include "base/single_thread_task_runner.h" +#include "cobalt/base/event_dispatcher.h" #include "cobalt/browser/metrics/cobalt_metrics_services_manager_client.h" -#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h" #include "components/metrics_services_manager/metrics_services_manager.h" #include "components/metrics_services_manager/metrics_services_manager_client.h" @@ -54,10 +54,9 @@ // Destructs the static instance of CobaltMetricsServicesManager. static void DeleteInstance(); - // Sets the upload handler onto the current static instance of - // CobaltMetricsServicesManager. - static void SetOnUploadHandler( - const CobaltMetricsUploaderCallback* uploader_callback); + // Sets an event dispatcher to call when metric events happen (e.g., on + // upload). + static void SetEventDispatcher(base::EventDispatcher* event_dispatcher); // Toggles whether metric reporting is enabled via // CobaltMetricsServicesManager. @@ -68,8 +67,7 @@ static void SetUploadInterval(uint32_t interval_seconds); private: - void SetOnUploadHandlerInternal( - const CobaltMetricsUploaderCallback* uploader_callback); + void SetEventDispatcherInternal(base::EventDispatcher* event_dispatcher); void ToggleMetricsEnabledInternal(bool is_enabled);
diff --git a/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h b/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h deleted file mode 100644 index db980c5..0000000 --- a/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h +++ /dev/null
@@ -1,36 +0,0 @@ -// Copyright 2023 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 COBALT_BROWSER_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_ -#define COBALT_BROWSER_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_ - -#include <string> - -#include "base/callback.h" -#include "cobalt/h5vcc/h5vcc_metric_type.h" - -namespace cobalt { -namespace browser { -namespace metrics { - -typedef base::RepeatingCallback<void( - const cobalt::h5vcc::H5vccMetricType& metric_type, - const std::string& serialized_proto)> - CobaltMetricsUploaderCallback; - -} // namespace metrics -} // namespace browser -} // namespace cobalt - -#endif // COBALT_BROWSER_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_
diff --git a/cobalt/browser/service_worker_registry.cc b/cobalt/browser/service_worker_registry.cc index 89b9395..2a62dac 100644 --- a/cobalt/browser/service_worker_registry.cc +++ b/cobalt/browser/service_worker_registry.cc
@@ -49,7 +49,7 @@ ServiceWorkerRegistry::ServiceWorkerRegistry( web::WebSettings* web_settings, network::NetworkModule* network_module, - web::UserAgentPlatformInfo* platform_info, const GURL& url) + web::UserAgentPlatformInfo* platform_info) : thread_("ServiceWorkerRegistry") { if (!thread_.Start()) return; DCHECK(message_loop()); @@ -73,7 +73,7 @@ message_loop()->task_runner()->PostTask( FROM_HERE, base::Bind(&ServiceWorkerRegistry::Initialize, base::Unretained(this), - web_settings, network_module, platform_info, url)); + web_settings, network_module, platform_info)); // Register as a destruction observer to shut down the Web Agent once all // pending tasks have been executed and the message loop is about to be @@ -145,11 +145,11 @@ void ServiceWorkerRegistry::Initialize( web::WebSettings* web_settings, network::NetworkModule* network_module, - web::UserAgentPlatformInfo* platform_info, const GURL& url) { + web::UserAgentPlatformInfo* platform_info) { TRACE_EVENT0("cobalt::browser", "ServiceWorkerRegistry::Initialize()"); DCHECK_EQ(base::MessageLoop::current(), message_loop()); service_worker_context_.reset(new worker::ServiceWorkerContext( - web_settings, network_module, platform_info, message_loop(), url)); + web_settings, network_module, platform_info, message_loop())); } } // namespace browser
diff --git a/cobalt/browser/service_worker_registry.h b/cobalt/browser/service_worker_registry.h index f0e934e..de31979 100644 --- a/cobalt/browser/service_worker_registry.h +++ b/cobalt/browser/service_worker_registry.h
@@ -35,8 +35,7 @@ public: ServiceWorkerRegistry(web::WebSettings* web_settings, network::NetworkModule* network_module, - web::UserAgentPlatformInfo* platform_info, - const GURL& url); + web::UserAgentPlatformInfo* platform_info); ~ServiceWorkerRegistry(); // The message loop this object is running on. @@ -58,7 +57,7 @@ // the dedicated thread. void Initialize(web::WebSettings* web_settings, network::NetworkModule* network_module, - web::UserAgentPlatformInfo* platform_info, const GURL& url); + web::UserAgentPlatformInfo* platform_info); void PingWatchdog();
diff --git a/cobalt/browser/splash_screen.h b/cobalt/browser/splash_screen.h index f50c2e4..3fc1202 100644 --- a/cobalt/browser/splash_screen.h +++ b/cobalt/browser/splash_screen.h
@@ -57,7 +57,6 @@ } // LifecycleObserver implementation. - // LifecycleObserver implementation. void Blur(SbTimeMonotonic timestamp) override { web_module_->Blur(0); } void Conceal(render_tree::ResourceProvider* resource_provider, SbTimeMonotonic timestamp) override {
diff --git a/cobalt/browser/switches.cc b/cobalt/browser/switches.cc index 70bb756..11c7db6 100644 --- a/cobalt/browser/switches.cc +++ b/cobalt/browser/switches.cc
@@ -229,7 +229,7 @@ const char kMinLogLevel[] = "min_log_level"; const char kMinLogLevelHelp[] = - "Set the minimum logging level: info|warning|error|fatal."; + "Set the minimum logging level: verbose|info|warning|error|fatal."; const char kDisableJavaScriptJit[] = "disable_javascript_jit"; const char kDisableJavaScriptJitHelp[] = "Specifies that javascript jit should be disabled.";
diff --git a/cobalt/build/cobalt_configuration.py b/cobalt/build/cobalt_configuration.py index e1320ab..ca1c943 100644 --- a/cobalt/build/cobalt_configuration.py +++ b/cobalt/build/cobalt_configuration.py
@@ -133,8 +133,7 @@ 'media_capture_test', 'media_session_test', 'media_stream_test', - # TODO(b/292030213): Crashes on evergreen - # 'media_test', + 'media_test', 'memory_store_test', 'metrics_test', 'nb_test',
diff --git a/cobalt/cache/cache.cc b/cobalt/cache/cache.cc index d88a6ec..42eee8e 100644 --- a/cobalt/cache/cache.cc +++ b/cobalt/cache/cache.cc
@@ -25,7 +25,6 @@ #include "base/strings/string_number_conversions.h" #include "base/values.h" #include "cobalt/configuration/configuration.h" -#include "cobalt/persistent_storage/persistent_settings.h" #include "net/disk_cache/cobalt/cobalt_backend_impl.h" #include "starboard/configuration_constants.h" #include "starboard/extension/javascript_cache.h" @@ -201,19 +200,6 @@ return nullptr; } -void Cache::set_enabled(bool enabled) { enabled_ = enabled; } - -void Cache::set_persistent_settings( - persistent_storage::PersistentSettings* persistent_settings) { - persistent_settings_ = persistent_settings; - - // Guaranteed to be called before any calls to Retrieve() - // since set_persistent_settings() is called from the Application() - // constructor before the NetworkModule is initialized. - set_enabled(persistent_settings_->GetPersistentSettingAsBool( - disk_cache::kCacheEnabledPersistentSettingsKey, true)); -} - MemoryCappedDirectory* Cache::GetMemoryCappedDirectory( disk_cache::ResourceType resource_type) { base::AutoLock auto_lock(lock_); @@ -222,16 +208,6 @@ return it->second.get(); } - // Read in size from persistent storage. - auto metadata = disk_cache::kTypeMetadata[resource_type]; - if (persistent_settings_) { - uint32_t bucket_size = static_cast<uint32_t>( - persistent_settings_->GetPersistentSettingAsDouble( - metadata.directory, metadata.max_size_bytes)); - disk_cache::kTypeMetadata[resource_type] = {metadata.directory, - bucket_size}; - } - auto cache_directory = GetCacheDirectory(resource_type); auto max_size = GetMaxCacheStorageInBytes(resource_type); if (!cache_directory || !max_size) { @@ -248,16 +224,9 @@ void Cache::Resize(disk_cache::ResourceType resource_type, uint32_t bytes) { if (resource_type != disk_cache::ResourceType::kCacheApi && resource_type != disk_cache::ResourceType::kCompiledScript && - resource_type != disk_cache::ResourceType::kServiceWorkerScript) + resource_type != disk_cache::ResourceType::kServiceWorkerScript) { return; - if (bytes == disk_cache::kTypeMetadata[resource_type].max_size_bytes) return; - - if (persistent_settings_) { - persistent_settings_->SetPersistentSetting( - disk_cache::kTypeMetadata[resource_type].directory, - std::make_unique<base::Value>(static_cast<double>(bytes))); } - disk_cache::kTypeMetadata[resource_type].max_size_bytes = bytes; auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type); if (memory_capped_directory) { memory_capped_directory->Resize(bytes); @@ -273,7 +242,7 @@ case disk_cache::ResourceType::kCacheApi: case disk_cache::ResourceType::kCompiledScript: case disk_cache::ResourceType::kServiceWorkerScript: - return disk_cache::kTypeMetadata[resource_type].max_size_bytes; + return disk_cache::settings::GetQuota(resource_type); default: return base::nullopt; } @@ -334,7 +303,7 @@ resource_type == disk_cache::ResourceType::kCacheApi) { return true; } - if (!enabled_) { + if (!disk_cache::settings::GetCacheEnabled()) { return false; } if (resource_type == disk_cache::ResourceType::kCompiledScript) {
diff --git a/cobalt/cache/cache.h b/cobalt/cache/cache.h index 40bd225..e98f335 100644 --- a/cobalt/cache/cache.h +++ b/cobalt/cache/cache.h
@@ -26,12 +26,12 @@ #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/macros.h" +#include "base/message_loop/message_loop.h" #include "base/optional.h" #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "base/values.h" #include "cobalt/cache/memory_capped_directory.h" -#include "cobalt/persistent_storage/persistent_settings.h" #include "net/disk_cache/cobalt/resource_type.h" namespace base { @@ -67,14 +67,9 @@ base::Optional<uint32_t> GetMaxCacheStorageInBytes( disk_cache::ResourceType resource_type); - void set_enabled(bool enabled); - - void set_persistent_settings( - persistent_storage::PersistentSettings* persistent_settings); - private: friend struct base::DefaultSingletonTraits<Cache>; - Cache() {} + Cache() = default; MemoryCappedDirectory* GetMemoryCappedDirectory( disk_cache::ResourceType resource_type); @@ -91,9 +86,6 @@ std::map<disk_cache::ResourceType, std::map<uint32_t, std::vector<base::WaitableEvent*>>> pending_; - bool enabled_ = true; - - persistent_storage::PersistentSettings* persistent_settings_ = nullptr; DISALLOW_COPY_AND_ASSIGN(Cache); }; // class Cache
diff --git a/cobalt/demos/content/BUILD.gn b/cobalt/demos/content/BUILD.gn index 1d6ec9a..9d60175 100644 --- a/cobalt/demos/content/BUILD.gn +++ b/cobalt/demos/content/BUILD.gn
@@ -32,6 +32,7 @@ "deviceorientation-demo/deviceorientation-demo.html", "disable-jit/index.html", "dom-gc-demo/dom-gc-demo.html", + "draw/index.html", "dual-playback-demo/bear.mp4", "dual-playback-demo/dual-playback-demo.html", "eme-demo/eme-demo.html",
diff --git a/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html new file mode 100644 index 0000000..3c40603 --- /dev/null +++ b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html
@@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> + <title>Configure Source Buffer Memory</title> + <style> + body { + background-color: #0f0; + } + video { + height: 240px; + width: 426px; + } + </style> +</head> +<body> + <script type="text/javascript" src="configure-source-buffer-memory.js"></script> + <video id="video"></video><br> + <div id="status"></div> +</body> +</html>
diff --git a/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js new file mode 100644 index 0000000..3f2c675 --- /dev/null +++ b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js
@@ -0,0 +1,142 @@ +// Copyright 2023 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. + +"use strict"; + +const QUOTA_EXCEEDED_ERROR_CODE = 22; +const mimeCodec = 'video/webm; codecs="vp9"'; +const assetURL = 'vp9-720p.webm'; + +const assetDuration = 15; +const assetSize = 344064; + +let status_div; +let video; + + +function fetchAB(url, cb) { + console.log("Fetching.. ", url); + const xhr = new XMLHttpRequest(); + xhr.open("get", url); + xhr.responseType = "arraybuffer"; + xhr.onload = () => { + console.log("onLoad - calling Callback"); + cb(xhr.response); + }; + console.log('Sending request for media segment ...'); + xhr.send(); +} + +function testAppendToBuffer(media_source, mem_limit) { + const mediaSource = media_source; + const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec); + if (mem_limit > 0) { + status_div.innerHTML += "Test SourceBuffer, setting memoryLimit to " + mem_limit + "<br>"; + sourceBuffer.memoryLimit = mem_limit; + } else { + status_div.innerHTML += "Test SourceBuffer, leaving memoryLimit at default<br>"; + } + + let MIN_SIZE = 12 * 1024 * 1024; + let ESTIMATED_MIN_TIME = 12; + + fetchAB(assetURL, (buf) => { + let expectedTime = 0; + let expectedSize = 0; + let appendCount = 0; + + + let onBufferFull = function(buffer_was_full) { + console.log("OnBufferFull! Quota exceeded? " + buffer_was_full + " appendCount:" + appendCount + " expectedTime:" + expectedTime); + status_div.innerHTML += "Finished! Quota exceeded? " + buffer_was_full + " appendCount:" + appendCount + " appended " + appendCount * assetSize + "<br>"; + } + + sourceBuffer.addEventListener("updateend", function onupdateend() { + console.log("Update end. State is " + sourceBuffer.updating); + appendCount++; + console.log("Append Count" + appendCount); + if (sourceBuffer.buffered.start(0) > 0 || expectedTime > sourceBuffer.buffered.end(0)) { + sourceBuffer.removeEventListener('updatedend', onupdateend); + onBufferFull(false); + } else { + expectedTime += assetDuration; + expectedSize += assetSize; + if (expectedSize > (10 * MIN_SIZE)) { + sourceBuffer.removeEventListener('updateend', onupdateend); + onBufferFull(false); + return; + } + + try { + sourceBuffer.timestampOffset = expectedTime; + } catch(e) { + console.log("Unexpected error: " + e); + } + + try { + sourceBuffer.appendBuffer(buf); + } catch(e) { + console.log("Wuff! QUOTA_EXCEEDED_ERROR!"); + status_div.innerHTML += "Wuff! QUOTA_EXCEEDED<br>"; + if (e.code == QUOTA_EXCEEDED_ERROR_CODE) { + sourceBuffer.removeEventListener('updateend', onupdateend); + onBufferFull(true); + } else { + console.log("Unexpected error: " + e); + } + } + } + }); + + console.log("First Append!"); + sourceBuffer.appendBuffer(buf); + status_div.innerHTML += "First append. MemoryLimit is:" + sourceBuffer.memoryLimit + ".<br>"; + }); +} + +function onSourceOpen() { + console.log("Source Open. This state:", this.readyState); // open + status_div.innerHTML += "Source Open. This state:" + this.readyState + "<br>"; + status_div.innerHTML += "Lets test first source_buffer, defaults..<br>"; + testAppendToBuffer(this, 0); + + let new_mem_limit = 400 * 1024 * 1024; + status_div.innerHTML += "<br><br>Lets test second source_buffer, setting memory to:" + new_mem_limit + "<br>"; + testAppendToBuffer(this, new_mem_limit); + video.play(); +} + + +function createMediaSource() { + + console.log('Video Get Element By Id...'); + video = document.getElementById('video'); + status_div = document.getElementById('status'); + status_div.innerHTML += 'Video Get Element By Id...<br>'; + + console.log('Create Media Source...'); + status_div.innerHTML += 'Create Media Source...<br>'; + var mediaSource = new MediaSource; + + console.log('Attaching MediaSource to video element ...'); + status_div.innerHTML += 'Attaching MediaSource to video element ...<br>'; + video.src = window.URL.createObjectURL(mediaSource); + + console.log('Add event listener..'); + status_div.innerHTML += 'Add event listener..<br>'; + mediaSource.addEventListener('sourceopen', onSourceOpen); + +} + +addEventListener('load', createMediaSource);
diff --git a/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm b/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm new file mode 100644 index 0000000..08a670e --- /dev/null +++ b/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm Binary files differ
diff --git a/cobalt/demos/content/draw/index.html b/cobalt/demos/content/draw/index.html new file mode 100644 index 0000000..6e596c6 --- /dev/null +++ b/cobalt/demos/content/draw/index.html
@@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <style> + .container { + width: 500px; + height: 500px; + position: relative; + background-color: white; + } + + .pixel { + width: 5px; + height: 5px; + position: absolute; + background-color: red; + } + </style> +</head> + +<body> + <div class="container" id="container"></div> +</body> + +<script> + window.addEventListener('load', () => registerHandlers()); + + function registerHandlers() { + const container = document.getElementById('container'); + let isDrawing = false; + + container.addEventListener('pointerdown', (e) => { + isDrawing = true; + colorPixel(e); + }); + document.addEventListener('pointerup', () => { isDrawing = false; }); + container.addEventListener('pointermove', colorPixel); + + function colorPixel(e) { + if (!isDrawing) return; + + const x = Math.floor((e.clientX - container.offsetLeft) / 5) * 5; + const y = Math.floor((e.clientY - container.offsetTop) / 5) * 5; + + const pixel = document.createElement('div'); + pixel.classList.add('pixel'); + pixel.style.left = `${x}px`; + pixel.style.top = `${y}px`; + + container.appendChild(pixel); + } + } +</script> + +</html>
diff --git a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html index 36e504f..8e3297a 100644 --- a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html +++ b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html
@@ -23,7 +23,7 @@ margin: 0; } - #player-layer { + #primary-player-layer { width: 100%; height: 100%; } @@ -33,13 +33,14 @@ height: 100%; } - #ui-layer { + #secondary-player-layer { position: absolute; - top: 15%; - height: 85%; + top: 60%; + height: 40%; width: 100%; - background-color: rgba(33, 33, 33, .75); padding: 24px; + display: flex; + justify-content: center; } .item { @@ -48,22 +49,23 @@ display: inline-block; margin: 24px; vertical-align: middle; + background-color: rgba(33, 33, 33, .75); } </style> </head> <body> - <div id="player-layer"> - <video class="primary" id="primary-video" muted="1" autoplay="1"></video> + <div id="primary-player-layer"> + <video id="primary-video" muted="1" autoplay="1"></video> </div> - <div id="ui-layer"> - <div class="item" style="background-color: #D44"> - <video class="secondary" id="secondary-video-1" muted="1" autoplay="1"></video> + <div id="secondary-player-layer"> + <div class="item"> + <video id="secondary-video-1" muted="1" autoplay="1"></video> </div> - <div class="item" style="background-color: #4D4"> - <video class="secondary" id="secondary-video-2" muted="1" autoplay="1"></video> + <div class="item"> + <video id="secondary-video-2" muted="1" autoplay="1"></video> </div> - <div class="item" style="background-color: #44D"> - <video class="secondary" id="secondary-video-3" muted="1" autoplay="1"></video> + <div class="item"> + <video id="secondary-video-3" muted="1" autoplay="1"></video> </div> </div> <script src="multi-encrypted-video.js"></script>
diff --git a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js index 028d11c..27dcddf 100644 --- a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js +++ b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js
@@ -12,6 +12,58 @@ // See the License for the specific language governing permissions and // limitations under the License. + +// Dictionary mapping string descriptions to media file descriptions in the +// form of [contentType, url, maxVideoCapabilities (for videos only), licenseUrl] +const MEDIA_FILES = { + 'av1_720p_60fps_drm': { + contentType: 'video/mp4; codecs="av01.0.05M.08"', + url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/av1-senc/sdr_720p60.mp4', + maxVideoCapabilities: 'width=1280; height=720', + licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=ik0&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=6508f99557a8385f&signature=5153900DAC410803EC269D252DAAA82BA6D8B825.495E631E406584A8EFCB4E9C9F3D45F6488B94E4', + }, + // 40 MB + 'h264_720p_24fps_drm': { + contentType: 'video/mp4; codecs="avc1.640028"', + url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-145-no-clear-start.mp4', + maxVideoCapabilities: 'width=1280; height=720', + licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD', + }, + // 38 MB + 'h264_720p_60fps_drm': { + contentType: 'video/mp4; codecs="avc1.640028"', + url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/drml3NoHdcp_h264_720p_60fps_cenc.mp4', + maxVideoCapabilities: 'width=1280; height=720', + licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD', + }, + // 32 MB + 'vp9_720p_60fps_drm': { + contentType: 'video/webm; codecs="vp9"', + url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/drml3NoHdcp_vp9_720p_60fps_enc.webm', + maxVideoCapabilities: 'width=1280; height=720', + licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=f320151fa3f061b2&signature=81E7B33929F9F35922F7D2E96A5E7AC36F3218B2.673F553EE51A48438AE5E707AEC87A071B4FEF65' + }, + // 1 MB + // Mono won't work with tunnel mode on many devices. + 'aac_mono_drm': { + contentType: 'audio/mp4; codecs="mp4a.40.2"', + url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-148.mp4', + licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD', + }, + // 2.8 MB + 'aac_clear': { + contentType: 'audio/mp4; codecs="mp4a.40.2"', + url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car-20120827-8c.mp4', + }, + // 1.7 MB + 'opus_clear': { + contentType: 'audio/webm; codecs="opus"', + url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/car_opus_med.webm', + }, +}; + +mediaCache = {} + function fetchArrayBuffer(method, url, body, callback) { var xhr = new XMLHttpRequest(); xhr.responseType = 'arraybuffer'; @@ -22,6 +74,16 @@ xhr.send(body); } +async function fetchMediaData(mediaFileId) { + if (mediaFileId in mediaCache) { + return mediaCache[mediaFileId]; + } + + const response = await fetch(MEDIA_FILES[mediaFileId].url); + mediaCache[mediaFileId] = await response.arrayBuffer(); + return mediaCache[mediaFileId]; +} + function extractLicense(licenseArrayBuffer) { var licenseArray = new Uint8Array(licenseArrayBuffer); var licenseStartIndex = licenseArray.length - 2; @@ -37,80 +99,156 @@ return licenseArray.subarray(licenseStartIndex); } -var videoContentType = 'video/mp4; codecs="avc1.640028"'; -var audioContentType = 'audio/mp4; codecs="mp4a.40.2"'; - -function play(videoElementId, keySystem) { - navigator.requestMediaKeySystemAccess(keySystem, [{ - 'initDataTypes': ['cenc'], - 'videoCapabilities': [{'contentType': videoContentType}], - 'audioCapabilities': [{'contentType': audioContentType}] - }]).then(function(mediaKeySystemAccess) { - return mediaKeySystemAccess.createMediaKeys(); - }).then(function(mediaKeys) { - var videoElement = document.getElementById(videoElementId); - - if (videoElementId != 'primary-video') { - videoElement.setMaxVideoCapabilities('width=1280; height=720'); +async function createMediaKeySystem(isPrimaryVideo, audioContentType, videoContentType) { + const keySystems = isPrimaryVideo ? ['com.widevine.alpha'] : ['com.youtube.widevine.l3', 'com.widevine.alpha']; + for (keySystem of keySystems) { + try { + mediaKeySystemAccess = await navigator.requestMediaKeySystemAccess(keySystem, [{ + 'initDataTypes': ['cenc', 'webm'], + 'audioCapabilities': [{'contentType': audioContentType}], + 'videoCapabilities': [{'contentType': videoContentType}]}]); + return mediaKeySystemAccess.createMediaKeys(); + } catch { + console.log('create keySystem ' + keySystem + ' failed.') + continue; } - - if (mediaKeys.getMetrics) { - console.log('Found getMetrics(), calling it ...'); - try { - mediaKeys.getMetrics(); - console.log('Calling getMetrics() succeeded.'); - } catch(e) { - console.log('Calling getMetrics() failed.'); - } - } - - videoElement.setMediaKeys(mediaKeys); - - mediaKeySession = mediaKeys.createSession(); - mediaKeySession.addEventListener('message', function(messageEvent) { - var licenseServerUrl = 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD'; - fetchArrayBuffer('POST', licenseServerUrl, messageEvent.message, - function(licenseArrayBuffer) { - mediaKeySession.update(extractLicense(licenseArrayBuffer)); - }); - }); - - videoElement.addEventListener('encrypted', function(encryptedEvent) { - mediaKeySession.generateRequest( - encryptedEvent.initDataType, encryptedEvent.initData); - }); - - var mediaSource = new MediaSource(); - mediaSource.addEventListener('sourceopen', function() { - var videoSourceBuffer = mediaSource.addSourceBuffer(videoContentType); - fetchArrayBuffer('GET', - 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-145-no-clear-start.mp4', - null, - function(videoArrayBuffer) { - videoSourceBuffer.appendBuffer(videoArrayBuffer); - }); - - var audioSourceBuffer = mediaSource.addSourceBuffer(audioContentType); - fetchArrayBuffer('GET', - 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-148.mp4', - null, - function(audioArrayBuffer) { - audioSourceBuffer.appendBuffer(audioArrayBuffer); - }); - }); - - videoElement.src = URL.createObjectURL(mediaSource); - videoElement.play(); - }); + } } -play('primary-video', 'com.widevine.alpha'); -window.setTimeout(function() { - play('secondary-video-1', 'com.youtube.widevine.l3'); -}, 10000); -window.setTimeout(function() { - play('secondary-video-2', 'com.youtube.widevine.l3'); -}, 20000); -window.setTimeout(function() { - play('secondary-video-3', 'com.youtube.widevine.l3'); -}, 30000); +function createTunnelModeContentType(videoContentType, tunnelModeAttributeValue) { + return videoContentType + '; tunnelmode=' + tunnelModeAttributeValue; +} + +function isTunnelModeSupported(videoContentType) { + if (!MediaSource.isTypeSupported(videoContentType)) { + // If the content type isn't supported at all, it won't be supported in + // tunnel mode. + return false; + } + if (MediaSource.isTypeSupported(createTunnelModeContentType(videoContentType, 'invalid'))) { + // The implementation doesn't understand the `tunnelmode` attribute. + return false; + } + return MediaSource.isTypeSupported(createTunnelModeContentType(videoContentType, 'true')); +} + +async function play(videoElementId, videoFileId, optionalAudioFileId) { + const isPrimaryVideo = videoElementId == 'primary-video'; + + videoContentType = MEDIA_FILES[videoFileId].contentType; + if (isTunnelModeSupported(videoContentType)) { + videoContentType = createTunnelModeContentType(videoContentType, 'true'); + } + + var mediaKeys = await createMediaKeySystem(isPrimaryVideo, optionalAudioFileId ? MEDIA_FILES[optionalAudioFileId].contentType : MEDIA_FILES['opus_clear'].contentType, videoContentType); + var videoElement = document.getElementById(videoElementId); + + if (!isPrimaryVideo && videoElement.setMaxVideoCapabilities) { + videoElement.setMaxVideoCapabilities(MEDIA_FILES[videoFileId].maxVideoCapabilities); + } + + videoElement.setMediaKeys(mediaKeys); + + mediaKeySession = mediaKeys.createSession(); + var licenseServerUrl = MEDIA_FILES[videoFileId].licenseUrl; + mediaKeySession.addEventListener('message', function(messageEvent) { + fetchArrayBuffer('POST', licenseServerUrl, messageEvent.message, + function(licenseArrayBuffer) { + mediaKeySession.update(extractLicense(licenseArrayBuffer)); + }); + }); + + videoElement.addEventListener('encrypted', function(encryptedEvent) { + mediaKeySession.generateRequest( + encryptedEvent.initDataType, encryptedEvent.initData); + }); + + var mediaSource = new MediaSource(); + mediaSource.addEventListener('sourceopen', async function() { + var videoSourceBuffer = mediaSource.addSourceBuffer(videoContentType); + var audioSourceBuffer; + + if (optionalAudioFileId) { + audioSourceBuffer = mediaSource.addSourceBuffer(MEDIA_FILES[optionalAudioFileId].contentType); + } + + var videoArrayBuffer = await fetchMediaData(videoFileId); + videoSourceBuffer.appendBuffer(videoArrayBuffer); + + if (audioSourceBuffer) { + var audioArrayBuffer = await fetchMediaData(optionalAudioFileId); + audioSourceBuffer.appendBuffer(audioArrayBuffer); + } + }); + + videoElement.src = URL.createObjectURL(mediaSource); + videoElement.play(); +} + +function getGetParameters() { + var parsedParameters = {}; + + const urlComponents = window.location.href.split('?'); + if (urlComponents.length < 2) { + return parsedParameters; + } + + const query = urlComponents[1]; + const parameters = query.split('&'); + + for (parameter of parameters) { + const split = parameter.split('='); + if (split.length == 0) { + continue; + } + if (split.length == 1) { + parsedParameters[split[0]] = ''; + } else { + parsedParameters[split[0]] = split[1]; + } + } + + return parsedParameters; +} + +function populateMediaFileIds() { + var mediaFileIds = []; + const getParameters = getGetParameters(); + + mediaFileIds['video0'] = getParameters['video0'] ?? 'vp9_720p_60fps_drm'; + mediaFileIds['video1'] = getParameters['video1'] ?? 'h264_720p_24fps_drm'; + mediaFileIds['video2'] = getParameters['video2'] ?? 'vp9_720p_60fps_drm'; + mediaFileIds['video3'] = getParameters['video3'] ?? 'h264_720p_24fps_drm'; + mediaFileIds['audio'] = getParameters['audio'] ?? 'opus_clear'; + + return mediaFileIds; +} + +async function prefetchMediaData(mediaFileIds) { + for (mediaFileId of Object.keys(mediaFileIds)) { + await fetchMediaData(mediaFileIds[mediaFileId]); + } +} + +async function main() { + if (window.h5vcc && window.h5vcc.settings) { + h5vcc.settings.set('MediaSource.EnableAvoidCopyingArrayBuffer', 1); + } + + const mediaFileIds = populateMediaFileIds(); + await prefetchMediaData(mediaFileIds); + + play('primary-video', mediaFileIds['video0'], mediaFileIds['audio']); + window.setTimeout(function() { + play('secondary-video-1', mediaFileIds['video1']); + }, 10000); + window.setTimeout(function() { + play('secondary-video-2', mediaFileIds['video2']); + }, 20000); + window.setTimeout(function() { + play('secondary-video-3', mediaFileIds['video3']); + }, 30000); +} + + +main();
diff --git a/cobalt/dom/html_element.cc b/cobalt/dom/html_element.cc index ef21c62..9ddbdcb 100644 --- a/cobalt/dom/html_element.cc +++ b/cobalt/dom/html_element.cc
@@ -161,6 +161,22 @@ base::LazyInstance<NonTrivialStaticFields>::DestructorAtExit non_trivial_static_fields = LAZY_INSTANCE_INITIALIZER; +void InvalidateScrollAreaCacheOfAncestors(Node* node) { + for (Node* ancestor_node = node; ancestor_node; + ancestor_node = ancestor_node->parent_node()) { + Element* ancestor_element = ancestor_node->AsElement(); + if (!ancestor_element) { + continue; + } + HTMLElement* ancestor_html_element = ancestor_element->AsHTMLElement(); + if (!ancestor_html_element) { + continue; + } + if (ancestor_html_element->layout_boxes()) + ancestor_html_element->layout_boxes()->scroll_area_cache().reset(); + } +} + } // namespace void HTMLElement::RuleMatchingState::Clear() { @@ -1167,6 +1183,7 @@ } void HTMLElement::InvalidateLayoutBoxSizes() { + InvalidateScrollAreaCacheOfAncestors(parent_node()); if (layout_boxes_) { layout_boxes_->InvalidateSizes(); @@ -1232,6 +1249,9 @@ void HTMLElement::OnUiNavScroll(SbTimeMonotonic /* time */) { Document* document = node_document(); + if (document->hidden()) { + return; + } scoped_refptr<Window> window(document ? document->window() : nullptr); DispatchEvent(new UIEvent(base::Tokens::scroll(), web::Event::kNotBubbles, web::Event::kNotCancelable, window));
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc index c3376ea..da10b2e 100644 --- a/cobalt/dom/html_media_element.cc +++ b/cobalt/dom/html_media_element.cc
@@ -1755,6 +1755,10 @@ exception_state); return; } + + LOG(INFO) << "max video capabilities is changed from \"" + << max_video_capabilities_ << "\" to \"" << max_video_capabilities + << "\""; max_video_capabilities_ = max_video_capabilities; }
diff --git a/cobalt/dom/layout_boxes.h b/cobalt/dom/layout_boxes.h index 440e982..b81a446 100644 --- a/cobalt/dom/layout_boxes.h +++ b/cobalt/dom/layout_boxes.h
@@ -15,6 +15,8 @@ #ifndef COBALT_DOM_LAYOUT_BOXES_H_ #define COBALT_DOM_LAYOUT_BOXES_H_ +#include <utility> + #include "base/memory/ref_counted.h" #include "cobalt/dom/directionality.h" #include "cobalt/dom/dom_rect_list.h" @@ -89,6 +91,9 @@ // Invalidate the layout box's render tree nodes. virtual void InvalidateRenderTreeNodes() = 0; + virtual base::Optional<std::pair<dom::Directionality, math::RectF>>& + scroll_area_cache() = 0; + // Update the navigation item associated with the layout boxes. virtual void SetUiNavItem( const scoped_refptr<ui_navigation::NavItem>& item) = 0;
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc index 039303f..1ad5a59 100644 --- a/cobalt/dom/source_buffer.cc +++ b/cobalt/dom/source_buffer.cc
@@ -521,6 +521,39 @@ tracer->Trace(video_tracks_); } +size_t SourceBuffer::memory_limit( + script::ExceptionState* exception_state) const { + if (!chunk_demuxer_) { + web::DOMException::Raise(web::DOMException::kInvalidStateErr, + exception_state); + return 0; + } + + size_t memory_limit = chunk_demuxer_->GetSourceBufferStreamMemoryLimit(id_); + + if (memory_limit == 0) { + web::DOMException::Raise(web::DOMException::kInvalidStateErr, + exception_state); + } + return memory_limit; +} + +void SourceBuffer::set_memory_limit(size_t memory_limit, + script::ExceptionState* exception_state) { + if (!chunk_demuxer_) { + web::DOMException::Raise(web::DOMException::kInvalidStateErr, + exception_state); + return; + } + + if (memory_limit == 0) { + web::DOMException::Raise(web::DOMException::kNotSupportedErr, + exception_state); + return; + } + chunk_demuxer_->SetSourceBufferStreamMemoryLimit(id_, memory_limit); +} + void SourceBuffer::OnInitSegmentReceived(std::unique_ptr<MediaTracks> tracks) { if (!first_initialization_segment_received_) { media_source_->SetSourceBufferActive(this, true);
diff --git a/cobalt/dom/source_buffer.h b/cobalt/dom/source_buffer.h index 5869760..0da8e81 100644 --- a/cobalt/dom/source_buffer.h +++ b/cobalt/dom/source_buffer.h
@@ -144,6 +144,9 @@ DEFINE_WRAPPABLE_TYPE(SourceBuffer); void TraceMembers(script::Tracer* tracer) override; + size_t memory_limit(script::ExceptionState* exception_state) const; + void set_memory_limit(size_t limit, script::ExceptionState* exception_state); + private: typedef ::media::MediaTracks MediaTracks; typedef script::ArrayBuffer ArrayBuffer;
diff --git a/cobalt/dom/source_buffer.idl b/cobalt/dom/source_buffer.idl index 0d81b03..a963802 100644 --- a/cobalt/dom/source_buffer.idl +++ b/cobalt/dom/source_buffer.idl
@@ -39,4 +39,10 @@ // `InvalidStateError` if the SourceBuffer object has been removed from the // MediaSource object. [RaisesException] readonly attribute double writeHead; + + // Non standard stream memory limit modifier. This will override the default + // stream memory limit which is tied to the resolution of the video. + // This will be passed down to the SourceBufferStream associated with this + // instance. + [RaisesException] attribute unsigned long long memoryLimit; };
diff --git a/cobalt/dom/testing/mock_layout_boxes.h b/cobalt/dom/testing/mock_layout_boxes.h index 7065808..3d00913 100644 --- a/cobalt/dom/testing/mock_layout_boxes.h +++ b/cobalt/dom/testing/mock_layout_boxes.h
@@ -15,6 +15,8 @@ #ifndef COBALT_DOM_TESTING_MOCK_LAYOUT_BOXES_H_ #define COBALT_DOM_TESTING_MOCK_LAYOUT_BOXES_H_ +#include <utility> + #include "cobalt/dom/layout_boxes.h" namespace cobalt { @@ -57,6 +59,8 @@ MOCK_METHOD0(InvalidateSizes, void()); MOCK_METHOD0(InvalidateCrossReferences, void()); MOCK_METHOD0(InvalidateRenderTreeNodes, void()); + MOCK_METHOD0(scroll_area_cache, + base::Optional<std::pair<dom::Directionality, math::RectF>>&()); MOCK_METHOD1(SetUiNavItem, void(const scoped_refptr<ui_navigation::NavItem>& item)); };
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn index 901f668..1de4356 100644 --- a/cobalt/h5vcc/BUILD.gn +++ b/cobalt/h5vcc/BUILD.gn
@@ -45,6 +45,8 @@ "h5vcc_event_listener_container.h", "h5vcc_metrics.cc", "h5vcc_metrics.h", + "h5vcc_net_log.cc", + "h5vcc_net_log.h", "h5vcc_platform_service.cc", "h5vcc_platform_service.h", "h5vcc_runtime.cc",
diff --git a/cobalt/h5vcc/h5vcc.cc b/cobalt/h5vcc/h5vcc.cc index 8d957b8..ebf7b56 100644 --- a/cobalt/h5vcc/h5vcc.cc +++ b/cobalt/h5vcc/h5vcc.cc
@@ -31,7 +31,8 @@ audio_config_array_ = new H5vccAudioConfigArray(); c_val_ = new dom::CValView(); crash_log_ = new H5vccCrashLog(); - metrics_ = new H5vccMetrics(settings.persistent_settings); + metrics_ = + new H5vccMetrics(settings.persistent_settings, settings.event_dispatcher); runtime_ = new H5vccRuntime(settings.event_dispatcher); settings_ = new H5vccSettings(settings.set_web_setting_func, settings.media_module, @@ -44,6 +45,7 @@ storage_ = new H5vccStorage(settings.network_module, settings.persistent_settings); trace_event_ = new H5vccTraceEvent(); + net_log_ = new H5vccNetLog(settings.network_module); #if SB_IS(EVERGREEN) updater_ = new H5vccUpdater(settings.updater_module); system_ = new H5vccSystem(updater_); @@ -81,6 +83,7 @@ tracer->Trace(storage_); tracer->Trace(system_); tracer->Trace(trace_event_); + tracer->Trace(net_log_); #if SB_IS(EVERGREEN) tracer->Trace(updater_); #endif
diff --git a/cobalt/h5vcc/h5vcc.h b/cobalt/h5vcc/h5vcc.h index 0d60a7c..0852eaa 100644 --- a/cobalt/h5vcc/h5vcc.h +++ b/cobalt/h5vcc/h5vcc.h
@@ -26,6 +26,7 @@ #include "cobalt/h5vcc/h5vcc_audio_config_array.h" #include "cobalt/h5vcc/h5vcc_crash_log.h" #include "cobalt/h5vcc/h5vcc_metrics.h" +#include "cobalt/h5vcc/h5vcc_net_log.h" #include "cobalt/h5vcc/h5vcc_runtime.h" #include "cobalt/h5vcc/h5vcc_settings.h" #include "cobalt/h5vcc/h5vcc_storage.h" @@ -89,6 +90,7 @@ const scoped_refptr<H5vccTraceEvent>& trace_event() const { return trace_event_; } + const scoped_refptr<H5vccNetLog>& net_log() const { return net_log_; } #if SB_IS(EVERGREEN) const scoped_refptr<H5vccUpdater>& updater() const { return updater_; } #endif @@ -108,6 +110,7 @@ scoped_refptr<H5vccStorage> storage_; scoped_refptr<H5vccSystem> system_; scoped_refptr<H5vccTraceEvent> trace_event_; + scoped_refptr<H5vccNetLog> net_log_; #if SB_IS(EVERGREEN) scoped_refptr<H5vccUpdater> updater_; #endif
diff --git a/cobalt/h5vcc/h5vcc.idl b/cobalt/h5vcc/h5vcc.idl index 188e66b..2beb5dc 100644 --- a/cobalt/h5vcc/h5vcc.idl +++ b/cobalt/h5vcc/h5vcc.idl
@@ -42,6 +42,7 @@ readonly attribute H5vccStorage storage; readonly attribute H5vccSystem system; readonly attribute H5vccTraceEvent traceEvent; + readonly attribute H5vccNetLog netLog; [Conditional=SB_IS_EVERGREEN] readonly attribute H5vccUpdater updater; };
diff --git a/cobalt/h5vcc/h5vcc_metrics.cc b/cobalt/h5vcc/h5vcc_metrics.cc index 0c9062a..583eafd 100644 --- a/cobalt/h5vcc/h5vcc_metrics.cc +++ b/cobalt/h5vcc/h5vcc_metrics.cc
@@ -17,6 +17,9 @@ #include <string> #include "base/values.h" +#include "cobalt/base/event.h" +#include "cobalt/base/event_dispatcher.h" +#include "cobalt/base/on_metric_upload_event.h" #include "cobalt/browser/metrics/cobalt_metrics_service_client.h" #include "cobalt/browser/metrics/cobalt_metrics_services_manager.h" #include "cobalt/h5vcc/h5vcc_metric_type.h" @@ -25,17 +28,34 @@ namespace cobalt { namespace h5vcc { + +H5vccMetrics::H5vccMetrics( + persistent_storage::PersistentSettings* persistent_settings, + base::EventDispatcher* event_dispatcher) + : task_runner_(base::ThreadTaskRunnerHandle::Get()), + persistent_settings_(persistent_settings), + event_dispatcher_(event_dispatcher) { + DCHECK(event_dispatcher_); + on_metric_upload_event_callback_ = + base::Bind(&H5vccMetrics::OnMetricUploadEvent, base::Unretained(this)); + event_dispatcher_->AddEventCallback(base::OnMetricUploadEvent::TypeId(), + on_metric_upload_event_callback_); +} + +H5vccMetrics::~H5vccMetrics() { + event_dispatcher_->RemoveEventCallback(base::OnMetricUploadEvent::TypeId(), + on_metric_upload_event_callback_); +} + +void H5vccMetrics::OnMetricUploadEvent(const base::Event* event) { + std::unique_ptr<base::OnMetricUploadEvent> on_metric_upload_event( + new base::OnMetricUploadEvent(event)); + RunEventHandler(on_metric_upload_event->metric_type(), + on_metric_upload_event->serialized_proto()); +} + void H5vccMetrics::OnMetricEvent( const h5vcc::MetricEventHandlerWrapper::ScriptValue& event_handler) { - if (!uploader_callback_) { - run_event_handler_callback_ = std::make_unique< - cobalt::browser::metrics::CobaltMetricsUploaderCallback>( - base::BindRepeating(&H5vccMetrics::RunEventHandler, - base::Unretained(this))); - browser::metrics::CobaltMetricsServicesManager::GetInstance() - ->SetOnUploadHandler(run_event_handler_callback_.get()); - } - uploader_callback_ = new h5vcc::MetricEventHandlerWrapper(this, event_handler); } @@ -43,16 +63,20 @@ void H5vccMetrics::RunEventHandler( const cobalt::h5vcc::H5vccMetricType& metric_type, const std::string& serialized_proto) { - task_runner_->PostTask( - FROM_HERE, - base::Bind(&H5vccMetrics::RunEventHandlerInternal, base::Unretained(this), - metric_type, serialized_proto)); + if (task_runner_ && task_runner_->HasAtLeastOneRef()) { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&H5vccMetrics::RunEventHandlerInternal, + base::Unretained(this), metric_type, serialized_proto)); + } } void H5vccMetrics::RunEventHandlerInternal( const cobalt::h5vcc::H5vccMetricType& metric_type, const std::string& serialized_proto) { - uploader_callback_->callback.value().Run(metric_type, serialized_proto); + if (uploader_callback_ != nullptr && uploader_callback_->HasAtLeastOneRef()) { + uploader_callback_->callback.value().Run(metric_type, serialized_proto); + } } void H5vccMetrics::Enable() { ToggleMetricsEnabled(true); }
diff --git a/cobalt/h5vcc/h5vcc_metrics.h b/cobalt/h5vcc/h5vcc_metrics.h index a8f69ea..93a444a 100644 --- a/cobalt/h5vcc/h5vcc_metrics.h +++ b/cobalt/h5vcc/h5vcc_metrics.h
@@ -20,7 +20,8 @@ #include "base/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" -#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h" +#include "cobalt/base/event.h" +#include "cobalt/base/event_dispatcher.h" #include "cobalt/h5vcc/h5vcc_metric_type.h" #include "cobalt/h5vcc/metric_event_handler_wrapper.h" #include "cobalt/persistent_storage/persistent_settings.h" @@ -43,14 +44,15 @@ typedef MetricEventHandler H5vccMetricEventHandler; explicit H5vccMetrics( - persistent_storage::PersistentSettings* persistent_settings) - : task_runner_(base::ThreadTaskRunnerHandle::Get()), - persistent_settings_(persistent_settings) {} + persistent_storage::PersistentSettings* persistent_settings, + base::EventDispatcher* event_dispatcher); + + ~H5vccMetrics(); H5vccMetrics(const H5vccMetrics&) = delete; H5vccMetrics& operator=(const H5vccMetrics&) = delete; - // Binds an event handler that will be invoked every time Cobalt wants to + // Binds a JS event handler that will be invoked every time Cobalt wants to // upload a metrics payload. void OnMetricEvent( const MetricEventHandlerWrapper::ScriptValue& event_handler); @@ -81,14 +83,20 @@ const cobalt::h5vcc::H5vccMetricType& metric_type, const std::string& serialized_proto); - scoped_refptr<h5vcc::MetricEventHandlerWrapper> uploader_callback_; + // Handler method triggered when EventDispatcher sends OnMetricUploadEvents. + void OnMetricUploadEvent(const base::Event* event); - std::unique_ptr<cobalt::browser::metrics::CobaltMetricsUploaderCallback> - run_event_handler_callback_; + scoped_refptr<h5vcc::MetricEventHandlerWrapper> uploader_callback_; scoped_refptr<base::SingleThreadTaskRunner> const task_runner_; persistent_storage::PersistentSettings* persistent_settings_; + + // Non-owned reference used to receive application event callbacks, namely + // metric log upload events. + base::EventDispatcher* event_dispatcher_; + + base::EventCallback on_metric_upload_event_callback_; }; } // namespace h5vcc
diff --git a/cobalt/h5vcc/h5vcc_net_log.cc b/cobalt/h5vcc/h5vcc_net_log.cc new file mode 100644 index 0000000..8a5b3a2 --- /dev/null +++ b/cobalt/h5vcc/h5vcc_net_log.cc
@@ -0,0 +1,43 @@ +// Copyright 2023 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/h5vcc/h5vcc_net_log.h" + +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "cobalt/base/cobalt_paths.h" +#include "cobalt/network/cobalt_net_log.h" + +namespace cobalt { +namespace h5vcc { + +H5vccNetLog::H5vccNetLog(cobalt::network::NetworkModule* network_module) + : network_module_{network_module} {} + +void H5vccNetLog::Start() { network_module_->StartNetLog(); } + +void H5vccNetLog::Stop() { network_module_->StopNetLog(); } + +std::string H5vccNetLog::StopAndRead() { + base::FilePath netlog_path = network_module_->StopNetLog(); + std::string netlog_output{}; + if (!netlog_path.empty()) { + ReadFileToString(netlog_path, &netlog_output); + } + return netlog_output; +} + + +} // namespace h5vcc +} // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_net_log.h b/cobalt/h5vcc/h5vcc_net_log.h new file mode 100644 index 0000000..801839b --- /dev/null +++ b/cobalt/h5vcc/h5vcc_net_log.h
@@ -0,0 +1,45 @@ +// Copyright 2023 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 COBALT_H5VCC_H5VCC_NET_LOG_H_ +#define COBALT_H5VCC_H5VCC_NET_LOG_H_ + +#include <string> + +#include "cobalt/network/network_module.h" +#include "cobalt/script/wrappable.h" + +namespace cobalt { +namespace h5vcc { + +class H5vccNetLog : public script::Wrappable { + public: + explicit H5vccNetLog(cobalt::network::NetworkModule* network_module); + + void Start(); + void Stop(); + std::string StopAndRead(); + + DEFINE_WRAPPABLE_TYPE(H5vccNetLog); + + private: + cobalt::network::NetworkModule* network_module_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(H5vccNetLog); +}; + +} // namespace h5vcc +} // namespace cobalt + +#endif // COBALT_H5VCC_H5VCC_NET_LOG_H_
diff --git a/starboard/shared/posix/time_zone_get_name.cc b/cobalt/h5vcc/h5vcc_net_log.idl similarity index 60% copy from starboard/shared/posix/time_zone_get_name.cc copy to cobalt/h5vcc/h5vcc_net_log.idl index 5a1e013..2c60792 100644 --- a/starboard/shared/posix/time_zone_get_name.cc +++ b/cobalt/h5vcc/h5vcc_net_log.idl
@@ -1,4 +1,4 @@ -// Copyright 2015 The Cobalt Authors. All Rights Reserved. +// Copyright 2023 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. @@ -12,13 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/time_zone.h" - -#include <time.h> - -const char* SbTimeZoneGetName() { - // TODO: Using tzname assumes that tzset() has been called at some - // point. That should happen as part of Starboard's main loop initialization, - // but that doesn't exist yet. - return tzname[0]; -} +interface H5vccNetLog { + void start(); + void stop(); + DOMString stopAndRead(); +};
diff --git a/cobalt/h5vcc/h5vcc_runtime.cc b/cobalt/h5vcc/h5vcc_runtime.cc index 15f682b..1ec7fcc 100644 --- a/cobalt/h5vcc/h5vcc_runtime.cc +++ b/cobalt/h5vcc/h5vcc_runtime.cc
@@ -24,7 +24,8 @@ namespace h5vcc { H5vccRuntime::H5vccRuntime(base::EventDispatcher* event_dispatcher) : event_dispatcher_(event_dispatcher), - message_loop_(base::MessageLoop::current()) { + task_runner_(base::ThreadTaskRunnerHandle::Get()) { + DCHECK(task_runner_); on_deep_link_ = new H5vccDeepLinkEventTarget( base::Bind(&H5vccRuntime::GetUnconsumedDeepLink, base::Unretained(this))); on_pause_ = new H5vccRuntimeEventTarget; @@ -87,8 +88,9 @@ void H5vccRuntime::OnEventForDeepLink(const base::Event* event) { std::unique_ptr<base::DeepLinkEvent> deep_link_event( new base::DeepLinkEvent(event)); - if (base::MessageLoop::current() != message_loop_) { - message_loop_->task_runner()->PostTask( + if (!task_runner_) return; + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->PostTask( FROM_HERE, base::Bind(&H5vccRuntime::OnDeepLinkEvent, base::Unretained(this), base::Passed(std::move(deep_link_event))));
diff --git a/cobalt/h5vcc/h5vcc_runtime.h b/cobalt/h5vcc/h5vcc_runtime.h index 560543f..6c0ebc1 100644 --- a/cobalt/h5vcc/h5vcc_runtime.h +++ b/cobalt/h5vcc/h5vcc_runtime.h
@@ -19,6 +19,7 @@ #include <string> #include "base/callback.h" +#include "base/single_thread_task_runner.h" #include "base/threading/thread_checker.h" #include "cobalt/base/deep_link_event.h" #include "cobalt/base/event_dispatcher.h" @@ -63,9 +64,9 @@ base::EventCallback deep_link_event_callback_; base::OnceClosure consumed_callback_; - // Track the message loop that created this object so deep link events are - // handled from the same thread. - base::MessageLoop* message_loop_; + // Track the task runner from where this object is created so deep link events + // are handled from the same task runner. + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; // Thread checker ensures all calls to DOM element are made from the same // thread that it is created in.
diff --git a/cobalt/h5vcc/h5vcc_settings.cc b/cobalt/h5vcc/h5vcc_settings.cc index e6c7b26..4c47039 100644 --- a/cobalt/h5vcc/h5vcc_settings.cc +++ b/cobalt/h5vcc/h5vcc_settings.cc
@@ -23,16 +23,6 @@ namespace cobalt { namespace h5vcc { -namespace { -// Only including needed video combinations for the moment. -// option 0 disables all video codecs except h264 -// option 1 disables all video codecs except av1 -// option 2 disables all video codecs except vp9 -constexpr std::array<const char*, 3> kDisableCodecCombinations{ - {"av01;hev1;hvc1;vp09;vp8.vp9", "avc1;avc3;hev1;hvc1;vp09;vp8;vp9", - "av01;avc1;avc3;hev1;hvc1;vp8"}}; -}; // namespace - H5vccSettings::H5vccSettings( const SetSettingFunc& set_web_setting_func, cobalt::media::MediaModule* media_module, @@ -56,62 +46,47 @@ persistent_settings_(persistent_settings) { } -bool H5vccSettings::Set(const std::string& name, int32 value) const { +bool H5vccSettings::Set(const std::string& name, SetValueType value) const { const char kMediaPrefix[] = "Media."; - const char kDisableMediaCodec[] = "DisableMediaCodec"; + const char kMediaCodecBlockList[] = "MediaCodecBlockList"; const char kNavigatorUAData[] = "NavigatorUAData"; - const char kClientHintHeaders[] = "ClientHintHeaders"; const char kQUIC[] = "QUIC"; #if SB_IS(EVERGREEN) const char kUpdaterMinFreeSpaceBytes[] = "Updater.MinFreeSpaceBytes"; #endif - if (name == kDisableMediaCodec && - value < static_cast<int32>(kDisableCodecCombinations.size())) { - can_play_type_handler_->SetDisabledMediaCodecs( - kDisableCodecCombinations[value]); + if (name == kMediaCodecBlockList && value.IsType<std::string>() && + value.AsType<std::string>().size() < 256) { + can_play_type_handler_->SetDisabledMediaCodecs(value.AsType<std::string>()); return true; } - if (set_web_setting_func_ && set_web_setting_func_.Run(name, value)) { + if (set_web_setting_func_ && value.IsType<int32>() && + set_web_setting_func_.Run(name, value.AsType<int32>())) { return true; } - if (name.rfind(kMediaPrefix, 0) == 0) { - return media_module_ ? media_module_->SetConfiguration( - name.substr(strlen(kMediaPrefix)), value) - : false; + if (name.rfind(kMediaPrefix, 0) == 0 && value.IsType<int32>()) { + return media_module_ + ? media_module_->SetConfiguration( + name.substr(strlen(kMediaPrefix)), value.AsType<int32>()) + : false; } - if (name.compare(kNavigatorUAData) == 0 && value == 1) { + if (name.compare(kNavigatorUAData) == 0 && value.IsType<int32>() && + value.AsType<int32>() == 1) { global_environment_->BindTo("userAgentData", user_agent_data_, "navigator"); return true; } - if (name.compare(kClientHintHeaders) == 0) { - if (!persistent_settings_) { - return false; - } else { - persistent_settings_->SetPersistentSetting( - network::kClientHintHeadersEnabledPersistentSettingsKey, - std::make_unique<base::Value>(value)); - // Tell NetworkModule (if exists) to re-query persistent settings. - if (network_module_) { - network_module_ - ->SetEnableClientHintHeadersFlagsFromPersistentSettings(); - } - return true; - } - } - - if (name.compare(kQUIC) == 0) { + if (name.compare(kQUIC) == 0 && value.IsType<int32>()) { if (!persistent_settings_) { return false; } else { persistent_settings_->SetPersistentSetting( network::kQuicEnabledPersistentSettingsKey, - std::make_unique<base::Value>(value != 0)); + std::make_unique<base::Value>(value.AsType<int32>() != 0)); // Tell NetworkModule (if exists) to re-query persistent settings. if (network_module_) { network_module_->SetEnableQuicFromPersistentSettings(); @@ -121,8 +96,8 @@ } #if SB_IS(EVERGREEN) - if (name.compare(kUpdaterMinFreeSpaceBytes) == 0) { - updater_module_->SetMinFreeSpaceBytes(value); + if (name.compare(kUpdaterMinFreeSpaceBytes) == 0 && value.IsType<int32>()) { + updater_module_->SetMinFreeSpaceBytes(value.AsType<int32>()); return true; } #endif
diff --git a/cobalt/h5vcc/h5vcc_settings.h b/cobalt/h5vcc/h5vcc_settings.h index 34e7a44..f7e10cf 100644 --- a/cobalt/h5vcc/h5vcc_settings.h +++ b/cobalt/h5vcc/h5vcc_settings.h
@@ -21,6 +21,7 @@ #include "cobalt/network/network_module.h" #include "cobalt/persistent_storage/persistent_settings.h" #include "cobalt/script/global_environment.h" +#include "cobalt/script/union_type.h" #include "cobalt/script/wrappable.h" #include "cobalt/web/navigator_ua_data.h" @@ -39,6 +40,8 @@ typedef base::Callback<bool(const std::string& name, int value)> SetSettingFunc; + typedef script::UnionType2<int32, std::string> SetValueType; + H5vccSettings(const SetSettingFunc& set_web_setting_func, cobalt::media::MediaModule* media_module, cobalt::media::CanPlayTypeHandler* can_play_type_handler, @@ -53,7 +56,7 @@ // Returns true when the setting is set successfully or if the setting has // already been set to the expected value. Returns false when the setting is // invalid or not set to the expected value. - bool Set(const std::string& name, int32 value) const; + bool Set(const std::string& name, SetValueType value) const; DEFINE_WRAPPABLE_TYPE(H5vccSettings);
diff --git a/cobalt/h5vcc/h5vcc_settings.idl b/cobalt/h5vcc/h5vcc_settings.idl index fe250c8..34650a9 100644 --- a/cobalt/h5vcc/h5vcc_settings.idl +++ b/cobalt/h5vcc/h5vcc_settings.idl
@@ -13,5 +13,5 @@ // limitations under the License. interface H5vccSettings { - boolean set(DOMString name, long value); + boolean set(DOMString name, (long or DOMString) value); };
diff --git a/cobalt/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc index dac5b5c..346c104 100644 --- a/cobalt/h5vcc/h5vcc_storage.cc +++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -74,13 +74,12 @@ } void DeleteCacheResourceTypeDirectory(disk_cache::ResourceType type) { - auto metadata = disk_cache::kTypeMetadata[type]; + std::string directory = disk_cache::defaults::GetSubdirectory(type); std::vector<char> cache_dir(kSbFileMaxPath + 1, 0); SbSystemGetPath(kSbSystemPathCacheDirectory, cache_dir.data(), kSbFileMaxPath); base::FilePath cache_type_dir = - base::FilePath(cache_dir.data()) - .Append(FILE_PATH_LITERAL(metadata.directory)); + base::FilePath(cache_dir.data()).Append(FILE_PATH_LITERAL(directory)); ClearDirectory(cache_type_dir); } @@ -313,6 +312,11 @@ void H5vccStorage::SetAndSaveQuotaForBackend(disk_cache::ResourceType type, uint32_t bytes) { + if (disk_cache::settings::GetQuota(type) == bytes) { + return; + } + disk_cache::settings::SetQuota(type, bytes); + network_module_->url_request_context()->UpdateCacheSizeSetting(type, bytes); if (cache_backend_) { cache_backend_->UpdateSizes(type, bytes); @@ -332,21 +336,19 @@ return quota; } - quota.set_other(cache_backend_->GetQuota(disk_cache::kOther)); - quota.set_html(cache_backend_->GetQuota(disk_cache::kHTML)); - quota.set_css(cache_backend_->GetQuota(disk_cache::kCSS)); - quota.set_image(cache_backend_->GetQuota(disk_cache::kImage)); - quota.set_font(cache_backend_->GetQuota(disk_cache::kFont)); - quota.set_splash(cache_backend_->GetQuota(disk_cache::kSplashScreen)); + quota.set_other(disk_cache::settings::GetQuota(disk_cache::kOther)); + quota.set_html(disk_cache::settings::GetQuota(disk_cache::kHTML)); + quota.set_css(disk_cache::settings::GetQuota(disk_cache::kCSS)); + quota.set_image(disk_cache::settings::GetQuota(disk_cache::kImage)); + quota.set_font(disk_cache::settings::GetQuota(disk_cache::kFont)); + quota.set_splash(disk_cache::settings::GetQuota(disk_cache::kSplashScreen)); quota.set_uncompiled_js( - cache_backend_->GetQuota(disk_cache::kUncompiledScript)); + disk_cache::settings::GetQuota(disk_cache::kUncompiledScript)); quota.set_compiled_js( - cobalt::cache::Cache::GetInstance() - ->GetMaxCacheStorageInBytes(disk_cache::kCompiledScript) - .value()); - quota.set_cache_api(cache_backend_->GetQuota(disk_cache::kCacheApi)); + disk_cache::settings::GetQuota(disk_cache::kCompiledScript)); + quota.set_cache_api(disk_cache::settings::GetQuota(disk_cache::kCacheApi)); quota.set_service_worker_js( - cache_backend_->GetQuota(disk_cache::kServiceWorkerScript)); + disk_cache::settings::GetQuota(disk_cache::kServiceWorkerScript)); uint32_t max_quota_size = 24 * 1024 * 1024; #if SB_API_VERSION >= 14 @@ -366,7 +368,7 @@ disk_cache::kCacheEnabledPersistentSettingsKey, std::make_unique<base::Value>(true)); - cobalt::cache::Cache::GetInstance()->set_enabled(true); + disk_cache::settings::SetCacheEnabled(true); if (http_cache_) { http_cache_->set_mode(net::HttpCache::Mode::NORMAL); @@ -378,7 +380,7 @@ disk_cache::kCacheEnabledPersistentSettingsKey, std::make_unique<base::Value>(false)); - cobalt::cache::Cache::GetInstance()->set_enabled(false); + disk_cache::settings::SetCacheEnabled(false); if (http_cache_) { http_cache_->set_mode(net::HttpCache::Mode::DISABLE);
diff --git a/cobalt/h5vcc/h5vcc_system.cc b/cobalt/h5vcc/h5vcc_system.cc index 89c9d9a..addb717 100644 --- a/cobalt/h5vcc/h5vcc_system.cc +++ b/cobalt/h5vcc/h5vcc_system.cc
@@ -21,6 +21,10 @@ #include "starboard/common/system_property.h" #include "starboard/system.h" +#if SB_API_VERSION < 14 +#include "starboard/extension/ifa.h" +#endif // SB_API_VERSION < 14 + using starboard::kSystemPropertyMaxLength; namespace cobalt { @@ -57,27 +61,56 @@ std::string H5vccSystem::advertising_id() const { std::string result; -#if SB_API_VERSION >= 14 char property[kSystemPropertyMaxLength] = {0}; +#if SB_API_VERSION >= 14 if (!SbSystemGetProperty(kSbSystemPropertyAdvertisingId, property, SB_ARRAY_SIZE_INT(property))) { DLOG(FATAL) << "Failed to get kSbSystemPropertyAdvertisingId."; } else { result = property; } +#else + static auto const* ifa_extension = + static_cast<const StarboardExtensionIfaApi*>( + SbSystemGetExtension(kStarboardExtensionIfaName)); + if (ifa_extension && + strcmp(ifa_extension->name, kStarboardExtensionIfaName) == 0 && + ifa_extension->version >= 1) { + if (!ifa_extension->GetAdvertisingId(property, + SB_ARRAY_SIZE_INT(property))) { + DLOG(FATAL) << "Failed to get AdvertisingId from IFA extension."; + } else { + result = property; + } + } #endif return result; } bool H5vccSystem::limit_ad_tracking() const { bool result = false; -#if SB_API_VERSION >= 14 char property[kSystemPropertyMaxLength] = {0}; +#if SB_API_VERSION >= 14 if (!SbSystemGetProperty(kSbSystemPropertyLimitAdTracking, property, SB_ARRAY_SIZE_INT(property))) { DLOG(FATAL) << "Failed to get kSbSystemPropertyAdvertisingId."; } else { result = std::atoi(property); } +#else + static auto const* ifa_extension = + static_cast<const StarboardExtensionIfaApi*>( + SbSystemGetExtension(kStarboardExtensionIfaName)); + + if (ifa_extension && + strcmp(ifa_extension->name, kStarboardExtensionIfaName) == 0 && + ifa_extension->version >= 1) { + if (!ifa_extension->GetLimitAdTracking(property, + SB_ARRAY_SIZE_INT(property))) { + DLOG(FATAL) << "Failed to get LimitAdTracking from IFA extension."; + } else { + result = std::atoi(property); + } + } #endif return result; }
diff --git a/cobalt/layout/layout_boxes.h b/cobalt/layout/layout_boxes.h index 42ee844..919e57e 100644 --- a/cobalt/layout/layout_boxes.h +++ b/cobalt/layout/layout_boxes.h
@@ -67,6 +67,11 @@ // const Boxes& boxes() { return boxes_; } + base::Optional<std::pair<dom::Directionality, math::RectF>>& + scroll_area_cache() override { + return scroll_area_cache_; + } + private: // Returns the bounding rectangle of the border edges of the boxes. math::RectF GetBoundingBorderRectangle() const;
diff --git a/cobalt/layout/topmost_event_target.cc b/cobalt/layout/topmost_event_target.cc index 3981de4..a67dd1ad 100644 --- a/cobalt/layout/topmost_event_target.cc +++ b/cobalt/layout/topmost_event_target.cc
@@ -142,6 +142,15 @@ return possible_scroll_targets; } +bool HasAnyScrollTarget( + const dom::PossibleScrollTargets* possible_scroll_targets) { + if (!possible_scroll_targets) { + return false; + } + return possible_scroll_targets->left || possible_scroll_targets->right || + possible_scroll_targets->up || possible_scroll_targets->down; +} + scoped_refptr<dom::HTMLElement> FindFirstElementWithScrollType( dom::PossibleScrollTargets* possible_scroll_targets, ui_navigation::scroll_engine::ScrollType major_scroll_axis, @@ -280,15 +289,6 @@ } } -void SendStateChangeLeaveEvents( - bool is_pointer_event, scoped_refptr<dom::HTMLElement> previous_element, - dom::PointerEventInit* event_init) { - scoped_refptr<dom::HTMLElement> target_element = nullptr; - scoped_refptr<dom::Element> nearest_common_ancestor = nullptr; - SendStateChangeLeaveEvents(is_pointer_event, previous_element, target_element, - nearest_common_ancestor, event_init); -} - void SendStateChangeEnterEvents( bool is_pointer_event, scoped_refptr<dom::HTMLElement> previous_element, scoped_refptr<dom::HTMLElement> target_element, @@ -420,7 +420,10 @@ new dom::PointerEvent(base::Tokens::pointercancel(), web::Event::kBubbles, web::Event::kNotCancelable, view, *event_init)); bool is_pointer_event = true; - SendStateChangeLeaveEvents(is_pointer_event, element, event_init); + scoped_refptr<dom::HTMLElement> target_element = nullptr; + scoped_refptr<dom::Element> nearest_common_ancestor = nullptr; + SendStateChangeLeaveEvents(is_pointer_event, element, target_element, + nearest_common_ancestor, event_init); } math::Matrix3F GetCompleteTransformMatrix(dom::Element* element) { @@ -563,6 +566,10 @@ FindPossibleScrollTargets(target_element); pointer_state->SetPossibleScrollTargets( pointer_id, std::move(initial_possible_scroll_targets)); + if (HasAnyScrollTarget(initial_possible_scroll_targets.get())) { + pointer_state->SetPendingPointerCaptureTargetOverride(pointer_id, + target_element); + } auto transform_matrix = GetCompleteTransformMatrix(target_element.get()); pointer_state->SetClientTransformMatrix(pointer_id, transform_matrix); @@ -598,8 +605,11 @@ return; } - DispatchPointerEventsForScrollStart(target_element, event_init); + scoped_refptr<dom::HTMLElement> previous_html_element( + previous_html_element_weak_); + DispatchPointerEventsForScrollStart(previous_html_element, event_init); pointer_state->SetWasCancelled(pointer_id); + pointer_state->ClearPendingPointerCaptureTargetOverride(pointer_id); should_clear_pointer_state = true; scroll_engine_->thread()->message_loop()->task_runner()->PostTask( @@ -694,6 +704,12 @@ &event_init); } + bool event_was_cancelled = pointer_event && pointer_state->GetWasCancelled( + pointer_event->pointer_id()); + if (pointer_event && pointer_event->type() == base::Tokens::pointerup()) { + pointer_state->ClearWasCancelled(pointer_event->pointer_id()); + } + scoped_refptr<dom::HTMLElement> previous_html_element( previous_html_element_weak_); @@ -702,14 +718,10 @@ scoped_refptr<dom::Element> nearest_common_ancestor( GetNearestCommonAncestor(previous_html_element, target_element)); - SendStateChangeLeaveEvents(pointer_event, previous_html_element, - target_element, nearest_common_ancestor, - &event_init); - - bool event_was_cancelled = pointer_event && pointer_state->GetWasCancelled( - pointer_event->pointer_id()); - if (pointer_event && pointer_event->type() == base::Tokens::pointerup()) { - pointer_state->ClearWasCancelled(pointer_event->pointer_id()); + if (!event_was_cancelled) { + SendStateChangeLeaveEvents(pointer_event, previous_html_element, + target_element, nearest_common_ancestor, + &event_init); } if (target_element) {
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc index 455c37e..01039d3 100644 --- a/cobalt/layout_tests/web_platform_tests.cc +++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -219,7 +219,7 @@ new browser::UserAgentPlatformInfo()); std::unique_ptr<browser::ServiceWorkerRegistry> service_worker_registry( new browser::ServiceWorkerRegistry(&web_settings, &network_module, - platform_info.get(), url)); + platform_info.get())); browser::WebModule::Options web_module_options; // Use test runner mode to allow the content itself to dictate when it is
diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn index 0e7c06d..bde4beb 100644 --- a/cobalt/media/BUILD.gn +++ b/cobalt/media/BUILD.gn
@@ -43,6 +43,8 @@ "base/format_support_query_metrics.h", "base/interleaved_sinc_resampler.cc", "base/interleaved_sinc_resampler.h", + "base/metrics_provider.cc", + "base/metrics_provider.h", "base/playback_statistics.cc", "base/playback_statistics.h", "base/sbplayer_bridge.cc",
diff --git a/cobalt/media/base/cval_stats_test.cc b/cobalt/media/base/cval_stats_test.cc index 673e20c..ceba320 100644 --- a/cobalt/media/base/cval_stats_test.cc +++ b/cobalt/media/base/cval_stats_test.cc
@@ -12,16 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifdef _WIN32 -#include <Windows.h> -#else -#include <unistd.h> -#endif +#include "cobalt/media/base/cval_stats.h" #include <set> #include <string> -#include "cobalt/media/base/cval_stats.h" +#include "starboard/thread.h" #include "testing/gtest/include/gtest/gtest.h" @@ -33,21 +29,11 @@ const char kPipelineIdentifier[] = "test_pipeline"; -constexpr int kSleepTimeMs = 50; +constexpr SbTime kSleepTime = 50; // 50 microseconds namespace cobalt { namespace media { -namespace { -void sleep_ms(int ms) { -#ifdef _WIN32 - Sleep(ms); -#else - usleep(ms); -#endif -} -} // namespace - TEST(MediaCValStatsTest, InitiallyEmpty) { base::CValManager* cvm = base::CValManager::GetInstance(); EXPECT_TRUE(cvm); @@ -71,7 +57,8 @@ CValStats cval_stats_; cval_stats_.StartTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier); - sleep_ms(kSleepTimeMs); + SbThreadSleep(kSleepTime); + cval_stats_.StopTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier); base::Optional<std::string> result = @@ -88,7 +75,7 @@ cval_stats_.Enable(true); cval_stats_.StartTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier); - sleep_ms(kSleepTimeMs); + SbThreadSleep(kSleepTime); cval_stats_.StopTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier); base::Optional<std::string> result = @@ -114,7 +101,7 @@ for (int i = 0; i < kMediaDefaultMaxSamplesBeforeCalculation - 1; i++) { cval_stats_.StartTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier); - sleep_ms(kSleepTimeMs); + SbThreadSleep(kSleepTime); cval_stats_.StopTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier); } @@ -141,7 +128,7 @@ for (int i = 0; i < kMediaDefaultMaxSamplesBeforeCalculation; i++) { cval_stats_.StartTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier); - sleep_ms(kSleepTimeMs); + SbThreadSleep(kSleepTime); cval_stats_.StopTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier); }
diff --git a/cobalt/media/base/metrics_provider.cc b/cobalt/media/base/metrics_provider.cc new file mode 100644 index 0000000..510d5a8 --- /dev/null +++ b/cobalt/media/base/metrics_provider.cc
@@ -0,0 +1,126 @@ +// Copyright 2023 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/media/base/metrics_provider.h" + +#include "base/logging.h" +#include "base/metrics/histogram_functions.h" + +namespace cobalt { +namespace media { + +using starboard::ScopedLock; + +MediaMetricsProvider::~MediaMetricsProvider() { + if (!IsInitialized()) return; + ReportPipelineUMA(); +} + +void MediaMetricsProvider::Initialize(bool is_mse) { + if (IsInitialized()) { + return; + } + + ScopedLock scoped_lock(mutex_); + media_info_.emplace(MediaInfo(is_mse)); +} + +void MediaMetricsProvider::OnError(const ::media::PipelineStatus status) { + DCHECK(IsInitialized()); + ScopedLock scoped_lock(mutex_); + uma_info_.last_pipeline_status = status; +} + +void MediaMetricsProvider::SetHasAudio(AudioCodec audio_codec) { + ScopedLock scoped_lock(mutex_); + uma_info_.audio_codec = audio_codec; + uma_info_.has_audio = true; +} + +void MediaMetricsProvider::SetHasVideo(VideoCodec video_codec) { + ScopedLock scoped_lock(mutex_); + uma_info_.video_codec = video_codec; + uma_info_.has_video = true; +} + +void MediaMetricsProvider::SetHasPlayed() { + ScopedLock scoped_lock(mutex_); + uma_info_.has_ever_played = true; +} + +void MediaMetricsProvider::SetHaveEnough() { + ScopedLock scoped_lock(mutex_); + uma_info_.has_reached_have_enough = true; +} + +void MediaMetricsProvider::SetIsEME() { + ScopedLock scoped_lock(mutex_); + // This may be called before Initialize(). + uma_info_.is_eme = true; +} + +void MediaMetricsProvider::ReportPipelineUMA() { + ScopedLock scoped_lock(mutex_); + if (uma_info_.has_video && uma_info_.has_audio) { + base::UmaHistogramExactLinear(GetUMANameForAVStream(uma_info_), + uma_info_.last_pipeline_status, + PipelineStatus::PIPELINE_STATUS_MAX + 1); + } else if (uma_info_.has_audio) { + base::UmaHistogramExactLinear("Cobalt.Media.PipelineStatus.AudioOnly", + uma_info_.last_pipeline_status, + PipelineStatus::PIPELINE_STATUS_MAX + 1); + } else if (uma_info_.has_video) { + base::UmaHistogramExactLinear("Cobalt.Media.PipelineStatus.VideoOnly", + uma_info_.last_pipeline_status, + PipelineStatus::PIPELINE_STATUS_MAX + 1); + } else { + // Note: This metric can be recorded as a result of normal operation with + // Media Source Extensions. If a site creates a MediaSource object but never + // creates a source buffer or appends data, PIPELINE_OK will be recorded. + base::UmaHistogramExactLinear("Cobalt.Media.PipelineStatus.Unsupported", + uma_info_.last_pipeline_status, + PipelineStatus::PIPELINE_STATUS_MAX + 1); + } + + // Report whether this player ever saw a playback event. Used to measure the + // effectiveness of efforts to reduce loaded-but-never-used players. + if (uma_info_.has_reached_have_enough) + base::UmaHistogramBoolean("Cobalt.Media.HasEverPlayed", + uma_info_.has_ever_played); +} + +std::string MediaMetricsProvider::GetUMANameForAVStream( + const PipelineInfo& player_info) const { + constexpr char kPipelineUmaPrefix[] = + "Cobalt.Media.PipelineStatus.AudioVideo."; + std::string uma_name = kPipelineUmaPrefix; + if (player_info.video_codec == VideoCodec::kVP9) + uma_name += "VP9"; + else if (player_info.video_codec == VideoCodec::kH264) + uma_name += "H264"; + else if (player_info.video_codec == VideoCodec::kAV1) + uma_name += "AV1"; + else + uma_name += "Other"; + + return uma_name; +} + +bool MediaMetricsProvider::IsInitialized() const { + ScopedLock scoped_lock(mutex_); + return media_info_.has_value(); +} + +} // namespace media +} // namespace cobalt
diff --git a/cobalt/media/base/metrics_provider.h b/cobalt/media/base/metrics_provider.h new file mode 100644 index 0000000..dff1df7 --- /dev/null +++ b/cobalt/media/base/metrics_provider.h
@@ -0,0 +1,91 @@ +// Copyright 2023 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 COBALT_MEDIA_BASE_METRICS_PROVIDER_H_ +#define COBALT_MEDIA_BASE_METRICS_PROVIDER_H_ + +#include <string> + +#include "starboard/common/mutex.h" +#include "third_party/chromium/media/base/audio_codecs.h" +#include "third_party/chromium/media/base/container_names.h" +#include "third_party/chromium/media/base/pipeline_status.h" +#include "third_party/chromium/media/base/timestamp_constants.h" +#include "third_party/chromium/media/base/video_codecs.h" + +namespace cobalt { +namespace media { + +using AudioCodec = ::media::AudioCodec; +using VideoCodec = ::media::VideoCodec; +using PipelineStatus = ::media::PipelineStatus; +using VideoDecoderType = ::media::VideoDecoderType; + +class MediaMetricsProvider { + public: + MediaMetricsProvider() = default; + ~MediaMetricsProvider(); + + private: + struct PipelineInfo { + PipelineInfo() = default; + ~PipelineInfo() = default; + bool has_ever_played = false; + bool has_reached_have_enough = false; + bool has_audio = false; + bool has_video = false; + bool is_eme = false; + bool video_decoder_changed = false; + ::media::AudioCodec audio_codec; + ::media::VideoCodec video_codec; + ::media::PipelineStatus last_pipeline_status = + ::media::PipelineStatus::PIPELINE_OK; + }; + + struct MediaInfo { + explicit MediaInfo(bool is_mse) : is_mse{is_mse} {}; + const bool is_mse; + }; + + + public: + // based on mojom::MediaMetricsProvider + void Initialize(bool is_mse); + void OnError(const ::media::PipelineStatus status); + void SetHasAudio(::media::AudioCodec audio_codec); + void SetHasVideo(::media::VideoCodec video_codec); + void SetHasPlayed(); + void SetHaveEnough(); + void SetIsEME(); + + void ReportPipelineUMA(); + + private: + std::string GetUMANameForAVStream(const PipelineInfo& player_info) const; + bool IsInitialized() const; + + private: + // UMA pipeline packaged data + PipelineInfo uma_info_; + + // The values below are only set if `Initialize` has been called. + absl::optional<MediaInfo> media_info_; + + starboard::Mutex mutex_; +}; + +} // namespace media +} // namespace cobalt + +#endif // COBALT_MEDIA_BASE_METRICS_PROVIDER_H_
diff --git a/cobalt/media/base/sbplayer_bridge.cc b/cobalt/media/base/sbplayer_bridge.cc index 92dc4da..0f3ec4c 100644 --- a/cobalt/media/base/sbplayer_bridge.cc +++ b/cobalt/media/base/sbplayer_bridge.cc
@@ -39,6 +39,7 @@ namespace { +using starboard::FormatString; using starboard::GetPlayerOutputModeName; class StatisticsWrapper { @@ -707,9 +708,9 @@ // a method of querying that texture. decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind( &SbPlayerBridge::GetCurrentSbDecodeTarget, base::Unretained(this))); - SB_LOG(INFO) << "Playing in decode-to-texture mode."; + LOG(INFO) << "Playing in decode-to-texture mode."; } else { - SB_LOG(INFO) << "Playing in punch-out mode."; + LOG(INFO) << "Playing in punch-out mode."; } decode_target_provider_->SetOutputMode( @@ -777,9 +778,9 @@ // a method of querying that texture. decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind( &SbPlayerBridge::GetCurrentSbDecodeTarget, base::Unretained(this))); - SB_LOG(INFO) << "Playing in decode-to-texture mode."; + LOG(INFO) << "Playing in decode-to-texture mode."; } else { - SB_LOG(INFO) << "Playing in punch-out mode."; + LOG(INFO) << "Playing in punch-out mode."; } decode_target_provider_->SetOutputMode( @@ -1237,8 +1238,8 @@ if (default_output_mode != kSbPlayerOutputModeDecodeToTexture && video_stream_info_.codec != kSbMediaVideoCodecNone) { - SB_DCHECK(video_stream_info_.mime); - SB_DCHECK(video_stream_info_.max_video_capabilities); + DCHECK(video_stream_info_.mime); + DCHECK(video_stream_info_.max_video_capabilities); // Set the `default_output_mode` to `kSbPlayerOutputModeDecodeToTexture` if // any of the mime associated with it has `decode-to-texture=true` set. @@ -1250,13 +1251,12 @@ "decode-to-texture=true"); if (is_decode_to_texture_preferred) { - SB_LOG(INFO) << "Setting `default_output_mode` from \"" - << GetPlayerOutputModeName(default_output_mode) << "\" to \"" - << GetPlayerOutputModeName( - kSbPlayerOutputModeDecodeToTexture) - << "\" because mime is set to \"" << video_stream_info_.mime - << "\", and max_video_capabilities is set to \"" - << video_stream_info_.max_video_capabilities << "\""; + LOG(INFO) << "Setting `default_output_mode` from \"" + << GetPlayerOutputModeName(default_output_mode) << "\" to \"" + << GetPlayerOutputModeName(kSbPlayerOutputModeDecodeToTexture) + << "\" because mime is set to \"" << video_stream_info_.mime + << "\", and max_video_capabilities is set to \"" + << video_stream_info_.max_video_capabilities << "\""; default_output_mode = kSbPlayerOutputModeDecodeToTexture; } } @@ -1265,6 +1265,9 @@ auto output_mode = sbplayer_interface_->GetPreferredOutputMode(&creation_param); + + LOG(INFO) << "Output mode is set to " << GetPlayerOutputModeName(output_mode); + CHECK_NE(kSbPlayerOutputModeInvalid, output_mode); return output_mode; } @@ -1272,15 +1275,14 @@ void SbPlayerBridge::LogStartupLatency() const { std::string first_events_str; if (set_drm_system_ready_cb_time_ == -1) { - first_events_str = - starboard::FormatString("%-40s0 us", "SbPlayerCreate() called"); + first_events_str = FormatString("%-40s0 us", "SbPlayerCreate() called"); } else if (set_drm_system_ready_cb_time_ < player_creation_time_) { - first_events_str = starboard::FormatString( + first_events_str = FormatString( "%-40s0 us\n%-40s%" PRId64 " us", "set_drm_system_ready_cb called", "SbPlayerCreate() called", player_creation_time_ - set_drm_system_ready_cb_time_); } else { - first_events_str = starboard::FormatString( + first_events_str = FormatString( "%-40s0 us\n%-40s%" PRId64 " us", "SbPlayerCreate() called", "set_drm_system_ready_cb called", set_drm_system_ready_cb_time_ - player_creation_time_); @@ -1305,7 +1307,7 @@ 1); // clang-format off - LOG(INFO) << starboard::FormatString( + LOG(INFO) << FormatString( "\nSbPlayer startup latencies: %" PRId64 " us\n" " Event name time since last event\n" " %s\n" // |first_events_str| populated above
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc index ee6a1dc..8b29628 100644 --- a/cobalt/media/base/sbplayer_pipeline.cc +++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -111,7 +111,8 @@ #if SB_API_VERSION >= 15 SbTime audio_write_duration_local, SbTime audio_write_duration_remote, #endif // SB_API_VERSION >= 15 - MediaLog* media_log, DecodeTargetProvider* decode_target_provider) + MediaLog* media_log, MediaMetricsProvider* media_metrics_provider, + DecodeTargetProvider* decode_target_provider) : pipeline_identifier_( base::StringPrintf("%X", g_pipeline_identifier_counter++)), sbplayer_interface_(interface), @@ -156,6 +157,7 @@ audio_write_duration_local_(audio_write_duration_local), audio_write_duration_remote_(audio_write_duration_remote), #endif // SB_API_VERSION >= 15 + media_metrics_provider_(media_metrics_provider), last_media_time_(base::StringPrintf("Media.Pipeline.%s.LastMediaTime", pipeline_identifier_.c_str()), 0, "Last media time reported by the underlying player."), @@ -337,6 +339,8 @@ if (demuxer_) { stop_cb_ = stop_cb; demuxer_->Stop(); + video_stream_ = nullptr; + audio_stream_ = nullptr; OnDemuxerStopped(); } else { stop_cb.Run(); @@ -563,6 +567,13 @@ std::vector<std::string> connectors; +#if SB_HAS(PLAYER_WITH_URL) + // Url based player does not support audio connectors. + if (is_url_based_) { + return connectors; + } +#endif // SB_HAS(PLAYER_WITH_URL) + auto configurations = player_bridge_->GetAudioConfigurations(); for (auto&& configuration : configurations) { connectors.push_back(GetMediaAudioConnectorName(configuration.connector)); @@ -999,12 +1010,17 @@ return; } + if (stopped_) { + return; + } + + DCHECK(player_bridge_); + DemuxerStream* stream = type == DemuxerStream::AUDIO ? audio_stream_ : video_stream_; DCHECK(stream); - // In case if Stop() has been called. - if (!player_bridge_) { + if (!player_bridge_ || !stream) { return; } @@ -1157,6 +1173,14 @@ playback_statistics_.OnPresenting( video_stream_->video_decoder_config()); } + +#if SB_HAS(PLAYER_WITH_URL) + // Url based player does not support |audio_write_duration_for_preroll_|. + if (is_url_based_) { + break; + } +#endif // SB_HAS(PLAYER_WITH_URL) + #if SB_API_VERSION >= 15 audio_write_duration_for_preroll_ = audio_write_duration_ = HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations()) @@ -1229,10 +1253,12 @@ if (stream->type() == DemuxerStream::AUDIO) { const AudioDecoderConfig& decoder_config = stream->audio_decoder_config(); + media_metrics_provider_->SetHasAudio(decoder_config.codec()); player_bridge_->UpdateAudioConfig(decoder_config, stream->mime_type()); } else { DCHECK_EQ(stream->type(), DemuxerStream::VIDEO); const VideoDecoderConfig& decoder_config = stream->video_decoder_config(); + media_metrics_provider_->SetHasVideo(decoder_config.codec()); base::AutoLock auto_lock(lock_); bool natural_size_changed = (decoder_config.natural_size().width() != natural_size_.width() || @@ -1352,6 +1378,8 @@ std::string SbPlayerPipeline::AppendStatisticsString( const std::string& message) const { + DCHECK(task_runner_->BelongsToCurrentThread()); + if (nullptr == video_stream_) { return message + ", playback statistics: n/a."; } else {
diff --git a/cobalt/media/base/sbplayer_pipeline.h b/cobalt/media/base/sbplayer_pipeline.h index 59e19e5..dc851c8 100644 --- a/cobalt/media/base/sbplayer_pipeline.h +++ b/cobalt/media/base/sbplayer_pipeline.h
@@ -28,6 +28,7 @@ #include "cobalt/base/c_val.h" #include "cobalt/math/size.h" #include "cobalt/media/base/media_export.h" +#include "cobalt/media/base/metrics_provider.h" #include "cobalt/media/base/pipeline.h" #include "cobalt/media/base/playback_statistics.h" #include "cobalt/media/base/sbplayer_bridge.h" @@ -65,7 +66,8 @@ #if SB_API_VERSION >= 15 SbTime audio_write_duration_local, SbTime audio_write_duration_remote, #endif // SB_API_VERSION >= 15 - MediaLog* media_log, DecodeTargetProvider* decode_target_provider); + MediaLog* media_log, MediaMetricsProvider* media_metrics_provider, + DecodeTargetProvider* decode_target_provider); ~SbPlayerPipeline() override; void Suspend() override; @@ -305,6 +307,8 @@ base::CVal<bool> ended_; base::CVal<SbPlayerState> player_state_; + MediaMetricsProvider* media_metrics_provider_; + DecodeTargetProvider* decode_target_provider_; #if SB_API_VERSION >= 15
diff --git a/cobalt/media/player/web_media_player_impl.cc b/cobalt/media/player/web_media_player_impl.cc index 8a121a6..6941c04 100644 --- a/cobalt/media/player/web_media_player_impl.cc +++ b/cobalt/media/player/web_media_player_impl.cc
@@ -156,7 +156,7 @@ #if SB_API_VERSION >= 15 audio_write_duration_local, audio_write_duration_remote, #endif // SB_API_VERSION >= 15 - media_log_, decode_target_provider_.get()); + media_log_, &media_metrics_provider_, decode_target_provider_.get()); // Also we want to be notified of |main_loop_| destruction. main_loop_->AddDestructionObserver(this); @@ -244,6 +244,7 @@ is_local_source_ = !url.SchemeIs("http") && !url.SchemeIs("https"); StartPipeline(url); + media_metrics_provider_.Initialize(false); } #endif // SB_HAS(PLAYER_WITH_URL) @@ -270,6 +271,7 @@ state_.is_media_source = true; StartPipeline(chunk_demuxer_.get()); + media_metrics_provider_.Initialize(true); } void WebMediaPlayerImpl::LoadProgressive( @@ -309,6 +311,7 @@ state_.is_progressive = true; StartPipeline(progressive_demuxer_.get()); + media_metrics_provider_.Initialize(false); } void WebMediaPlayerImpl::CancelLoad() { @@ -324,6 +327,7 @@ pipeline_->SetPlaybackRate(state_.playback_rate); media_log_->AddEvent<::media::MediaLogEvent::kPlay>(); + media_metrics_provider_.SetHasPlayed(); } void WebMediaPlayerImpl::Pause() { @@ -653,6 +657,7 @@ if (suppress_destruction_errors_) return; media_log_->NotifyError(error); + media_metrics_provider_.OnError(error); if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing) { // Any error that occurs before reaching ReadyStateHaveMetadata should @@ -785,6 +790,7 @@ break; case Pipeline::kPrerollCompleted: SetReadyState(WebMediaPlayer::kReadyStateHaveEnoughData); + media_metrics_provider_.SetHaveEnough(); break; } } @@ -963,6 +969,7 @@ NOTREACHED(); break; } + media_metrics_provider_.SetIsEME(); } WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
diff --git a/cobalt/media/player/web_media_player_impl.h b/cobalt/media/player/web_media_player_impl.h index 9db4419..adb7ed2 100644 --- a/cobalt/media/player/web_media_player_impl.h +++ b/cobalt/media/player/web_media_player_impl.h
@@ -60,6 +60,7 @@ #include "base/time/time.h" #include "cobalt/math/size.h" #include "cobalt/media/base/decode_target_provider.h" +#include "cobalt/media/base/metrics_provider.h" #include "cobalt/media/base/pipeline.h" #include "cobalt/media/base/sbplayer_interface.h" #include "cobalt/media/player/web_media_player.h" @@ -306,6 +307,8 @@ ::media::MediaLog* const media_log_; + MediaMetricsProvider media_metrics_provider_; + bool is_local_source_; std::unique_ptr<::media::Demuxer> progressive_demuxer_;
diff --git a/cobalt/media_capture/media_devices.cc b/cobalt/media_capture/media_devices.cc index 7a75b18..3c671f3 100644 --- a/cobalt/media_capture/media_devices.cc +++ b/cobalt/media_capture/media_devices.cc
@@ -18,6 +18,7 @@ #include <string> #include <utility> +#include "base/metrics/histogram_functions.h" #include "base/threading/thread_task_runner_handle.h" #include "cobalt/base/polymorphic_downcast.h" #include "cobalt/media_capture/media_device_info.h" @@ -66,6 +67,11 @@ return mic; } +void LogMicCreationSucceededHistogramItem(bool mic_creation_succeeded) { + base::UmaHistogramBoolean("Cobalt.MediaDevices.MicCreationSucceeded", + mic_creation_succeeded); +} + } // namespace. MediaDevices::MediaDevices(script::EnvironmentSettings* settings, @@ -169,6 +175,9 @@ speech::MicrophoneManager::MicrophoneError error, std::string message) { DLOG(INFO) << "MediaDevices::OnMicrophoneError " << message; + // Log failed mic creation in UMA histogram + LogMicCreationSucceededHistogramItem(false); + // No special error handling logic besides logging the message above, so just // delegate to the OnMicrophoneStopped() functionality. OnMicrophoneStopped(); @@ -195,6 +204,9 @@ } void MediaDevices::OnMicrophoneSuccess() { + // Log successful mic creation in UMA histogram + LogMicCreationSucceededHistogramItem(true); + if (javascript_message_loop_->task_runner() != base::ThreadTaskRunnerHandle::Get()) { javascript_message_loop_->task_runner()->PostTask(
diff --git a/cobalt/network/cobalt_net_log.cc b/cobalt/network/cobalt_net_log.cc index 3df4117..f597b87 100644 --- a/cobalt/network/cobalt_net_log.cc +++ b/cobalt/network/cobalt_net_log.cc
@@ -25,14 +25,29 @@ CobaltNetLog::CobaltNetLog(const base::FilePath& log_path, net::NetLogCaptureMode capture_mode) - : net_log_logger_( - net::FileNetLogObserver::CreateUnbounded(log_path, nullptr)) { - net_log_logger_->StartObserving(this, capture_mode); -} + : capture_mode_(capture_mode), + net_log_logger_( + net::FileNetLogObserver::CreateUnbounded(log_path, nullptr)) {} CobaltNetLog::~CobaltNetLog() { // Remove the observers we own before we're destroyed. - net_log_logger_->StopObserving(nullptr, base::OnceClosure()); + StopObserving(); +} + +void CobaltNetLog::StartObserving() { + if (!is_observing_) { + is_observing_ = true; + net_log_logger_->StartObserving(this, capture_mode_); + } else { + DLOG(WARNING) << "Already observing NetLog."; + } +} + +void CobaltNetLog::StopObserving() { + if (is_observing_) { + is_observing_ = false; + net_log_logger_->StopObserving(nullptr, base::OnceClosure()); + } } } // namespace network
diff --git a/cobalt/network/cobalt_net_log.h b/cobalt/network/cobalt_net_log.h index 4ee6fa8..dcc1e67 100644 --- a/cobalt/network/cobalt_net_log.h +++ b/cobalt/network/cobalt_net_log.h
@@ -34,8 +34,13 @@ ::net::NetLogCaptureMode capture_mode); ~CobaltNetLog() override; + void StartObserving(); + void StopObserving(); + private: - std::unique_ptr<net::FileNetLogObserver> net_log_logger_; + bool is_observing_{false}; + net::NetLogCaptureMode capture_mode_; + std::unique_ptr<net::FileNetLogObserver> net_log_logger_{nullptr}; DISALLOW_COPY_AND_ASSIGN(CobaltNetLog); };
diff --git a/cobalt/network/network_module.cc b/cobalt/network/network_module.cc index 8154b7c..0c8f67e 100644 --- a/cobalt/network/network_module.cc +++ b/cobalt/network/network_module.cc
@@ -17,10 +17,12 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "cobalt/base/cobalt_paths.h" #include "cobalt/network/network_system.h" #include "cobalt/network/switches.h" #include "net/url_request/static_http_user_agent_settings.h" @@ -33,6 +35,7 @@ const char kCaptureModeIncludeCookiesAndCredentials[] = "IncludeCookiesAndCredentials"; const char kCaptureModeIncludeSocketBytes[] = "IncludeSocketBytes"; +const char kDefaultNetLogName[] = "cobalt_netlog.json"; #endif } // namespace @@ -110,15 +113,6 @@ } } -void NetworkModule::SetEnableClientHintHeadersFlagsFromPersistentSettings() { - // Called on initialization and when the persistent setting is changed. - if (options_.persistent_settings != nullptr) { - enable_client_hint_headers_flags_.store( - options_.persistent_settings->GetPersistentSettingAsInt( - kClientHintHeadersEnabledPersistentSettingsKey, 0)); - } -} - void NetworkModule::EnsureStorageManagerStarted() { DCHECK(storage_manager_); storage_manager_->EnsureStarted(); @@ -137,8 +131,6 @@ http_user_agent_settings_.reset(new net::StaticHttpUserAgentSettings( options_.preferred_language, user_agent_string)); - SetEnableClientHintHeadersFlagsFromPersistentSettings(); - #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); @@ -156,11 +148,12 @@ } #if defined(ENABLE_NETWORK_LOGGING) + base::FilePath result; + base::PathService::Get(cobalt::paths::DIR_COBALT_DEBUG_OUT, &result); + net_log_path_ = result.Append(kDefaultNetLogName); + net::NetLogCaptureMode capture_mode; if (command_line->HasSwitch(switches::kNetLog)) { - // If this is not a valid path, net logs will be sent to VLOG(1). - base::FilePath net_log_path = - command_line->GetSwitchValuePath(switches::kNetLog); - net::NetLogCaptureMode capture_mode; + net_log_path_ = command_line->GetSwitchValuePath(switches::kNetLog); if (command_line->HasSwitch(switches::kNetLogCaptureMode)) { std::string capture_mode_string = command_line->GetSwitchValueASCII(switches::kNetLogCaptureMode); @@ -170,7 +163,10 @@ capture_mode = net::NetLogCaptureMode::IncludeSocketBytes(); } } - net_log_.reset(new CobaltNetLog(net_log_path, capture_mode)); + net_log_.reset(new CobaltNetLog(net_log_path_, capture_mode)); + net_log_->StartObserving(); + } else { + net_log_.reset(new CobaltNetLog(net_log_path_, capture_mode)); } #endif @@ -234,12 +230,27 @@ void NetworkModule::AddClientHintHeaders( net::URLFetcher& url_fetcher, ClientHintHeadersCallType call_type) const { - if (enable_client_hint_headers_flags_.load() & call_type) { + if (kEnabledClientHintHeaders & call_type) { for (const auto& header : client_hint_headers_) { url_fetcher.AddExtraRequestHeader(header); } } } +void NetworkModule::StartNetLog() { +#if defined(ENABLE_NETWORK_LOGGING) + LOG(INFO) << "Starting NetLog capture"; + net_log_->StartObserving(); +#endif +} + +base::FilePath NetworkModule::StopNetLog() { +#if defined(ENABLE_NETWORK_LOGGING) + LOG(INFO) << "Stopping NetLog capture"; + net_log_->StopObserving(); +#endif + return net_log_path_; +} + } // namespace network } // namespace cobalt
diff --git a/cobalt/network/network_module.h b/cobalt/network/network_module.h index b6c8bd2..18ea05c 100644 --- a/cobalt/network/network_module.h +++ b/cobalt/network/network_module.h
@@ -49,7 +49,7 @@ namespace network { // Used to differentiate type of network call for Client Hint Headers. -// Values correspond to bit masks against |enable_client_hint_headers_flags_|. +// Values correspond to bit masks against |kEnabledClientHintHeaders|. enum ClientHintHeadersCallType : int32_t { kCallTypeLoader = (1u << 0), kCallTypeMedia = (1u << 1), @@ -59,11 +59,10 @@ kCallTypeXHR = (1u << 5), }; -const char kQuicEnabledPersistentSettingsKey[] = "QUICEnabled"; +// Determines which type of network calls should include Client Hint Headers. +constexpr int32_t kEnabledClientHintHeaders = (kCallTypeLoader | kCallTypeXHR); -// Holds bit mask flag, read into |enable_client_hint_headers_flags_|. -const char kClientHintHeadersEnabledPersistentSettingsKey[] = - "clientHintHeadersEnabled"; +const char kQuicEnabledPersistentSettingsKey[] = "QUICEnabled"; class NetworkSystem; // NetworkModule wraps various networking-related components such as @@ -132,9 +131,6 @@ void SetEnableQuicFromPersistentSettings(); - // Checks persistent settings to determine if Client Hint Headers are enabled. - void SetEnableClientHintHeadersFlagsFromPersistentSettings(); - // Adds the Client Hint Headers to the provided URLFetcher if enabled. void AddClientHintHeaders(net::URLFetcher& url_fetcher, ClientHintHeadersCallType call_type) const; @@ -142,6 +138,11 @@ // From base::MessageLoop::DestructionObserver. void WillDestroyCurrentMessageLoop() override; + // Used to capture NetLog from Devtools + void StartNetLog(); + base::FilePath StopNetLog(); + + private: void Initialize(const std::string& user_agent_string, base::EventDispatcher* event_dispatcher); @@ -149,7 +150,6 @@ std::unique_ptr<network_bridge::NetPoster> CreateNetPoster(); std::vector<std::string> client_hint_headers_; - starboard::atomic_int32_t enable_client_hint_headers_flags_; std::unique_ptr<storage::StorageManager> storage_manager_; std::unique_ptr<base::Thread> thread_; std::unique_ptr<URLRequestContext> url_request_context_; @@ -163,7 +163,9 @@ scoped_refptr<net::DialServiceProxy> dial_service_proxy_; #endif std::unique_ptr<network_bridge::NetPoster> net_poster_; - std::unique_ptr<CobaltNetLog> net_log_; + + base::FilePath net_log_path_; + std::unique_ptr<CobaltNetLog> net_log_{nullptr}; Options options_; DISALLOW_COPY_AND_ASSIGN(NetworkModule);
diff --git a/cobalt/network/url_request_context.cc b/cobalt/network/url_request_context.cc index 7467b98..2efd31e 100644 --- a/cobalt/network/url_request_context.cc +++ b/cobalt/network/url_request_context.cc
@@ -14,6 +14,8 @@ #include "cobalt/network/url_request_context.h" +#include <algorithm> +#include <map> #include <memory> #include <string> #include <utility> @@ -48,6 +50,49 @@ #include "net/url_request/data_protocol_handler.h" #include "net/url_request/url_request_job_factory_impl.h" +namespace { + +const char kPersistentSettingsJson[] = "cache_settings.json"; + +void LoadDiskCacheQuotaSettings( + cobalt::persistent_storage::PersistentSettings* settings, + int64_t max_bytes) { + auto total_size = 0; + std::map<disk_cache::ResourceType, uint32_t> quotas; + for (int i = 0; i < disk_cache::kTypeCount; i++) { + disk_cache::ResourceType resource_type = (disk_cache::ResourceType)i; + std::string directory = + disk_cache::defaults::GetSubdirectory(resource_type); + uint32_t bucket_size = + static_cast<uint32_t>(settings->GetPersistentSettingAsDouble( + directory, disk_cache::defaults::GetQuota(resource_type))); + quotas[resource_type] = bucket_size; + total_size += bucket_size; + } + + if (total_size <= max_bytes) { + for (int i = 0; i < disk_cache::kTypeCount; i++) { + disk_cache::ResourceType resource_type = (disk_cache::ResourceType)i; + disk_cache::settings::SetQuota(resource_type, quotas[resource_type]); + } + return; + } + + // Sum of quotas exceeds |max_bytes|. Set quotas to default values. + for (int i = 0; i < disk_cache::kTypeCount; i++) { + disk_cache::ResourceType resource_type = (disk_cache::ResourceType)i; + uint32_t default_quota = disk_cache::defaults::GetQuota(resource_type); + disk_cache::settings::SetQuota(resource_type, default_quota); + std::string directory = + disk_cache::defaults::GetSubdirectory(resource_type); + settings->SetPersistentSetting( + directory, + std::make_unique<base::Value>(static_cast<double>(default_quota))); + } +} + +} // namespace + namespace cobalt { namespace network { namespace { @@ -189,17 +234,24 @@ // is less than 1 mb and subtract this from the max_cache_bytes. max_cache_bytes -= (1 << 20); + // Initialize and read caching persistent settings + cache_persistent_settings_ = + std::make_unique<cobalt::persistent_storage::PersistentSettings>( + kPersistentSettingsJson); + LoadDiskCacheQuotaSettings(cache_persistent_settings_.get(), + max_cache_bytes); + auto http_cache = std::make_unique<net::HttpCache>( storage_.http_network_session(), std::make_unique<net::HttpCache::DefaultBackend>( - net::DISK_CACHE, net::CACHE_BACKEND_COBALT, + net::DISK_CACHE, net::CACHE_BACKEND_DEFAULT, base::FilePath(std::string(path.data())), /* max_bytes */ max_cache_bytes), true); if (persistent_settings != nullptr) { auto cache_enabled = persistent_settings->GetPersistentSettingAsBool( disk_cache::kCacheEnabledPersistentSettingsKey, true); - + disk_cache::settings::SetCacheEnabled(cache_enabled); if (!cache_enabled) { http_cache->set_mode(net::HttpCache::Mode::DISABLE); } @@ -243,5 +295,17 @@ } #endif // defined(ENABLE_DEBUGGER) +void URLRequestContext::UpdateCacheSizeSetting(disk_cache::ResourceType type, + uint32_t bytes) { + CHECK(cache_persistent_settings_); + cache_persistent_settings_->SetPersistentSetting( + disk_cache::defaults::GetSubdirectory(type), + std::make_unique<base::Value>(static_cast<double>(bytes))); +} + +void URLRequestContext::ValidateCachePersistentSettings() { + cache_persistent_settings_->ValidatePersistentSettings(); +} + } // namespace network } // namespace cobalt
diff --git a/cobalt/network/url_request_context.h b/cobalt/network/url_request_context.h index 6e80479..a8e635c 100644 --- a/cobalt/network/url_request_context.h +++ b/cobalt/network/url_request_context.h
@@ -15,6 +15,7 @@ #ifndef COBALT_NETWORK_URL_REQUEST_CONTEXT_H_ #define COBALT_NETWORK_URL_REQUEST_CONTEXT_H_ +#include <memory> #include <string> #include "base/basictypes.h" @@ -22,6 +23,7 @@ #include "base/sequence_checker.h" #include "cobalt/persistent_storage/persistent_settings.h" #include "net/cookies/cookie_monster.h" +#include "net/disk_cache/cobalt/resource_type.h" #include "net/log/net_log.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" @@ -53,6 +55,9 @@ bool using_http_cache(); + void UpdateCacheSizeSetting(disk_cache::ResourceType type, uint32_t bytes); + void ValidateCachePersistentSettings(); + private: SEQUENCE_CHECKER(sequence_checker_); net::URLRequestContextStorage storage_; @@ -70,6 +75,10 @@ void OnQuicToggle(const std::string&); #endif // defined(ENABLE_DEBUGGER) + // Persistent settings module for Cobalt disk cache quotas + std::unique_ptr<cobalt::persistent_storage::PersistentSettings> + cache_persistent_settings_; + DISALLOW_COPY_AND_ASSIGN(URLRequestContext); };
diff --git a/cobalt/persistent_storage/persistent_settings.cc b/cobalt/persistent_storage/persistent_settings.cc index 52bc864..2f2e5dd 100644 --- a/cobalt/persistent_storage/persistent_settings.cc +++ b/cobalt/persistent_storage/persistent_settings.cc
@@ -129,7 +129,9 @@ base::AutoLock auto_lock(pref_store_lock_); auto persistent_settings = pref_store_->GetValues(); const base::Value* result = persistent_settings->FindKey(key); - if (result && result->is_double()) return result->GetDouble(); + if (result && result->is_double()) { + return result->GetDouble(); + } return default_setting; }
diff --git a/cobalt/ui_navigation/scroll_engine/scroll_engine.cc b/cobalt/ui_navigation/scroll_engine/scroll_engine.cc index 4c14f73..2c76797 100644 --- a/cobalt/ui_navigation/scroll_engine/scroll_engine.cc +++ b/cobalt/ui_navigation/scroll_engine/scroll_engine.cc
@@ -395,6 +395,14 @@ } } +void ScrollEngine::Conceal(render_tree::ResourceProvider*, SbTimeMonotonic) { + nav_items_with_decaying_scroll_.clear(); +} + +void ScrollEngine::Freeze(SbTimeMonotonic) { + nav_items_with_decaying_scroll_.clear(); +} + } // namespace scroll_engine } // namespace ui_navigation } // namespace cobalt
diff --git a/cobalt/ui_navigation/scroll_engine/scroll_engine.h b/cobalt/ui_navigation/scroll_engine/scroll_engine.h index 93e1e33..3e2653a 100644 --- a/cobalt/ui_navigation/scroll_engine/scroll_engine.h +++ b/cobalt/ui_navigation/scroll_engine/scroll_engine.h
@@ -23,6 +23,7 @@ #include "base/time/time.h" #include "base/timer/timer.h" #include "cobalt/base/token.h" +#include "cobalt/browser/lifecycle_observer.h" #include "cobalt/dom/pointer_event.h" #include "cobalt/dom/pointer_event_init.h" #include "cobalt/math/vector2d_f.h" @@ -50,7 +51,7 @@ base::Time time_stamp; }; -class ScrollEngine { +class ScrollEngine : public browser::LifecycleObserver { public: ScrollEngine(); ~ScrollEngine(); @@ -73,6 +74,17 @@ base::Thread* thread() { return &scroll_engine_; } + // LifecycleObserver implementation. + void Blur(SbTimeMonotonic timestamp) override {} + void Conceal(render_tree::ResourceProvider* resource_provider, + SbTimeMonotonic timestamp) override; + void Freeze(SbTimeMonotonic timestamp) override; + void Unfreeze(render_tree::ResourceProvider* resource_provider, + SbTimeMonotonic timestamp) override {} + void Reveal(render_tree::ResourceProvider* resource_provider, + SbTimeMonotonic timestamp) override {} + void Focus(SbTimeMonotonic timestamp) override {} + private: base::Thread scroll_engine_{"ScrollEngineThread"}; base::RepeatingTimer free_scroll_timer_;
diff --git a/cobalt/version.h b/cobalt/version.h index bd8a169..f54bb3b 100644 --- a/cobalt/version.h +++ b/cobalt/version.h
@@ -35,6 +35,6 @@ // release is cut. //. -#define COBALT_VERSION "24.lts.10" +#define COBALT_VERSION "24.lts.20" #endif // COBALT_VERSION_H_
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc index 1e43688..f328ec1 100644 --- a/cobalt/watchdog/watchdog.cc +++ b/cobalt/watchdog/watchdog.cc
@@ -43,9 +43,11 @@ // The minimum number of microseconds between writes. const int64_t kWatchdogWriteWaitTime = 300000000; // The maximum number of most recent ping infos. -const int kWatchdogMaxPingInfos = 20; +const int kWatchdogMaxPingInfos = 60; // The maximum length of each ping info. -const int kWatchdogMaxPingInfoLength = 128; +const int kWatchdogMaxPingInfoLength = 1024; +// The maximum number of milliseconds old of an unfetched Watchdog violation. +const int64_t kWatchdogMaxViolationsAge = 86400000; // Persistent setting name and default setting for the boolean that controls // whether or not Watchdog is enabled. When disabled, Watchdog behaves like a @@ -109,7 +111,6 @@ // Starts monitor thread. is_monitoring_.store(true); - InitializeViolationsMap(this); SB_DCHECK(!SbThreadIsValid(watchdog_thread_)); watchdog_thread_ = SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity, true, "Watchdog", &Watchdog::Monitor, this); @@ -133,6 +134,29 @@ SbThreadJoin(watchdog_thread_, nullptr); } +std::shared_ptr<base::Value> Watchdog::GetViolationsMap() { + // Gets the Watchdog violations map with lazy initialization which loads the + // previous Watchdog violations file containing violations before app start, + // if it exists. + if (violations_map_ == nullptr) { + starboard::ScopedFile read_file(GetWatchdogFilePath().c_str(), + kSbFileOpenOnly | kSbFileRead); + if (read_file.IsValid()) { + int64_t kFileSize = read_file.GetSize(); + std::vector<char> buffer(kFileSize + 1, 0); + read_file.ReadAll(buffer.data(), kFileSize); + violations_map_ = base::JSONReader::Read(std::string(buffer.data())); + } + + if (violations_map_ == nullptr) { + SB_LOG(INFO) << "[Watchdog] No previous violations JSON."; + violations_map_ = + std::make_unique<base::Value>(base::Value::Type::DICTIONARY); + } + } + return violations_map_; +} + std::string Watchdog::GetWatchdogFilePath() { // Gets the Watchdog violations file path with lazy initialization. if (watchdog_file_path_ == "") { @@ -148,33 +172,17 @@ } std::vector<std::string> Watchdog::GetWatchdogViolationClientNames() { - if (pending_write_) WriteWatchdogViolations(); - - std::string watchdog_json = ReadViolationFile(GetWatchdogFilePath().c_str()); std::vector<std::string> names; - if (watchdog_json != "") { - std::unique_ptr<base::Value> violations_map = - base::JSONReader::Read(watchdog_json); - for (const auto& it : violations_map->DictItems()) { - names.push_back(it.first); - } + + if (is_disabled_) return names; + + starboard::ScopedLock scoped_lock(mutex_); + for (const auto& it : GetViolationsMap()->DictItems()) { + names.push_back(it.first); } return names; } -void Watchdog::WriteWatchdogViolations() { - // Writes Watchdog violations to persistent storage as a json file. - std::string watchdog_json; - base::JSONWriter::Write(*violations_map_, &watchdog_json); - SB_LOG(INFO) << "[Watchdog] Writing violations to JSON:\n" << watchdog_json; - starboard::ScopedFile watchdog_file(GetWatchdogFilePath().c_str(), - kSbFileCreateAlways | kSbFileWrite); - watchdog_file.WriteAll(watchdog_json.c_str(), - static_cast<int>(watchdog_json.size())); - pending_write_ = false; - time_last_written_microseconds_ = SbTimeGetMonotonicNow(); -} - void Watchdog::UpdateState(base::ApplicationState state) { if (is_disabled_) return; @@ -182,43 +190,41 @@ state_ = state; } +void Watchdog::WriteWatchdogViolations() { + // Writes Watchdog violations to persistent storage as a json file. + std::string watchdog_json; + base::JSONWriter::Write(*GetViolationsMap(), &watchdog_json); + SB_LOG(INFO) << "[Watchdog] Writing violations to JSON:\n" << watchdog_json; + starboard::ScopedFile watchdog_file(GetWatchdogFilePath().c_str(), + kSbFileCreateAlways | kSbFileWrite); + watchdog_file.WriteAll(watchdog_json.c_str(), + static_cast<int>(watchdog_json.size())); + pending_write_ = false; + time_last_written_microseconds_ = SbTimeGetMonotonicNow(); +} + void* Watchdog::Monitor(void* context) { starboard::ScopedLock scoped_lock(static_cast<Watchdog*>(context)->mutex_); while (1) { SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow(); - - // Iterates through client map to monitor all registered clients. bool watchdog_violation = false; + + // Iterates through client map to monitor all name registered clients. 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 start values. - if (static_cast<Watchdog*>(context)->state_ > client->monitor_state) { - client->time_registered_monotonic_microseconds = current_monotonic_time; - client->time_last_updated_monotonic_microseconds = - current_monotonic_time; - continue; - } - - SbTimeMonotonic time_delta = - current_monotonic_time - - client->time_last_updated_monotonic_microseconds; - SbTimeMonotonic time_wait = - current_monotonic_time - - client->time_registered_monotonic_microseconds; - - // Watchdog violation - if (time_delta > client->time_interval_microseconds && - time_wait > client->time_wait_microseconds) { + if (MonitorClient(context, client, current_monotonic_time)) { watchdog_violation = true; - UpdateViolationsMap(context, client, time_delta); - - // Resets time last updated. - client->time_last_updated_monotonic_microseconds = - current_monotonic_time; } } + + // Iterates through client list to monitor all client registered clients. + for (auto& it : static_cast<Watchdog*>(context)->client_list_) { + Client* client = it.get(); + if (MonitorClient(context, client, current_monotonic_time)) { + watchdog_violation = true; + } + } + if (static_cast<Watchdog*>(context)->pending_write_) MaybeWriteWatchdogViolations(context); if (watchdog_violation) MaybeTriggerCrash(context); @@ -233,11 +239,39 @@ return nullptr; } +bool Watchdog::MonitorClient(void* context, Client* client, + SbTimeMonotonic current_monotonic_time) { + // 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 start values. + if (static_cast<Watchdog*>(context)->state_ > client->monitor_state) { + client->time_registered_monotonic_microseconds = current_monotonic_time; + client->time_last_updated_monotonic_microseconds = current_monotonic_time; + return false; + } + + SbTimeMonotonic time_delta = + current_monotonic_time - client->time_last_updated_monotonic_microseconds; + SbTimeMonotonic time_wait = + current_monotonic_time - client->time_registered_monotonic_microseconds; + + // Watchdog violation + if (time_delta > client->time_interval_microseconds && + time_wait > client->time_wait_microseconds) { + UpdateViolationsMap(context, client, time_delta); + // Resets time last updated. + client->time_last_updated_monotonic_microseconds = current_monotonic_time; + return true; + } + return false; +} + void Watchdog::UpdateViolationsMap(void* context, Client* client, SbTimeMonotonic time_delta) { - // Gets violation dictionary with key client name from violations_map_. + // Gets violation dictionary with key client name from violations map. base::Value* violation_dict = - (static_cast<Watchdog*>(context)->violations_map_)->FindKey(client->name); + (static_cast<Watchdog*>(context)->GetViolationsMap()) + ->FindKey(client->name); // Checks if new unique violation. bool new_violation = false; @@ -256,9 +290,8 @@ new_violation = true; } - // New unique violation. if (new_violation) { - // Creates new violation. + // New unique violation, creates violation in violations map. base::Value violation(base::Value::Type::DICTIONARY); violation.SetKey("pingInfos", client->ping_infos.Clone()); violation.SetKey("monitorState", @@ -287,28 +320,26 @@ for (auto& it : static_cast<Watchdog*>(context)->client_map_) { registered_clients.GetList().emplace_back(base::Value(it.first)); } + for (auto& it : static_cast<Watchdog*>(context)->client_list_) { + registered_clients.GetList().emplace_back(base::Value(it->name)); + } violation.SetKey("registeredClients", registered_clients.Clone()); - // Adds new violation to violations_map_. + // Adds new violation to violations map. if (violation_dict == nullptr) { base::Value dict(base::Value::Type::DICTIONARY); dict.SetKey("description", base::Value(client->description)); base::Value list(base::Value::Type::LIST); list.GetList().emplace_back(violation.Clone()); dict.SetKey("violations", list.Clone()); - (static_cast<Watchdog*>(context)->violations_map_) + (static_cast<Watchdog*>(context)->GetViolationsMap()) ->SetKey(client->name, dict.Clone()); } else { base::Value* violations = violation_dict->FindKey("violations"); violations->GetList().emplace_back(violation.Clone()); } - static_cast<Watchdog*>(context)->violations_count_++; - if (static_cast<Watchdog*>(context)->violations_count_ > - kWatchdogMaxViolations) - EvictWatchdogViolation(context); - // Consecutive non-unique violation. } else { - // Updates consecutive violation in violations_map_. + // Consecutive non-unique violation, updates violation in violations map. base::Value* violations = violation_dict->FindKey("violations"); int last_index = violations->GetList().size() - 1; int64_t violation_duration = @@ -319,81 +350,68 @@ "violationDurationMilliseconds", base::Value(std::to_string(violation_duration + (time_delta / 1000)))); } - static_cast<Watchdog*>(context)->pending_write_ = true; -} -std::string Watchdog::ReadViolationFile(const char* file_path) { - starboard::ScopedFile read_file(file_path, kSbFileOpenOnly | kSbFileRead); - if (read_file.IsValid()) { - int64_t kFileSize = read_file.GetSize(); - std::vector<char> buffer(kFileSize + 1, 0); - read_file.ReadAll(buffer.data(), kFileSize); - return std::string(buffer.data()); + int violations_count = 0; + for (const auto& it : + (static_cast<Watchdog*>(context)->GetViolationsMap())->DictItems()) { + base::Value& violation_dict = it.second; + base::Value* violations = violation_dict.FindKey("violations"); + violations_count += violations->GetList().size(); } - return ""; -} - -void Watchdog::InitializeViolationsMap(void* context) { - // Loads the previous Watchdog violations file containing violations before - // app start, if it exists, to populate violations_map_. - static_cast<Watchdog*>(context)->violations_count_ = 0; - - std::string watchdog_json = - static_cast<Watchdog*>(context)->ReadViolationFile( - (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str()); - if (watchdog_json != "") { - static_cast<Watchdog*>(context)->violations_map_ = - base::JSONReader::Read(watchdog_json); - } - - if (static_cast<Watchdog*>(context)->violations_map_ == nullptr) { - SB_LOG(INFO) << "[Watchdog] No previous violations JSON."; - static_cast<Watchdog*>(context)->violations_map_ = - std::make_unique<base::Value>(base::Value::Type::DICTIONARY); - } else { - for (const auto& it : - (static_cast<Watchdog*>(context)->violations_map_)->DictItems()) { - base::Value& violation_dict = it.second; - base::Value* violations = violation_dict.FindKey("violations"); - static_cast<Watchdog*>(context)->violations_count_ += - violations->GetList().size(); - } + if (violations_count > kWatchdogMaxViolations) { + EvictWatchdogViolation(context); } } void Watchdog::EvictWatchdogViolation(void* context) { - // Evicts a violation in violations_map_ prioritizing first the most frequent + // Evicts a violation in violations map prioritizing first the most frequent // violations (largest violations count by client name) and second the oldest // violation. std::string evicted_name = ""; int evicted_count = 0; - int64_t evicted_timestamp = 0; + int64_t evicted_timestamp_millis = 0; for (const auto& it : - (static_cast<Watchdog*>(context)->violations_map_)->DictItems()) { + (static_cast<Watchdog*>(context)->GetViolationsMap())->DictItems()) { std::string name = it.first; base::Value& violation_dict = it.second; base::Value* violations = violation_dict.FindKey("violations"); int count = violations->GetList().size(); - int64_t timestamp = + int64_t violation_timestamp_millis = std::stoll(violations->GetList()[0] .FindKey("timestampViolationMilliseconds") ->GetString()); if ((evicted_name == "") || (count > evicted_count) || - ((count == evicted_count) && (timestamp < evicted_timestamp))) { + ((count == evicted_count) && + (violation_timestamp_millis < evicted_timestamp_millis))) { evicted_name = name; evicted_count = count; - evicted_timestamp = timestamp; + evicted_timestamp_millis = violation_timestamp_millis; } } base::Value* violation_dict = - (static_cast<Watchdog*>(context)->violations_map_)->FindKey(evicted_name); - base::Value* violations = violation_dict->FindKey("violations"); - violations->GetList().erase(violations->GetList().begin()); - static_cast<Watchdog*>(context)->violations_count_--; + (static_cast<Watchdog*>(context)->GetViolationsMap()) + ->FindKey(evicted_name); + + if (violation_dict != nullptr) { + base::Value* violations = violation_dict->FindKey("violations"); + violations->GetList().erase(violations->GetList().begin()); + static_cast<Watchdog*>(context)->pending_write_ = true; + + // Removes empty violations. + if (violations->GetList().empty()) { + (static_cast<Watchdog*>(context)->GetViolationsMap()) + ->RemoveKey(evicted_name); + } + if (static_cast<Watchdog*>(context)->GetViolationsMap()->DictEmpty()) { + starboard::SbFileDeleteRecursive( + static_cast<Watchdog*>(context)->GetWatchdogFilePath().c_str(), true); + static_cast<Watchdog*>(context)->pending_write_ = false; + } + } } void Watchdog::MaybeWriteWatchdogViolations(void* context) { @@ -409,7 +427,7 @@ if (static_cast<Watchdog*>(context)->pending_write_) static_cast<Watchdog*>(context)->WriteWatchdogViolations(); SB_LOG(ERROR) << "[Watchdog] Triggering violation Crash!"; - CHECK(false); + *(reinterpret_cast<volatile char*>(0)) = 0; } } @@ -419,19 +437,6 @@ int64_t time_wait_microseconds, Replace replace) { if (is_disabled_) return true; - // Validates parameters. - if (time_interval_microseconds < watchdog_monitor_frequency_ || - time_wait_microseconds < 0) { - SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name; - if (time_interval_microseconds < watchdog_monitor_frequency_) { - SB_DLOG(ERROR) << "[Watchdog] Time interval less than min: " - << watchdog_monitor_frequency_; - } else { - SB_DLOG(ERROR) << "[Watchdog] Time wait is negative."; - } - return false; - } - starboard::ScopedLock scoped_lock(mutex_); int64_t current_time = SbTimeToPosix(SbTimeGetNow()); @@ -453,6 +458,65 @@ } } + // Creates new client. + std::unique_ptr<Client> client = CreateClient( + name, description, monitor_state, time_interval_microseconds, + time_wait_microseconds, current_time, current_monotonic_time); + if (client == nullptr) return false; + + // Registers. + auto result = client_map_.emplace(name, std::move(client)); + + if (result.second) { + SB_DLOG(INFO) << "[Watchdog] Registered: " << name; + } else { + SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name; + } + return result.second; +} + +std::shared_ptr<Client> Watchdog::RegisterByClient( + std::string name, std::string description, + base::ApplicationState monitor_state, int64_t time_interval_microseconds, + int64_t time_wait_microseconds) { + if (is_disabled_) return nullptr; + + starboard::ScopedLock scoped_lock(mutex_); + + int64_t current_time = SbTimeToPosix(SbTimeGetNow()); + SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow(); + + // Creates new client. + std::shared_ptr<Client> client = CreateClient( + name, description, monitor_state, time_interval_microseconds, + time_wait_microseconds, current_time, current_monotonic_time); + if (client == nullptr) return nullptr; + + // Registers. + client_list_.emplace_back(client); + + SB_DLOG(INFO) << "[Watchdog] Registered: " << name; + return client; +} + +std::unique_ptr<Client> Watchdog::CreateClient( + std::string name, std::string description, + base::ApplicationState monitor_state, int64_t time_interval_microseconds, + int64_t time_wait_microseconds, int64_t current_time, + SbTimeMonotonic current_monotonic_time) { + // Validates parameters. + if (time_interval_microseconds < watchdog_monitor_frequency_ || + time_wait_microseconds < 0) { + SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name; + if (time_interval_microseconds < watchdog_monitor_frequency_) { + SB_DLOG(ERROR) << "[Watchdog] Time interval less than min: " + << watchdog_monitor_frequency_; + } else { + SB_DLOG(ERROR) << "[Watchdog] Time wait is negative."; + } + return nullptr; + } + // Creates new Client. std::unique_ptr<Client> client(new Client); client->name = name; @@ -466,15 +530,7 @@ client->time_last_pinged_microseconds = current_time; client->time_last_updated_monotonic_microseconds = current_monotonic_time; - // Registers. - auto result = client_map_.emplace(name, std::move(client)); - - if (result.second) { - SB_DLOG(INFO) << "[Watchdog] Registered: " << name; - } else { - SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name; - } - return result.second; + return std::move(client); } bool Watchdog::Unregister(const std::string& name, bool lock) { @@ -493,11 +549,68 @@ return result; } +bool Watchdog::UnregisterByClient(std::shared_ptr<Client> client) { + if (is_disabled_) return true; + + starboard::ScopedLock scoped_lock(mutex_); + + std::string name = ""; + if (client) name = client->name; + + // Unregisters. + for (auto it = client_list_.begin(); it != client_list_.end(); it++) { + if (client == *it) { + client_list_.erase(it); + SB_DLOG(INFO) << "[Watchdog] Unregistered: " << name; + return true; + } + } + SB_DLOG(ERROR) << "[Watchdog] Unable to Unregister: " << name; + return false; +} + bool Watchdog::Ping(const std::string& name) { return Ping(name, ""); } bool Watchdog::Ping(const std::string& name, const std::string& info) { if (is_disabled_) return true; + starboard::ScopedLock scoped_lock(mutex_); + + auto it = client_map_.find(name); + bool client_exists = it != client_map_.end(); + + if (client_exists) { + Client* client = it->second.get(); + return PingHelper(client, name, info); + } + SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name; + return false; +} + +bool Watchdog::PingByClient(std::shared_ptr<Client> client) { + return PingByClient(client, ""); +} + +bool Watchdog::PingByClient(std::shared_ptr<Client> client, + const std::string& info) { + if (is_disabled_) return true; + + std::string name = ""; + if (client) name = client->name; + + starboard::ScopedLock scoped_lock(mutex_); + + for (auto it = client_list_.begin(); it != client_list_.end(); it++) { + if (client == *it) { + return PingHelper(client.get(), name, info); + } + } + SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name; + return false; +} + +bool Watchdog::PingHelper(Client* client, const std::string& name, + const std::string& info) { // Validates parameters. if (info.length() > kWatchdogMaxPingInfoLength) { SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name; @@ -506,95 +619,109 @@ return false; } - starboard::ScopedLock scoped_lock(mutex_); + int64_t current_time = SbTimeToPosix(SbTimeGetNow()); + SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow(); - auto it = client_map_.find(name); - bool client_exists = it != client_map_.end(); + // Updates last ping. + client->time_last_pinged_microseconds = current_time; + client->time_last_updated_monotonic_microseconds = current_monotonic_time; - if (client_exists) { - int64_t current_time = SbTimeToPosix(SbTimeGetNow()); - SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow(); + if (info != "") { + // Creates new ping_info. + base::Value ping_info(base::Value::Type::DICTIONARY); + ping_info.SetKey("timestampMilliseconds", + base::Value(std::to_string(current_time / 1000))); + ping_info.SetKey("info", base::Value(info)); - Client* client = it->second.get(); - // Updates last ping. - client->time_last_pinged_microseconds = current_time; - client->time_last_updated_monotonic_microseconds = current_monotonic_time; - - if (info != "") { - // Creates new ping_info. - base::Value ping_info(base::Value::Type::DICTIONARY); - ping_info.SetKey("timestampMilliseconds", - base::Value(std::to_string(current_time / 1000))); - ping_info.SetKey("info", base::Value(info)); - - client->ping_infos.GetList().emplace_back(ping_info.Clone()); - if (client->ping_infos.GetList().size() > kWatchdogMaxPingInfos) - client->ping_infos.GetList().erase( - client->ping_infos.GetList().begin()); - } - } else { - SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name; + client->ping_infos.GetList().emplace_back(ping_info.Clone()); + if (client->ping_infos.GetList().size() > kWatchdogMaxPingInfos) + client->ping_infos.GetList().erase(client->ping_infos.GetList().begin()); } - return client_exists; + return true; } std::string Watchdog::GetWatchdogViolations( const std::vector<std::string>& clients, bool clear) { - // Gets a json string containing the Watchdog violations since the last - // call (up to the kWatchdogMaxViolations limit). + // Gets a json string containing the Watchdog violations of the given clients + // since the last call (up to the kWatchdogMaxViolations limit). if (is_disabled_) return ""; - std::string watchdog_json = ""; - std::string watchdog_json_fetched = ""; + std::string fetched_violations_json = ""; starboard::ScopedLock scoped_lock(mutex_); - if (pending_write_) WriteWatchdogViolations(); - - if (!static_cast<base::DictionaryValue*>(violations_map_.get())->empty()) { - // Get all Watchdog violations if clients is given. + if (!GetViolationsMap()->DictEmpty()) { if (clients.empty()) { - // Removes all Watchdog violations. - base::JSONWriter::Write(*violations_map_, &watchdog_json_fetched); + // Gets all Watchdog violations if no clients are given. + base::JSONWriter::Write(*GetViolationsMap(), &fetched_violations_json); if (clear) { - static_cast<base::DictionaryValue*>(violations_map_.get())->Clear(); - violations_count_ = 0; + static_cast<base::DictionaryValue*>(GetViolationsMap().get())->Clear(); starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true); } } else { - base::Value filtered_client_data(base::Value::Type::DICTIONARY); - for (int i = 0; i < clients.size(); i++) { - base::Value* violation_dict = - static_cast<base::DictionaryValue*>(violations_map_.get()) - ->FindKey(clients[i]); + // Gets all Watchdog violations of the given clients. + base::Value fetched_violations(base::Value::Type::DICTIONARY); + for (std::string name : clients) { + base::Value* violation_dict = GetViolationsMap()->FindKey(name); if (violation_dict != nullptr) { - filtered_client_data.SetKey(clients[i], (*violation_dict).Clone()); + fetched_violations.SetKey(name, (*violation_dict).Clone()); if (clear) { - base::Value* violations = violation_dict->FindKey("violations"); - int violations_count = violations->GetList().size(); - - static_cast<base::DictionaryValue*>(violations_map_.get()) - ->RemoveKey(clients[i]); - violations_count_ -= violations_count; - if (!static_cast<base::DictionaryValue*>(violations_map_.get()) - ->empty()) { - WriteWatchdogViolations(); - } else { - starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), - true); - } + GetViolationsMap()->RemoveKey(name); + pending_write_ = true; } } } - if (!filtered_client_data.DictEmpty()) { - base::JSONWriter::Write(filtered_client_data, &watchdog_json_fetched); + if (!fetched_violations.DictEmpty()) { + base::JSONWriter::Write(fetched_violations, &fetched_violations_json); + } + if (clear) { + EvictOldWatchdogViolations(); } } - SB_LOG(INFO) << "[Watchdog] Reading violations:\n" << watchdog_json_fetched; + SB_LOG(INFO) << "[Watchdog] Reading violations:\n" + << fetched_violations_json; } else { SB_LOG(INFO) << "[Watchdog] No violations."; } - return watchdog_json_fetched; + return fetched_violations_json; +} + +void Watchdog::EvictOldWatchdogViolations() { + int64_t current_timestamp_millis = SbTimeToPosix(SbTimeGetNow()) / 1000; + int64_t cutoff_timestamp_millis = + current_timestamp_millis - kWatchdogMaxViolationsAge; + std::vector<std::string> empty_violations; + + // Iterates through map removing old violations. + for (const auto& map_it : GetViolationsMap()->DictItems()) { + std::string name = map_it.first; + base::Value& violation_dict = map_it.second; + base::Value* violations = violation_dict.FindKey("violations"); + for (auto list_it = violations->GetList().begin(); + list_it != violations->GetList().end();) { + int64_t violation_timestamp_millis = std::stoll( + list_it->FindKey("timestampViolationMilliseconds")->GetString()); + + if (violation_timestamp_millis < cutoff_timestamp_millis) { + list_it = violations->GetList().erase(list_it); + pending_write_ = true; + } else { + list_it++; + } + } + if (violations->GetList().empty()) { + empty_violations.push_back(name); + } + } + + // Removes empty violations. + for (std::string name : empty_violations) { + GetViolationsMap()->RemoveKey(name); + } + if (GetViolationsMap()->DictEmpty()) { + starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true); + pending_write_ = false; + } } bool Watchdog::GetPersistentSettingWatchdogEnable() {
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h index 8aa9a7c..aed2ec1 100644 --- a/cobalt/watchdog/watchdog.h +++ b/cobalt/watchdog/watchdog.h
@@ -88,9 +88,19 @@ base::ApplicationState monitor_state, int64_t time_interval_microseconds, int64_t time_wait_microseconds = 0, Replace replace = NONE); + std::shared_ptr<Client> RegisterByClient(std::string name, + std::string description, + base::ApplicationState monitor_state, + int64_t time_interval_microseconds, + int64_t time_wait_microseconds = 0); bool Unregister(const std::string& name, bool lock = true); + bool UnregisterByClient(std::shared_ptr<Client> client); bool Ping(const std::string& name); bool Ping(const std::string& name, const std::string& info); + bool PingByClient(std::shared_ptr<Client> client); + bool PingByClient(std::shared_ptr<Client> client, const std::string& info); + bool PingHelper(Client* client, const std::string& name, + const std::string& info); std::string GetWatchdogViolations( const std::vector<std::string>& clients = {}, bool clear = true); bool GetPersistentSettingWatchdogEnable(); @@ -104,12 +114,21 @@ #endif // defined(_DEBUG) private: + std::shared_ptr<base::Value> GetViolationsMap(); void WriteWatchdogViolations(); - std::string ReadViolationFile(const char* file_path); + void EvictOldWatchdogViolations(); + std::unique_ptr<Client> CreateClient(std::string name, + std::string description, + base::ApplicationState monitor_state, + int64_t time_interval_microseconds, + int64_t time_wait_microseconds, + int64_t current_time, + SbTimeMonotonic current_monotonic_time); static void* Monitor(void* context); + static bool MonitorClient(void* context, Client* client, + SbTimeMonotonic current_monotonic_time); static void UpdateViolationsMap(void* context, Client* client, SbTimeMonotonic time_delta); - static void InitializeViolationsMap(void* context); static void EvictWatchdogViolation(void* context); static void MaybeWriteWatchdogViolations(void* context); static void MaybeTriggerCrash(void* context); @@ -139,12 +158,13 @@ SbTimeMonotonic time_last_written_microseconds_ = 0; // Number of microseconds between writes. int64_t write_wait_time_microseconds_; - // Dictionary of registered Watchdog clients. + // Dictionary of name registered Watchdog clients. std::unordered_map<std::string, std::unique_ptr<Client>> client_map_; + // List of client registered Watchdog clients, parallel data structure to + // client_map_. + std::vector<std::shared_ptr<Client>> client_list_; // Dictionary of lists of Watchdog violations represented as dictionaries. - std::unique_ptr<base::Value> violations_map_; - // Number of violations in violations_map_; - int violations_count_; + std::shared_ptr<base::Value> violations_map_; // Monitor thread. SbThread watchdog_thread_; // Flag to stop monitor thread.
diff --git a/cobalt/watchdog/watchdog_test.cc b/cobalt/watchdog/watchdog_test.cc index 1cafc43..6668c2a 100644 --- a/cobalt/watchdog/watchdog_test.cc +++ b/cobalt/watchdog/watchdog_test.cc
@@ -86,7 +86,6 @@ ASSERT_TRUE(watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted, kWatchdogMonitorFrequency)); - ASSERT_FALSE(watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted, kWatchdogMonitorFrequency)); @@ -100,10 +99,6 @@ } TEST_F(WatchdogTest, RegisterOnlyAcceptsValidParameters) { - ASSERT_TRUE(watchdog_->Register("test-name", "test-desc", - base::kApplicationStateStarted, - kWatchdogMonitorFrequency, 0)); - ASSERT_TRUE(watchdog_->Unregister("test-name")); ASSERT_FALSE(watchdog_->Register("test-name-1", "test-desc-1", base::kApplicationStateStarted, 1, 0)); ASSERT_FALSE(watchdog_->Unregister("test-name-1")); @@ -127,7 +122,37 @@ ASSERT_FALSE(watchdog_->Unregister("test-name-6")); } -TEST_F(WatchdogTest, UnmatchedUnregistersShouldFail) { +TEST_F(WatchdogTest, RegisterByClientOnlyAcceptsValidParameters) { + std::shared_ptr<Client> client = watchdog_->RegisterByClient( + "test-name", "test-desc", base::kApplicationStateStarted, 1, 0); + ASSERT_EQ(client, nullptr); + ASSERT_FALSE(watchdog_->UnregisterByClient(client)); + client = watchdog_->RegisterByClient("test-name", "test-desc", + base::kApplicationStateStarted, 0, 0); + ASSERT_EQ(client, nullptr); + ASSERT_FALSE(watchdog_->UnregisterByClient(client)); + client = watchdog_->RegisterByClient("test-name", "test-desc", + base::kApplicationStateStarted, -1, 0); + ASSERT_EQ(client, nullptr); + ASSERT_FALSE(watchdog_->UnregisterByClient(client)); + client = watchdog_->RegisterByClient("test-name", "test-desc", + base::kApplicationStateStarted, + kWatchdogMonitorFrequency, 1); + ASSERT_NE(client, nullptr); + ASSERT_TRUE(watchdog_->UnregisterByClient(client)); + client = watchdog_->RegisterByClient("test-name", "test-desc", + base::kApplicationStateStarted, + kWatchdogMonitorFrequency, 0); + ASSERT_NE(client, nullptr); + ASSERT_TRUE(watchdog_->UnregisterByClient(client)); + client = watchdog_->RegisterByClient("test-name", "test-desc", + base::kApplicationStateStarted, + kWatchdogMonitorFrequency, -1); + ASSERT_EQ(client, nullptr); + ASSERT_FALSE(watchdog_->UnregisterByClient(client)); +} + +TEST_F(WatchdogTest, UnmatchedUnregisterShouldFail) { ASSERT_FALSE(watchdog_->Unregister("test-name")); ASSERT_TRUE(watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted, @@ -136,30 +161,58 @@ ASSERT_FALSE(watchdog_->Unregister("test-name")); } -TEST_F(WatchdogTest, UnmatchedPingsShouldFail) { +TEST_F(WatchdogTest, UnmatchedUnregisterByClientShouldFail) { + std::shared_ptr<Client> client_test(new Client); + ASSERT_FALSE(watchdog_->UnregisterByClient(client_test)); + std::shared_ptr<Client> client = watchdog_->RegisterByClient( + "test-name", "test-desc", base::kApplicationStateStarted, + kWatchdogMonitorFrequency); + ASSERT_NE(client, nullptr); + ASSERT_TRUE(watchdog_->UnregisterByClient(client)); + ASSERT_FALSE(watchdog_->UnregisterByClient(client)); +} + +TEST_F(WatchdogTest, UnmatchedPingShouldFail) { ASSERT_FALSE(watchdog_->Ping("test-name")); ASSERT_TRUE(watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted, kWatchdogMonitorFrequency)); ASSERT_TRUE(watchdog_->Ping("test-name")); - ASSERT_TRUE(watchdog_->Ping("test-name")); ASSERT_TRUE(watchdog_->Unregister("test-name")); ASSERT_FALSE(watchdog_->Ping("test-name")); } +TEST_F(WatchdogTest, UnmatchedPingByClientShouldFail) { + std::shared_ptr<Client> client_test(new Client); + ASSERT_FALSE(watchdog_->PingByClient(client_test)); + std::shared_ptr<Client> client = watchdog_->RegisterByClient( + "test-name", "test-desc", base::kApplicationStateStarted, + kWatchdogMonitorFrequency); + ASSERT_NE(client, nullptr); + ASSERT_TRUE(watchdog_->PingByClient(client)); + ASSERT_TRUE(watchdog_->UnregisterByClient(client)); + ASSERT_FALSE(watchdog_->PingByClient(client)); +} + TEST_F(WatchdogTest, PingOnlyAcceptsValidParameters) { ASSERT_TRUE(watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted, kWatchdogMonitorFrequency)); ASSERT_TRUE(watchdog_->Ping("test-name", "42")); - ASSERT_FALSE( - watchdog_->Ping("test-name", - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - "xxxxxxxxxxxxxxxxxxxxxxxxxxx")); + ASSERT_FALSE(watchdog_->Ping("test-name", std::string(1025, 'x'))); ASSERT_TRUE(watchdog_->Unregister("test-name")); } +TEST_F(WatchdogTest, PingByClientOnlyAcceptsValidParameters) { + std::shared_ptr<Client> client = watchdog_->RegisterByClient( + "test-name", "test-desc", base::kApplicationStateStarted, + kWatchdogMonitorFrequency); + ASSERT_NE(client, nullptr); + ASSERT_TRUE(watchdog_->PingByClient(client, "42")); + ASSERT_FALSE(watchdog_->PingByClient(client, std::string(1025, 'x'))); + ASSERT_TRUE(watchdog_->UnregisterByClient(client)); +} + TEST_F(WatchdogTest, ViolationsJsonShouldPersistAndBeValid) { ASSERT_EQ(watchdog_->GetWatchdogViolations(), ""); ASSERT_TRUE(watchdog_->Register("test-name", "test-desc", @@ -281,11 +334,12 @@ std::string json = watchdog_->GetWatchdogViolations(); ASSERT_NE(json.find("test-name-1"), std::string::npos); ASSERT_EQ(json.find("test-name-2"), std::string::npos); - ASSERT_TRUE(watchdog_->Register("test-name-2", "test-desc-2", - base::kApplicationStateStarted, - kWatchdogMonitorFrequency)); + std::shared_ptr<Client> client = watchdog_->RegisterByClient( + "test-name-2", "test-desc-2", base::kApplicationStateStarted, + kWatchdogMonitorFrequency); + ASSERT_NE(client, nullptr); SbThreadSleep(kWatchdogSleepDuration); - ASSERT_TRUE(watchdog_->Unregister("test-name-2")); + ASSERT_TRUE(watchdog_->UnregisterByClient(client)); json = watchdog_->GetWatchdogViolations(); ASSERT_EQ(json.find("test-name-1"), std::string::npos); ASSERT_NE(json.find("test-name-2"), std::string::npos); @@ -296,7 +350,7 @@ ASSERT_TRUE(watchdog_->Register("test-name", "test_desc", base::kApplicationStateStarted, kWatchdogMonitorFrequency)); - for (int i = 0; i < 21; i++) { + for (int i = 0; i < 61; i++) { ASSERT_TRUE(watchdog_->Ping("test-name", std::to_string(i))); } SbThreadSleep(kWatchdogSleepDuration); @@ -307,7 +361,7 @@ base::Value* violation_dict = violations_map->FindKey("test-name"); base::Value* violations = violation_dict->FindKey("violations"); base::Value* pingInfos = violations->GetList()[0].FindKey("pingInfos"); - ASSERT_EQ(pingInfos->GetList().size(), 20); + ASSERT_EQ(pingInfos->GetList().size(), 60); ASSERT_EQ(pingInfos->GetList()[0].FindKey("info")->GetString(), "1"); ASSERT_TRUE(watchdog_->Unregister("test-name")); } @@ -408,7 +462,7 @@ ASSERT_TRUE(watchdog_->Unregister("test-name")); } -TEST_F(WatchdogTest, PingsShouldPreventViolations) { +TEST_F(WatchdogTest, PingShouldPreventViolations) { ASSERT_TRUE(watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted, kWatchdogMonitorFrequency)); @@ -438,6 +492,20 @@ ASSERT_TRUE(watchdog_->Unregister("test-name")); } +TEST_F(WatchdogTest, PingByClientShouldPreventViolations) { + std::shared_ptr<Client> client = watchdog_->RegisterByClient( + "test-name", "test-desc", base::kApplicationStateStarted, + kWatchdogMonitorFrequency); + SbThreadSleep(kWatchdogMonitorFrequency / 2); + ASSERT_TRUE(watchdog_->PingByClient(client)); + SbThreadSleep(kWatchdogMonitorFrequency / 2); + ASSERT_TRUE(watchdog_->PingByClient(client)); + ASSERT_EQ(watchdog_->GetWatchdogViolations(), ""); + SbThreadSleep(kWatchdogSleepDuration); + ASSERT_NE(watchdog_->GetWatchdogViolations(), ""); + ASSERT_TRUE(watchdog_->UnregisterByClient(client)); +} + TEST_F(WatchdogTest, UnregisterShouldPreventViolations) { ASSERT_TRUE(watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted, @@ -447,6 +515,15 @@ ASSERT_EQ(watchdog_->GetWatchdogViolations(), ""); } +TEST_F(WatchdogTest, UnregisterByClientShouldPreventViolations) { + std::shared_ptr<Client> client = watchdog_->RegisterByClient( + "test-name", "test-desc", base::kApplicationStateStarted, + kWatchdogMonitorFrequency); + ASSERT_TRUE(watchdog_->UnregisterByClient(client)); + SbThreadSleep(kWatchdogSleepDuration); + ASSERT_EQ(watchdog_->GetWatchdogViolations(), ""); +} + TEST_F(WatchdogTest, KillSwitchShouldPreventViolations) { TearDown(); watchdog_ = new watchdog::Watchdog(); @@ -502,14 +579,13 @@ SbThreadSleep(kWatchdogSleepDuration); ASSERT_TRUE(watchdog_->Unregister("test-name-1")); ASSERT_TRUE(watchdog_->Unregister("test-name-2")); + std::vector<std::string> names = watchdog_->GetWatchdogViolationClientNames(); ASSERT_EQ(names.size(), 2); - std::set<std::string> expected_names = {"test-name-1", "test-name-2"}; - for (std::vector<std::string>::const_iterator it = names.begin(); - it != names.end(); ++it) { - const std::string name = *it; - ASSERT_TRUE((expected_names.find(name) != expected_names.end())); - } + ASSERT_TRUE(std::find(names.begin(), names.end(), "test-name-1") != + names.end()); + ASSERT_TRUE(std::find(names.begin(), names.end(), "test-name-2") != + names.end()); watchdog_->GetWatchdogViolations(); names = watchdog_->GetWatchdogViolationClientNames(); ASSERT_EQ(names.size(), 0); @@ -522,13 +598,10 @@ ASSERT_TRUE(watchdog_->Register("test-name-2", "test-desc-2", base::kApplicationStateStarted, kWatchdogMonitorFrequency)); - ASSERT_TRUE(watchdog_->Register("test-name-3", "test-desc-3", - base::kApplicationStateStarted, - kWatchdogMonitorFrequency)); SbThreadSleep(kWatchdogSleepDuration); ASSERT_TRUE(watchdog_->Unregister("test-name-1")); ASSERT_TRUE(watchdog_->Unregister("test-name-2")); - ASSERT_TRUE(watchdog_->Unregister("test-name-3")); + const std::vector<std::string> clients = {"test-name-1"}; std::string json = watchdog_->GetWatchdogViolations(clients); ASSERT_NE(json, ""); @@ -538,45 +611,29 @@ ASSERT_NE(violation_dict, nullptr); violation_dict = violations_map->FindKey("test-name-2"); ASSERT_EQ(violation_dict, nullptr); - violation_dict = violations_map->FindKey("test-name-3"); - ASSERT_EQ(violation_dict, nullptr); - - std::string file_json = ""; - starboard::ScopedFile read_file(watchdog_->GetWatchdogFilePath().c_str(), - kSbFileOpenOnly | kSbFileRead); - if (read_file.IsValid()) { - int64_t kFileSize = read_file.GetSize(); - std::vector<char> buffer(kFileSize + 1, 0); - read_file.ReadAll(buffer.data(), kFileSize); - file_json = std::string(buffer.data()); - } - ASSERT_NE(file_json, ""); - violations_map = base::JSONReader::Read(file_json); - ASSERT_NE(violations_map, nullptr); - violation_dict = violations_map->FindKey("test-name-2"); - ASSERT_NE(violation_dict, nullptr); - violation_dict = violations_map->FindKey("test-name-3"); - ASSERT_NE(violation_dict, nullptr); - violation_dict = violations_map->FindKey("test-name-1"); - ASSERT_EQ(violation_dict, nullptr); - json = watchdog_->GetWatchdogViolations(clients); ASSERT_EQ(json, ""); +} - const std::vector<std::string> clients2 = {"test-name-2", "test-name-3"}; - json = watchdog_->GetWatchdogViolations(clients2); - ASSERT_NE(json, ""); - violations_map = base::JSONReader::Read(json); - ASSERT_NE(violations_map, nullptr); - violation_dict = violations_map->FindKey("test-name-1"); - ASSERT_EQ(violation_dict, nullptr); - violation_dict = violations_map->FindKey("test-name-2"); - ASSERT_NE(violation_dict, nullptr); - violation_dict = violations_map->FindKey("test-name-3"); - ASSERT_NE(violation_dict, nullptr); - starboard::ScopedFile read_file_again( - watchdog_->GetWatchdogFilePath().c_str(), kSbFileOpenOnly | kSbFileRead); - ASSERT_EQ(read_file_again.IsValid(), false); +TEST_F(WatchdogTest, EvictOldWatchdogViolations) { + // Creates old Violation json file. + std::unique_ptr<base::Value> dummy_map = + std::make_unique<base::Value>(base::Value::Type::DICTIONARY); + dummy_map->SetKey("test-name-old", + CreateDummyViolationDict("test-desc-old", 0, 1)); + std::string json; + base::JSONWriter::Write(*dummy_map, &json); + starboard::ScopedFile file(watchdog_->GetWatchdogFilePath().c_str(), + kSbFileCreateAlways | kSbFileWrite); + TearDown(); + file.WriteAll(json.c_str(), static_cast<int>(json.size())); + watchdog_ = new watchdog::Watchdog(); + watchdog_->InitializeCustom(nullptr, std::string(kWatchdogViolationsJson), + kWatchdogMonitorFrequency); + + ASSERT_NE(watchdog_->GetWatchdogViolations({}, false), ""); + ASSERT_EQ(watchdog_->GetWatchdogViolations({"test-name-new"}), ""); + ASSERT_EQ(watchdog_->GetWatchdogViolations({}, false), ""); } } // namespace watchdog
diff --git a/cobalt/webdriver/testdata/simple_test.py b/cobalt/webdriver/testdata/simple_test.py index 5b5a9a1..168436b 100755 --- a/cobalt/webdriver/testdata/simple_test.py +++ b/cobalt/webdriver/testdata/simple_test.py
@@ -176,7 +176,7 @@ """ request = ElementRequest(session_id, element_id, GET, 'screenshot') if request: - with open(filename, 'w', encoding='utf-8') as f: + with open(filename, 'wb') as f: f.write(binascii.a2b_base64(request['value'])) f.close()
diff --git a/cobalt/worker/service_worker_context.cc b/cobalt/worker/service_worker_context.cc index b4b4bac..ad96c62 100644 --- a/cobalt/worker/service_worker_context.cc +++ b/cobalt/worker/service_worker_context.cc
@@ -163,15 +163,14 @@ ServiceWorkerContext::ServiceWorkerContext( web::WebSettings* web_settings, network::NetworkModule* network_module, - web::UserAgentPlatformInfo* platform_info, base::MessageLoop* message_loop, - const GURL& url) + web::UserAgentPlatformInfo* platform_info, base::MessageLoop* message_loop) : message_loop_(message_loop) { DCHECK_EQ(message_loop_, base::MessageLoop::current()); jobs_ = std::make_unique<ServiceWorkerJobs>(this, network_module, message_loop); ServiceWorkerPersistentSettings::Options options(web_settings, network_module, - platform_info, this, url); + platform_info, this); scope_to_registration_map_.reset(new ServiceWorkerRegistrationMap(options)); DCHECK(scope_to_registration_map_); }
diff --git a/cobalt/worker/service_worker_context.h b/cobalt/worker/service_worker_context.h index e51882d..460200f 100644 --- a/cobalt/worker/service_worker_context.h +++ b/cobalt/worker/service_worker_context.h
@@ -51,7 +51,7 @@ ServiceWorkerContext(web::WebSettings* web_settings, network::NetworkModule* network_module, web::UserAgentPlatformInfo* platform_info, - base::MessageLoop* message_loop, const GURL& url); + base::MessageLoop* message_loop); ~ServiceWorkerContext(); base::MessageLoop* message_loop() { return message_loop_; }
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc index fa0451b..ecbb126 100644 --- a/cobalt/worker/service_worker_jobs.cc +++ b/cobalt/worker/service_worker_jobs.cc
@@ -852,7 +852,8 @@ ServiceWorkerObject* installing_worker = registration->installing_worker(); // 11. If the result of running the Should Skip Event algorithm with // installingWorker and "install" is false, then: - if (!installing_worker->ShouldSkipEvent(base::Tokens::install())) { + if (installing_worker && + !installing_worker->ShouldSkipEvent(base::Tokens::install())) { // 11.1. Let forceBypassCache be true if job’s force bypass cache flag is // set, and false otherwise. bool force_bypass_cache = job->force_bypass_cache_flag;
diff --git a/cobalt/worker/service_worker_persistent_settings.cc b/cobalt/worker/service_worker_persistent_settings.cc index fc81821..368ba1a 100644 --- a/cobalt/worker/service_worker_persistent_settings.cc +++ b/cobalt/worker/service_worker_persistent_settings.cc
@@ -119,12 +119,6 @@ url::Origin storage_key = url::Origin::Create(GURL(dict[kSettingsStorageKeyKey]->GetString())); - // Only add persisted workers to the registration_map - // if their storage_key matches the origin of the initial_url. - if (!storage_key.IsSameOriginWith(url::Origin::Create(options_.url))) { - continue; - } - if (!CheckPersistentValue(key_string, kSettingsScopeUrlKey, dict, base::Value::Type::STRING)) continue;
diff --git a/cobalt/worker/service_worker_persistent_settings.h b/cobalt/worker/service_worker_persistent_settings.h index f855895..55142a1 100644 --- a/cobalt/worker/service_worker_persistent_settings.h +++ b/cobalt/worker/service_worker_persistent_settings.h
@@ -48,17 +48,15 @@ Options(web::WebSettings* web_settings, network::NetworkModule* network_module, web::UserAgentPlatformInfo* platform_info, - ServiceWorkerContext* service_worker_context, const GURL& url) + ServiceWorkerContext* service_worker_context) : web_settings(web_settings), network_module(network_module), platform_info(platform_info), - service_worker_context(service_worker_context), - url(url) {} + service_worker_context(service_worker_context) {} web::WebSettings* web_settings; network::NetworkModule* network_module; web::UserAgentPlatformInfo* platform_info; ServiceWorkerContext* service_worker_context; - const GURL& url; }; explicit ServiceWorkerPersistentSettings(const Options& options);
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn index 53e47b4..e69be69 100644 --- a/components/metrics/BUILD.gn +++ b/components/metrics/BUILD.gn
@@ -141,6 +141,11 @@ "system_memory_stats_recorder_win.cc", "system_session_analyzer_win.cc", "system_session_analyzer_win.h", + + # Files with unused symbols. ex: DriveMetricsProvider::HasSeekPenalty + # These files give linker errors about undefined symbols during modualr builds. + "drive_metrics_provider.cc", + "drive_metrics_provider.h", ] }
diff --git a/components/variations/BUILD.gn b/components/variations/BUILD.gn index ad64baf..46eba0c 100644 --- a/components/variations/BUILD.gn +++ b/components/variations/BUILD.gn
@@ -87,6 +87,17 @@ ] } + if (use_cobalt_customizations) { + # Files with unused symbols. ex: base::FeatureList::RegisterFieldTrialOverride + # These files give linker errors about undefined symbols during modualr builds. + sources -= [ + "variations_seed_processor.cc", + "variations_seed_processor.h", + "variations_seed_simulator.cc", + "variations_seed_simulator.h", + ] + } + if (!use_cobalt_customizations && (is_android || is_ios)) { sources += [ "variations_request_scheduler_mobile.cc", @@ -128,7 +139,6 @@ } } -# TODO(b/283258321): Re-enable as many tests as posible. if (!use_cobalt_customizations) { static_library("test_support") { testonly = true
diff --git a/net/BUILD.gn b/net/BUILD.gn index e694523..2fb59e7 100644 --- a/net/BUILD.gn +++ b/net/BUILD.gn
@@ -427,6 +427,7 @@ "disk_cache/cobalt/cobalt_disk_cache.cc", "disk_cache/cobalt/cobalt_backend_impl.cc", "disk_cache/cobalt/cobalt_backend_impl.h", + "disk_cache/cobalt/resource_type.cc", "disk_cache/cobalt/resource_type.h", "disk_cache/net_log_parameters.cc", "disk_cache/net_log_parameters.h", @@ -4217,7 +4218,6 @@ "//base:i18n", "//base/test:test_support", "//base/third_party/dynamic_annotations", - "//cobalt/persistent_storage:persistent_settings", "//crypto", "//testing/gmock", "//testing/gtest",
diff --git a/net/android/unittest_support/AndroidManifest.xml b/net/android/unittest_support/AndroidManifest.xml index d4f4261..63a5fea 100644 --- a/net/android/unittest_support/AndroidManifest.xml +++ b/net/android/unittest_support/AndroidManifest.xml
@@ -10,7 +10,7 @@ android:versionCode="1" android:versionName="1.0"> - <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" /> + <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
diff --git a/net/base/cache_type.h b/net/base/cache_type.h index debae51..c5e72ef 100644 --- a/net/base/cache_type.h +++ b/net/base/cache_type.h
@@ -24,9 +24,6 @@ CACHE_BACKEND_DEFAULT, CACHE_BACKEND_BLOCKFILE, // The |BackendImpl|. CACHE_BACKEND_SIMPLE, // The |SimpleBackendImpl|. -#if defined(STARBOARD) - CACHE_BACKEND_COBALT // The |CobaltBackendImpl|, -#endif }; } // namespace net
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.cc b/net/disk_cache/cobalt/cobalt_backend_impl.cc index b60ab38..1bf9108 100644 --- a/net/disk_cache/cobalt/cobalt_backend_impl.cc +++ b/net/disk_cache/cobalt/cobalt_backend_impl.cc
@@ -30,8 +30,6 @@ namespace { -const char kPersistentSettingsJson[] = "cache_settings.json"; - void CompletionOnceCallbackHandler( scoped_refptr<CobaltBackendImpl::RefCountedRunner> runner, int result) { @@ -53,36 +51,23 @@ return kOther; } -void ReadDiskCacheSize( - cobalt::persistent_storage::PersistentSettings* settings, - int64_t max_bytes) { - auto total_size = 0; - disk_cache::ResourceTypeMetadata kTypeMetadataNew[disk_cache::kTypeCount]; - - for (int i = 0; i < disk_cache::kTypeCount; i++) { - auto metadata = disk_cache::kTypeMetadata[i]; - uint32_t bucket_size = - static_cast<uint32_t>(settings->GetPersistentSettingAsDouble( - metadata.directory, metadata.max_size_bytes)); - kTypeMetadataNew[i] = {metadata.directory, bucket_size}; - - total_size += bucket_size; +bool NeedsBackend(ResourceType resource_type) { + switch (resource_type) { + case kHTML: + case kCSS: + case kImage: + case kFont: + case kUncompiledScript: + case kOther: + case kSplashScreen: + return true; + case kCompiledScript: + case kCacheApi: + case kServiceWorkerScript: + default: + return false; } - - // Check if PersistentSettings values are valid and can replace the disk_cache::kTypeMetadata. - if (total_size <= max_bytes) { - std::copy(std::begin(kTypeMetadataNew), std::end(kTypeMetadataNew), std::begin(disk_cache::kTypeMetadata)); - return; - } - - // PersistentSettings values are invalid and will be replaced by the default values in - // disk_cache::kTypeMetadata. - for (int i = 0; i < disk_cache::kTypeCount; i++) { - auto metadata = disk_cache::kTypeMetadata[i]; - settings->SetPersistentSetting( - metadata.directory, - std::make_unique<base::Value>(static_cast<double>(metadata.max_size_bytes))); - } + return false; } } // namespace @@ -94,57 +79,43 @@ net::CacheType cache_type, net::NetLog* net_log) : weak_factory_(this) { - persistent_settings_ = - std::make_unique<cobalt::persistent_storage::PersistentSettings>( - kPersistentSettingsJson); - ReadDiskCacheSize(persistent_settings_.get(), max_bytes); - - // Initialize disk backend for each resource type. int64_t total_size = 0; for (int i = 0; i < kTypeCount; i++) { - auto metadata = kTypeMetadata[i]; - base::FilePath dir = path.Append(FILE_PATH_LITERAL(metadata.directory)); - int64_t bucket_size = metadata.max_size_bytes; + ResourceType resource_type = (ResourceType)i; + if (!NeedsBackend(resource_type)) { + continue; + } + std::string sub_directory = defaults::GetSubdirectory(resource_type); + base::FilePath dir = path.Append(FILE_PATH_LITERAL(sub_directory)); + uint32_t bucket_size = disk_cache::settings::GetQuota(resource_type); total_size += bucket_size; SimpleBackendImpl* simple_backend = new SimpleBackendImpl( - dir, cleanup_tracker, /* file_tracker = */ nullptr, bucket_size, + dir, cleanup_tracker, /*file_tracker=*/nullptr, bucket_size, cache_type, net_log); - simple_backend_map_[(ResourceType)i] = simple_backend; + simple_backend_map_[resource_type] = simple_backend; } - // Must be at least enough space for each backend. DCHECK(total_size <= max_bytes); } CobaltBackendImpl::~CobaltBackendImpl() { for (int i = 0; i < kTypeCount; i++) { - delete simple_backend_map_[(ResourceType)i]; + ResourceType resource_type = (ResourceType)i; + if (simple_backend_map_.count(resource_type) == 1) { + delete simple_backend_map_[resource_type]; + } } simple_backend_map_.clear(); } void CobaltBackendImpl::UpdateSizes(ResourceType type, uint32_t bytes) { - if (bytes == disk_cache::kTypeMetadata[type].max_size_bytes) + if (simple_backend_map_.count(type) == 0) { return; - - // Static cast value to double since base::Value cannot be a long. - persistent_settings_->SetPersistentSetting( - disk_cache::kTypeMetadata[type].directory, - std::make_unique<base::Value>(static_cast<double>(bytes))); - - disk_cache::kTypeMetadata[type].max_size_bytes = bytes; + } SimpleBackendImpl* simple_backend = simple_backend_map_[type]; simple_backend->SetMaxSize(bytes); } -uint32_t CobaltBackendImpl::GetQuota(ResourceType type) { - return disk_cache::kTypeMetadata[type].max_size_bytes; -} - -void CobaltBackendImpl::ValidatePersistentSettings() { - persistent_settings_->ValidatePersistentSettings(); -} - net::Error CobaltBackendImpl::Init(CompletionOnceCallback completion_callback) { auto closure_runner = base::MakeRefCounted<RefCountedRunner>(std::move(completion_callback)); @@ -173,6 +144,9 @@ net::RequestPriority request_priority, Entry** entry, CompletionOnceCallback callback) { + if (simple_backend_map_.count(GetType(key)) == 0) { + return net::Error::ERR_BLOCKED_BY_CLIENT; + } SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)]; return simple_backend->OpenEntry(key, request_priority, entry, std::move(callback)); @@ -183,11 +157,11 @@ Entry** entry, CompletionOnceCallback callback) { ResourceType type = GetType(key); - auto quota = disk_cache::kTypeMetadata[type].max_size_bytes; - if (quota == 0) { + auto quota = disk_cache::settings::GetQuota(type); + if (quota == 0 || simple_backend_map_.count(type) == 0) { return net::Error::ERR_BLOCKED_BY_CLIENT; } - SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)]; + SimpleBackendImpl* simple_backend = simple_backend_map_[type]; return simple_backend->CreateEntry(key, request_priority, entry, std::move(callback)); } @@ -195,6 +169,9 @@ net::Error CobaltBackendImpl::DoomEntry(const std::string& key, net::RequestPriority priority, CompletionOnceCallback callback) { + if (simple_backend_map_.count(GetType(key)) == 0) { + return net::Error::ERR_BLOCKED_BY_CLIENT; + } SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)]; return simple_backend->DoomEntry(key, priority, std::move(callback)); } @@ -273,6 +250,9 @@ } void CobaltBackendImpl::OnExternalCacheHit(const std::string& key) { + if (simple_backend_map_.count(GetType(key)) == 0) { + return; + } SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)]; simple_backend->OnExternalCacheHit(key); } @@ -291,6 +271,10 @@ net::Error CobaltBackendImpl::DoomAllEntriesOfType(disk_cache::ResourceType type, CompletionOnceCallback callback) { + if (simple_backend_map_.count(type) == 0) { + std::move(callback).Run(net::OK); + return net::OK; + } SimpleBackendImpl* simple_backend = simple_backend_map_[type]; return simple_backend->DoomAllEntries(std::move(callback)); }
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.h b/net/disk_cache/cobalt/cobalt_backend_impl.h index b3b1b54..b1bfe31 100644 --- a/net/disk_cache/cobalt/cobalt_backend_impl.h +++ b/net/disk_cache/cobalt/cobalt_backend_impl.h
@@ -23,7 +23,6 @@ #include <utility> #include "base/callback_helpers.h" -#include "cobalt/persistent_storage/persistent_settings.h" #include "net/base/completion_once_callback.h" #include "net/disk_cache/cobalt/resource_type.h" #include "net/disk_cache/disk_cache.h" @@ -50,8 +49,6 @@ net::Error Init(CompletionOnceCallback completion_callback); void UpdateSizes(ResourceType type, uint32_t bytes); - uint32_t GetQuota(ResourceType type); - void ValidatePersistentSettings(); // Backend interface. net::CacheType GetCacheType() const override; @@ -117,10 +114,6 @@ base::WeakPtrFactory<CobaltBackendImpl> weak_factory_; std::map<ResourceType, SimpleBackendImpl*> simple_backend_map_; - - // Json PrefStore used for persistent settings. - std::unique_ptr<cobalt::persistent_storage::PersistentSettings> - persistent_settings_; }; } // namespace disk_cache
diff --git a/net/disk_cache/cobalt/resource_type.cc b/net/disk_cache/cobalt/resource_type.cc new file mode 100644 index 0000000..6a3f2f3 --- /dev/null +++ b/net/disk_cache/cobalt/resource_type.cc
@@ -0,0 +1,175 @@ +// Copyright 2023 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 "net/disk_cache/cobalt/resource_type.h" + +#include "base/logging.h" + +namespace disk_cache { +namespace defaults { + +std::string GetSubdirectory(ResourceType resource_type) { + switch (resource_type) { + case kOther: + return "other"; + case kHTML: + return "html"; + case kCSS: + return "css"; + case kImage: + return "image"; + case kFont: + return "font"; + case kSplashScreen: + return "splash"; + case kUncompiledScript: + return "uncompiled_js"; + case kCompiledScript: + return "compiled_js"; + case kCacheApi: + return "cache_api"; + case kServiceWorkerScript: + return "service_worker_js"; + default: + NOTREACHED() << "Unexpected resource_type " << resource_type; + break; + } + return ""; +} + +uint32_t GetQuota(ResourceType resource_type) { + switch (resource_type) { + case kOther: + return 3 * 1024 * 1024; + case kHTML: + return 2 * 1024 * 1024; + case kCSS: + return 1 * 1024 * 1024; + case kImage: + return 0; + case kFont: + return 3 * 1024 * 1024; + case kSplashScreen: + return 2 * 1024 * 1024; + case kUncompiledScript: + return 3 * 1024 * 1024; + case kCompiledScript: + return 3 * 1024 * 1024; + case kCacheApi: + return 3 * 1024 * 1024; + case kServiceWorkerScript: + return 3 * 1024 * 1024; + default: + NOTREACHED() << "Unexpected resource_type " << resource_type; + break; + } + return 0; +} + +} // namespace defaults + +namespace settings { + +namespace { + +starboard::atomic_int32_t other_quota = starboard::atomic_int32_t(defaults::GetQuota(kOther)); +starboard::atomic_int32_t html_quota = starboard::atomic_int32_t(defaults::GetQuota(kHTML)); +starboard::atomic_int32_t css_quota = starboard::atomic_int32_t(defaults::GetQuota(kCSS)); +starboard::atomic_int32_t image_quota = starboard::atomic_int32_t(defaults::GetQuota(kImage)); +starboard::atomic_int32_t font_quota = starboard::atomic_int32_t(defaults::GetQuota(kFont)); +starboard::atomic_int32_t splash_screen_quota = starboard::atomic_int32_t(defaults::GetQuota(kSplashScreen)); +starboard::atomic_int32_t uncompiled_script_quota = starboard::atomic_int32_t(defaults::GetQuota(kUncompiledScript)); +starboard::atomic_int32_t compiled_script_quota = starboard::atomic_int32_t(defaults::GetQuota(kCompiledScript)); +starboard::atomic_int32_t cache_api_quota = starboard::atomic_int32_t(defaults::GetQuota(kCacheApi)); +starboard::atomic_int32_t service_worker_script_quota = starboard::atomic_int32_t(defaults::GetQuota(kServiceWorkerScript)); +starboard::atomic_bool cache_enabled = starboard::atomic_bool(true); + +} // namespace + +uint32_t GetQuota(ResourceType resource_type) { + switch (resource_type) { + case kOther: + return other_quota.load(); + case kHTML: + return html_quota.load(); + case kCSS: + return css_quota.load(); + case kImage: + return image_quota.load(); + case kFont: + return font_quota.load(); + case kSplashScreen: + return splash_screen_quota.load(); + case kUncompiledScript: + return uncompiled_script_quota.load(); + case kCompiledScript: + return compiled_script_quota.load(); + case kCacheApi: + return cache_api_quota.load(); + case kServiceWorkerScript: + return service_worker_script_quota.load(); + default: + NOTREACHED() << "Unexpected resource_type " << resource_type; + } + return 0; +} + +void SetQuota(ResourceType resource_type, uint32_t value) { + switch (resource_type) { + case kOther: + other_quota.store(static_cast<int32_t>(value)); + break; + case kHTML: + html_quota.store(static_cast<int32_t>(value)); + break; + case kCSS: + css_quota.store(static_cast<int32_t>(value)); + break; + case kImage: + image_quota.store(static_cast<int32_t>(value)); + break; + case kFont: + font_quota.store(static_cast<int32_t>(value)); + break; + case kSplashScreen: + splash_screen_quota.store(static_cast<int32_t>(value)); + break; + case kUncompiledScript: + uncompiled_script_quota.store(static_cast<int32_t>(value)); + break; + case kCompiledScript: + compiled_script_quota.store(static_cast<int32_t>(value)); + break; + case kCacheApi: + cache_api_quota.store(static_cast<int32_t>(value)); + break; + case kServiceWorkerScript: + service_worker_script_quota.store(static_cast<int32_t>(value)); + break; + default: + NOTREACHED() << "Unexpected resource_type " << resource_type; + break; + } +} + +bool GetCacheEnabled() { + return cache_enabled.load(); +} + +void SetCacheEnabled(bool value) { + cache_enabled.store(value); +} + +} // namespace settings +} // namespace disk_cache
diff --git a/net/disk_cache/cobalt/resource_type.h b/net/disk_cache/cobalt/resource_type.h index f291033..f401b65 100644 --- a/net/disk_cache/cobalt/resource_type.h +++ b/net/disk_cache/cobalt/resource_type.h
@@ -17,6 +17,9 @@ #include <string> +#include "starboard/common/atomic.h" +#include "starboard/types.h" + namespace disk_cache { /* Note: If adding a new resource type, add corresponding metadata below. */ @@ -34,22 +37,21 @@ kTypeCount = 10, }; -struct ResourceTypeMetadata { - std::string directory; - uint32_t max_size_bytes; -}; +namespace defaults { -static uint32_t kInitialBytes = static_cast<uint32_t> (3 * 1024 * 1024); -// These values are updated on start up in application.cc, using the -// persisted values saved in settings.json. -static ResourceTypeMetadata kTypeMetadata[] = { - {"other", kInitialBytes}, {"html", 2 * 1024 * 1024}, - {"css", 1 * 1024 * 1024}, {"image", 0}, - {"font", kInitialBytes}, {"splash", 2 * 1024 * 1024}, - {"uncompiled_js", kInitialBytes}, {"compiled_js", kInitialBytes}, - {"cache_api", kInitialBytes}, {"service_worker_js", kInitialBytes}, -}; +std::string GetSubdirectory(ResourceType resource_type); +uint32_t GetQuota(ResourceType resource_type); +} // namespace defaults + +namespace settings { + +uint32_t GetQuota(ResourceType resource_type); +void SetQuota(ResourceType resource_type, uint32_t value); +bool GetCacheEnabled(); +void SetCacheEnabled(bool value); + +} // namespace settings } // namespace disk_cache #endif // NET_DISK_CACHE_COBALT_RESOURCE_TYPE_H_
diff --git a/net/disk_cache/disk_cache.cc b/net/disk_cache/disk_cache.cc index b8abfac..81457ca 100644 --- a/net/disk_cache/disk_cache.cc +++ b/net/disk_cache/disk_cache.cc
@@ -269,7 +269,8 @@ #if defined(OS_ANDROID) nullptr, #endif - net_log, backend, base::OnceClosure(), + net_log, backend, + base::OnceClosure(), std::move(callback)); } @@ -286,7 +287,8 @@ base::android::ApplicationStatusListener* app_status_listener) { return CreateCacheBackendImpl(type, backend_type, path, max_bytes, force, std::move(app_status_listener), net_log, - backend, base::OnceClosure(), + backend, + base::OnceClosure(), std::move(callback)); } #endif
diff --git a/net/test/android/javatests/AndroidManifest.xml b/net/test/android/javatests/AndroidManifest.xml index af051a4..9bccb8e 100644 --- a/net/test/android/javatests/AndroidManifest.xml +++ b/net/test/android/javatests/AndroidManifest.xml
@@ -8,7 +8,7 @@ xmlns:tools="http://schemas.android.com/tools" package="org.chromium.net.test.support"> - <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" /> + <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"/>
diff --git a/starboard/BUILD.gn b/starboard/BUILD.gn index 3ab468c..aab526b 100644 --- a/starboard/BUILD.gn +++ b/starboard/BUILD.gn
@@ -51,8 +51,13 @@ if (sb_filter_based_player) { deps += [ "//starboard/shared/starboard/player/filter/testing:player_filter_tests($starboard_toolchain)", - "//starboard/shared/starboard/player/filter/tools:audio_dmp_player($starboard_toolchain)", + "//starboard/shared/starboard/player/filter/testing:player_filter_tests_install($starboard_toolchain)", ] + + # TODO: b/296715826 - Fix build error for windows modular builds. + if (!(sb_is_modular && is_host_win)) { + deps += [ "//starboard/shared/starboard/player/filter/tools:audio_dmp_player($starboard_toolchain)" ] + } } if (sb_enable_benchmark) { @@ -71,6 +76,7 @@ data_deps = [ "//starboard/loader_app($starboard_toolchain)", "//third_party/crashpad/handler:crashpad_handler(//$starboard_path/toolchain:native_target)", + "//third_party/crashpad/tools:crashpad_database_util(//$starboard_path/toolchain:native_target)", ] } } @@ -109,7 +115,7 @@ } } else { public_deps += [ - ":starboard_platform_group($starboard_toolchain)", + ":starboard_platform_group_static($starboard_toolchain)", "//starboard/common", ] @@ -184,6 +190,10 @@ starboard_platform_target("starboard_platform_group") { } + starboard_platform_target("starboard_platform_group_static") { + target_type = "group" + } + if (platform_tests_path == "") { # If 'starboard_platform_tests' is not defined by the platform, then an # empty 'starboard_platform_tests' target is defined.
diff --git a/starboard/android/apk/apk_sources.gni b/starboard/android/apk/apk_sources.gni index d1f668c..4564202 100644 --- a/starboard/android/apk/apk_sources.gni +++ b/starboard/android/apk/apk_sources.gni
@@ -47,6 +47,7 @@ "//starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/media/Log.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java", + "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecCapabilitiesLogger.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java", "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaImage.java",
diff --git a/starboard/android/apk/app/build.gradle b/starboard/android/apk/app/build.gradle index b64b5d3..a50b50b 100644 --- a/starboard/android/apk/app/build.gradle +++ b/starboard/android/apk/app/build.gradle
@@ -75,7 +75,7 @@ defaultConfig { applicationId "dev.cobalt.coat" minSdkVersion 24 - targetSdkVersion 31 + targetSdkVersion 34 versionCode 1 versionName "${buildId}" manifestPlaceholders = [
diff --git a/starboard/android/apk/app/src/app/AndroidManifest.xml b/starboard/android/apk/app/src/app/AndroidManifest.xml index 6b2602d..0c23b47 100644 --- a/starboard/android/apk/app/src/app/AndroidManifest.xml +++ b/starboard/android/apk/app/src/app/AndroidManifest.xml
@@ -31,6 +31,7 @@ <uses-permission android:name="android.permission.RECORD_AUDIO"/> <!-- This is needed when targeting API 28+ to use foreground services --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <!-- https://iabtechlab.com/OTT-IFA, AdvertisingIdClient.Info.getId() --> <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java index d942102..129b3c1 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -32,7 +32,7 @@ import androidx.annotation.CallSuper; import com.google.androidgamesdk.GameActivity; import dev.cobalt.media.AudioOutputManager; -import dev.cobalt.media.MediaCodecUtil; +import dev.cobalt.media.MediaCodecCapabilitiesLogger; import dev.cobalt.media.VideoSurfaceView; import dev.cobalt.util.DisplayUtil; import dev.cobalt.util.Log; @@ -146,7 +146,7 @@ protected void onStart() { if (!isReleaseBuild()) { getStarboardBridge().getAudioOutputManager().dumpAllOutputDevices(); - MediaCodecUtil.dumpAllDecoders(); + MediaCodecCapabilitiesLogger.dumpAllDecoders(); } if (forceCreateNewVideoSurfaceView) { Log.w(TAG, "Force to create a new video surface."); @@ -159,8 +159,6 @@ getStarboardBridge().onActivityStart(this, keyboardEditor); super.onStart(); - - nativeInitializeMediaCapabilitiesInBackground(); } @Override @@ -396,6 +394,4 @@ public long getAppStartTimestamp() { return timeInNanoseconds; } - - private static native void nativeInitializeMediaCapabilitiesInBackground(); }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java index e24faf6..8f7742b 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
@@ -22,7 +22,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; -import android.os.Build; +import android.content.pm.ServiceInfo; import android.os.Build.VERSION; import android.os.IBinder; import android.os.RemoteException; @@ -37,17 +37,19 @@ private static final int NOTIFICATION_ID = 193266736; // CL number for uniqueness. private static final String NOTIFICATION_CHANNEL_ID = "dev.cobalt.coat media playback service"; private static final String NOTIFICATION_CHANNEL_NAME = "Media playback service"; + private boolean channelCreated = true; + private NotificationManager notificationManager = null; @Override public void onCreate() { - if (getStarboardBridge() == null) { - Log.e(TAG, "StarboardBridge already destroyed."); - return; - } Log.i(TAG, "Creating a Media playback foreground service."); super.onCreate(); - getStarboardBridge().onServiceStart(this); - createNotificationChannel(); + if (getStarboardBridge() != null) { + getStarboardBridge().onServiceStart(this); + } + this.notificationManager = + (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); + this.channelCreated = createNotificationChannel(); } @Override @@ -66,64 +68,79 @@ @Override public void onDestroy() { - if (getStarboardBridge() == null) { - Log.e(TAG, "StarboardBridge already destroyed."); - return; - } Log.i(TAG, "Destroying the Media playback service."); - getStarboardBridge().onServiceDestroy(this); + + if (VERSION.SDK_INT >= 26 && this.channelCreated) { + this.notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID); + } + + if (getStarboardBridge() != null) { + getStarboardBridge().onServiceDestroy(this); + } super.onDestroy(); } public void startService() { - try { - startForeground(NOTIFICATION_ID, buildNotification()); - } catch (IllegalStateException e) { - Log.e(TAG, "Failed to start Foreground Service", e); + if (this.channelCreated) { + try { + if (VERSION.SDK_INT >= 29) { + startForeground( + NOTIFICATION_ID, buildNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST); + } else { + startForeground(NOTIFICATION_ID, buildNotification()); + } + } catch (IllegalStateException e) { + Log.e(TAG, "Failed to start Foreground Service", e); + } } } public void stopService() { // Let service itself handle notification deletion. - stopForeground(true); + if (this.channelCreated) { + stopForeground(true); + } stopSelf(); } - private void createNotificationChannel() { - if (Build.VERSION.SDK_INT >= 26) { + private boolean createNotificationChannel() { + if (VERSION.SDK_INT >= 26) { try { createNotificationChannelInternalV26(); } catch (RemoteException e) { Log.e(TAG, "Failed to create Notification Channel.", e); + return false; } } + return true; } @RequiresApi(26) private void createNotificationChannelInternalV26() throws RemoteException { - NotificationManager notificationManager = - (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); NotificationChannel channel = new NotificationChannel( NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, notificationManager.IMPORTANCE_DEFAULT); channel.setDescription("Channel for showing persistent notification"); - notificationManager.createNotificationChannel(channel); + this.notificationManager.createNotificationChannel(channel); } Notification buildNotification() { + String channelId = ""; + if (VERSION.SDK_INT >= 26) { + // Channel with ID=NOTIFICATION_CHANNEL_ID is created for version >= 26 + channelId = NOTIFICATION_CHANNEL_ID; + } + NotificationCompat.Builder builder = - new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) + new NotificationCompat.Builder(this, channelId) .setShowWhen(false) .setPriority(NotificationCompat.PRIORITY_MIN) .setSmallIcon(android.R.drawable.stat_sys_warning) .setContentTitle("Media playback service") .setContentText("Media playback service is running"); - if (VERSION.SDK_INT >= 26) { - builder.setChannelId(NOTIFICATION_CHANNEL_ID); - } return builder.build(); }
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 61f4bca..5d6e8f9 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
@@ -96,7 +96,7 @@ private final Holder<Activity> activityHolder; private final Holder<Service> serviceHolder; private final String[] args; - private final String startDeepLink; + private String startDeepLink; private final Runnable stopRequester = new Runnable() { @Override @@ -105,7 +105,8 @@ } }; - private volatile boolean starboardStopped = false; + private volatile boolean starboardApplicationStopped = false; + private volatile boolean starboardApplicationReady = false; private final HashMap<String, CobaltService.Factory> cobaltServiceFactories = new HashMap<>(); private final HashMap<String, CobaltService> cobaltServices = new HashMap<>(); @@ -166,7 +167,7 @@ } protected void onActivityDestroy(Activity activity) { - if (starboardStopped) { + if (starboardApplicationStopped) { // We can't restart the starboard app, so kill the process for a clean start next time. Log.i(TAG, "Activity destroyed after shutdown; killing app."); System.exit(0); @@ -265,7 +266,7 @@ @SuppressWarnings("unused") @UsedByNative protected void afterStopped() { - starboardStopped = true; + starboardApplicationStopped = true; ttsHelper.shutdown(); userAuthorizer.shutdown(); for (CobaltService service : cobaltServices.values()) { @@ -285,8 +286,21 @@ @SuppressWarnings("unused") @UsedByNative + protected void starboardApplicationStarted() { + starboardApplicationReady = true; + } + + @SuppressWarnings("unused") + @UsedByNative + protected void starboardApplicationStopping() { + starboardApplicationReady = false; + starboardApplicationStopped = true; + } + + @SuppressWarnings("unused") + @UsedByNative public void requestStop(int errorLevel) { - if (!starboardStopped) { + if (starboardApplicationReady) { Log.i(TAG, "Request to stop"); nativeStopApp(errorLevel); } @@ -305,7 +319,10 @@ } public boolean onSearchRequested() { - return nativeOnSearchRequested(); + if (starboardApplicationReady) { + return nativeOnSearchRequested(); + } + return false; } private native boolean nativeOnSearchRequested(); @@ -349,7 +366,13 @@ /** Sends an event to the web app to navigate to the given URL */ public void handleDeepLink(String url) { - nativeHandleDeepLink(url); + if (starboardApplicationReady) { + nativeHandleDeepLink(url); + } else { + // If this deep link event is received before the starboard application + // is ready, it replaces the start deep link. + startDeepLink = url; + } } private native void nativeHandleDeepLink(String url); @@ -760,6 +783,10 @@ return service; } + public CobaltService getOpenedCobaltService(String serviceName) { + return cobaltServices.get(serviceName); + } + @SuppressWarnings("unused") @UsedByNative void closeCobaltService(String serviceName) {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java index 9fb274f..0e02ebc 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -142,39 +142,6 @@ audioTrackBridgeList.remove(audioTrackBridge); } - /** Returns the maximum number of HDMI channels. */ - @SuppressWarnings("unused") - @UsedByNative - int getMaxChannels() { - // The aac audio decoder on this platform will switch its output from 5.1 - // to stereo right before providing the first output buffer when - // attempting to decode 5.1 input. Since this heavily violates invariants - // of the shared starboard player framework, disable 5.1 on this platform. - // It is expected that we will be able to resolve this issue with Xiaomi - // by Android P, so only do this workaround for SDK versions < 27. - if (android.os.Build.MODEL.equals("MIBOX3") && android.os.Build.VERSION.SDK_INT < 27) { - return 2; - } - - AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); - int maxChannels = 2; - for (AudioDeviceInfo info : deviceInfos) { - int type = info.getType(); - if (type == AudioDeviceInfo.TYPE_HDMI || type == AudioDeviceInfo.TYPE_HDMI_ARC) { - int[] channelCounts = info.getChannelCounts(); - if (channelCounts.length == 0) { - // An empty array indicates that the device supports arbitrary channel masks. - return 8; - } - for (int count : channelCounts) { - maxChannels = Math.max(maxChannels, count); - } - } - } - return maxChannels; - } - /** Stores info from AudioDeviceInfo to be passed to the native app. */ @SuppressWarnings("unused") @UsedByNative @@ -208,8 +175,19 @@ outDeviceInfo.type = deviceInfos[index].getType(); outDeviceInfo.channels = 2; + // The aac audio decoder on this platform will switch its output from 5.1 + // to stereo right before providing the first output buffer when + // attempting to decode 5.1 input. Since this heavily violates invariants + // of the shared starboard player framework, disable 5.1 on this platform. + // It is expected that we will be able to resolve this issue with Xiaomi + // by Android P, so only do this workaround for SDK versions < 27. + if (android.os.Build.MODEL.equals("MIBOX3") && android.os.Build.VERSION.SDK_INT < 27) { + return true; + } + int[] channelCounts = deviceInfos[index].getChannelCounts(); if (channelCounts.length == 0) { + // An empty array indicates that the device supports arbitrary channel masks. outDeviceInfo.channels = 8; } else { for (int count : channelCounts) {
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 8db14f6..8164add 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
@@ -279,15 +279,15 @@ } private boolean formatHasCropValues() { - if (!mFormatHasCropValues.isPresent()) { + if (!mFormatHasCropValues.isPresent() && mFormat != null) { boolean hasCropValues = mFormat.containsKey(KEY_CROP_RIGHT) && mFormat.containsKey(KEY_CROP_LEFT) && mFormat.containsKey(KEY_CROP_BOTTOM) && mFormat.containsKey(KEY_CROP_TOP); - mFormatHasCropValues = Optional.of(hasCropValues); + mFormatHasCropValues = Optional.ofNullable(hasCropValues); } - return mFormatHasCropValues.get(); + return mFormatHasCropValues.orElse(false); } @SuppressWarnings("unused") @@ -299,13 +299,17 @@ @SuppressWarnings("unused") @UsedByNative private int textureWidth() { - return mFormat.getInteger(MediaFormat.KEY_WIDTH); + return (mFormat != null && mFormat.containsKey(MediaFormat.KEY_WIDTH)) + ? mFormat.getInteger(MediaFormat.KEY_WIDTH) + : 0; } @SuppressWarnings("unused") @UsedByNative private int textureHeight() { - return mFormat.getInteger(MediaFormat.KEY_HEIGHT); + return (mFormat != null && mFormat.containsKey(MediaFormat.KEY_HEIGHT)) + ? mFormat.getInteger(MediaFormat.KEY_HEIGHT) + : 0; } @SuppressWarnings("unused")
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecCapabilitiesLogger.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecCapabilitiesLogger.java new file mode 100644 index 0000000..f1c640d --- /dev/null +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecCapabilitiesLogger.java
@@ -0,0 +1,285 @@ +// Copyright 2017 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. + +package dev.cobalt.media; + +import static dev.cobalt.media.Log.TAG; + +import android.media.MediaCodecInfo; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecInfo.VideoCapabilities; +import android.media.MediaCodecList; +import android.os.Build; +import dev.cobalt.util.Log; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +/** Utility class to log MediaCodec capabilities. */ +public class MediaCodecCapabilitiesLogger { + /** Utility class to save the maximum supported resolution and frame rate of a decoder. */ + static class ResolutionAndFrameRate { + public ResolutionAndFrameRate(Integer width, Integer height, Double frameRate) { + this.width = width; + this.height = height; + this.frameRate = frameRate; + } + + public boolean isCovered(Integer width, Integer height, Double frameRate) { + return this.width >= width && this.height >= height && this.frameRate >= frameRate; + } + + public Integer width = -1; + public Integer height = -1; + public Double frameRate = -1.0; + } + + /** Returns a string detailing SDR and HDR capabilities of a decoder. */ + private static String getSupportedResolutionsAndFrameRates( + VideoCapabilities videoCapabilities, boolean isHdrCapable) { + ArrayList<ArrayList<Integer>> resolutionList = + new ArrayList<>( + Arrays.asList( + new ArrayList<>(Arrays.asList(7680, 4320)), + new ArrayList<>(Arrays.asList(3840, 2160)), + new ArrayList<>(Arrays.asList(2560, 1440)), + new ArrayList<>(Arrays.asList(1920, 1080)), + new ArrayList<>(Arrays.asList(1280, 720)))); + ArrayList<Double> frameRateList = + new ArrayList<>(Arrays.asList(60.0, 59.997, 50.0, 48.0, 30.0, 29.997, 25.0, 24.0, 23.997)); + ArrayList<ResolutionAndFrameRate> supportedResolutionsAndFrameRates = new ArrayList<>(); + for (Double frameRate : frameRateList) { + for (ArrayList<Integer> resolution : resolutionList) { + boolean isResolutionAndFrameRateCovered = false; + for (ResolutionAndFrameRate resolutionAndFrameRate : supportedResolutionsAndFrameRates) { + if (resolutionAndFrameRate.isCovered(resolution.get(0), resolution.get(1), frameRate)) { + isResolutionAndFrameRateCovered = true; + break; + } + } + if (videoCapabilities.areSizeAndRateSupported( + resolution.get(0), resolution.get(1), frameRate)) { + if (!isResolutionAndFrameRateCovered) { + supportedResolutionsAndFrameRates.add( + new ResolutionAndFrameRate(resolution.get(0), resolution.get(1), frameRate)); + } + continue; + } + if (isResolutionAndFrameRateCovered) { + // This configuration should be covered by a supported configuration, return long form. + return getLongFormSupportedResolutionsAndFrameRates( + resolutionList, frameRateList, videoCapabilities, isHdrCapable); + } + } + } + return convertResolutionAndFrameRatesToString(supportedResolutionsAndFrameRates, isHdrCapable); + } + + /** + * Like getSupportedResolutionsAndFrameRates(), but returns the full information for each frame + * rate and resolution combination. + */ + private static String getLongFormSupportedResolutionsAndFrameRates( + ArrayList<ArrayList<Integer>> resolutionList, + ArrayList<Double> frameRateList, + VideoCapabilities videoCapabilities, + boolean isHdrCapable) { + ArrayList<ResolutionAndFrameRate> supported = new ArrayList<>(); + for (Double frameRate : frameRateList) { + for (ArrayList<Integer> resolution : resolutionList) { + if (videoCapabilities.areSizeAndRateSupported( + resolution.get(0), resolution.get(1), frameRate)) { + supported.add( + new ResolutionAndFrameRate(resolution.get(0), resolution.get(1), frameRate)); + } + } + } + return convertResolutionAndFrameRatesToString(supported, isHdrCapable); + } + + /** Convert a list of ResolutionAndFrameRate to a human readable string. */ + private static String convertResolutionAndFrameRatesToString( + ArrayList<ResolutionAndFrameRate> supported, boolean isHdrCapable) { + if (supported.isEmpty()) { + return "None."; + } + String frameRateAndResolutionString = ""; + for (ResolutionAndFrameRate resolutionAndFrameRate : supported) { + frameRateAndResolutionString += + String.format( + Locale.US, + "[%d x %d, %.3f fps], ", + resolutionAndFrameRate.width, + resolutionAndFrameRate.height, + resolutionAndFrameRate.frameRate); + } + frameRateAndResolutionString += isHdrCapable ? "hdr/sdr" : "sdr"; + return frameRateAndResolutionString; + } + + private interface CodecFeatureSupported { + boolean isSupported(String name, CodecCapabilities codecCapabilities); + } + + static TreeMap<String, CodecFeatureSupported> featureMap; + + private static void ensurefeatureMapInitialized() { + if (featureMap != null) { + return; + } + featureMap = new TreeMap<>(); + featureMap.put( + "AdaptivePlayback", + (name, codecCapabilities) -> { + return codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback); + }); + if (Build.VERSION.SDK_INT >= 29) { + featureMap.put( + "FrameParsing", + (name, codecCapabilities) -> { + return codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_FrameParsing); + }); + } + if (Build.VERSION.SDK_INT >= 30) { + featureMap.put( + "LowLatency", + (name, codecCapabilities) -> { + return codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency); + }); + } + if (Build.VERSION.SDK_INT >= 29) { + featureMap.put( + "MultipleFrames", + (name, codecCapabilities) -> { + return codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_MultipleFrames); + }); + } + if (Build.VERSION.SDK_INT >= 26) { + featureMap.put( + "PartialFrame", + (name, codecCapabilities) -> { + return codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_PartialFrame); + }); + } + featureMap.put( + "SecurePlayback", + (name, codecCapabilities) -> { + return codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); + }); + featureMap.put( + "TunneledPlayback", + (name, codecCapabilities) -> { + return codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback); + }); + } + + private static String getAllFeatureNames() { + ensurefeatureMapInitialized(); + return featureMap.keySet().toString(); + } + + private static String getSupportedFeaturesAsString( + String name, CodecCapabilities codecCapabilities) { + StringBuilder featuresAsString = new StringBuilder(); + + ensurefeatureMapInitialized(); + for (Map.Entry<String, CodecFeatureSupported> entry : featureMap.entrySet()) { + if (entry.getValue().isSupported(name, codecCapabilities)) { + if (featuresAsString.length() > 0) { + featuresAsString.append(", "); + } + featuresAsString.append(entry.getKey()); + } + } + return featuresAsString.toString(); + } + + /** + * Debug utility function that can be locally added to dump information about all decoders on a + * particular system. + */ + public static void dumpAllDecoders() { + StringBuilder decoderDumpString = new StringBuilder(); + for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) { + if (info.isEncoder()) { + continue; + } + for (String supportedType : info.getSupportedTypes()) { + String name = info.getName(); + decoderDumpString.append( + String.format( + Locale.US, + "name: %s (%s, %s):", + name, + supportedType, + MediaCodecUtil.isCodecDenyListed(name) ? "denylisted" : "not denylisted")); + CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType); + VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); + String resultName = + (codecCapabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback) + && !name.endsWith(MediaCodecUtil.getSecureDecoderSuffix())) + ? (name + MediaCodecUtil.getSecureDecoderSuffix()) + : name; + boolean isHdrCapable = + MediaCodecUtil.isHdrCapableVideoDecoder( + codecCapabilities.getMimeType(), codecCapabilities); + if (videoCapabilities != null) { + String frameRateAndResolutionString = + getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable); + decoderDumpString.append( + String.format( + Locale.US, + "\n\t" + + "widths: %s, " + + "heights: %s, " + + "bitrates: %s, " + + "framerates: %s, " + + "supported sizes and framerates: %s", + videoCapabilities.getSupportedWidths().toString(), + videoCapabilities.getSupportedHeights().toString(), + videoCapabilities.getBitrateRange().toString(), + videoCapabilities.getSupportedFrameRates().toString(), + frameRateAndResolutionString)); + } + String featuresAsString = getSupportedFeaturesAsString(name, codecCapabilities); + if (featuresAsString.isEmpty()) { + decoderDumpString.append(" No extra features supported"); + } else { + decoderDumpString.append("\n\tsupported features: "); + decoderDumpString.append(featuresAsString); + } + decoderDumpString.append("\n"); + } + } + Log.v( + TAG, + "\n" + + "==================================================\n" + + "Full list of decoder features: " + + getAllFeatureNames() + + "\nUnsupported features for each codec are not listed\n" + + decoderDumpString.toString() + + "=================================================="); + } +}
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 9afa2bc..50610e0 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
@@ -28,7 +28,6 @@ import dev.cobalt.util.Log; import dev.cobalt.util.UsedByNative; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -442,7 +441,7 @@ // Filter blacklisted video decoders. String name = codecInfo.getName(); - if (!isVp9AllowListed && videoCodecDenyList.contains(name)) { + if (!isVp9AllowListed && isCodecDenyListed(name)) { Log.v(TAG, "Rejecting %s, reason: codec is on deny list", name); continue; } @@ -451,7 +450,7 @@ // denylisted. String nameWithoutSecureSuffix = name.substring(0, name.length() - SECURE_DECODER_SUFFIX.length()); - if (!isVp9AllowListed && videoCodecDenyList.contains(nameWithoutSecureSuffix)) { + if (!isVp9AllowListed && isCodecDenyListed(nameWithoutSecureSuffix)) { String format = "Rejecting %s, reason: offpsec denylisted secure decoder"; Log.v(TAG, format, name); continue; @@ -466,6 +465,16 @@ return codecCapabilityInfos.toArray(array); } + /** Returns whether the codec is denylisted. */ + public static boolean isCodecDenyListed(String codecName) { + return videoCodecDenyList.contains(codecName); + } + + /** Simply returns SECURE_DECODER_SUFFIX to allow access to it. */ + public static String getSecureDecoderSuffix() { + return SECURE_DECODER_SUFFIX; + } + /** Determine whether codecCapabilities is capable of playing HDR. */ public static boolean isHdrCapableVideoDecoder( String mimeType, CodecCapabilities codecCapabilities) { @@ -570,7 +579,7 @@ for (VideoDecoderCache.CachedDecoder decoder : VideoDecoderCache.getCachedDecoders(mimeType, decoderCacheTtlMs)) { String name = decoder.info.getName(); - if (!isVp9AllowListed && videoCodecDenyList.contains(name)) { + if (!isVp9AllowListed && isCodecDenyListed(name)) { Log.v(TAG, "Rejecting " + name + ", reason: codec is on deny list"); continue; } @@ -596,7 +605,7 @@ // denylisted. String nameWithoutSecureSuffix = name.substring(0, name.length() - SECURE_DECODER_SUFFIX.length()); - if (!isVp9AllowListed && videoCodecDenyList.contains(nameWithoutSecureSuffix)) { + if (!isVp9AllowListed && isCodecDenyListed(nameWithoutSecureSuffix)) { Log.v(TAG, "Rejecting " + name + ", reason: denylisted secure decoder"); } } @@ -738,8 +747,7 @@ * "" otherwise. */ @UsedByNative - public static String findAudioDecoder( - String mimeType, int bitrate, boolean mustSupportTunnelMode) { + public static String findAudioDecoder(String mimeType, int bitrate) { // Note: MediaCodecList is sorted by the framework such that the best decoders come first. for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) { if (info.isEncoder()) { @@ -757,202 +765,9 @@ if (!bitrateRange.contains(bitrate)) { continue; } - if (mustSupportTunnelMode - && !codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback)) { - continue; - } - // TODO: Determine if we can safely check if an audio codec requires the tunneled playback - // feature. i.e., reject when |mustSupportTunnelMode| == false - // and codecCapabilities.isFeatureRequired(CodecCapabilities.FEATURE_TunneledPlayback) == - // true. return name; } } return ""; } - - /** Utility class to save the maximum supported resolution and frame rate of a decoder. */ - static class ResolutionAndFrameRate { - public ResolutionAndFrameRate(Integer width, Integer height, Double frameRate) { - this.width = width; - this.height = height; - this.frameRate = frameRate; - } - - public boolean isCovered(Integer width, Integer height, Double frameRate) { - return this.width >= width && this.height >= height && this.frameRate >= frameRate; - } - - public Integer width = -1; - public Integer height = -1; - public Double frameRate = -1.0; - } - - /** Returns a string detailing SDR and HDR capabilities of a decoder. */ - public static String getSupportedResolutionsAndFrameRates( - VideoCapabilities videoCapabilities, boolean isHdrCapable) { - ArrayList<ArrayList<Integer>> resolutionList = - new ArrayList<>( - Arrays.asList( - new ArrayList<>(Arrays.asList(7680, 4320)), - new ArrayList<>(Arrays.asList(3840, 2160)), - new ArrayList<>(Arrays.asList(2560, 1440)), - new ArrayList<>(Arrays.asList(1920, 1080)), - new ArrayList<>(Arrays.asList(1280, 720)))); - ArrayList<Double> frameRateList = - new ArrayList<>(Arrays.asList(60.0, 59.997, 50.0, 48.0, 30.0, 29.997, 25.0, 24.0, 23.997)); - ArrayList<ResolutionAndFrameRate> supportedResolutionsAndFrameRates = new ArrayList<>(); - for (Double frameRate : frameRateList) { - for (ArrayList<Integer> resolution : resolutionList) { - boolean isResolutionAndFrameRateCovered = false; - for (ResolutionAndFrameRate resolutionAndFrameRate : supportedResolutionsAndFrameRates) { - if (resolutionAndFrameRate.isCovered(resolution.get(0), resolution.get(1), frameRate)) { - isResolutionAndFrameRateCovered = true; - break; - } - } - if (videoCapabilities.areSizeAndRateSupported( - resolution.get(0), resolution.get(1), frameRate)) { - if (!isResolutionAndFrameRateCovered) { - supportedResolutionsAndFrameRates.add( - new ResolutionAndFrameRate(resolution.get(0), resolution.get(1), frameRate)); - } - continue; - } - if (isResolutionAndFrameRateCovered) { - // This configuration should be covered by a supported configuration, return long form. - return getLongFormSupportedResolutionsAndFrameRates( - resolutionList, frameRateList, videoCapabilities, isHdrCapable); - } - } - } - return convertResolutionAndFrameRatesToString(supportedResolutionsAndFrameRates, isHdrCapable); - } - - /** - * Like getSupportedResolutionsAndFrameRates(), but returns the full information for each frame - * rate and resolution combination. - */ - public static String getLongFormSupportedResolutionsAndFrameRates( - ArrayList<ArrayList<Integer>> resolutionList, - ArrayList<Double> frameRateList, - VideoCapabilities videoCapabilities, - boolean isHdrCapable) { - ArrayList<ResolutionAndFrameRate> supported = new ArrayList<>(); - for (Double frameRate : frameRateList) { - for (ArrayList<Integer> resolution : resolutionList) { - if (videoCapabilities.areSizeAndRateSupported( - resolution.get(0), resolution.get(1), frameRate)) { - supported.add( - new ResolutionAndFrameRate(resolution.get(0), resolution.get(1), frameRate)); - } - } - } - return convertResolutionAndFrameRatesToString(supported, isHdrCapable); - } - - public static String convertResolutionAndFrameRatesToString( - ArrayList<ResolutionAndFrameRate> supported, boolean isHdrCapable) { - if (supported.isEmpty()) { - return "None. "; - } - String frameRateAndResolutionString = ""; - for (ResolutionAndFrameRate resolutionAndFrameRate : supported) { - frameRateAndResolutionString += - String.format( - Locale.US, - "[%d x %d, %.3f fps], ", - resolutionAndFrameRate.width, - resolutionAndFrameRate.height, - resolutionAndFrameRate.frameRate); - } - frameRateAndResolutionString += isHdrCapable ? "hdr/sdr, " : "sdr, "; - return frameRateAndResolutionString; - } - - /** - * Debug utility function that can be locally added to dump information about all decoders on a - * particular system. - */ - public static void dumpAllDecoders() { - String decoderDumpString = ""; - for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) { - if (info.isEncoder()) { - continue; - } - for (String supportedType : info.getSupportedTypes()) { - String name = info.getName(); - decoderDumpString += - String.format( - Locale.US, - "name: %s (%s, %s): ", - name, - supportedType, - videoCodecDenyList.contains(name) ? "denylisted" : "not denylisted"); - CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType); - VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); - String resultName = - (codecCapabilities.isFeatureSupported( - MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback) - && !name.endsWith(SECURE_DECODER_SUFFIX)) - ? (name + SECURE_DECODER_SUFFIX) - : name; - boolean isHdrCapable = - isHdrCapableVideoDecoder(codecCapabilities.getMimeType(), codecCapabilities); - if (videoCapabilities != null) { - String frameRateAndResolutionString = - getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable); - decoderDumpString += - String.format( - Locale.US, - "\n\t\t" - + "widths: %s, " - + "heights: %s, " - + "bitrates: %s, " - + "framerates: %s, " - + "supported sizes and framerates: %s", - videoCapabilities.getSupportedWidths().toString(), - videoCapabilities.getSupportedHeights().toString(), - videoCapabilities.getBitrateRange().toString(), - videoCapabilities.getSupportedFrameRates().toString(), - frameRateAndResolutionString); - } - boolean isAdaptivePlaybackSupported = - codecCapabilities.isFeatureSupported( - MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback); - boolean isSecurePlaybackSupported = - codecCapabilities.isFeatureSupported( - MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); - boolean isTunneledPlaybackSupported = - codecCapabilities.isFeatureSupported( - MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback); - if (isAdaptivePlaybackSupported - || isSecurePlaybackSupported - || isTunneledPlaybackSupported) { - decoderDumpString += - String.format( - Locale.US, - "(%s%s%s", - isAdaptivePlaybackSupported ? "AdaptivePlayback, " : "", - isSecurePlaybackSupported ? "SecurePlayback, " : "", - isTunneledPlaybackSupported ? "TunneledPlayback, " : ""); - // Remove trailing space and comma - decoderDumpString = decoderDumpString.substring(0, decoderDumpString.length() - 2); - decoderDumpString += ")"; - } else { - decoderDumpString += " No extra features supported"; - } - decoderDumpString += "\n"; - } - } - Log.v( - TAG, - " \n" - + "==================================================\n" - + "Full list of decoder features: [AdaptivePlayback, SecurePlayback," - + " TunneledPlayback]\n" - + "Unsupported features for each codec are not listed\n" - + decoderDumpString - + "=================================================="); - } }
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 d4a12a5..190a160 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
@@ -373,6 +373,11 @@ mSchemeUUID = schemeUUID; mMediaDrm = new MediaDrm(schemeUUID); + // Get info of hdcp connection + if (Build.VERSION.SDK_INT >= 29) { + getConnectedHdcpLevelInfoV29(mMediaDrm); + } + mNativeMediaDrmBridge = nativeMediaDrmBridge; if (!isNativeMediaDrmBridgeValid()) { throw new IllegalArgumentException( @@ -738,6 +743,40 @@ mediaDrm.close(); } + @RequiresApi(29) + private void getConnectedHdcpLevelInfoV29(MediaDrm mediaDrm) { + int hdcpLevel = mediaDrm.getConnectedHdcpLevel(); + switch (hdcpLevel) { + case MediaDrm.HDCP_V1: + Log.i(TAG, "MediaDrm HDCP Level is HDCP_V1."); + break; + case MediaDrm.HDCP_V2: + Log.i(TAG, "MediaDrm HDCP Level is HDCP_V2."); + break; + case MediaDrm.HDCP_V2_1: + Log.i(TAG, "MediaDrm HDCP Level is HDCP_V2_1."); + break; + case MediaDrm.HDCP_V2_2: + Log.i(TAG, "MediaDrm HDCP Level is HDCP_V2_2."); + break; + case MediaDrm.HDCP_V2_3: + Log.i(TAG, "MediaDrm HDCP Level is HDCP_V2_3."); + break; + case MediaDrm.HDCP_NONE: + Log.i(TAG, "MediaDrm HDCP Level is HDCP_NONE."); + break; + case MediaDrm.HDCP_NO_DIGITAL_OUTPUT: + Log.i(TAG, "MediaDrm HDCP Level is HDCP_NO_DIGITAL_OUTPUT."); + break; + case MediaDrm.HDCP_LEVEL_UNKNOWN: + Log.i(TAG, "MediaDrm HDCP Level is HDCP_LEVEL_UNKNOWN."); + break; + default: + Log.i(TAG, String.format("Unknown MediaDrm HDCP level %d.", hdcpLevel)); + break; + } + } + private boolean isNativeMediaDrmBridgeValid() { return mNativeMediaDrmBridge != INVALID_NATIVE_MEDIA_DRM_BRIDGE; }
diff --git a/starboard/android/shared/android_main.cc b/starboard/android/shared/android_main.cc index dbdb8c0..7bccf50 100644 --- a/starboard/android/shared/android_main.cc +++ b/starboard/android/shared/android_main.cc
@@ -17,6 +17,7 @@ #include "starboard/android/shared/jni_env_ext.h" #include "starboard/android/shared/jni_utils.h" #include "starboard/android/shared/log_internal.h" +#include "starboard/common/atomic.h" #include "starboard/common/file.h" #include "starboard/common/semaphore.h" #include "starboard/common/string.h" @@ -48,7 +49,7 @@ // Safeguard to avoid sending AndroidCommands either when there is no instance // of the Starboard application, or after the run loop has exited and the // ALooper receiving the commands is no longer being polled. -bool g_app_running = false; +atomic_bool g_app_running; std::vector<std::string> GetArgs() { std::vector<std::string> args; @@ -235,7 +236,7 @@ // Mark the app running before signaling app created so there's no race to // allow sending the first AndroidCommand after onCreate() returns. - g_app_running = true; + g_app_running.store(true); // Signal GameActivity_onCreate() that it may proceed. g_app_created_semaphore->Put(); @@ -243,12 +244,12 @@ // Enter the Starboard run loop until stopped. int error_level = app.Run(std::move(command_line), GetStartDeepLink().c_str()); -#endif // SB_API_VERSION >= 15 // Mark the app not running before informing StarboardBridge that the app is // stopped so that we won't send any more AndroidCommands as a result of // shutting down the Activity. - g_app_running = false; + g_app_running.store(false); +#endif // SB_API_VERSION >= 15 // Our launcher.py looks for this to know when the app (test) is done. SB_LOG(INFO) << "***Application Stopped*** " << error_level; @@ -262,13 +263,13 @@ } void OnStart(GameActivity* activity) { - if (g_app_running) { + if (g_app_running.load()) { ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStart); } } void OnResume(GameActivity* activity) { - if (g_app_running) { + if (g_app_running.load()) { // Stop the MediaPlaybackService if activity state transits from background // to foreground. Note that the MediaPlaybackService may already have // been stopped before Cobalt's lifecycle state transits from Concealed @@ -279,7 +280,7 @@ } void OnPause(GameActivity* activity) { - if (g_app_running) { + if (g_app_running.load()) { // Start the MediaPlaybackService before activity state transits from // foreground to background. ApplicationAndroid::Get()->StartMediaPlaybackService(); @@ -288,28 +289,28 @@ } void OnStop(GameActivity* activity) { - if (g_app_running) { + if (g_app_running.load()) { ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStop); } } bool OnTouchEvent(GameActivity* activity, const GameActivityMotionEvent* event) { - if (g_app_running) { + if (g_app_running.load()) { return ApplicationAndroid::Get()->SendAndroidMotionEvent(event); } return false; } bool OnKey(GameActivity* activity, const GameActivityKeyEvent* event) { - if (g_app_running) { + if (g_app_running.load()) { return ApplicationAndroid::Get()->SendAndroidKeyEvent(event); } return false; } void OnWindowFocusChanged(GameActivity* activity, bool focused) { - if (g_app_running) { + if (g_app_running.load()) { ApplicationAndroid::Get()->SendAndroidCommand( focused ? AndroidCommand::kWindowFocusGained : AndroidCommand::kWindowFocusLost); @@ -317,14 +318,14 @@ } void OnNativeWindowCreated(GameActivity* activity, ANativeWindow* window) { - if (g_app_running) { + if (g_app_running.load()) { ApplicationAndroid::Get()->SendAndroidCommand( AndroidCommand::kNativeWindowCreated, window); } } void OnNativeWindowDestroyed(GameActivity* activity, ANativeWindow* window) { - if (g_app_running) { + if (g_app_running.load()) { ApplicationAndroid::Get()->SendAndroidCommand( AndroidCommand::kNativeWindowDestroyed); } @@ -380,7 +381,7 @@ Java_dev_cobalt_coat_VolumeStateReceiver_nativeVolumeChanged(JNIEnv* env, jobject jcaller, jint volumeDelta) { - if (g_app_running) { + if (g_app_running.load()) { SbKey key = volumeDelta > 0 ? SbKey::kSbKeyVolumeUp : SbKey::kSbKeyVolumeDown; ApplicationAndroid::Get()->SendKeyboardInject(key); @@ -390,7 +391,7 @@ extern "C" SB_EXPORT_PLATFORM void Java_dev_cobalt_coat_VolumeStateReceiver_nativeMuteChanged(JNIEnv* env, jobject jcaller) { - if (g_app_running) { + if (g_app_running.load()) { ApplicationAndroid::Get()->SendKeyboardInject(SbKey::kSbKeyVolumeMute); } } @@ -407,9 +408,13 @@ CommandLine command_line(GetArgs()); LogInit(command_line); +#if SB_IS(EVERGREEN_COMPATIBLE) + InstallCrashpadHandler(command_line); +#endif // SB_IS(EVERGREEN_COMPATIBLE) + // Mark the app running before signaling app created so there's no race to // allow sending the first AndroidCommand after onCreate() returns. - g_app_running = true; + g_app_running.store(true); // Signal GameActivity_onCreate() that it may proceed. g_app_created_semaphore->Put(); @@ -417,6 +422,12 @@ // Enter the Starboard run loop until stopped. int error_level = app.Run(std::move(command_line), GetStartDeepLink().c_str()); + + // Mark the app not running before informing StarboardBridge that the app is + // stopped so that we won't send any more AndroidCommands as a result of + // shutting down the Activity. + g_app_running.store(false); + return error_level; } #endif // SB_API_VERSION >= 15
diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc index 33b26ad..189517e 100644 --- a/starboard/android/shared/application_android.cc +++ b/starboard/android/shared/application_android.cc
@@ -140,9 +140,15 @@ jobject local_ref = env->CallStarboardObjectMethodOrAbort( "getResourceOverlay", "()Ldev/cobalt/coat/ResourceOverlay;"); resource_overlay_ = env->ConvertLocalRefToGlobalRef(local_ref); + + env->CallStarboardVoidMethodOrAbort("starboardApplicationStarted", "()V"); } ApplicationAndroid::~ApplicationAndroid() { + // Inform StarboardBridge that + JniEnvExt* env = JniEnvExt::Get(); + env->CallStarboardVoidMethodOrAbort("starboardApplicationStopping", "()V"); + // The application is exiting. // Release the global reference. if (resource_overlay_) { @@ -161,6 +167,7 @@ { // Signal for any potentially waiting window creation or destroy commands. ScopedLock lock(android_command_mutex_); + application_destroying_.store(true); android_command_condition_.Signal(); } } @@ -412,7 +419,7 @@ switch (type) { case AndroidCommand::kNativeWindowCreated: case AndroidCommand::kNativeWindowDestroyed: - while (native_window_ != data) { + while ((native_window_ != data) && !application_destroying_.load()) { android_command_condition_.Wait(); } break;
diff --git a/starboard/android/shared/application_android.h b/starboard/android/shared/application_android.h index e8917c1..bca3b1f 100644 --- a/starboard/android/shared/application_android.h +++ b/starboard/android/shared/application_android.h
@@ -25,6 +25,7 @@ #include "starboard/android/shared/input_events_generator.h" #include "starboard/android/shared/jni_env_ext.h" #include "starboard/atomic.h" +#include "starboard/common/atomic.h" #include "starboard/common/condition_variable.h" #include "starboard/common/mutex.h" #include "starboard/common/scoped_ptr.h" @@ -154,6 +155,9 @@ // already requested it be stopped. SbAtomic32 android_stop_count_ = 0; + // Set to true in the destructor to ensure other threads stop waiting. + atomic_bool application_destroying_; + // The last Activity lifecycle state command received. AndroidCommand::CommandType activity_state_;
diff --git a/starboard/android/shared/audio_decoder.cc b/starboard/android/shared/audio_decoder.cc index dfa32dd..60e7a44 100644 --- a/starboard/android/shared/audio_decoder.cc +++ b/starboard/android/shared/audio_decoder.cc
@@ -94,9 +94,10 @@ output_cb_ = output_cb; error_cb_ = error_cb; - - media_decoder_->Initialize( - std::bind(&AudioDecoder::ReportError, this, _1, _2)); + if (media_decoder_) { + media_decoder_->Initialize( + std::bind(&AudioDecoder::ReportError, this, _1, _2)); + } } void AudioDecoder::Decode(const InputBuffers& input_buffers, @@ -108,15 +109,20 @@ audio_frame_discarder_.OnInputBuffers(input_buffers); +#if STARBOARD_ANDROID_SHARED_AUDIO_DECODER_VERBOSE for (const auto& input_buffer : input_buffers) { VERBOSE_MEDIA_LOG() << "T1: timestamp " << input_buffer->timestamp(); } +#endif - media_decoder_->WriteInputBuffers(input_buffers); + if (media_decoder_) { + media_decoder_->WriteInputBuffers(input_buffers); + } ScopedLock lock(decoded_audios_mutex_); - if (media_decoder_->GetNumberOfPendingTasks() + decoded_audios_.size() <= - kMaxPendingWorkSize) { + if (media_decoder_ && + (media_decoder_->GetNumberOfPendingTasks() + decoded_audios_.size() <= + kMaxPendingWorkSize)) { Schedule(consumed_cb); } else { consumed_cb_ = consumed_cb; @@ -128,7 +134,9 @@ SB_DCHECK(output_cb_); SB_DCHECK(media_decoder_); - media_decoder_->WriteEndOfStream(); + if (media_decoder_) { + media_decoder_->WriteEndOfStream(); + } } scoped_refptr<AudioDecoder::DecodedAudio> AudioDecoder::Read(
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 b8139a2..1d156b9 100644 --- a/starboard/android/shared/audio_sink_min_required_frames_tester.cc +++ b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -113,6 +113,7 @@ frame_buffers[0] = silence_buffer.data(); // Set default values. + has_error_ = false; min_required_frames_ = task.default_required_frames; total_consumed_frames_ = 0; last_underrun_count_ = -1; @@ -148,6 +149,13 @@ min_required_frames_ = max_required_frames_; } + if (has_error_) { + SB_LOG(ERROR) << "There's an error while running the test. Fallback to " + "max required frames " + << max_required_frames_ << "."; + 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 " @@ -197,9 +205,10 @@ const std::string& error_message, void* context) { SB_LOG(ERROR) << "Error occurred while writing frames: " << error_message; - // TODO: Handle errors during minimum frames test, maybe by terminating the - // test earlier. - SB_NOTREACHED(); + + MinRequiredFramesTester* tester = + static_cast<MinRequiredFramesTester*>(context); + tester->has_error_ = true; } void MinRequiredFramesTester::UpdateSourceStatus(int* frames_in_buffer,
diff --git a/starboard/android/shared/audio_sink_min_required_frames_tester.h b/starboard/android/shared/audio_sink_min_required_frames_tester.h index 669217a..934bdb9 100644 --- a/starboard/android/shared/audio_sink_min_required_frames_tester.h +++ b/starboard/android/shared/audio_sink_min_required_frames_tester.h
@@ -107,6 +107,7 @@ std::vector<const TestTask> test_tasks_; AudioTrackAudioSink* audio_sink_ = nullptr; int min_required_frames_; + std::atomic_bool has_error_; // Used only by audio sink thread. int total_consumed_frames_;
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc index ada20e9..98810e7 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.cc +++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -18,6 +18,7 @@ #include <string> #include <vector> +#include "starboard/android/shared/media_capabilities_cache.h" #include "starboard/common/string.h" #include "starboard/shared/starboard/media/media_util.h" #include "starboard/shared/starboard/player/filter/common.h" @@ -50,8 +51,10 @@ const size_t kSilenceFramesPerAppend = 1024; -const int kMaxRequiredFrames = 16 * 1024; -const int kRequiredFramesIncrement = 2 * 1024; +const int kMaxRequiredFramesLocal = 16 * 1024; +const int kMaxRequiredFramesRemote = 32 * 1024; +const int kMaxRequiredFrames = kMaxRequiredFramesRemote; +const int kRequiredFramesIncrement = 4 * 1024; const int kMinStablePlayedFrames = 12 * 1024; const int kSampleFrequency22050 = 22050; @@ -67,6 +70,38 @@ return (max_frames + 15) / 16 * 16; // align to 16 } +bool HasRemoteAudioOutput() { +#if SB_API_VERSION >= 15 + // SbPlayerBridge::GetAudioConfigurations() reads up to 32 configurations. The + // limit here is to avoid infinite loop and also match + // SbPlayerBridge::GetAudioConfigurations(). + const int kMaxAudioConfigurations = 32; + SbMediaAudioConfiguration configuration; + int index = 0; + while (index < kMaxAudioConfigurations && + MediaCapabilitiesCache::GetInstance()->GetAudioConfiguration( + index, &configuration)) { + switch (configuration.connector) { + case kSbMediaAudioConnectorUnknown: + case kSbMediaAudioConnectorAnalog: + case kSbMediaAudioConnectorBuiltIn: + case kSbMediaAudioConnectorHdmi: + case kSbMediaAudioConnectorSpdif: + case kSbMediaAudioConnectorUsb: + break; + case kSbMediaAudioConnectorBluetooth: + case kSbMediaAudioConnectorRemoteWired: + case kSbMediaAudioConnectorRemoteWireless: + case kSbMediaAudioConnectorRemoteOther: + return true; + } + index++; + } + return false; +#endif // SB_API_VERSION >= 15 + return false; +} + } // namespace AudioTrackAudioSink::AudioTrackAudioSink( @@ -487,11 +522,18 @@ auto onMinRequiredFramesForWebAudioReceived = [&](int number_of_channels, SbMediaAudioSampleType sample_type, int sample_rate, int min_required_frames) { + bool has_remote_audio_output = HasRemoteAudioOutput(); SB_LOG(INFO) << "Received min required frames " << min_required_frames << " for " << number_of_channels << " channels, " - << sample_rate << "hz."; + << sample_rate << "hz, with " + << (has_remote_audio_output ? "remote" : "local") + << " audio output device."; ScopedLock lock(min_required_frames_map_mutex_); - min_required_frames_map_[sample_rate] = min_required_frames; + has_remote_audio_output_ = has_remote_audio_output; + min_required_frames_map_[sample_rate] = + std::min(min_required_frames, has_remote_audio_output_ + ? kMaxRequiredFramesRemote + : kMaxRequiredFramesLocal); }; SbMediaAudioSampleType sample_type = kSbMediaAudioSampleTypeFloat32; @@ -512,20 +554,27 @@ int channels, SbMediaAudioSampleType sample_type, int sampling_frequency_hz) { - if (sampling_frequency_hz <= kSampleFrequency22050) { - ScopedLock lock(min_required_frames_map_mutex_); - if (min_required_frames_map_.find(kSampleFrequency22050) != - min_required_frames_map_.end()) { - return min_required_frames_map_[kSampleFrequency22050]; - } - } else if (sampling_frequency_hz <= kSampleFrequency48000) { - ScopedLock lock(min_required_frames_map_mutex_); - if (min_required_frames_map_.find(kSampleFrequency48000) != - min_required_frames_map_.end()) { - return min_required_frames_map_[kSampleFrequency48000]; + bool has_remote_audio_output = HasRemoteAudioOutput(); + ScopedLock lock(min_required_frames_map_mutex_); + if (has_remote_audio_output == has_remote_audio_output_) { + // There's no audio output type change, we can use the numbers we got from + // the tests at app launch. + if (sampling_frequency_hz <= kSampleFrequency22050) { + if (min_required_frames_map_.find(kSampleFrequency22050) != + min_required_frames_map_.end()) { + return min_required_frames_map_[kSampleFrequency22050]; + } + } else if (sampling_frequency_hz <= kSampleFrequency48000) { + if (min_required_frames_map_.find(kSampleFrequency48000) != + min_required_frames_map_.end()) { + return min_required_frames_map_[kSampleFrequency48000]; + } } } - return kMaxRequiredFrames; + // We cannot find a matched result from our tests, or the audio output type + // has changed. We use the default max required frames to avoid underruns. + return has_remote_audio_output ? kMaxRequiredFramesRemote + : kMaxRequiredFramesLocal; } } // namespace shared
diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h index 88974ff..b115deb 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.h +++ b/starboard/android/shared/audio_track_audio_sink_type.h
@@ -97,6 +97,7 @@ // The minimum frames required to avoid underruns of different frequencies. std::map<int, int> min_required_frames_map_; MinRequiredFramesTester min_required_frames_tester_; + bool has_remote_audio_output_ = false; }; class AudioTrackAudioSink : public SbAudioSinkPrivate {
diff --git a/starboard/android/shared/input_events_generator.cc b/starboard/android/shared/input_events_generator.cc index 0b345242..8115b6b 100644 --- a/starboard/android/shared/input_events_generator.cc +++ b/starboard/android/shared/input_events_generator.cc
@@ -679,6 +679,12 @@ PushKeyEvent(key, type, window_, android_event, events); } + // Cobalt does not handle the kSbKeyUnknown event, return false, + // so the key event can be handled by the next receiver. + if (key == kSbKeyUnknown) { + return false; + } + return true; }
diff --git a/starboard/android/shared/jni_env_ext.cc b/starboard/android/shared/jni_env_ext.cc index 06535d5..98a65fd 100644 --- a/starboard/android/shared/jni_env_ext.cc +++ b/starboard/android/shared/jni_env_ext.cc
@@ -72,7 +72,7 @@ } JniEnvExt* JniEnvExt::Get() { - JNIEnv* env; + JNIEnv* env = nullptr; if (JNI_OK != g_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) { // Tell the JVM our thread name so it doesn't change it. char thread_name[16];
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc index 7b8997b..fe96cc8 100644 --- a/starboard/android/shared/media_capabilities_cache.cc +++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -40,6 +40,114 @@ const char SECURE_DECODER_SUFFIX[] = ".secure"; +// Constants for output types from +// https://developer.android.com/reference/android/media/AudioDeviceInfo. +constexpr int TYPE_AUX_LINE = 19; +constexpr int TYPE_BLE_BROADCAST = 30; +constexpr int TYPE_BLE_HEADSET = 26; +constexpr int TYPE_BLE_SPEAKER = 27; +constexpr int TYPE_BLUETOOTH_A2DP = 8; +constexpr int TYPE_BLUETOOTH_SCO = 7; +constexpr int TYPE_BUILTIN_EARPIECE = 1; +constexpr int TYPE_BUILTIN_MIC = 15; +constexpr int TYPE_BUILTIN_SPEAKER = 2; +constexpr int TYPE_BUILTIN_SPEAKER_SAFE = 24; +constexpr int TYPE_BUS = 21; +constexpr int TYPE_DOCK = 13; +constexpr int TYPE_DOCK_ANALOG = 31; +constexpr int TYPE_FM = 14; +constexpr int TYPE_FM_TUNER = 16; +constexpr int TYPE_HDMI = 9; +constexpr int TYPE_HDMI_ARC = 10; +constexpr int TYPE_HDMI_EARC = 29; +constexpr int TYPE_HEARING_AID = 23; +constexpr int TYPE_IP = 20; +constexpr int TYPE_LINE_ANALOG = 5; +constexpr int TYPE_LINE_DIGITAL = 6; +constexpr int TYPE_REMOTE_SUBMIX = 25; +constexpr int TYPE_TELEPHONY = 18; +constexpr int TYPE_TV_TUNER = 17; +constexpr int TYPE_UNKNOWN = 0; +constexpr int TYPE_USB_ACCESSORY = 12; +constexpr int TYPE_USB_DEVICE = 11; +constexpr int TYPE_USB_HEADSET = 22; +constexpr int TYPE_WIRED_HEADPHONES = 4; +constexpr int TYPE_WIRED_HEADSET = 3; + +#if SB_API_VERSION >= 15 +SbMediaAudioConnector GetConnectorFromAndroidOutputType( + int android_output_device_type) { + switch (android_output_device_type) { + case TYPE_AUX_LINE: + return kSbMediaAudioConnectorAnalog; + case TYPE_BLE_BROADCAST: + return kSbMediaAudioConnectorBluetooth; + case TYPE_BLE_HEADSET: + return kSbMediaAudioConnectorBluetooth; + case TYPE_BLE_SPEAKER: + return kSbMediaAudioConnectorBluetooth; + case TYPE_BLUETOOTH_A2DP: + return kSbMediaAudioConnectorBluetooth; + case TYPE_BLUETOOTH_SCO: + return kSbMediaAudioConnectorBluetooth; + case TYPE_BUILTIN_EARPIECE: + return kSbMediaAudioConnectorBuiltIn; + case TYPE_BUILTIN_MIC: + return kSbMediaAudioConnectorBuiltIn; + case TYPE_BUILTIN_SPEAKER: + return kSbMediaAudioConnectorBuiltIn; + case TYPE_BUILTIN_SPEAKER_SAFE: + return kSbMediaAudioConnectorBuiltIn; + case TYPE_BUS: + return kSbMediaAudioConnectorUnknown; + case TYPE_DOCK: + return kSbMediaAudioConnectorUnknown; + case TYPE_DOCK_ANALOG: + return kSbMediaAudioConnectorAnalog; + case TYPE_FM: + return kSbMediaAudioConnectorUnknown; + case TYPE_FM_TUNER: + return kSbMediaAudioConnectorUnknown; + case TYPE_HDMI: + return kSbMediaAudioConnectorHdmi; + case TYPE_HDMI_ARC: + return kSbMediaAudioConnectorHdmi; + case TYPE_HDMI_EARC: + return kSbMediaAudioConnectorHdmi; + case TYPE_HEARING_AID: + return kSbMediaAudioConnectorUnknown; + case TYPE_IP: + return kSbMediaAudioConnectorRemoteWired; + case TYPE_LINE_ANALOG: + return kSbMediaAudioConnectorAnalog; + case TYPE_LINE_DIGITAL: + return kSbMediaAudioConnectorUnknown; + case TYPE_REMOTE_SUBMIX: + return kSbMediaAudioConnectorRemoteOther; + case TYPE_TELEPHONY: + return kSbMediaAudioConnectorUnknown; + case TYPE_TV_TUNER: + return kSbMediaAudioConnectorUnknown; + case TYPE_UNKNOWN: + return kSbMediaAudioConnectorUnknown; + case TYPE_USB_ACCESSORY: + return kSbMediaAudioConnectorUsb; + case TYPE_USB_DEVICE: + return kSbMediaAudioConnectorUsb; + case TYPE_USB_HEADSET: + return kSbMediaAudioConnectorUsb; + case TYPE_WIRED_HEADPHONES: + return kSbMediaAudioConnectorAnalog; + case TYPE_WIRED_HEADSET: + return kSbMediaAudioConnectorAnalog; + } + + SB_LOG(WARNING) << "Encountered unknown audio output device type " + << android_output_device_type; + return kSbMediaAudioConnectorUnknown; +} +#endif // SB_API_VERSION >= 15 + bool EndsWith(const std::string& str, const std::string& suffix) { if (str.size() < suffix.size()) { return false; @@ -135,13 +243,47 @@ encoding) == JNI_TRUE; } -int GetMaxAudioOutputChannels() { +bool GetAudioConfiguration(int index, + SbMediaAudioConfiguration* configuration) { + *configuration = {}; + 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")); + ScopedLocalJavaRef<jobject> j_output_device_info(env->NewObjectOrAbort( + "dev/cobalt/media/AudioOutputManager$OutputDeviceInfo", "()V")); + + bool succeeded = env->CallBooleanMethodOrAbort( + j_audio_output_manager.Get(), "getOutputDeviceInfo", + "(ILdev/cobalt/media/AudioOutputManager$OutputDeviceInfo;)Z", index, + j_output_device_info.Get()); + + if (!succeeded) { + SB_LOG(WARNING) + << "Call to AudioOutputManager.getOutputDeviceInfo() failed."; + return false; + } + + auto call_int_method = [env, &j_output_device_info](const char* name) { + return env->CallIntMethodOrAbort(j_output_device_info.Get(), name, "()I"); + }; + +#if SB_API_VERSION >= 15 + configuration->connector = + GetConnectorFromAndroidOutputType(call_int_method("getType")); +#else // SB_API_VERSION >= 15 + configuration->connector = kSbMediaAudioConnectorHdmi; +#endif // SB_API_VERSION >= 15 + configuration->latency = 0; + configuration->coding_type = kSbMediaAudioCodingTypePcm; + configuration->number_of_channels = call_int_method("getChannels"); + + if (configuration->connector != kSbMediaAudioConnectorHdmi) { + configuration->number_of_channels = 2; + } + + return true; } } // namespace @@ -311,21 +453,27 @@ return supported; } -int MediaCapabilitiesCache::GetMaxAudioOutputChannels() { +bool MediaCapabilitiesCache::GetAudioConfiguration( + int index, + SbMediaAudioConfiguration* configuration) { + SB_DCHECK(index >= 0); if (!is_enabled_) { - return ::starboard::android::shared::GetMaxAudioOutputChannels(); + return ::starboard::android::shared::GetAudioConfiguration(index, + configuration); } ScopedLock scoped_lock(mutex_); UpdateMediaCapabilities_Locked(); - return max_audio_output_channels_; + if (index < audio_configurations_.size()) { + *configuration = audio_configurations_[index]; + return true; + } + return false; } 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(); + int bitrate) { + return !FindAudioDecoder(mime_type, bitrate).empty(); } bool MediaCapabilitiesCache::HasVideoDecoderFor( @@ -347,16 +495,14 @@ std::string MediaCapabilitiesCache::FindAudioDecoder( const std::string& mime_type, - int bitrate, - bool must_support_tunnel_mode) { + int bitrate) { 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); + "(Ljava/lang/String;I)Ljava/lang/String;", j_mime.Get(), bitrate); return env->GetStringStandardUTFOrAbort( static_cast<jstring>(j_decoder_name)); } @@ -365,11 +511,6 @@ UpdateMediaCapabilities_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; @@ -463,25 +604,6 @@ return ""; } -void MediaCapabilitiesCache::ClearCache() { - ScopedLock scoped_lock(mutex_); - is_initialized_ = false; - supported_hdr_types_is_dirty_ = true; - audio_output_channels_is_dirty_ = true; - 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::Initialize() { - ScopedLock scoped_lock(mutex_); - UpdateMediaCapabilities_Locked(); -} - MediaCapabilitiesCache::MediaCapabilitiesCache() { // Enable mime and key system caches. MimeSupportabilityCache::GetInstance()->SetCacheEnabled(true); @@ -490,22 +612,20 @@ void MediaCapabilitiesCache::UpdateMediaCapabilities_Locked() { mutex_.DCheckAcquired(); + if (capabilities_is_dirty_.exchange(false)) { + // We use a different cache strategy (load and cache) for passthrough + // supportabilities, so we only clear |passthrough_supportabilities_| here. + passthrough_supportabilities_.clear(); - if (supported_hdr_types_is_dirty_.exchange(false)) { + audio_codec_capabilities_map_.clear(); + video_codec_capabilities_map_.clear(); + audio_configurations_.clear(); + is_widevine_supported_ = GetIsWidevineSupported(); + is_cbcs_supported_ = GetIsCbcsSupported(); supported_transfer_ids_ = GetSupportedHdrTypes(); + LoadCodecInfos_Locked(); + LoadAudioConfigurations_Locked(); } - if (audio_output_channels_is_dirty_.exchange(false)) { - max_audio_output_channels_ = - ::starboard::android::shared::GetMaxAudioOutputChannels(); - } - - if (is_initialized_) { - return; - } - is_widevine_supported_ = GetIsWidevineSupported(); - is_cbcs_supported_ = GetIsCbcsSupported(); - LoadCodecInfos_Locked(); - is_initialized_ = true; } void MediaCapabilitiesCache::LoadCodecInfos_Locked() { @@ -558,33 +678,37 @@ } } +void MediaCapabilitiesCache::LoadAudioConfigurations_Locked() { + SB_DCHECK(audio_configurations_.empty()); + mutex_.DCheckAcquired(); + + // SbPlayerBridge::GetAudioConfigurations() reads up to 32 configurations. The + // limit here is to avoid infinite loop and also match + // SbPlayerBridge::GetAudioConfigurations(). + const int kMaxAudioConfigurations = 32; + SbMediaAudioConfiguration configuration; + while (audio_configurations_.size() < kMaxAudioConfigurations && + ::starboard::android::shared::GetAudioConfiguration( + audio_configurations_.size(), &configuration)) { + audio_configurations_.push_back(configuration); + } +} + extern "C" SB_EXPORT_PLATFORM void Java_dev_cobalt_util_DisplayUtil_nativeOnDisplayChanged() { - SB_DLOG(INFO) << "Display device has changed."; - MediaCapabilitiesCache::GetInstance()->ClearSupportedHdrTypes(); + // Display device change could change hdr capabilities. + MediaCapabilitiesCache::GetInstance()->ClearCache(); MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); } extern "C" SB_EXPORT_PLATFORM void Java_dev_cobalt_media_AudioOutputManager_nativeOnAudioDeviceChanged() { - SB_DLOG(INFO) << "Audio device has changed."; - MediaCapabilitiesCache::GetInstance()->ClearAudioOutputChannels(); + // Audio output device change could change passthrough decoder capabilities, + // so we have to reload codec capabilities. + MediaCapabilitiesCache::GetInstance()->ClearCache(); MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); } -void* MediaCapabilitiesCacheInitializationThreadEntry(void* context) { - SB_LOG(INFO) << "Initialize MediaCapabilitiesCache in background."; - MediaCapabilitiesCache::GetInstance()->Initialize(); - return 0; -} - -extern "C" SB_EXPORT_PLATFORM void -Java_dev_cobalt_coat_CobaltActivity_nativeInitializeMediaCapabilitiesInBackground() { - SbThreadCreate(0, kSbThreadPriorityNormal, kSbThreadNoAffinity, false, - "media_capabilities_cache_thread", - MediaCapabilitiesCacheInitializationThreadEntry, nullptr); -} - } // namespace shared } // namespace android } // namespace starboard
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h index db3879e..656398a 100644 --- a/starboard/android/shared/media_capabilities_cache.h +++ b/starboard/android/shared/media_capabilities_cache.h
@@ -125,11 +125,10 @@ bool IsPassthroughSupported(SbMediaAudioCodec codec); - int GetMaxAudioOutputChannels(); + bool GetAudioConfiguration(int index, + SbMediaAudioConfiguration* configuration); - bool HasAudioDecoderFor(const std::string& mime_type, - int bitrate, - bool must_support_tunnel_mode); + bool HasAudioDecoderFor(const std::string& mime_type, int bitrate); bool HasVideoDecoderFor(const std::string& mime_type, bool must_support_secure, @@ -141,9 +140,7 @@ int bitrate, int fps); - std::string FindAudioDecoder(const std::string& mime_type, - int bitrate, - bool must_support_tunnel_mode); + std::string FindAudioDecoder(const std::string& mime_type, int bitrate); std::string FindVideoDecoder(const std::string& mime_type, bool must_support_secure, @@ -158,11 +155,7 @@ bool IsEnabled() const { return is_enabled_; } void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } - void ClearCache(); - - void Initialize(); - void ClearSupportedHdrTypes() { supported_hdr_types_is_dirty_ = true; } - void ClearAudioOutputChannels() { audio_output_channels_is_dirty_ = true; } + void ClearCache() { capabilities_is_dirty_ = true; } private: MediaCapabilitiesCache(); @@ -172,6 +165,7 @@ MediaCapabilitiesCache& operator=(const MediaCapabilitiesCache&) = delete; void UpdateMediaCapabilities_Locked(); + void LoadAudioConfigurations_Locked(); void LoadCodecInfos_Locked(); Mutex mutex_; @@ -186,14 +180,12 @@ std::map<std::string, AudioCodecCapabilities> audio_codec_capabilities_map_; std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_; - - std::atomic_bool is_enabled_{true}; - std::atomic_bool supported_hdr_types_is_dirty_{true}; - std::atomic_bool audio_output_channels_is_dirty_{true}; - bool is_initialized_ = false; + std::vector<SbMediaAudioConfiguration> audio_configurations_; bool is_widevine_supported_ = false; bool is_cbcs_supported_ = false; - int max_audio_output_channels_ = -1; + + std::atomic_bool is_enabled_{true}; + std::atomic_bool capabilities_is_dirty_{true}; }; } // namespace shared
diff --git a/starboard/android/shared/media_codec_bridge.cc b/starboard/android/shared/media_codec_bridge.cc index fe97038..87bdf32 100644 --- a/starboard/android/shared/media_codec_bridge.cc +++ b/starboard/android/shared/media_codec_bridge.cc
@@ -169,8 +169,7 @@ std::string decoder_name = MediaCapabilitiesCache::GetInstance()->FindAudioDecoder( - mime, /* bitrate = */ 0, - /* must_support_tunnel_mode = */ false); + mime, /* bitrate = */ 0); if (decoder_name.empty()) { SB_LOG(ERROR) << "Failed to find decoder for " << audio_stream_info.codec
diff --git a/starboard/android/shared/media_decoder.cc b/starboard/android/shared/media_decoder.cc index 78425b7..cf93733 100644 --- a/starboard/android/shared/media_decoder.cc +++ b/starboard/android/shared/media_decoder.cc
@@ -146,9 +146,11 @@ MediaDecoder::~MediaDecoder() { SB_DCHECK(thread_checker_.CalledOnValidThread()); - destroying_.store(true); - condition_variable_.Signal(); + { + ScopedLock scoped_lock(mutex_); + condition_variable_.Signal(); + } if (SbThreadIsValid(decoder_thread_)) { SbThreadJoin(decoder_thread_, NULL); @@ -184,11 +186,15 @@ void MediaDecoder::WriteInputBuffers(const InputBuffers& input_buffers) { SB_DCHECK(thread_checker_.CalledOnValidThread()); - SB_DCHECK(!input_buffers.empty()); if (stream_ended_.load()) { SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called."; return; } + if (input_buffers.empty()) { + SB_LOG(ERROR) << "No input buffer to decode."; + SB_DCHECK(!input_buffers.empty()); + return; + } if (!SbThreadIsValid(decoder_thread_)) { decoder_thread_ = SbThreadCreate( @@ -463,24 +469,20 @@ } jint status; - if (event.type == Event::kWriteCodecConfig) { - if (!drm_system_ || (drm_system_ && drm_system_->IsReady())) { - status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index, - kNoOffset, size, kNoPts, - BUFFER_FLAG_CODEC_CONFIG); - } else { - status = MEDIA_CODEC_NO_KEY; - } + if (drm_system_ && !drm_system_->IsReady()) { + // Drm system initialization is asynchronous. If there's a drm system, we + // should wait until it's initialized to avoid errors. + status = MEDIA_CODEC_NO_KEY; + } else if (event.type == Event::kWriteCodecConfig) { + status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index, + kNoOffset, size, kNoPts, + BUFFER_FLAG_CODEC_CONFIG); } else if (event.type == Event::kWriteInputBuffer) { jlong pts_us = input_buffer->timestamp(); if (drm_system_ && input_buffer->drm_info()) { - if (drm_system_->IsReady()) { - status = media_codec_bridge_->QueueSecureInputBuffer( - dequeue_input_result.index, kNoOffset, *input_buffer->drm_info(), - pts_us); - } else { - status = MEDIA_CODEC_NO_KEY; - } + status = media_codec_bridge_->QueueSecureInputBuffer( + dequeue_input_result.index, kNoOffset, *input_buffer->drm_info(), + pts_us); } else { status = media_codec_bridge_->QueueInputBuffer( dequeue_input_result.index, kNoOffset, size, pts_us, kNoBufferFlags);
diff --git a/starboard/android/shared/media_get_audio_configuration.cc b/starboard/android/shared/media_get_audio_configuration.cc index 6b645b6..572fceb 100644 --- a/starboard/android/shared/media_get_audio_configuration.cc +++ b/starboard/android/shared/media_get_audio_configuration.cc
@@ -19,112 +19,6 @@ #include "starboard/android/shared/media_capabilities_cache.h" #include "starboard/common/media.h" -// Constants for output types from -// https://developer.android.com/reference/android/media/AudioDeviceInfo. -constexpr int TYPE_AUX_LINE = 19; -constexpr int TYPE_BLE_BROADCAST = 30; -constexpr int TYPE_BLE_HEADSET = 26; -constexpr int TYPE_BLE_SPEAKER = 27; -constexpr int TYPE_BLUETOOTH_A2DP = 8; -constexpr int TYPE_BLUETOOTH_SCO = 7; -constexpr int TYPE_BUILTIN_EARPIECE = 1; -constexpr int TYPE_BUILTIN_MIC = 15; -constexpr int TYPE_BUILTIN_SPEAKER = 2; -constexpr int TYPE_BUILTIN_SPEAKER_SAFE = 24; -constexpr int TYPE_BUS = 21; -constexpr int TYPE_DOCK = 13; -constexpr int TYPE_DOCK_ANALOG = 31; -constexpr int TYPE_FM = 14; -constexpr int TYPE_FM_TUNER = 16; -constexpr int TYPE_HDMI = 9; -constexpr int TYPE_HDMI_ARC = 10; -constexpr int TYPE_HDMI_EARC = 29; -constexpr int TYPE_HEARING_AID = 23; -constexpr int TYPE_IP = 20; -constexpr int TYPE_LINE_ANALOG = 5; -constexpr int TYPE_LINE_DIGITAL = 6; -constexpr int TYPE_REMOTE_SUBMIX = 25; -constexpr int TYPE_TELEPHONY = 18; -constexpr int TYPE_TV_TUNER = 17; -constexpr int TYPE_UNKNOWN = 0; -constexpr int TYPE_USB_ACCESSORY = 12; -constexpr int TYPE_USB_DEVICE = 11; -constexpr int TYPE_USB_HEADSET = 22; -constexpr int TYPE_WIRED_HEADPHONES = 4; -constexpr int TYPE_WIRED_HEADSET = 3; - -SbMediaAudioConnector GetConnectorFromAndroidOutputType( - int android_output_device_type) { - switch (android_output_device_type) { - case TYPE_AUX_LINE: - return kSbMediaAudioConnectorAnalog; - case TYPE_BLE_BROADCAST: - return kSbMediaAudioConnectorBluetooth; - case TYPE_BLE_HEADSET: - return kSbMediaAudioConnectorBluetooth; - case TYPE_BLE_SPEAKER: - return kSbMediaAudioConnectorBluetooth; - case TYPE_BLUETOOTH_A2DP: - return kSbMediaAudioConnectorBluetooth; - case TYPE_BLUETOOTH_SCO: - return kSbMediaAudioConnectorBluetooth; - case TYPE_BUILTIN_EARPIECE: - return kSbMediaAudioConnectorBuiltIn; - case TYPE_BUILTIN_MIC: - return kSbMediaAudioConnectorBuiltIn; - case TYPE_BUILTIN_SPEAKER: - return kSbMediaAudioConnectorBuiltIn; - case TYPE_BUILTIN_SPEAKER_SAFE: - return kSbMediaAudioConnectorBuiltIn; - case TYPE_BUS: - return kSbMediaAudioConnectorUnknown; - case TYPE_DOCK: - return kSbMediaAudioConnectorUnknown; - case TYPE_DOCK_ANALOG: - return kSbMediaAudioConnectorAnalog; - case TYPE_FM: - return kSbMediaAudioConnectorUnknown; - case TYPE_FM_TUNER: - return kSbMediaAudioConnectorUnknown; - case TYPE_HDMI: - return kSbMediaAudioConnectorHdmi; - case TYPE_HDMI_ARC: - return kSbMediaAudioConnectorHdmi; - case TYPE_HDMI_EARC: - return kSbMediaAudioConnectorHdmi; - case TYPE_HEARING_AID: - return kSbMediaAudioConnectorUnknown; - case TYPE_IP: - return kSbMediaAudioConnectorRemoteWired; - case TYPE_LINE_ANALOG: - return kSbMediaAudioConnectorAnalog; - case TYPE_LINE_DIGITAL: - return kSbMediaAudioConnectorUnknown; - case TYPE_REMOTE_SUBMIX: - return kSbMediaAudioConnectorRemoteOther; - case TYPE_TELEPHONY: - return kSbMediaAudioConnectorUnknown; - case TYPE_TV_TUNER: - return kSbMediaAudioConnectorUnknown; - case TYPE_UNKNOWN: - return kSbMediaAudioConnectorUnknown; - case TYPE_USB_ACCESSORY: - return kSbMediaAudioConnectorUsb; - case TYPE_USB_DEVICE: - return kSbMediaAudioConnectorUsb; - case TYPE_USB_HEADSET: - return kSbMediaAudioConnectorUsb; - case TYPE_WIRED_HEADPHONES: - return kSbMediaAudioConnectorAnalog; - case TYPE_WIRED_HEADSET: - return kSbMediaAudioConnectorAnalog; - } - - SB_LOG(WARNING) << "Encountered unknown audio output device type " - << android_output_device_type; - return kSbMediaAudioConnectorUnknown; -} - // TODO(b/284140486): Refine the implementation so it works when the audio // outputs are changed during the query. bool SbMediaGetAudioConfiguration( @@ -146,56 +40,13 @@ return false; } - *out_configuration = {}; - - JniEnvExt* env = JniEnvExt::Get(); - ScopedLocalJavaRef<jobject> j_audio_output_manager( - env->CallStarboardObjectMethodOrAbort( - "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;")); - ScopedLocalJavaRef<jobject> j_output_device_info(env->NewObjectOrAbort( - "dev/cobalt/media/AudioOutputManager$OutputDeviceInfo", "()V")); - - bool succeeded = env->CallBooleanMethodOrAbort( - j_audio_output_manager.Get(), "getOutputDeviceInfo", - "(ILdev/cobalt/media/AudioOutputManager$OutputDeviceInfo;)Z", - output_index, j_output_device_info.Get()); - - if (!succeeded) { - SB_LOG(WARNING) - << "Call to AudioOutputManager.getOutputDeviceInfo() failed."; - return false; - } - - auto call_int_method = [env, &j_output_device_info](const char* name) { - return env->CallIntMethodOrAbort(j_output_device_info.Get(), name, "()I"); - }; - - out_configuration->connector = - GetConnectorFromAndroidOutputType(call_int_method("getType")); - out_configuration->latency = 0; - out_configuration->coding_type = kSbMediaAudioCodingTypePcm; - out_configuration->number_of_channels = call_int_method("getChannels"); - - if (out_configuration->connector == kSbMediaAudioConnectorHdmi) { - // Keep the previous logic for HDMI to reduce risk. - // TODO(b/284140486): Update this using same logic as other connectors. - int channels = - MediaCapabilitiesCache::GetInstance()->GetMaxAudioOutputChannels(); - if (channels < 2) { - SB_LOG(WARNING) << "The supported channels from output device is " - << channels << ", set to 2 channels instead."; - out_configuration->number_of_channels = 2; - } else { - out_configuration->number_of_channels = channels; - } - } else { - out_configuration->number_of_channels = 2; - } + bool result = MediaCapabilitiesCache::GetInstance()->GetAudioConfiguration( + output_index, out_configuration); SB_LOG(INFO) << "Audio connector type for index " << output_index << " is " << GetMediaAudioConnectorName(out_configuration->connector) << " and it has " << out_configuration->number_of_channels << " channels."; - return true; + return result; }
diff --git a/starboard/android/shared/media_is_audio_supported.cc b/starboard/android/shared/media_is_audio_supported.cc index 46b73e5..9844406 100644 --- a/starboard/android/shared/media_is_audio_supported.cc +++ b/starboard/android/shared/media_is_audio_supported.cc
@@ -40,7 +40,6 @@ return false; } - bool enable_tunnel_mode = false; bool enable_audio_passthrough = true; if (mime_type) { if (!mime_type->is_valid()) { @@ -53,13 +52,6 @@ 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; - } - enable_tunnel_mode = mime_type->GetParamBoolValue("tunnelmode", false); - // Enables audio passthrough if the codec supports it. if (!mime_type->ValidateBoolParameter("audiopassthrough")) { return false; @@ -75,14 +67,6 @@ } } - if (enable_tunnel_mode && !SbAudioSinkIsAudioSampleTypeSupported( - kSbMediaAudioSampleTypeInt16Deprecated)) { - SB_LOG(WARNING) - << "Tunnel mode is rejected because int16 sample is required " - "but not supported."; - return false; - } - // Android uses a libopus based opus decoder for clear content, or a platform // opus decoder for encrypted content, if available. if (audio_codec == kSbMediaAudioCodecOpus) { @@ -90,8 +74,7 @@ } bool media_codec_supported = - MediaCapabilitiesCache::GetInstance()->HasAudioDecoderFor( - mime, bitrate, enable_tunnel_mode); + MediaCapabilitiesCache::GetInstance()->HasAudioDecoderFor(mime, bitrate); if (!media_codec_supported) { return false;
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h index 18db0ba..57d3b44 100644 --- a/starboard/android/shared/player_components_factory.h +++ b/starboard/android/shared/player_components_factory.h
@@ -303,7 +303,6 @@ MimeType audio_mime_type(audio_mime); if (!audio_mime.empty()) { if (!audio_mime_type.is_valid() || - !audio_mime_type.ValidateBoolParameter("tunnelmode") || !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") || !audio_mime_type.ValidateBoolParameter("enablepcmcontenttypemovie")) { *error_message = @@ -331,20 +330,15 @@ bool enable_tunnel_mode = false; if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone && creation_parameters.video_codec() != kSbMediaVideoCodecNone) { - bool enable_tunnel_mode = - audio_mime_type.GetParamBoolValue("tunnelmode", false) && + enable_tunnel_mode = video_mime_type.GetParamBoolValue("tunnelmode", false); - if (!enable_tunnel_mode) { - SB_LOG(INFO) << "Tunnel mode is disabled. " - << "Audio mime parameter \"tunnelmode\" value: " - << audio_mime_type.GetParamStringValue("tunnelmode", - "<not provided>") - << ", video mime parameter \"tunnelmode\" value: " - << video_mime_type.GetParamStringValue("tunnelmode", - "<not provided>") - << "."; - } + SB_LOG(INFO) << "Tunnel mode is " + << (enable_tunnel_mode ? "enabled. " : "disabled. ") + << "Video mime parameter \"tunnelmode\" value: " + << video_mime_type.GetParamStringValue("tunnelmode", + "<not provided>") + << "."; } else { SB_LOG(INFO) << "Tunnel mode requires both an audio and video stream. " << "Audio codec: " @@ -365,12 +359,19 @@ SB_LOG_IF(INFO, !force_improved_support_check) << "Improved support check is disabled for queries under 4K."; bool force_secure_pipeline_under_tunnel_mode = false; - if (enable_tunnel_mode && - IsTunnelModeSupported(creation_parameters, - &force_secure_pipeline_under_tunnel_mode, - force_improved_support_check)) { - tunnel_mode_audio_session_id = GenerateAudioSessionId( - creation_parameters, force_improved_support_check); + if (enable_tunnel_mode) { + if (IsTunnelModeSupported(creation_parameters, + &force_secure_pipeline_under_tunnel_mode, + force_improved_support_check)) { + tunnel_mode_audio_session_id = GenerateAudioSessionId( + creation_parameters, force_improved_support_check); + SB_LOG(INFO) << "Generated tunnel mode audio session id " + << tunnel_mode_audio_session_id; + } else { + SB_LOG(INFO) << "IsTunnelModeSupported() failed, disable tunnel mode."; + } + } else { + SB_LOG(INFO) << "Tunnel mode not enabled."; } if (tunnel_mode_audio_session_id == -1) {
diff --git a/starboard/android/shared/video_decoder.cc b/starboard/android/shared/video_decoder.cc index 723a1cc..44e899b 100644 --- a/starboard/android/shared/video_decoder.cc +++ b/starboard/android/shared/video_decoder.cc
@@ -29,6 +29,8 @@ #include "starboard/android/shared/media_common.h" #include "starboard/android/shared/video_render_algorithm.h" #include "starboard/android/shared/window_internal.h" +#include "starboard/common/media.h" +#include "starboard/common/player.h" #include "starboard/common/string.h" #include "starboard/configuration.h" #include "starboard/decode_target.h" @@ -397,6 +399,13 @@ TeardownCodec(); } } + + SB_LOG(INFO) << "Created VideoDecoder for codec " + << GetMediaVideoCodecName(video_codec_) << ", with output mode " + << GetPlayerOutputModeName(output_mode_) + << ", max video capabilities \"" << max_video_capabilities_ + << "\", and tunnel mode audio session id " + << tunnel_mode_audio_session_id_; } VideoDecoder::~VideoDecoder() {
diff --git a/starboard/android/shared/video_window.cc b/starboard/android/shared/video_window.cc index 5e401ec..d6998e3 100644 --- a/starboard/android/shared/video_window.cc +++ b/starboard/android/shared/video_window.cc
@@ -211,20 +211,23 @@ return; } + JniEnvExt* env = JniEnvExt::Get(); + if (!env) { + SB_LOG(INFO) << "Tried to clear video window when JniEnvExt was null."; + return; + } + if (force_reset_surface) { - JniEnvExt::Get()->CallStarboardVoidMethodOrAbort("resetVideoSurface", - "()V"); + env->CallStarboardVoidMethodOrAbort("resetVideoSurface", "()V"); return; } else if (g_reset_surface_on_clear_window) { int width = ANativeWindow_getWidth(g_native_video_window); int height = ANativeWindow_getHeight(g_native_video_window); if (width <= height) { - JniEnvExt::Get()->CallStarboardVoidMethodOrAbort("resetVideoSurface", - "()V"); + env->CallStarboardVoidMethodOrAbort("resetVideoSurface", "()V"); return; } } - ClearNativeWindow(g_native_video_window); }
diff --git a/starboard/build/config/BUILDCONFIG.gn b/starboard/build/config/BUILDCONFIG.gn index 4075ed3..315e402 100644 --- a/starboard/build/config/BUILDCONFIG.gn +++ b/starboard/build/config/BUILDCONFIG.gn
@@ -37,6 +37,10 @@ build_with_separate_cobalt_toolchain = false } +_is_on_pythonpath = exec_script("//starboard/build/is_on_path.py", [], "json") +assert(!is_starboard || _is_on_pythonpath, + "The current repo is not first on the PYTHONPATH.") + assert(!(is_starboard && is_native_target_build), "Targets should be built for Starboard or natively, but not both") @@ -99,7 +103,7 @@ host_toolchain = "//starboard/build/toolchain/$host_os:$_host_toolchain_cpu" if (build_with_separate_cobalt_toolchain) { - cobalt_toolchain = "//starboard/build/toolchain:clang" + cobalt_toolchain = "//$starboard_path/toolchain:cobalt" starboard_toolchain = "//$starboard_path/toolchain:starboard" } else { cobalt_toolchain = "//$starboard_path/toolchain:target" @@ -253,7 +257,8 @@ } } - if (defined(invoker.install_content) && invoker.install_content) { + if (defined(invoker.install_content) && invoker.install_content && + current_toolchain == default_toolchain) { # We're using a custom script to copy the files here because rebase_path # can't be used with {{}} expansions in the outputs of a copy target. action("${target_name}_install_content") { @@ -294,6 +299,13 @@ rebase_path(files_list, root_build_dir), ] } + } else if (current_toolchain == starboard_toolchain && + build_with_separate_cobalt_toolchain) { + outer_target_name = target_name + group("${target_name}_install_content") { + forward_variables_from(invoker, [ "testonly" ]) + deps = [ ":${outer_target_name}_install_content($default_toolchain)" ] + } } } @@ -401,8 +413,11 @@ forward_variables_from(invoker, [ "testonly" ]) deps = [ ":${actual_target_name}_loader($starboard_toolchain)", - ":${actual_target_name}_loader_copy($starboard_toolchain)", + ":${actual_target_name}_loader_install($starboard_toolchain)", ] + if (!is_host_win) { + deps += [ ":${actual_target_name}_loader_copy($starboard_toolchain)" ] + } } if (current_toolchain == starboard_toolchain) { executable("${actual_target_name}_loader") { @@ -410,6 +425,10 @@ forward_variables_from(invoker, [ "testonly" ]) sources = [ "//$starboard_path/starboard_loader.cc" ] + if (defined(extra_platform_loader_sources)) { + sources += extra_platform_loader_sources + } + if (use_asan) { sources += [ "//$starboard_path/sanitizer_options.cc" ] } @@ -424,18 +443,25 @@ ldflags = [ "-Wl,-rpath=" + rebase_path("$root_build_dir/starboard"), "-Wl,-rpath=" + rebase_path("$root_build_dir"), + "-Wl,-rpath=\$ORIGIN/../lib", + "-Wl,-rpath=\$ORIGIN", ] deps = [ ":$original_target_name($cobalt_toolchain)", "//starboard:starboard_platform_group($starboard_toolchain)", ] + if (!separate_install_targets_for_bundling) { + deps += [ "//starboard:starboard_platform_group_install($starboard_toolchain)" ] + } } - copy("${actual_target_name}_loader_copy") { - forward_variables_from(invoker, [ "testonly" ]) - sources = [ "$root_out_dir/${actual_target_name}_loader" ] - outputs = [ "$root_build_dir/${actual_target_name}_loader" ] - deps = [ ":${actual_target_name}_loader" ] + if (!is_host_win) { + copy("${actual_target_name}_loader_copy") { + forward_variables_from(invoker, [ "testonly" ]) + sources = [ "$root_out_dir/${actual_target_name}_loader" ] + outputs = [ "$root_build_dir/${actual_target_name}_loader" ] + deps = [ ":${actual_target_name}_loader" ] + } } } }
diff --git a/starboard/build/config/base_configuration.gni b/starboard/build/config/base_configuration.gni index 1edd94b..6c9d354 100644 --- a/starboard/build/config/base_configuration.gni +++ b/starboard/build/config/base_configuration.gni
@@ -44,9 +44,6 @@ # Directory path to static contents' data. sb_static_contents_output_data_dir = "$root_out_dir/content/data" - # Whether this is a modular build. - sb_is_modular = false - # Whether this is an Evergreen build. sb_is_evergreen = false
diff --git a/starboard/build/config/install.gni b/starboard/build/config/install.gni index 9ef3f3d..b58479a 100644 --- a/starboard/build/config/install.gni +++ b/starboard/build/config/install.gni
@@ -16,7 +16,7 @@ # Top-level directory for staging deploy build output. Platform install # actions should use ${sb_install_output_dir} defined in this file to place # artifacts for each deploy target in its own subdirectoy. - sb_install_output_dir = "$root_out_dir/install" + sb_install_output_dir = "$root_build_dir/install" # Sub-directory for install content. sb_install_content_subdir = ""
diff --git a/starboard/build/config/modular/BUILD.gn b/starboard/build/config/modular/BUILD.gn new file mode 100644 index 0000000..d266109 --- /dev/null +++ b/starboard/build/config/modular/BUILD.gn
@@ -0,0 +1,210 @@ +# Copyright 2023 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. + +config("modular") { + cflags = [ + "-ffunction-sections", + "-fdata-sections", + "-nostdlibinc", + "-isystem" + rebase_path("//third_party/llvm-project/libcxxabi/include", + root_build_dir), + "-isystem" + rebase_path("//third_party/llvm-project/libunwind/include", + root_build_dir), + "-isystem" + rebase_path("//third_party/llvm-project/libcxx/include", + root_build_dir), + "-isystem" + rebase_path("//third_party/musl/include", root_build_dir), + "-isystem" + rebase_path("//third_party/musl/arch/generic", root_build_dir), + ] + + if (!is_host_win) { + # Causes error on windows. clang++: error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc' + cflags += [ "-fPIC" ] + } + + cflags_cc = [ + "-nostdinc++", + "-std=c++17", + ] + + defines = [ + # Ensure that the Starboardized __external_threading file is included. + "_LIBCPP_HAS_THREAD_API_EXTERNAL", + + # Ensure that only the forward declarations and type definitions are included + # in __external_threading. + "_LIBCPP_HAS_THREAD_LIBRARY_EXTERNAL", + + # Enable GNU extensions to get prototypes like ffsl. + "_GNU_SOURCE=1", + + "_LIBCPP_HAS_MUSL_LIBC", + "__STDC_FORMAT_MACROS", # so that we get PRI* + + # File format of the shared object we will generate. + "__ELF__", + + # Use scalar portable implementations instead of Clang/GCC vector + # extensions in SkVx.h. + "SKNX_NO_SIMD", + + # By default, <EGL/eglplatform.h> pulls in some X11 headers that have some + # nasty macros (|Status|, for example) that conflict with Chromium base. + "MESA_EGL_NO_X11_HEADERS", + ] + + if (is_debug) { + cflags += [ + "-O0", + "-frtti", + ] + if (!cobalt_fastbuild) { + cflags += [ + # This flag causes an increase in binary size on certain platforms. Refer b/297357707 + "-g", + ] + } + } else if (is_devel) { + cflags += [ + "-O2", + "-frtti", + ] + if (!cobalt_fastbuild) { + cflags += [ + # This flag causes an increase in binary size on certain platforms. Refer b/297357707 + "-g", + ] + } + } else { + cflags += [ + "-fno-rtti", + "-gline-tables-only", + ] + } + + if (is_clang) { + cflags += [ + "-fcolor-diagnostics", + + # Default visibility to hidden, to enable dead stripping. + "-fvisibility=hidden", + + # Warns on switches on enums that cover all enum values but + # also contain a default: branch. Chrome is full of that. + "-Wno-covered-switch-default", + + # protobuf uses hash_map. + "-Wno-deprecated", + + "-fno-exceptions", + + # Enable unwind tables used by libunwind for stack traces. + "-funwind-tables", + + # Disable usage of frame pointers. + "-fomit-frame-pointer", + + # Don"t warn about the "struct foo f = {0};" initialization pattern. + "-Wno-missing-field-initializers", + + # Do not warn for implicit sign conversions. + "-Wno-sign-conversion", + + "-fno-strict-aliasing", # See http://crbug.com/32204 + + "-Wno-unnamed-type-template-args", + + # Triggered by the COMPILE_ASSERT macro. + "-Wno-unused-local-typedef", + + # Do not warn if a function or variable cannot be implicitly + # instantiated. + "-Wno-undefined-var-template", + + # Do not warn about an implicit exception spec mismatch. + "-Wno-implicit-exception-spec-mismatch", + + # It's OK not to use some input parameters. + "-Wno-unused-parameter", + "-Wno-conversion", + "-Wno-bitwise-op-parentheses", + "-Wno-shift-op-parentheses", + "-Wno-shorten-64-to-32", + "-fno-use-cxa-atexit", + ] + } + + if (is_clang_16 || is_host_win) { + cflags += [ + # Do not remove null pointer checks. + "-fno-delete-null-pointer-checks", + ] + } + + if (use_asan) { + cflags += [ + "-fsanitize=address", + "-fno-omit-frame-pointer", + ] + + defines += [ "ADDRESS_SANITIZER" ] + + if (asan_symbolizer_path != "") { + defines += [ "ASAN_SYMBOLIZER_PATH=\"${asan_symbolizer_path}\"" ] + } + } else if (use_tsan) { + cflags += [ + "-fsanitize=thread", + "-fno-omit-frame-pointer", + ] + + defines += [ "THREAD_SANITIZER" ] + } +} + +config("speed") { + cflags = [ "-O2" ] +} + +config("size") { + cflags = [ "-Os" ] +} + +config("pedantic_warnings") { + cflags = [ + "-Wall", + "-Wextra", + "-Wunreachable-code", + ] +} + +config("no_pedantic_warnings") { + cflags = [ + # 'this' pointer cannot be NULL...pointer may be assumed + # to always convert to true. + "-Wno-undefined-bool-conversion", + + # Skia doesn't use overrides. + "-Wno-inconsistent-missing-override", + + # Do not warn for implicit type conversions that may change a value. + "-Wno-conversion", + + # shifting a negative signed value is undefined + "-Wno-shift-negative-value", + + # Width of bit-field exceeds width of its type- value will be truncated + "-Wno-bitfield-width", + "-Wno-undefined-var-template", + ] +}
diff --git a/starboard/build/config/modular/variables.gni b/starboard/build/config/modular/variables.gni new file mode 100644 index 0000000..7b220fa --- /dev/null +++ b/starboard/build/config/modular/variables.gni
@@ -0,0 +1,20 @@ +# Copyright 2023 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. + +assert(current_toolchain == default_toolchain, + "Cannot access variables for non-default toolchains") + +if (!is_host_win) { + is_clang_16 = true +}
diff --git a/starboard/build/config/modular/x64/BUILD.gn b/starboard/build/config/modular/x64/BUILD.gn new file mode 100644 index 0000000..3eb0f53 --- /dev/null +++ b/starboard/build/config/modular/x64/BUILD.gn
@@ -0,0 +1,31 @@ +# Copyright 2023 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. + +config("sabi_flags") { + cflags = [ + "-march=x86-64", + "-target", + "x86_64-unknown-linux-elf", + ] +} + +config("x64") { + configs = [ + "//starboard/build/config/sabi", + ":sabi_flags", + ] + + cflags = [ "-isystem" + + rebase_path("//third_party/musl/arch/x86_64", root_build_dir) ] +}
diff --git a/starboard/build/config/starboard_target_type.gni b/starboard/build/config/starboard_target_type.gni index c1ebc17..c3c5916 100644 --- a/starboard/build/config/starboard_target_type.gni +++ b/starboard/build/config/starboard_target_type.gni
@@ -25,13 +25,17 @@ } template("starboard_platform_target") { - target(starboard_target_type, target_name) { + target_type = starboard_target_type + if (defined(invoker.target_type)) { + target_type = invoker.target_type + } + target(target_type, target_name) { forward_variables_from(invoker, [ "extra_configs" ]) if (defined(invoker.extra_configs)) { configs += extra_configs } - if (starboard_target_type == "shared_library") { + if (target_type == "shared_library") { build_loader = false } public_deps = [
diff --git a/starboard/build/is_on_path.py b/starboard/build/is_on_path.py new file mode 100644 index 0000000..c9c149b --- /dev/null +++ b/starboard/build/is_on_path.py
@@ -0,0 +1,32 @@ +#!/usr/bin/env python +# Copyright 2023 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. +"""Script for checking if the current repo on the path.""" + +import os + + +def main(): + try: + # Try to import this file and compare its path to the current file. + import starboard.build.is_on_path # pylint: disable=import-outside-toplevel + this_file = os.path.realpath(__file__) + imported_file = os.path.realpath(starboard.build.is_on_path.__file__) + print(str(this_file == imported_file).lower()) + except ImportError: + print('false') + + +if __name__ == '__main__': + main()
diff --git a/starboard/build/toolchain/BUILD.gn b/starboard/build/toolchain/BUILD.gn index 21a6a3c..5f665db 100644 --- a/starboard/build/toolchain/BUILD.gn +++ b/starboard/build/toolchain/BUILD.gn
@@ -64,8 +64,5 @@ if (defined(native_snarl_linker)) { using_snarl_linker = true } - toolchain_args = { - is_clang = true - } } }
diff --git a/starboard/build/toolchain/cobalt_toolchains.gni b/starboard/build/toolchain/cobalt_toolchains.gni new file mode 100644 index 0000000..4277164 --- /dev/null +++ b/starboard/build/toolchain/cobalt_toolchains.gni
@@ -0,0 +1,53 @@ +# Copyright 2023 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. + +import("//build/config/win/visual_studio_version.gni") +import("//build/toolchain/gcc_toolchain.gni") + +template("cobalt_clang_toolchain") { + gcc_toolchain(target_name) { + forward_variables_from(invoker.variables, + [ + "native_linker_path", + "executable_extension", + "tail_lib_dependencies", + "shlib_extension", + ]) + assert(defined(native_linker_path), + "native_linker_path has to be defined by the platform") + if (!is_host_win) { + prefix = rebase_path("$clang_base_path/bin", root_build_dir) + cc = "$prefix/clang" + cxx = "$prefix/clang++" + ld = native_linker_path + readelf = "readelf" + ar = "${prefix}/llvm-ar" + nm = "nm" + } else { + prefix = llvm_clang_path + cc = "$prefix/clang.exe" + cxx = "$prefix/clang++.exe" + ld = native_linker_path + readelf = "$prefix/llvm-readobj.exe" + ar = "${prefix}/llvm-ar.exe" + nm = "${prefix}/llvm-nm.exe" + } + toolchain_args = { + if (defined(invoker.toolchain_args)) { + forward_variables_from(invoker.toolchain_args, "*") + } + is_clang = true + } + } +}
diff --git a/starboard/common/metrics/stats_tracker.h b/starboard/common/metrics/stats_tracker.h index 9406175..7a217d9 100644 --- a/starboard/common/metrics/stats_tracker.h +++ b/starboard/common/metrics/stats_tracker.h
@@ -52,8 +52,6 @@ StatsTracker& stats_tracker() { if (!stats_tracker_) { - SB_DLOG_IF(ERROR, !undefined_logged_) - << "[once] StatsTracker is not defined."; undefined_logged_ = true; return undefined_stats_tracker_; }
diff --git a/starboard/configuration.h b/starboard/configuration.h index 651f131..da4eff5 100644 --- a/starboard/configuration.h +++ b/starboard/configuration.h
@@ -35,7 +35,7 @@ // The minimum API version allowed by this version of the Starboard headers, // inclusive. -#define SB_MINIMUM_API_VERSION 12 +#define SB_MINIMUM_API_VERSION 13 // The maximum API version allowed by this version of the Starboard headers, // inclusive.
diff --git a/starboard/doc/evergreen/cobalt_evergreen_overview.md b/starboard/doc/evergreen/cobalt_evergreen_overview.md index 2f35e8b..f258cbb 100644 --- a/starboard/doc/evergreen/cobalt_evergreen_overview.md +++ b/starboard/doc/evergreen/cobalt_evergreen_overview.md
@@ -305,8 +305,8 @@ 1. Build the `crashpad_database_util` target and deploy it onto the device. ``` -$ cobalt/build/gn.py -p <partner_port_name> -c qa -$ ninja -C out/<partner_port_name>_qa crashpad_database_util +$ gn gen out/<partner_port_name>_qa --args='target_platform="<partner_port_name>" build_type="qa"' +$ ninja -C out/<partner_port_name>_qa native_target/crashpad_database_util ``` 2. Remove the existing state for crashpad as it throttles uploads to 1 per hour: ```
diff --git a/starboard/elf_loader/BUILD.gn b/starboard/elf_loader/BUILD.gn index bca4126..184ccb2 100644 --- a/starboard/elf_loader/BUILD.gn +++ b/starboard/elf_loader/BUILD.gn
@@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import("//starboard/build/config/os_definitions.gni") + _elf_loader_sources = [ "dynamic_section.cc", "dynamic_section.h", @@ -88,7 +90,8 @@ } } -if (current_toolchain == starboard_toolchain) { +# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms. +if (current_toolchain == starboard_toolchain && !is_host_win) { target(starboard_level_final_executable_type, "elf_loader_sandbox") { data_deps = [ "//third_party/icu:icudata" ] if (cobalt_font_package == "empty") { @@ -152,31 +155,34 @@ } } -target(gtest_target_type, "elf_loader_test") { - testonly = true - sources = [ "//starboard/common/test_main.cc" ] - deps = [ - "//starboard", - "//testing/gmock", - "//testing/gtest", - ] - - if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" || - target_cpu == "arm64") { - sources += [ - "dynamic_section_test.cc", - "elf_header_test.cc", - "elf_loader_test.cc", - "lz4_file_impl_test.cc", - "program_table_test.cc", - "relocations_test.cc", - ] - deps += [ - ":copy_elf_loader_testdata", - ":elf_loader", +# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms. +if (!is_host_win) { + target(gtest_target_type, "elf_loader_test") { + testonly = true + sources = [ "//starboard/common/test_main.cc" ] + deps = [ + "//starboard", + "//testing/gmock", + "//testing/gtest", ] - data_deps = [ ":copy_elf_loader_testdata" ] + if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" || + target_cpu == "arm64") { + sources += [ + "dynamic_section_test.cc", + "elf_header_test.cc", + "elf_loader_test.cc", + "lz4_file_impl_test.cc", + "program_table_test.cc", + "relocations_test.cc", + ] + deps += [ + ":copy_elf_loader_testdata", + ":elf_loader", + ] + + data_deps = [ ":copy_elf_loader_testdata" ] + } } }
diff --git a/starboard/elf_loader/sandbox.cc b/starboard/elf_loader/sandbox.cc index 2f2eb05..d86595e 100644 --- a/starboard/elf_loader/sandbox.cc +++ b/starboard/elf_loader/sandbox.cc
@@ -58,7 +58,7 @@ GetEvergreenInfo(&evergreen_info); if (!third_party::crashpad::wrapper::AddEvergreenInfoToCrashpad( evergreen_info)) { - SB_LOG(ERROR) << "Could not send Cobalt library information into Crashapd."; + SB_LOG(ERROR) << "Could not send Cobalt library information into Crashpad."; } else { SB_LOG(INFO) << "Loaded Cobalt library information into Crashpad."; }
diff --git a/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn b/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn index c4403ea..d3474e2 100644 --- a/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn +++ b/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn
@@ -27,8 +27,10 @@ ":sabi_flags", "//starboard/evergreen/arm/shared/platform_configuration", ] - ldflags = [ - "-Wl,-m", - "-Wl,armelf", - ] + if (sb_is_evergreen) { + ldflags = [ + "-Wl,-m", + "-Wl,armelf", + ] + } }
diff --git a/starboard/evergreen/shared/platform_configuration/BUILD.gn b/starboard/evergreen/shared/platform_configuration/BUILD.gn index 2340c86..3bb69ba 100644 --- a/starboard/evergreen/shared/platform_configuration/BUILD.gn +++ b/starboard/evergreen/shared/platform_configuration/BUILD.gn
@@ -13,69 +13,30 @@ # limitations under the License. config("platform_configuration") { - ldflags = [ - "-fuse-ld=lld", - "-Wl,--build-id", - "-Wl,--gc-sections", - "-Wl,-X", - "-Wl,-v", - "-Wl,-eh-frame-hdr", - "-Wl,--fini=__cxa_finalize", - "-Wl,-shared", - "-Wl,-L$clang_base_path", - "-Wl,-L/usr/lib", - "-Wl,-L/lib", - "-Wl,-u GetEvergreenSabiString", - ] + configs = [ "//starboard/build/config/modular" ] + ldflags = [] + if (sb_is_evergreen) { + ldflags += [ + "-fuse-ld=lld", + "-Wl,--build-id", + "-Wl,--gc-sections", + "-Wl,-X", + "-Wl,-v", + "-Wl,-eh-frame-hdr", + "-Wl,--fini=__cxa_finalize", + "-Wl,-shared", + "-Wl,-L$clang_base_path", + "-Wl,-L/usr/lib", + "-Wl,-L/lib", + "-Wl,-u GetEvergreenSabiString", + ] + } if (sb_is_evergreen) { ldflags += [ "-nostdlib" ] } - cflags = [ - "-ffunction-sections", - "-fdata-sections", - "-fPIC", - "-nostdlibinc", - "-isystem" + rebase_path("//third_party/llvm-project/libcxxabi/include", - root_build_dir), - "-isystem" + rebase_path("//third_party/llvm-project/libunwind/include", - root_build_dir), - "-isystem" + rebase_path("//third_party/llvm-project/libcxx/include", - root_build_dir), - "-isystem" + rebase_path("//third_party/musl/include", root_build_dir), - "-isystem" + rebase_path("//third_party/musl/arch/generic", root_build_dir), - ] - - cflags_cc = [ - "-nostdinc++", - "-std=c++17", - ] defines = [ - # Ensure that the Starboardized __external_threading file is included. - "_LIBCPP_HAS_THREAD_API_EXTERNAL", - - # Ensure that only the forward declarations and type definitions are included - # in __external_threading. - "_LIBCPP_HAS_THREAD_LIBRARY_EXTERNAL", - - # Enable GNU extensions to get prototypes like ffsl. - "_GNU_SOURCE=1", - - "_LIBCPP_HAS_MUSL_LIBC", - "__STDC_FORMAT_MACROS", # so that we get PRI* - - # File format of the shared object we will generate. - "__ELF__", - - # Use scalar portable implementations instead of Clang/GCC vector - # extensions in SkVx.h. - "SKNX_NO_SIMD", - - # By default, <EGL/eglplatform.h> pulls in some X11 headers that have some - # nasty macros (|Status|, for example) that conflict with Chromium base. - "MESA_EGL_NO_X11_HEADERS", - # During Evergreen updates the CRX package is kept in-memory, instead of # on the file system, before getting unpacked. # TODO(b/158043520): we need to make significant customizations to Chromium @@ -88,150 +49,21 @@ "IN_MEMORY_UPDATES", ] - if (is_debug) { - cflags += [ - "-O0", - "-frtti", - "-g", - ] - } else if (is_devel) { - cflags += [ - "-O2", - "-frtti", - "-g", - ] - } else { - cflags += [ - "-gline-tables-only", - "-fno-rtti", - ] - } - - if (is_clang) { - cflags += [ - "-fcolor-diagnostics", - - # Default visibility to hidden, to enable dead stripping. - "-fvisibility=hidden", - - # Warns on switches on enums that cover all enum values but - # also contain a default: branch. Chrome is full of that. - "-Wno-covered-switch-default", - - # protobuf uses hash_map. - "-Wno-deprecated", - - "-fno-exceptions", - - # Enable unwind tables used by libunwind for stack traces. - "-funwind-tables", - - # Disable usage of frame pointers. - "-fomit-frame-pointer", - - # Don"t warn about the "struct foo f = {0};" initialization pattern. - "-Wno-missing-field-initializers", - - # Do not warn for implicit sign conversions. - "-Wno-sign-conversion", - - "-fno-strict-aliasing", # See http://crbug.com/32204 - - "-Wno-unnamed-type-template-args", - - # Triggered by the COMPILE_ASSERT macro. - "-Wno-unused-local-typedef", - - # Do not warn if a function or variable cannot be implicitly - # instantiated. - "-Wno-undefined-var-template", - - # Do not warn about an implicit exception spec mismatch. - "-Wno-implicit-exception-spec-mismatch", - - # It's OK not to use some input parameters. - "-Wno-unused-parameter", - "-Wno-conversion", - "-Wno-bitwise-op-parentheses", - "-Wno-shift-op-parentheses", - "-Wno-shorten-64-to-32", - "-fno-use-cxa-atexit", - ] - } - - if (is_clang_16) { - cflags += [ - # Do not remove null pointer checks. - "-fno-delete-null-pointer-checks", - ] - } - if (use_asan) { - cflags += [ - "-fsanitize=address", - "-fno-omit-frame-pointer", - ] - ldflags += [ "-fsanitize=address", # Force linking of the helpers in sanitizer_options.cc "-Wl,-u_sanitizer_options_link_helper", ] - - defines += [ "ADDRESS_SANITIZER" ] - - if (asan_symbolizer_path != "") { - defines += [ "ASAN_SYMBOLIZER_PATH=\"${asan_symbolizer_path}\"" ] - } } else if (use_tsan) { - cflags += [ - "-fsanitize=thread", - "-fno-omit-frame-pointer", - ] - ldflags += [ "-fsanitize=thread" ] - - defines += [ "THREAD_SANITIZER" ] } } -config("speed") { - cflags = [ "-O2" ] -} - config("size") { - cflags = [ "-Os" ] - if (is_qa || is_gold) { + configs = [ "//starboard/build/config/modular:size" ] + if (sb_is_evergreen && (is_qa || is_gold)) { ldflags = [ "-Wl,--icf=safe" ] } } - -config("pedantic_warnings") { - cflags = [ - "-Wall", - "-Wextra", - "-Wunreachable-code", - ] -} - -config("no_pedantic_warnings") { - cflags = [ - # 'this' pointer cannot be NULL...pointer may be assumed - # to always convert to true. - "-Wno-undefined-bool-conversion", - - # Skia doesn't use overrides. - "-Wno-inconsistent-missing-override", - - # Do not warn for implicit type conversions that may change a value. - "-Wno-conversion", - - # shifting a negative signed value is undefined - "-Wno-shift-negative-value", - - # Width of bit-field exceeds width of its type- value will be truncated - "-Wno-bitfield-width", - "-Wno-undefined-var-template", - ] -}
diff --git a/starboard/evergreen/shared/platform_configuration/configuration.gni b/starboard/evergreen/shared/platform_configuration/configuration.gni index 4327e1d..98bf65f 100644 --- a/starboard/evergreen/shared/platform_configuration/configuration.gni +++ b/starboard/evergreen/shared/platform_configuration/configuration.gni
@@ -29,13 +29,13 @@ starboard_level_final_executable_type = "shared_library" starboard_level_gtest_target_type = "shared_library" -speed_config_path = "//starboard/evergreen/shared/platform_configuration:speed" +speed_config_path = "//starboard/build/config/modular:speed" size_config_path = "//starboard/evergreen/shared/platform_configuration:size" pedantic_warnings_config_path = - "//starboard/evergreen/shared/platform_configuration:pedantic_warnings" + "//starboard/build/config/modular:pedantic_warnings" no_pedantic_warnings_config_path = - "//starboard/evergreen/shared/platform_configuration:no_pedantic_warnings" + "//starboard/build/config/modular:no_pedantic_warnings" cobalt_licenses_platform = "evergreen"
diff --git a/starboard/evergreen/x64/platform_configuration/BUILD.gn b/starboard/evergreen/x64/platform_configuration/BUILD.gn index d49869b..ead85ee 100644 --- a/starboard/evergreen/x64/platform_configuration/BUILD.gn +++ b/starboard/evergreen/x64/platform_configuration/BUILD.gn
@@ -12,18 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -config("sabi_flags") { - cflags = [ - "-march=x86-64", - "-target", - "x86_64-unknown-linux-elf", - ] -} - config("platform_configuration") { configs = [ - "//starboard/build/config/sabi", - ":sabi_flags", + "//starboard/build/config/modular/x64", "//starboard/evergreen/shared/platform_configuration", ] @@ -31,6 +22,4 @@ "-Wl,-m", "-Wl,elf_x86_64", ] - cflags = [ "-isystem" + - rebase_path("//third_party/musl/arch/x86_64", root_build_dir) ] }
diff --git a/starboard/extension/extension_test.cc b/starboard/extension/extension_test.cc index d32cc84..874cce1 100644 --- a/starboard/extension/extension_test.cc +++ b/starboard/extension/extension_test.cc
@@ -21,12 +21,14 @@ #include "starboard/extension/font.h" #include "starboard/extension/free_space.h" #include "starboard/extension/graphics.h" +#include "starboard/extension/ifa.h" #include "starboard/extension/installation_manager.h" #include "starboard/extension/javascript_cache.h" #include "starboard/extension/media_session.h" #include "starboard/extension/memory_mapped_file.h" #include "starboard/extension/platform_info.h" #include "starboard/extension/platform_service.h" +#include "starboard/extension/time_zone.h" #include "starboard/extension/updater_notification.h" #include "starboard/extension/url_fetcher_observer.h" #include "starboard/system.h" @@ -438,5 +440,46 @@ << "Extension struct should be a singleton"; } +TEST(ExtensionTest, TimeZone) { + typedef StarboardExtensionTimeZoneApi ExtensionApi; + const char* kExtensionName = kStarboardExtensionTimeZoneName; + + const ExtensionApi* extension_api = + static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); + if (!extension_api) { + return; + } + + EXPECT_STREQ(extension_api->name, kExtensionName); + EXPECT_EQ(extension_api->version, 1u); + EXPECT_NE(extension_api->SetTimeZone, nullptr); + + const ExtensionApi* second_extension_api = + static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); + EXPECT_EQ(second_extension_api, extension_api) + << "Extension struct should be a singleton"; +} + +TEST(ExtensionTest, Ifa) { + typedef StarboardExtensionIfaApi ExtensionApi; + const char* kExtensionName = kStarboardExtensionIfaName; + + const ExtensionApi* extension_api = + static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); + if (!extension_api) { + return; + } + + EXPECT_STREQ(extension_api->name, kExtensionName); + EXPECT_EQ(extension_api->version, 1u); + EXPECT_NE(extension_api->GetAdvertisingId, nullptr); + EXPECT_NE(extension_api->GetLimitAdTracking, nullptr); + + const ExtensionApi* second_extension_api = + static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName)); + EXPECT_EQ(second_extension_api, extension_api) + << "Extension struct should be a singleton"; +} + } // namespace extension } // namespace starboard
diff --git a/starboard/extension/ifa.h b/starboard/extension/ifa.h new file mode 100644 index 0000000..a207049 --- /dev/null +++ b/starboard/extension/ifa.h
@@ -0,0 +1,58 @@ +// Copyright 2023 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_EXTENSION_IFA_H_ +#define STARBOARD_EXTENSION_IFA_H_ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define kStarboardExtensionIfaName "dev.cobalt.extension.Ifa" + +typedef struct StarboardExtensionIfaApi { + // Name should be the string |kCobaltExtensionIfaName|. + // This helps to validate that the extension API is correct. + const char* name; + + // This specifies the version of the API that is implemented. + uint32_t version; + + // The fields below this point were added in version 1 or later. + + // Advertising ID or IFA, typically a 128-bit UUID + // Please see https://iabtechlab.com/OTT-IFA for details. + // Corresponds to 'ifa' field. Note: `ifa_type` field is not provided. + // In Starboard 14 this the value is retrieved through the system + // property `kSbSystemPropertyAdvertisingId` defined in + // `starboard/system.h`. + bool (*GetAdvertisingId)(char* out_value, int value_length); + + // Limit advertising tracking, treated as boolean. Set to nonzero to indicate + // a true value. Corresponds to 'lmt' field. + // In Starboard 14 this the value is retrieved through the system + // property `kSbSystemPropertyLimitAdTracking` defined in + // `starboard/system.h`. + + bool (*GetLimitAdTracking)(char* out_value, int value_length); + +} CobaltExtensionIfaApi; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // STARBOARD_EXTENSION_IFA_H_
diff --git a/starboard/extension/time_zone.h b/starboard/extension/time_zone.h new file mode 100644 index 0000000..bfddcea --- /dev/null +++ b/starboard/extension/time_zone.h
@@ -0,0 +1,47 @@ +// Copyright 2023 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_EXTENSION_TIME_ZONE_H_ +#define STARBOARD_EXTENSION_TIME_ZONE_H_ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define kStarboardExtensionTimeZoneName "dev.starboard.extension.TimeZone" + +typedef struct StarboardExtensionTimeZoneApi { + // Name should be the string |kStarboardExtensionSetTimeZoneName|. + // This helps to validate that the extension API is correct. + const char* name; + + // This specifies the version of the API that is implemented. + uint32_t version; + + // Sets the current time zone to the specified time zone name. + // Note: This function should not be called with a NULL or empty + // string. It does not actually change the system clock, so it + // will not affect the time displayed on the system clock or + // used by other system processes. + bool (*SetTimeZone)(const char* time_zone_name); + +} StarboardExtensionTimeZoneApi; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // STARBOARD_EXTENSION_TIME_ZONE_H_
diff --git a/starboard/linux/shared/BUILD.gn b/starboard/linux/shared/BUILD.gn index 57b7908..4b4e3fa 100644 --- a/starboard/linux/shared/BUILD.gn +++ b/starboard/linux/shared/BUILD.gn
@@ -66,6 +66,8 @@ "//starboard/linux/shared/decode_target_internal.cc", "//starboard/linux/shared/decode_target_internal.h", "//starboard/linux/shared/decode_target_release.cc", + "//starboard/linux/shared/ifa.cc", + "//starboard/linux/shared/ifa.h", "//starboard/linux/shared/media_is_audio_supported.cc", "//starboard/linux/shared/media_is_video_supported.cc", "//starboard/linux/shared/netlink.cc", @@ -80,6 +82,8 @@ "//starboard/linux/shared/system_get_extensions.cc", "//starboard/linux/shared/system_get_path.cc", "//starboard/linux/shared/system_has_capability.cc", + "//starboard/linux/shared/time_zone.cc", + "//starboard/linux/shared/time_zone.h", "//starboard/shared/alsa/alsa_audio_sink_type.cc", "//starboard/shared/alsa/alsa_audio_sink_type.h", "//starboard/shared/alsa/alsa_util.cc", @@ -133,6 +137,7 @@ "//starboard/shared/linux/thread_get_id.cc", "//starboard/shared/linux/thread_get_name.cc", "//starboard/shared/linux/thread_set_name.cc", + "//starboard/shared/linux/time_zone_get_name.cc", "//starboard/shared/nouser/user_get_current.cc", "//starboard/shared/nouser/user_get_property.cc", "//starboard/shared/nouser/user_get_signed_in.cc", @@ -217,7 +222,6 @@ "//starboard/shared/posix/time_get_now.cc", "//starboard/shared/posix/time_is_time_thread_now_supported.cc", "//starboard/shared/posix/time_zone_get_current.cc", - "//starboard/shared/posix/time_zone_get_name.cc", "//starboard/shared/pthread/condition_variable_broadcast.cc", "//starboard/shared/pthread/condition_variable_create.cc", "//starboard/shared/pthread/condition_variable_destroy.cc",
diff --git a/starboard/linux/shared/ifa.cc b/starboard/linux/shared/ifa.cc new file mode 100644 index 0000000..21b17dc --- /dev/null +++ b/starboard/linux/shared/ifa.cc
@@ -0,0 +1,65 @@ +// Copyright 2023 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/linux/shared/ifa.h" + +#include "starboard/extension/ifa.h" + +#include "starboard/common/string.h" +#include "starboard/shared/environment.h" + +namespace starboard { +namespace shared { + +namespace { + +bool CopyStringAndTestIfSuccess(char* out_value, + int value_length, + const char* from_value) { + if (strlen(from_value) + 1 > value_length) + return false; + starboard::strlcpy(out_value, from_value, value_length); + return true; +} + +// Definitions of any functions included as components in the extension +// are added here. + +bool GetAdvertisingId(char* out_value, int value_length) { + return CopyStringAndTestIfSuccess( + out_value, value_length, + starboard::GetEnvironment("COBALT_ADVERTISING_ID").c_str()); +} + +bool GetLimitAdTracking(char* out_value, int value_length) { + return CopyStringAndTestIfSuccess( + out_value, value_length, + GetEnvironment("COBALT_LIMIT_AD_TRACKING").c_str()); +} + +const StarboardExtensionIfaApi kIfaApi = { + kStarboardExtensionIfaName, + 1, // API version that's implemented. + &GetAdvertisingId, + &GetLimitAdTracking, +}; + +} // namespace + +const void* GetIfaApi() { + return &kIfaApi; +} + +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/posix/time_zone_get_name.cc b/starboard/linux/shared/ifa.h similarity index 61% rename from starboard/shared/posix/time_zone_get_name.cc rename to starboard/linux/shared/ifa.h index 5a1e013..fbe61ab 100644 --- a/starboard/shared/posix/time_zone_get_name.cc +++ b/starboard/linux/shared/ifa.h
@@ -1,4 +1,4 @@ -// Copyright 2015 The Cobalt Authors. All Rights Reserved. +// Copyright 2023 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. @@ -12,13 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/time_zone.h" +#ifndef STARBOARD_LINUX_SHARED_IFA_H_ +#define STARBOARD_LINUX_SHARED_IFA_H_ -#include <time.h> +// Omit namespace linux due to symbol name conflict. +namespace starboard { +namespace shared { -const char* SbTimeZoneGetName() { - // TODO: Using tzname assumes that tzset() has been called at some - // point. That should happen as part of Starboard's main loop initialization, - // but that doesn't exist yet. - return tzname[0]; -} +const void* GetIfaApi(); + +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_LINUX_SHARED_IFA_H_
diff --git a/starboard/linux/shared/system_get_extensions.cc b/starboard/linux/shared/system_get_extensions.cc index 4a0b280..efc9169 100644 --- a/starboard/linux/shared/system_get_extensions.cc +++ b/starboard/linux/shared/system_get_extensions.cc
@@ -20,9 +20,13 @@ #include "starboard/extension/demuxer.h" #include "starboard/extension/enhanced_audio.h" #include "starboard/extension/free_space.h" +#include "starboard/extension/ifa.h" #include "starboard/extension/memory_mapped_file.h" #include "starboard/extension/platform_service.h" +#include "starboard/extension/time_zone.h" +#include "starboard/linux/shared/ifa.h" #include "starboard/linux/shared/soft_mic_platform_service.h" +#include "starboard/linux/shared/time_zone.h" #include "starboard/shared/enhanced_audio/enhanced_audio.h" #include "starboard/shared/ffmpeg/ffmpeg_demuxer.h" #include "starboard/shared/posix/free_space.h" @@ -74,5 +78,13 @@ return use_ffmpeg_demuxer ? starboard::shared::ffmpeg::GetFFmpegDemuxerApi() : NULL; } + if (strcmp(name, kStarboardExtensionTimeZoneName) == 0) { + return starboard::shared::GetTimeZoneApi(); + } +#if SB_API_VERSION < 14 + if (strcmp(name, kStarboardExtensionIfaName) == 0) { + return starboard::shared::GetIfaApi(); + } +#endif // SB_API_VERSION < 14 return NULL; }
diff --git a/starboard/linux/shared/system_get_path.cc b/starboard/linux/shared/system_get_path.cc index 0f70002..999a016 100644 --- a/starboard/linux/shared/system_get_path.cc +++ b/starboard/linux/shared/system_get_path.cc
@@ -113,6 +113,19 @@ } #endif +bool GetParentDirectory(char* out_path) { + if (!out_path) { + return false; + } + char* last_slash = const_cast<char*>(strrchr(out_path, '/')); + if (!last_slash) { + return false; + } + + *last_slash = '\0'; + return true; +} + // Places up to |path_size| - 1 characters of the path to the directory // containing the current executable in |out_path|, ensuring it is // NULL-terminated. Returns success status. The result being greater than @@ -122,14 +135,7 @@ if (!GetExecutablePath(out_path, path_size)) { return false; } - - char* last_slash = const_cast<char*>(strrchr(out_path, '/')); - if (!last_slash) { - return false; - } - - *last_slash = '\0'; - return true; + return GetParentDirectory(out_path); } // Gets only the name portion of the current executable. @@ -168,6 +174,11 @@ if (!GetExecutableDirectory(out_path, path_size)) { return false; } +#ifdef USE_COMMON_CONTENT_DIR + if (!GetParentDirectory(out_path)) { + return false; + } +#endif if (starboard::strlcat(out_path, "/content", path_size) >= path_size) { return false; }
diff --git a/starboard/linux/shared/time_zone.cc b/starboard/linux/shared/time_zone.cc new file mode 100644 index 0000000..30919af --- /dev/null +++ b/starboard/linux/shared/time_zone.cc
@@ -0,0 +1,60 @@ +// Copyright 2023 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/linux/shared/time_zone.h" + +#include "starboard/extension/time_zone.h" + +#include <stdlib.h> +#include <time.h> +#include <cstring> + +#include "starboard/common/log.h" + +namespace starboard { +namespace shared { + +namespace { + +// Definitions of any functions included as components in the extension +// are added here. + +bool SetTimeZone(const char* time_zone_name) { + if (time_zone_name == nullptr || strlen(time_zone_name) == 0) { + SB_LOG(ERROR) << "Set time zone failed!"; + SB_LOG(ERROR) << "Time zone name can't be null or empty string."; + return false; + } + if (setenv("TZ", time_zone_name, 1) != 0) { + SB_LOG(WARNING) << "Set time zone failed!"; + return false; + } + tzset(); + return true; +} + +const StarboardExtensionTimeZoneApi kTimeZoneApi = { + kStarboardExtensionTimeZoneName, + 1, // API version that's implemented. + &SetTimeZone, +}; + +} // namespace + +const void* GetTimeZoneApi() { + return &kTimeZoneApi; +} + +} // namespace shared +} // namespace starboard
diff --git a/starboard/linux/shared/time_zone.h b/starboard/linux/shared/time_zone.h new file mode 100644 index 0000000..6f063b0 --- /dev/null +++ b/starboard/linux/shared/time_zone.h
@@ -0,0 +1,27 @@ +// Copyright 2023 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_LINUX_SHARED_TIME_ZONE_H_ +#define STARBOARD_LINUX_SHARED_TIME_ZONE_H_ + +// Omit namespace linux due to symbol name conflict. +namespace starboard { +namespace shared { + +const void* GetTimeZoneApi(); + +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_LINUX_SHARED_TIME_ZONE_H_
diff --git a/starboard/linux/x64x11/main.cc b/starboard/linux/x64x11/main.cc index 5cfd197..1929336 100644 --- a/starboard/linux/x64x11/main.cc +++ b/starboard/linux/x64x11/main.cc
@@ -50,7 +50,6 @@ : starboard::common::GetCACertificatesPath(evergreen_content_path); if (ca_certificates_path.empty()) { SB_LOG(ERROR) << "Failed to get CA certificates path"; - return 1; } #if !SB_IS(MODULAR)
diff --git a/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn b/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn index ab9cf36..cbdd8af 100644 --- a/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn +++ b/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
@@ -16,6 +16,7 @@ if (current_toolchain == default_toolchain && sb_is_modular && !sb_is_evergreen) { configs = [ "//starboard/evergreen/x64/platform_configuration" ] + ldflags = [ "-Wl,--gc-sections" ] } else { configs = [ ":libraries",
diff --git a/starboard/linux/x64x11/toolchain/BUILD.gn b/starboard/linux/x64x11/toolchain/BUILD.gn index bcee641..79b7991 100644 --- a/starboard/linux/x64x11/toolchain/BUILD.gn +++ b/starboard/linux/x64x11/toolchain/BUILD.gn
@@ -13,12 +13,19 @@ # limitations under the License. import("//build/config/clang/clang.gni") +import("//starboard/build/toolchain/cobalt_toolchains.gni") import("//starboard/shared/toolchain/overridable_gcc_toolchain.gni") overridable_clang_toolchain("starboard") { clang_base_path = clang_base_path } +cobalt_clang_toolchain("cobalt") { + variables = { + native_linker_path = "$clang_base_path/bin/clang++" + } +} + overridable_clang_toolchain("target") { clang_base_path = clang_base_path }
diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn index e0384a1..053c0c2 100644 --- a/starboard/loader_app/BUILD.gn +++ b/starboard/loader_app/BUILD.gn
@@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import("//starboard/build/config/os_definitions.gni") + _common_loader_app_sources = [ "loader_app.cc", "loader_app_switches.cc", @@ -247,20 +249,23 @@ ] } -target(gtest_target_type, "installation_manager_test") { - testonly = true - sources = [ - "//starboard/common/test_main.cc", - "installation_manager_test.cc", - "pending_restart_test.cc", - ] - deps = [ - ":installation_manager", - ":installation_store_proto", - ":pending_restart", - "//testing/gmock", - "//testing/gtest", - ] +# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms. +if (!is_host_win) { + target(gtest_target_type, "installation_manager_test") { + testonly = true + sources = [ + "//starboard/common/test_main.cc", + "installation_manager_test.cc", + "pending_restart_test.cc", + ] + deps = [ + ":installation_manager", + ":installation_store_proto", + ":pending_restart", + "//testing/gmock", + "//testing/gtest", + ] + } } static_library("slot_management") { @@ -285,22 +290,25 @@ } } -target(gtest_target_type, "slot_management_test") { - testonly = true - sources = [ - "//starboard/common/test_main.cc", - "slot_management_test.cc", - ] - deps = [ - ":app_key_files", - ":drain_file", - ":installation_manager", - ":installation_store_proto", - ":slot_management", - "//starboard/elf_loader:sabi_string", - "//testing/gmock", - "//testing/gtest", - ] +# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms. +if (!is_host_win) { + target(gtest_target_type, "slot_management_test") { + testonly = true + sources = [ + "//starboard/common/test_main.cc", + "slot_management_test.cc", + ] + deps = [ + ":app_key_files", + ":drain_file", + ":installation_manager", + ":installation_store_proto", + ":slot_management", + "//starboard/elf_loader:sabi_string", + "//testing/gmock", + "//testing/gtest", + ] + } } static_library("pending_restart") { @@ -332,15 +340,18 @@ deps = [ "//starboard" ] } -target(gtest_target_type, "reset_evergreen_update_test") { - testonly = true - sources = [ - "//starboard/common/test_main.cc", - "reset_evergreen_update_test.cc", - ] - deps = [ - ":reset_evergreen_update", - "//testing/gmock", - "//testing/gtest", - ] +# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms. +if (!is_host_win) { + target(gtest_target_type, "reset_evergreen_update_test") { + testonly = true + sources = [ + "//starboard/common/test_main.cc", + "reset_evergreen_update_test.cc", + ] + deps = [ + ":reset_evergreen_update", + "//testing/gmock", + "//testing/gtest", + ] + } }
diff --git a/starboard/loader_app/loader_app.cc b/starboard/loader_app/loader_app.cc index 95f7ece..93cf9f4 100644 --- a/starboard/loader_app/loader_app.cc +++ b/starboard/loader_app/loader_app.cc
@@ -143,7 +143,7 @@ GetEvergreenInfo(&evergreen_info); if (!third_party::crashpad::wrapper::AddEvergreenInfoToCrashpad( evergreen_info)) { - SB_LOG(ERROR) << "Could not send Cobalt library information into Crashapd."; + SB_LOG(ERROR) << "Could not send Cobalt library information into Crashpad."; } else { SB_LOG(INFO) << "Loaded Cobalt library information into Crashpad."; }
diff --git a/starboard/loader_app/slot_management.cc b/starboard/loader_app/slot_management.cc index fc5549a..8b1af98 100644 --- a/starboard/loader_app/slot_management.cc +++ b/starboard/loader_app/slot_management.cc
@@ -289,7 +289,7 @@ if (!third_party::crashpad::wrapper::AddEvergreenInfoToCrashpad( evergreen_info)) { SB_LOG(ERROR) - << "Could not send Cobalt library information into Crashapd."; + << "Could not send Cobalt library information into Crashpad."; } else { SB_LOG(INFO) << "Loaded Cobalt library information into Crashpad."; }
diff --git a/starboard/nplb/BUILD.gn b/starboard/nplb/BUILD.gn index 664751b..3f314cf 100644 --- a/starboard/nplb/BUILD.gn +++ b/starboard/nplb/BUILD.gn
@@ -271,7 +271,7 @@ cflags = [ "-Wno-enum-constexpr-conversion" ] } - # TODO b/296238576 Add these tests for windows based platform modular builds + # TODO: b/297808555 - Add these tests for windows based platform modular builds. if (sb_is_modular && !sb_is_evergreen && is_host_win) { sources -= [ "maximum_player_configuration_explorer.cc", @@ -279,6 +279,7 @@ "maximum_player_configuration_explorer_test.cc", "media_buffer_test.cc", "media_set_audio_write_duration_test.cc", + "multiple_player_test.cc", "player_create_test.cc", "player_creation_param_helpers.cc", "player_creation_param_helpers.h",
diff --git a/starboard/nplb/media_configuration_test.cc b/starboard/nplb/media_configuration_test.cc index 07c5937..432b12a 100644 --- a/starboard/nplb/media_configuration_test.cc +++ b/starboard/nplb/media_configuration_test.cc
@@ -13,7 +13,9 @@ // limitations under the License. #include "starboard/media.h" + #include "starboard/nplb/performance_helpers.h" +#include "starboard/time.h" #include "testing/gtest/include/gtest/gtest.h" namespace starboard { @@ -25,9 +27,13 @@ const int count_audio_output = SbMediaGetAudioOutputCount(); for (int i = 0; i < count_audio_output; ++i) { + constexpr int kNumberOfCalls = 100; + constexpr SbTime kMaxAverageTimePerCall = 500; + SbMediaAudioConfiguration configuration; - TEST_PERF_FUNCWITHARGS_DEFAULT(SbMediaGetAudioConfiguration, i, - &configuration); + TEST_PERF_FUNCWITHARGS_EXPLICIT(kNumberOfCalls, kMaxAverageTimePerCall, + SbMediaGetAudioConfiguration, i, + &configuration); } }
diff --git a/starboard/nplb/performance_helpers.h b/starboard/nplb/performance_helpers.h index 62639df..7a9b538 100644 --- a/starboard/nplb/performance_helpers.h +++ b/starboard/nplb/performance_helpers.h
@@ -38,11 +38,16 @@ // Measure time pre calls to |f|. const SbTimeMonotonic time_start = SbTimeGetMonotonicNow(); + SbLogPriority initial_log_level = starboard::logging::GetMinLogLevel(); + starboard::logging::SetMinLogLevel(kSbLogPriorityFatal); + // Call |f| |count_calls| times. for (int i = 0; i < count_calls; ++i) { f(args...); } + starboard::logging::SetMinLogLevel(initial_log_level); + // Measure time post calls to |f|. const SbTimeMonotonic time_last = SbTimeGetMonotonicNow(); const SbTimeMonotonic time_delta = time_last - time_start;
diff --git a/starboard/nplb/player_write_sample_test.cc b/starboard/nplb/player_write_sample_test.cc index 414a4ea..5bc1653 100644 --- a/starboard/nplb/player_write_sample_test.cc +++ b/starboard/nplb/player_write_sample_test.cc
@@ -263,11 +263,15 @@ SbTime current_time_offset = 0; int num_of_buffers_per_write = player_fixture.ConvertDurationToAudioBufferCount(kDurationPerWrite); + int count = 0; while (current_time_offset < kDurationToPlay) { + const SbTime kDurationToDiscard = + count % 2 == 0 ? kSbTimeSecond : kSbTimeMax; + count++; // Discard from front. for (int i = 0; i < kNumberOfBuffersToDiscard; i++) { samples.AddAudioSamples(written_buffer_index, 1, current_time_offset, - kSbTimeSecond, 0); + kDurationToDiscard, 0); } samples.AddAudioSamples(written_buffer_index, num_of_buffers_per_write); @@ -277,7 +281,7 @@ // Discard from back. for (int i = 0; i < kNumberOfBuffersToDiscard; i++) { samples.AddAudioSamples(written_buffer_index, 1, current_time_offset, 0, - kSbTimeSecond); + kDurationToDiscard); } } samples.AddAudioEOS();
diff --git a/starboard/nplb/time_zone_get_current_test.cc b/starboard/nplb/time_zone_get_current_test.cc index 0c136d0..0c2b3b8 100644 --- a/starboard/nplb/time_zone_get_current_test.cc +++ b/starboard/nplb/time_zone_get_current_test.cc
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "starboard/extension/time_zone.h" #include "starboard/nplb/time_constants.h" +#include "starboard/system.h" #include "starboard/time_zone.h" #include "testing/gtest/include/gtest/gtest.h" @@ -34,9 +36,64 @@ // ... and +24 hours from the Prime Meridian, inclusive EXPECT_LE(zone, 24 * 60); - if (zone == 0) { - SB_LOG(WARNING) << "SbTimeZoneGetCurrent() returns 0. This is only correct " - "if the current time zone is the same as UTC"; + static auto const* time_zone_extension = + static_cast<const StarboardExtensionTimeZoneApi*>( + SbSystemGetExtension(kStarboardExtensionTimeZoneName)); + if (time_zone_extension) { + ASSERT_STREQ(time_zone_extension->name, kStarboardExtensionTimeZoneName); + ASSERT_EQ(time_zone_extension->version, 1u); + time_zone_extension->SetTimeZone("UTC"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 0); + + // Atlantic time zone, UTC−04:00 + time_zone_extension->SetTimeZone("America/Puerto_Rico"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 240); + + // Eastern time zone, UTC−05:00 + time_zone_extension->SetTimeZone("America/New_York"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 300); + + time_zone_extension->SetTimeZone("US/Eastern"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 300); + + // Central time zone, UTC−06:00 + time_zone_extension->SetTimeZone("America/Chicago"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 360); + + // Mountain time zone, UTC−07:00 + time_zone_extension->SetTimeZone("US/Mountain"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 420); + + // Pacific time zone, UTC-08:00 + time_zone_extension->SetTimeZone("US/Pacific"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 480); + + // Alaska time zone, UTC-09:00 + time_zone_extension->SetTimeZone("US/Alaska"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 540); + + // Hawaii-Aleutian time zone, UTC-10:00 + time_zone_extension->SetTimeZone("Pacific/Honolulu"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 600); + + // American Samoa time zone, UTC-11:00 + time_zone_extension->SetTimeZone("US/Samoa"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, 660); + + // American Samoa time zone, UTC+10:00 + time_zone_extension->SetTimeZone("Pacific/Guam"); + zone = SbTimeZoneGetCurrent(); + EXPECT_EQ(zone, -600); } }
diff --git a/starboard/nplb/time_zone_get_name_test.cc b/starboard/nplb/time_zone_get_name_test.cc index a3e013c..318cbfc 100644 --- a/starboard/nplb/time_zone_get_name_test.cc +++ b/starboard/nplb/time_zone_get_name_test.cc
@@ -12,10 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <string.h> + +#include "starboard/common/log.h" +#include "starboard/extension/time_zone.h" #include "starboard/nplb/time_constants.h" +#include "starboard/system.h" #include "starboard/time_zone.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using testing::AnyOf; +using testing::MatchesRegex; + namespace starboard { namespace nplb { namespace { @@ -41,6 +50,27 @@ // ":Pacific/Kiritimati" is the western-most timezone at UTC+14. } +TEST(SbTimeZoneGetNameTest, IsIANAFormat) { + const char* name = SbTimeZoneGetName(); + SB_LOG(INFO) << "time zone name: " << name; + char cpy[100]; + snprintf(cpy, sizeof(cpy), "%s", name); + char* continent = strtok(cpy, "/"); + // The time zone ID starts with a Continent or Ocean name. + EXPECT_THAT( + continent, + testing::AnyOf(std::string("Asia"), std::string("America"), + std::string("Africa"), std::string("Europe"), + std::string("Australia"), std::string("Pacific"), + std::string("Atlantic"), std::string("Antarctica"), + // time zone can be "Etc/UTC" if unset(such as on + // CI builders), shouldn't happen in production. + // TODO(b/304351956): Remove Etc after fixing builders. + std::string("Indian"), std::string("Etc"))); + char* city = strtok(NULL, "/"); + EXPECT_TRUE(strlen(city) != 0); +} + } // namespace } // namespace nplb } // namespace starboard
diff --git a/starboard/raspi/2/BUILD.gn b/starboard/raspi/2/BUILD.gn index 92dbb16..0b84634 100644 --- a/starboard/raspi/2/BUILD.gn +++ b/starboard/raspi/2/BUILD.gn
@@ -22,3 +22,12 @@ configs += [ "//starboard/build/config:starboard_implementation" ] public_deps = [ "//starboard/raspi/shared:starboard_platform" ] } + +if (sb_is_modular) { + static_library("starboard_platform_with_main") { + check_includes = false + sources = [ "//starboard/raspi/shared/main.cc" ] + configs += [ "//starboard/build/config:starboard_implementation" ] + public_deps = [ ":starboard_platform" ] + } +}
diff --git a/starboard/raspi/2/args.gn b/starboard/raspi/2/args.gn index 0b190f8..9c29777 100644 --- a/starboard/raspi/2/args.gn +++ b/starboard/raspi/2/args.gn
@@ -15,4 +15,5 @@ target_platform = "raspi-2" target_os = "linux" target_cpu = "arm" -is_clang = false +build_with_separate_cobalt_toolchain = true +use_asan = false
diff --git a/starboard/raspi/2/platform_configuration/BUILD.gn b/starboard/raspi/2/platform_configuration/BUILD.gn index ef03b60..b1d0422 100644 --- a/starboard/raspi/2/platform_configuration/BUILD.gn +++ b/starboard/raspi/2/platform_configuration/BUILD.gn
@@ -17,11 +17,16 @@ "//starboard/build/config/sabi", "//starboard/raspi/shared/platform_configuration", ] - cflags = [ - "-march=armv7-a", - "-mfpu=neon-vfpv4", - "-mfloat-abi=hard", - "-mcpu=cortex-a8", - "-mtune=cortex-a8", - ] + if (current_toolchain != default_toolchain || !sb_is_modular) { + cflags = [ + "-march=armv7-a", + "-mfpu=neon-vfpv4", + "-mfloat-abi=hard", + "-mcpu=cortex-a8", + "-mtune=cortex-a8", + ] + } + if (sb_is_modular && !sb_is_evergreen) { + defines = [ "USE_COMMON_CONTENT_DIR" ] + } }
diff --git a/starboard/raspi/2/platform_configuration/configuration.gni b/starboard/raspi/2/platform_configuration/configuration.gni index 3b23661..ad65232 100644 --- a/starboard/raspi/2/platform_configuration/configuration.gni +++ b/starboard/raspi/2/platform_configuration/configuration.gni
@@ -13,10 +13,14 @@ # limitations under the License. import("//starboard/raspi/shared/platform_configuration/configuration.gni") +if (current_toolchain != default_toolchain || + !build_with_separate_cobalt_toolchain) { + arm_float_abi = "hard" -arm_float_abi = "hard" + sb_evergreen_compatible_use_libunwind = true + sb_is_evergreen_compatible = true +} -sb_evergreen_compatible_use_libunwind = true -sb_is_evergreen_compatible = true - -separate_install_targets_for_bundling = true +if (!build_with_separate_cobalt_toolchain) { + separate_install_targets_for_bundling = true +}
diff --git a/starboard/raspi/2/skia/BUILD.gn b/starboard/raspi/2/skia/BUILD.gn index bcb5b92..80f549c 100644 --- a/starboard/raspi/2/skia/BUILD.gn +++ b/starboard/raspi/2/skia/BUILD.gn
@@ -22,3 +22,9 @@ configs += [ "//starboard/build/config:starboard_implementation" ] public_deps = [ "//starboard/raspi/shared:starboard_platform" ] } + +if (build_with_separate_cobalt_toolchain) { + group("starboard_platform_with_main") { + deps = [ "//starboard/raspi/2:starboard_platform_with_main" ] + } +}
diff --git a/starboard/shared/posix/time_zone_get_name.cc b/starboard/raspi/2/skia/starboard_loader.cc similarity index 61% copy from starboard/shared/posix/time_zone_get_name.cc copy to starboard/raspi/2/skia/starboard_loader.cc index 5a1e013..065bfaa 100644 --- a/starboard/shared/posix/time_zone_get_name.cc +++ b/starboard/raspi/2/skia/starboard_loader.cc
@@ -1,4 +1,4 @@ -// Copyright 2015 The Cobalt Authors. All Rights Reserved. +// Copyright 2023 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. @@ -12,13 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/time_zone.h" +#include "starboard/event.h" -#include <time.h> - -const char* SbTimeZoneGetName() { - // TODO: Using tzname assumes that tzset() has been called at some - // point. That should happen as part of Starboard's main loop initialization, - // but that doesn't exist yet. - return tzname[0]; +int main(int argc, char** argv) { + return SbRunStarboardMain(argc, argv, SbEventHandle); }
diff --git a/starboard/raspi/2/skia/toolchain/BUILD.gn b/starboard/raspi/2/skia/toolchain/BUILD.gn index 569a254..8524670 100644 --- a/starboard/raspi/2/skia/toolchain/BUILD.gn +++ b/starboard/raspi/2/skia/toolchain/BUILD.gn
@@ -14,6 +14,7 @@ import("//build/config/clang/clang.gni") import("//build/toolchain/gcc_toolchain.gni") +import("//starboard/build/toolchain/cobalt_toolchains.gni") import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni") gcc_toolchain("target") { @@ -23,7 +24,27 @@ ar = gcc_toolchain_ar - tail_lib_dependencies = "-l:libpthread.so.0" + tail_lib_dependencies = "-l:libpthread.so.0 -l:libdl.so.2" + + toolchain_args = { + is_clang = false + } +} + +cobalt_clang_toolchain("cobalt") { + variables = { + native_linker_path = gcc_toolchain_cxx + } +} + +gcc_toolchain("starboard") { + cc = gcc_toolchain_cc + cxx = gcc_toolchain_cxx + ld = cxx + + ar = gcc_toolchain_ar + + tail_lib_dependencies = "-l:libpthread.so.0 -l:libdl.so.2" toolchain_args = { is_clang = false
diff --git a/starboard/shared/posix/time_zone_get_name.cc b/starboard/raspi/2/starboard_loader.cc similarity index 61% copy from starboard/shared/posix/time_zone_get_name.cc copy to starboard/raspi/2/starboard_loader.cc index 5a1e013..065bfaa 100644 --- a/starboard/shared/posix/time_zone_get_name.cc +++ b/starboard/raspi/2/starboard_loader.cc
@@ -1,4 +1,4 @@ -// Copyright 2015 The Cobalt Authors. All Rights Reserved. +// Copyright 2023 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. @@ -12,13 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/time_zone.h" +#include "starboard/event.h" -#include <time.h> - -const char* SbTimeZoneGetName() { - // TODO: Using tzname assumes that tzset() has been called at some - // point. That should happen as part of Starboard's main loop initialization, - // but that doesn't exist yet. - return tzname[0]; +int main(int argc, char** argv) { + return SbRunStarboardMain(argc, argv, SbEventHandle); }
diff --git a/starboard/raspi/2/toolchain/BUILD.gn b/starboard/raspi/2/toolchain/BUILD.gn index dcce760..2c49b20 100644 --- a/starboard/raspi/2/toolchain/BUILD.gn +++ b/starboard/raspi/2/toolchain/BUILD.gn
@@ -13,8 +13,30 @@ # limitations under the License. import("//build/toolchain/gcc_toolchain.gni") +import("//starboard/build/toolchain/cobalt_toolchains.gni") import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni") +cobalt_clang_toolchain("cobalt") { + variables = { + native_linker_path = gcc_toolchain_cxx + } +} + +gcc_toolchain("starboard") { + cc = gcc_toolchain_cc + cxx = gcc_toolchain_cxx + ld = cxx + + # We use whatever 'ar' resolves to. + ar = gcc_toolchain_ar + + tail_lib_dependencies = "-l:libpthread.so.0 -l:libdl.so.2" + + toolchain_args = { + is_clang = false + } +} + gcc_toolchain("target") { cc = gcc_toolchain_cc cxx = gcc_toolchain_cxx
diff --git a/starboard/raspi/shared/BUILD.gn b/starboard/raspi/shared/BUILD.gn index a81b614..39ca20e 100644 --- a/starboard/raspi/shared/BUILD.gn +++ b/starboard/raspi/shared/BUILD.gn
@@ -35,13 +35,14 @@ "//starboard/linux/shared/system_get_connection_type.cc", "//starboard/linux/shared/system_get_path.cc", "//starboard/linux/shared/system_has_capability.cc", + "//starboard/linux/shared/time_zone.cc", + "//starboard/linux/shared/time_zone.h", "//starboard/raspi/shared/application_dispmanx.cc", "//starboard/raspi/shared/audio_sink_type_dispatcher.cc", "//starboard/raspi/shared/dispmanx_util.cc", "//starboard/raspi/shared/dispmanx_util.h", "//starboard/raspi/shared/graphics.cc", "//starboard/raspi/shared/graphics.h", - "//starboard/raspi/shared/main.cc", "//starboard/raspi/shared/media_is_video_supported.cc", "//starboard/raspi/shared/open_max/decode_target_create.cc", "//starboard/raspi/shared/open_max/decode_target_create.h", @@ -112,6 +113,7 @@ "//starboard/shared/linux/thread_get_id.cc", "//starboard/shared/linux/thread_get_name.cc", "//starboard/shared/linux/thread_set_name.cc", + "//starboard/shared/linux/time_zone_get_name.cc", "//starboard/shared/nouser/user_get_current.cc", "//starboard/shared/nouser/user_get_property.cc", "//starboard/shared/nouser/user_get_signed_in.cc", @@ -188,7 +190,6 @@ "//starboard/shared/posix/time_get_now.cc", "//starboard/shared/posix/time_is_time_thread_now_supported.cc", "//starboard/shared/posix/time_zone_get_current.cc", - "//starboard/shared/posix/time_zone_get_name.cc", "//starboard/shared/pthread/condition_variable_broadcast.cc", "//starboard/shared/pthread/condition_variable_create.cc", "//starboard/shared/pthread/condition_variable_destroy.cc", @@ -333,10 +334,15 @@ "//starboard/shared/stub/window_set_on_screen_keyboard_keep_focus.cc", "//starboard/shared/stub/window_show_on_screen_keyboard.cc", "//starboard/shared/stub/window_update_on_screen_keyboard_suggestions.cc", + "run_starboard_main.cc", ] sources += common_player_sources + if (!sb_is_modular) { + sources += [ "//starboard/raspi/shared/main.cc" ] + } + configs += [ "//starboard/build/config:starboard_implementation" ] public_deps = [ @@ -378,17 +384,20 @@ public_deps = [ "//starboard/elf_loader:evergreen_info" ] } -target(gtest_target_type, "starboard_platform_tests") { - testonly = true +if (current_toolchain == starboard_toolchain) { + target(starboard_level_gtest_target_type, "starboard_platform_tests") { + build_loader = false + testonly = true - sources = player_tests_sources + [ "//starboard/common/test_main.cc" ] + sources = player_tests_sources + [ "//starboard/common/test_main.cc" ] - configs += [ "//starboard/build/config:starboard_implementation" ] + configs += [ "//starboard/build/config:starboard_implementation" ] - deps = [ - "//starboard", - "//starboard/shared/starboard/player/filter/testing:test_util", - "//testing/gmock", - "//testing/gtest", - ] + deps = [ + "//starboard:starboard_with_main", + "//starboard/shared/starboard/player/filter/testing:test_util", + "//testing/gmock", + "//testing/gtest", + ] + } }
diff --git a/starboard/raspi/shared/install_target.gni b/starboard/raspi/shared/install_target.gni index 90ac725..7413840 100644 --- a/starboard/raspi/shared/install_target.gni +++ b/starboard/raspi/shared/install_target.gni
@@ -21,7 +21,7 @@ # subdir and install content subdir. if (invoker.type == "executable") { # install_subdir = "bin" - install_subdir = "" + install_subdir = installable_target_name source_name = installable_target_name } else if (invoker.type == "shared_library") { install_subdir = "lib" @@ -29,28 +29,37 @@ } else { assert(false, "You can only install an executable or shared library.") } + output = "$sb_install_output_dir/$install_subdir/$source_name" + input = "$root_out_dir/$source_name" - action(target_name) { - forward_variables_from(invoker, [ "testonly" ]) + if (is_gold || cobalt_fastbuild) { + action(target_name) { + forward_variables_from(invoker, [ "testonly" ]) - script = "//starboard/build/run_bash.py" + script = "//starboard/build/run_bash.py" - strip_executable = gcc_toolchain_strip - inputs = [ - strip_executable, - "$root_out_dir/$source_name", - ] + strip_executable = gcc_toolchain_strip + inputs = [ input ] - deps = invoker.deps - deps += [ ":$installable_target_name" ] + deps = invoker.deps + deps += [ invoker.installable_target_dep ] - outputs = [ "$sb_install_output_dir/$install_subdir/$installable_target_name/$source_name" ] + outputs = [ output ] - args = [ - rebase_path(strip_executable, root_build_dir), - "-o", - rebase_path(outputs[0], root_out_dir), - rebase_path("$root_out_dir/$source_name", root_out_dir), - ] + args = [ + rebase_path(strip_executable, root_build_dir), + "-o", + rebase_path(outputs[0], root_build_dir), + rebase_path(inputs[0], root_build_dir), + ] + } + } else { + copy(target_name) { + forward_variables_from(invoker, [ "testonly" ]) + sources = [ input ] + outputs = [ output ] + deps = invoker.deps + deps += [ invoker.installable_target_dep ] + } } }
diff --git a/starboard/raspi/shared/launcher.py b/starboard/raspi/shared/launcher.py index 6aad68d..455467b 100644 --- a/starboard/raspi/shared/launcher.py +++ b/starboard/raspi/shared/launcher.py
@@ -30,6 +30,10 @@ from starboard.raspi.shared import retry +class TargetPathError(ValueError): + pass + + # pylint: disable=unused-argument def _sigint_or_sigterm_handler(signum, frame): """Clean up and exit with status |signum|. @@ -83,8 +87,8 @@ _PROMPT_WAIT_MAX_RETRIES = 5 # Wait up to 10 seconds for the password prompt from the raspi _PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 10 - # Wait up to 900 seconds for new output from the raspi - _PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 900 + # Wait up to 600 seconds for new output from the raspi + _PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 600 # Delay between subsequent SSH commands _INTER_COMMAND_DELAY_SECONDS = 1.5 @@ -126,31 +130,43 @@ self.last_run_pexpect_cmd = '' + def _GetAndCheckTestFile(self, target_name): + # TODO(b/218889313): This should reference the bin/ subdir when that's + # used. + test_dir = os.path.join(self.out_directory, 'install', target_name) + test_file = target_name + test_path = os.path.join(test_dir, test_file) + + if not os.path.isfile(test_path): + raise TargetPathError(f'TargetPath ({test_path}) must be a file.') + return test_file + + def _GetAndCheckTestFileWithFallback(self): + try: + return self._GetAndCheckTestFile(self.target_name + '_loader') + except TargetPathError: + # For starboard level test targets like player_filter_tests built as an + # executable, return the target name. + return self._GetAndCheckTestFile(self.target_name) + def _InitPexpectCommands(self): """Initializes all of the pexpect commands needed for running the test.""" # Ensure no trailing slashes self.out_directory = self.out_directory.rstrip('/') - # TODO(b/218889313): This should reference the bin/ subdir when that's - # used. - test_dir = os.path.join(self.out_directory, 'install', self.target_name) - test_file = self.target_name - - test_path = os.path.join(test_dir, test_file) - if not os.path.isfile(test_path): - raise ValueError(f'TargetPath ({test_path}) must be a file.') + test_file = self._GetAndCheckTestFileWithFallback() raspi_user_hostname = Launcher._RASPI_USERNAME + '@' + self.device_id # Use the basename of the out directory as a common directory on the device # so content can be reused for several targets w/o re-syncing for each one. raspi_test_dir = os.path.basename(self.out_directory) - raspi_test_path = os.path.join(raspi_test_dir, test_file) + raspi_test_path = os.path.join(raspi_test_dir, test_file, test_file) # rsync command setup - options = '-avzLhc' - source = test_dir + '/' + options = '-avzLh' + source = os.path.join(self.out_directory, 'install') + '/' destination = f'{raspi_user_hostname}:~/{raspi_test_dir}/' self.rsync_command = 'rsync ' + options + ' ' + source + ' ' + destination @@ -242,28 +258,25 @@ def _PexpectReadLines(self): """Reads all lines from the pexpect process.""" - # pylint: disable=unnecessary-lambda - @retry.retry( - exceptions=Launcher._RETRY_EXCEPTIONS, - retries=Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES, - backoff=lambda: self.shutdown_initiated.is_set(), - wrap_exceptions=False) - def _readloop(): - while True: - # Sanitize the line to remove ansi color codes. - line = Launcher._PEXPECT_SANITIZE_LINE_RE.sub( - '', self.pexpect_process.readline()) - self.output_file.flush() - if not line: - return - # Check for the test complete tag. It will be followed by either a - # success or failure tag. - if line.startswith(self.test_complete_tag): - if line.find(self.test_success_tag) != -1: - self.return_value = 0 - return - - _readloop() + while True: + # pylint: disable=unnecessary-lambda + line = retry.with_retry( + self.pexpect_process.readline, + exceptions=Launcher._RETRY_EXCEPTIONS, + retries=Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES, + backoff=lambda: self.shutdown_initiated.is_set(), + wrap_exceptions=False) + # Sanitize the line to remove ansi color codes. + line = Launcher._PEXPECT_SANITIZE_LINE_RE.sub('', line) + self.output_file.flush() + if not line: + return + # Check for the test complete tag. It will be followed by either a + # success or failure tag. + if line.startswith(self.test_complete_tag): + if line.find(self.test_success_tag) != -1: + self.return_value = 0 + return def _Sleep(self, val): self._PexpectSendLine(f'sleep {val};echo {Launcher._SSH_SLEEP_SIGNAL}')
diff --git a/starboard/raspi/shared/main.cc b/starboard/raspi/shared/main.cc index e0aa5bf..b07e33e 100644 --- a/starboard/raspi/shared/main.cc +++ b/starboard/raspi/shared/main.cc
@@ -49,7 +49,6 @@ : starboard::common::GetCACertificatesPath(evergreen_content_path); if (ca_certificates_path.empty()) { SB_LOG(ERROR) << "Failed to get CA certificates path"; - return 1; } bool start_handler_at_crash = @@ -72,11 +71,3 @@ starboard::shared::signal::UninstallCrashSignalHandlers(); return result; } - -#if SB_API_VERSION >= 15 -int SbRunStarboardMain(int argc, char** argv, SbEventHandleCallback callback) { - starboard::raspi::shared::ApplicationDispmanx application(callback); - int result = application.Run(argc, argv); - return result; -} -#endif // SB_API_VERSION >= 15
diff --git a/starboard/raspi/shared/platform_configuration/BUILD.gn b/starboard/raspi/shared/platform_configuration/BUILD.gn index b1c930e..e2c7f03 100644 --- a/starboard/raspi/shared/platform_configuration/BUILD.gn +++ b/starboard/raspi/shared/platform_configuration/BUILD.gn
@@ -16,6 +16,39 @@ raspi_home = getenv("RASPI_HOME") } +config("common_flags") { + ldflags = [ + "--sysroot=$raspi_home/busterroot", + + # This is a quirk of Raspbian, these are required to link any GL-related + # libraries. + "-L$raspi_home/busterroot/opt/vc/lib", + "-Wl,-rpath=$raspi_home/busterroot/opt/vc/lib", + "-L$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf", + "-Wl,-rpath=$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf", + "-L$raspi_home/busterroot/lib/arm-linux-gnueabihf", + "-Wl,-rpath=$raspi_home/busterroot/lib/arm-linux-gnueabihf", + + # Cleanup unused sections + "-Wl,-gc-sections", + "-Wl,--unresolved-symbols=ignore-all", + ] + libs = [ + "asound", + "rt", + "openmaxil", + "bcm_host", + "vcos", + "vchiq_arm", + "brcmGLESv2", + "brcmEGL", + + # Static libs must be last, to avoid __dlopen linker errors + "EGL_static", + "GLESv2_static", + ] +} + config("compiler_flags") { cflags = [] cflags_c = [] @@ -38,23 +71,6 @@ cflags += [ "-Wno-unused-but-set-variable" ] } - ldflags += [ - "--sysroot=$raspi_home/busterroot", - - # This is a quirk of Raspbian, these are required to link any GL-related - # libraries. - "-L$raspi_home/busterroot/opt/vc/lib", - "-Wl,-rpath=$raspi_home/busterroot/opt/vc/lib", - "-L$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf", - "-Wl,-rpath=$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf", - "-L$raspi_home/busterroot/lib/arm-linux-gnueabihf", - "-Wl,-rpath=$raspi_home/busterroot/lib/arm-linux-gnueabihf", - - # Cleanup unused sections - "-Wl,-gc-sections", - "-Wl,--unresolved-symbols=ignore-in-shared-libs", - ] - cflags += [ # Generated by code in the raspi/shared/open_max. "-Wno-sign-compare", @@ -121,27 +137,16 @@ } config("platform_configuration") { - libs = [ - "asound", - "dl", - "pthread", - "rt", - "openmaxil", - "bcm_host", - "vcos", - "vchiq_arm", - "brcmGLESv2", - "brcmEGL", + configs = [ ":common_flags" ] + if (current_toolchain == default_toolchain && sb_is_modular) { + configs += [ "//starboard/evergreen/arm/hardfp/platform_configuration" ] + } else { + configs += + [ "//starboard/raspi/shared/platform_configuration:compiler_flags" ] - # Static libs must be last, to avoid __dlopen linker errors - "EGL_static", - "GLESv2_static", - ] - - configs = [ "//starboard/raspi/shared/platform_configuration:compiler_flags" ] - - if (is_debug || is_devel) { - configs += [ "//build/config/compiler:rtti" ] + if (is_debug || is_devel) { + configs += [ "//build/config/compiler:rtti" ] + } } }
diff --git a/starboard/raspi/shared/platform_configuration/configuration.gni b/starboard/raspi/shared/platform_configuration/configuration.gni index bd06d18..b1e053e 100644 --- a/starboard/raspi/shared/platform_configuration/configuration.gni +++ b/starboard/raspi/shared/platform_configuration/configuration.gni
@@ -12,26 +12,36 @@ # See the License for the specific language governing permissions and # limitations under the License. -import("//starboard/build/config/base_configuration.gni") +if (current_toolchain == default_toolchain && + build_with_separate_cobalt_toolchain) { + import( + "//starboard/evergreen/arm/softfp/platform_configuration/configuration.gni") -arm_float_abi = "hard" + platform_tests_path = "//starboard/raspi/shared:starboard_platform_tests_install($starboard_toolchain)" + cobalt_font_package = "standard" +} else { + import("//starboard/build/config/base_configuration.gni") -sb_static_contents_output_data_dir = "$root_out_dir/content" + arm_float_abi = "hard" -no_pedantic_warnings_config_path = - "//starboard/raspi/shared/platform_configuration:no_pedantic_warnings" -pedantic_warnings_config_path = - "//starboard/raspi/shared/platform_configuration:pedantic_warnings" -sabi_path = "//starboard/sabi/arm/hardfp/sabi-v$sb_api_version.json" + sb_static_contents_output_data_dir = "$root_out_dir/content" -platform_tests_path = "//starboard/raspi/shared:starboard_platform_tests" + no_pedantic_warnings_config_path = + "//starboard/raspi/shared/platform_configuration:no_pedantic_warnings" + pedantic_warnings_config_path = + "//starboard/raspi/shared/platform_configuration:pedantic_warnings" + sabi_path = "//starboard/sabi/arm/hardfp/sabi-v$sb_api_version.json" + platform_tests_path = "//starboard/raspi/shared:starboard_platform_tests_install($starboard_toolchain)" + + speed_config_path = "//starboard/raspi/shared/platform_configuration:speed" + size_config_path = "//starboard/raspi/shared/platform_configuration:size" + + # TODO(b/219073252): Enable -fno-exceptions and don't mix it with -fexceptions. + enable_exceptions_override = true + + v8_enable_webassembly = true + + is_raspi = true +} install_target_path = "//starboard/raspi/shared/install_target.gni" - -speed_config_path = "//starboard/raspi/shared/platform_configuration:speed" -size_config_path = "//starboard/raspi/shared/platform_configuration:size" - -# TODO(b/219073252): Enable -fno-exceptions and don't mix it with -fexceptions. -enable_exceptions_override = true - -v8_enable_webassembly = true
diff --git a/starboard/raspi/shared/run_starboard_main.cc b/starboard/raspi/shared/run_starboard_main.cc new file mode 100644 index 0000000..9e957af --- /dev/null +++ b/starboard/raspi/shared/run_starboard_main.cc
@@ -0,0 +1,24 @@ +// 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/event.h" +#include "starboard/raspi/shared/application_dispmanx.h" + +#if SB_API_VERSION >= 15 +int SbRunStarboardMain(int argc, char** argv, SbEventHandleCallback callback) { + starboard::raspi::shared::ApplicationDispmanx application(callback); + int result = application.Run(argc, argv); + return result; +} +#endif // SB_API_VERSION >= 15
diff --git a/starboard/raspi/shared/system_get_extensions.cc b/starboard/raspi/shared/system_get_extensions.cc index bd7854d..511fb79 100644 --- a/starboard/raspi/shared/system_get_extensions.cc +++ b/starboard/raspi/shared/system_get_extensions.cc
@@ -18,11 +18,13 @@ #include "starboard/extension/configuration.h" #include "starboard/extension/crash_handler.h" #include "starboard/extension/graphics.h" +#include "starboard/extension/time_zone.h" #include "starboard/shared/starboard/crash_handler.h" #if SB_IS(EVERGREEN_COMPATIBLE) #include "starboard/elf_loader/evergreen_config.h" #endif +#include "starboard/linux/shared/time_zone.h" #include "starboard/raspi/shared/configuration.h" #include "starboard/raspi/shared/graphics.h" @@ -48,5 +50,8 @@ if (strcmp(name, kCobaltExtensionCrashHandlerName) == 0) { return starboard::common::GetCrashHandlerApi(); } + if (strcmp(name, kStarboardExtensionTimeZoneName) == 0) { + return starboard::shared::GetTimeZoneApi(); + } return NULL; }
diff --git a/starboard/raspi/shared/test_filters.py b/starboard/raspi/shared/test_filters.py index d031a5d..21a428f 100644 --- a/starboard/raspi/shared/test_filters.py +++ b/starboard/raspi/shared/test_filters.py
@@ -16,6 +16,7 @@ from starboard.tools.testing import test_filter # pylint: disable=line-too-long + _FILTERED_TESTS = { 'nplb': [ 'SbAudioSinkTest.*',
diff --git a/starboard/shared/ffmpeg/BUILD.gn b/starboard/shared/ffmpeg/BUILD.gn index ec30571..5ad4693 100644 --- a/starboard/shared/ffmpeg/BUILD.gn +++ b/starboard/shared/ffmpeg/BUILD.gn
@@ -18,18 +18,29 @@ "ffmpeg_common.h", "ffmpeg_demuxer_impl.cc", "ffmpeg_demuxer_impl.h", - "ffmpeg_video_decoder_impl.cc", - "ffmpeg_video_decoder_impl.h", ] +if (!defined(is_raspi)) { + ffmpeg_specialization_sources += [ + # TODO(b/291783511): This code is unimplemented on certain platforms. + "ffmpeg_video_decoder_impl.cc", + "ffmpeg_video_decoder_impl.h", + ] +} + static_library("ffmpeg_dynamic_load") { check_includes = false sources = [ "ffmpeg_dynamic_load_audio_decoder_impl.cc", "ffmpeg_dynamic_load_demuxer_impl.cc", "ffmpeg_dynamic_load_dispatch_impl.cc", - "ffmpeg_dynamic_load_video_decoder_impl.cc", ] + if (!defined(is_raspi)) { + sources += [ + # TODO(b/291783511): This code is unimplemented on certain platforms. + "ffmpeg_dynamic_load_video_decoder_impl.cc", + ] + } public_deps = [ ":ffmpeg.57.107.100", ":ffmpeg.58.35.100",
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc index 4cf444b..1086f3f 100644 --- a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc +++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
@@ -140,14 +140,17 @@ SB_DCHECK(output_cb_); SB_CHECK(codec_context_ != NULL); - const auto& input_buffer = input_buffers[0]; - Schedule(consumed_cb); + if (input_buffers.empty() || !input_buffers[0]) { + SB_LOG(ERROR) << "No input buffer to decode."; + return; + } if (stream_ended_) { SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called."; return; } + const auto& input_buffer = input_buffers[0]; AVPacket packet; ffmpeg_->av_init_packet(&packet);
diff --git a/starboard/shared/libfdkaac/fdk_aac_audio_decoder.cc b/starboard/shared/libfdkaac/fdk_aac_audio_decoder.cc index 693c133..1df5b8b 100644 --- a/starboard/shared/libfdkaac/fdk_aac_audio_decoder.cc +++ b/starboard/shared/libfdkaac/fdk_aac_audio_decoder.cc
@@ -52,6 +52,10 @@ SB_DCHECK(output_cb_); SB_DCHECK(decoder_ != NULL); + if (input_buffers.empty() || !input_buffers[0]) { + SB_LOG(ERROR) << "No input buffer to decode."; + return; + } if (stream_ended_) { SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called."; return;
diff --git a/starboard/shared/linux/time_zone_get_name.cc b/starboard/shared/linux/time_zone_get_name.cc new file mode 100644 index 0000000..4b1819e --- /dev/null +++ b/starboard/shared/linux/time_zone_get_name.cc
@@ -0,0 +1,75 @@ +// Copyright 2015 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 <string.h> +#include <time.h> +#include <unistd.h> + +#include "starboard/common/log.h" +#include "starboard/time_zone.h" + +#define TZDEFAULT "/etc/localtime" +#define TZZONEINFOTAIL "/zoneinfo/" +#define isNonDigit(ch) (ch < '0' || '9' < ch) + +static char gTimeZoneBuffer[PATH_MAX]; +static char* gTimeZoneBufferPtr = NULL; + +static bool isValidOlsonID(const char* id) { + int32_t idx = 0; + + /* Determine if this is something like Iceland (Olson ID) + or AST4ADT (non-Olson ID) */ + while (id[idx] && isNonDigit(id[idx]) && id[idx] != ',') { + idx++; + } + + /* If we went through the whole string, then it might be okay. + The timezone is sometimes set to "CST-7CDT", "CST6CDT5,J129,J131/19:30", + "GRNLNDST3GRNLNDDT" or similar, so we cannot use it. + The rest of the time it could be an Olson ID. George */ + return static_cast<bool>(id[idx] == 0 || strcmp(id, "PST8PDT") == 0 || + strcmp(id, "MST7MDT") == 0 || + strcmp(id, "CST6CDT") == 0 || + strcmp(id, "EST5EDT") == 0); +} + +// Similar to how ICU::putil.cpp gets IANA(Olsen) timezone ID. +const char* SbTimeZoneGetName() { + /* + This is a trick to look at the name of the link to get the Olson ID + because the tzfile contents is underspecified. + This isn't guaranteed to work because it may not be a symlink. + But this is production-tested solution for most versions of Linux. + */ + + if (gTimeZoneBufferPtr == NULL) { + int32_t ret = (int32_t)readlink(TZDEFAULT, gTimeZoneBuffer, + sizeof(gTimeZoneBuffer) - 1); + if (0 < ret) { + int32_t tzZoneInfoTailLen = strlen(TZZONEINFOTAIL); + gTimeZoneBuffer[ret] = 0; + char* tzZoneInfoTailPtr = strstr(gTimeZoneBuffer, TZZONEINFOTAIL); + + if (tzZoneInfoTailPtr != NULL && + isValidOlsonID(tzZoneInfoTailPtr + tzZoneInfoTailLen)) { + return (gTimeZoneBufferPtr = tzZoneInfoTailPtr + tzZoneInfoTailLen); + } + } + SB_NOTREACHED(); + return ""; + } else { + return gTimeZoneBufferPtr; + } +}
diff --git a/starboard/shared/starboard/media/codec_util.cc b/starboard/shared/starboard/media/codec_util.cc index 1c2c1b3..ff3b325 100644 --- a/starboard/shared/starboard/media/codec_util.cc +++ b/starboard/shared/starboard/media/codec_util.cc
@@ -72,7 +72,8 @@ return !(*this == that); } -SbMediaAudioCodec GetAudioCodecFromString(const char* codec) { +SbMediaAudioCodec GetAudioCodecFromString(const char* codec, + const char* subtype) { #if SB_API_VERSION < 15 const bool kCheckAc3Audio = kSbHasAc3Audio; #else @@ -105,7 +106,9 @@ } // For WAV, the "codecs" parameter of a MIME type refers to the WAVE format // ID, where 1 represents PCM: https://datatracker.ietf.org/doc/html/rfc2361 - if (strcmp(codec, "1") == 0) { + const bool is_wav = + strcmp(subtype, "wav") == 0 || strcmp(subtype, "wave") == 0; + if (is_wav && strcmp(codec, "1") == 0) { return kSbMediaAudioCodecPcm; } #endif // SB_API_VERSION >= 14
diff --git a/starboard/shared/starboard/media/codec_util.h b/starboard/shared/starboard/media/codec_util.h index 11f3102..b6f6b61 100644 --- a/starboard/shared/starboard/media/codec_util.h +++ b/starboard/shared/starboard/media/codec_util.h
@@ -64,7 +64,11 @@ optional<AvcParameterSets> avc_parameter_sets_; }; -SbMediaAudioCodec GetAudioCodecFromString(const char* codec); +// Attempts to determine an SbMediaAudioCodec from |codec|, returning +// kSbMediaAudioCodecNone if no match is found. |subtype| may be checked in +// cases of ambiguous codec strings. +SbMediaAudioCodec GetAudioCodecFromString(const char* codec, + const char* subtype); } // namespace media } // namespace starboard
diff --git a/starboard/shared/starboard/media/codec_util_test.cc b/starboard/shared/starboard/media/codec_util_test.cc index 2a334be..a57d769 100644 --- a/starboard/shared/starboard/media/codec_util_test.cc +++ b/starboard/shared/starboard/media/codec_util_test.cc
@@ -16,6 +16,7 @@ #include <vector> +#include "starboard/media.h" #include "starboard/shared/starboard/media/avc_util.h" #include "testing/gtest/include/gtest/gtest.h" @@ -214,6 +215,60 @@ EXPECT_FALSE(config_h264 == config_vp9); } +TEST(CodecUtilTest, ParsesAacCodecs) { + EXPECT_EQ(GetAudioCodecFromString("mp4a.40.2", ""), kSbMediaAudioCodecAac); + EXPECT_EQ(GetAudioCodecFromString("mp4a.40.5", ""), kSbMediaAudioCodecAac); +} + +#if SB_API_VERSION < 15 +const bool kCheckAc3Audio = kSbHasAc3Audio; +#else +const bool kCheckAc3Audio = true; +#endif // SB_API_VERSION < 15 + +TEST(CodecUtilTest, ParsesAc3CodecIfEnabled) { + EXPECT_EQ(GetAudioCodecFromString("ac-3", ""), + kCheckAc3Audio ? kSbMediaAudioCodecAc3 : kSbMediaAudioCodecNone); +} + +TEST(CodecUtilTest, ParsesEac3CodecIfEnabled) { + EXPECT_EQ(GetAudioCodecFromString("ec-3", ""), + kCheckAc3Audio ? kSbMediaAudioCodecEac3 : kSbMediaAudioCodecNone); +} + +TEST(CodecUtilTest, ParsesOpusCodec) { + EXPECT_EQ(GetAudioCodecFromString("opus", ""), kSbMediaAudioCodecOpus); +} + +TEST(CodecUtilTest, ParsesVorbisCodec) { + EXPECT_EQ(GetAudioCodecFromString("vorbis", ""), kSbMediaAudioCodecVorbis); +} + +#if SB_API_VERSION >= 14 +TEST(CodecUtilTest, ParsesMp3Codecs) { + EXPECT_EQ(GetAudioCodecFromString("mp3", ""), kSbMediaAudioCodecMp3); + EXPECT_EQ(GetAudioCodecFromString("mp4a.69", ""), kSbMediaAudioCodecMp3); + EXPECT_EQ(GetAudioCodecFromString("mp4a.6B", ""), kSbMediaAudioCodecMp3); +} + +TEST(CodecUtilTest, ParsesFlacCodec) { + EXPECT_EQ(GetAudioCodecFromString("flac", ""), kSbMediaAudioCodecFlac); +} + +TEST(CodecUtilTest, ParsesPcmCodecForWav) { + EXPECT_EQ(GetAudioCodecFromString("1", "wav"), kSbMediaAudioCodecPcm); + EXPECT_EQ(GetAudioCodecFromString("1", "wave"), kSbMediaAudioCodecPcm); +} + +TEST(CodecUtilTest, DoesNotParse1AsPcmForNonWavSubtypes) { + EXPECT_EQ(GetAudioCodecFromString("1", ""), kSbMediaAudioCodecNone); + EXPECT_EQ(GetAudioCodecFromString("1", "mp4"), kSbMediaAudioCodecNone); + EXPECT_EQ(GetAudioCodecFromString("1", "mp3"), kSbMediaAudioCodecNone); + EXPECT_EQ(GetAudioCodecFromString("1", "mpeg"), kSbMediaAudioCodecNone); + EXPECT_EQ(GetAudioCodecFromString("1", "webm"), kSbMediaAudioCodecNone); +} +#endif // SB_API_VERSION >= 14 + } // namespace } // namespace media } // namespace starboard
diff --git a/starboard/shared/starboard/media/media_tests.gni b/starboard/shared/starboard/media/media_tests.gni index 4777863..269a60c 100644 --- a/starboard/shared/starboard/media/media_tests.gni +++ b/starboard/shared/starboard/media/media_tests.gni
@@ -18,6 +18,7 @@ "//starboard/shared/starboard/media/media_util_test.cc", "//starboard/shared/starboard/media/mime_type_test.cc", "//starboard/shared/starboard/media/mime_util_test.cc", + "//starboard/shared/starboard/media/parsed_mime_info_test.cc", "//starboard/shared/starboard/media/video_capabilities_test.cc", "//starboard/shared/starboard/media/vp9_util_test.cc", ]
diff --git a/starboard/shared/starboard/media/mime_util.cc b/starboard/shared/starboard/media/mime_util.cc index 0ef4b2d..52650f8 100644 --- a/starboard/shared/starboard/media/mime_util.cc +++ b/starboard/shared/starboard/media/mime_util.cc
@@ -37,23 +37,19 @@ // Use SbMediaGetAudioConfiguration() to check if the platform can support // |channels|. bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) { - // TODO(b/284140486, b/297426689): Consider removing the call to - // `SbMediaGetAudioOutputCount()` completely as the loop will be terminated - // once `SbMediaGetAudioConfiguration()` returns false. - int count = SbMediaGetAudioOutputCount(); - - for (int output_index = 0; output_index < count; ++output_index) { - SbMediaAudioConfiguration configuration; - if (!SbMediaGetAudioConfiguration(output_index, &configuration)) { - break; - } - + // SbPlayerBridge::GetAudioConfigurations() reads up to 32 configurations. The + // limit here is to avoid infinite loop and also match + // SbPlayerBridge::GetAudioConfigurations(). + const int kMaxAudioConfigurations = 32; + int output_index = 0; + SbMediaAudioConfiguration configuration; + while (output_index < kMaxAudioConfigurations && + SbMediaGetAudioConfiguration(output_index++, &configuration)) { if (configuration.coding_type == coding_type && configuration.number_of_channels >= channels) { return true; } } - return false; }
diff --git a/starboard/shared/starboard/media/parsed_mime_info.cc b/starboard/shared/starboard/media/parsed_mime_info.cc index e61cb84..ae2e147 100644 --- a/starboard/shared/starboard/media/parsed_mime_info.cc +++ b/starboard/shared/starboard/media/parsed_mime_info.cc
@@ -101,7 +101,8 @@ SB_DCHECK(mime_type_.is_valid()); SB_DCHECK(!has_audio_info()); - SbMediaAudioCodec audio_codec = GetAudioCodecFromString(codec.c_str()); + SbMediaAudioCodec audio_codec = + GetAudioCodecFromString(codec.c_str(), mime_type_.subtype().c_str()); if (audio_codec == kSbMediaAudioCodecNone) { return false; }
diff --git a/starboard/shared/starboard/media/parsed_mime_info_test.cc b/starboard/shared/starboard/media/parsed_mime_info_test.cc new file mode 100644 index 0000000..94c1f7b --- /dev/null +++ b/starboard/shared/starboard/media/parsed_mime_info_test.cc
@@ -0,0 +1,98 @@ +// Copyright 2023 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 "starboard/media.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { +namespace { + +#if SB_API_VERSION < 15 +const bool kCheckAc3Audio = kSbHasAc3Audio; +#else +const bool kCheckAc3Audio = true; +#endif // SB_API_VERSION < 15 + +TEST(ParsedMimeInfoTest, ParsesAacLowComplexityCodec) { + ParsedMimeInfo mime_info("audio/mp4; codecs=\"mp4a.40.2\""); + ASSERT_TRUE(mime_info.has_audio_info()); + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecAac); +} + +TEST(ParsedMimeInfoTest, ParsesAacHighEfficiencyCodec) { + ParsedMimeInfo mime_info("audio/mp4; codecs=\"mp4a.40.5\""); + ASSERT_TRUE(mime_info.has_audio_info()); + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecAac); +} + +TEST(ParsedMimeInfoTest, ParsesAc3Codec) { + ParsedMimeInfo mime_info("audio/mp4; codecs=\"ac-3\""); + ASSERT_EQ(mime_info.has_audio_info(), kCheckAc3Audio); + + if (kCheckAc3Audio) { + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecAc3); + } +} + +TEST(ParsedMimeInfoTest, ParsesEac3Codec) { + ParsedMimeInfo mime_info("audio/mp4; codecs=\"ec-3\""); + ASSERT_EQ(mime_info.has_audio_info(), kCheckAc3Audio); + + if (kCheckAc3Audio) { + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecEac3); + } +} + +TEST(ParsedMimeInfoTest, ParsesOpusCodec) { + ParsedMimeInfo mime_info("audio/webm; codecs=\"opus\""); + ASSERT_TRUE(mime_info.has_audio_info()); + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecOpus); +} + +TEST(ParsedMimeInfoTest, ParsesVorbisCodec) { + ParsedMimeInfo mime_info("audio/webm; codecs=\"vorbis\""); + ASSERT_TRUE(mime_info.has_audio_info()); + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecVorbis); +} + +#if SB_API_VERSION >= 14 +TEST(ParsedMimeInfoTest, ParsesMp3Codec) { + ParsedMimeInfo mime_info("audio/mpeg; codecs=\"mp3\""); + ASSERT_TRUE(mime_info.has_audio_info()); + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecMp3); +} + +TEST(ParsedMimeInfoTest, ParsesFlacCodec) { + ParsedMimeInfo mime_info("audio/ogg; codecs=\"flac\""); + ASSERT_TRUE(mime_info.has_audio_info()); + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecFlac); +} + +TEST(ParsedMimeInfoTest, ParsesPcmCodec) { + ParsedMimeInfo mime_info("audio/wav; codecs=\"1\""); + ASSERT_TRUE(mime_info.has_audio_info()); + EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecPcm); +} +#endif // SB_API_VERSION >= 14 + +} // namespace +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/player/decoded_audio_internal.cc b/starboard/shared/starboard/player/decoded_audio_internal.cc index 2834bc2..0d90fe9 100644 --- a/starboard/shared/starboard/player/decoded_audio_internal.cc +++ b/starboard/shared/starboard/player/decoded_audio_internal.cc
@@ -142,17 +142,26 @@ SB_DCHECK(discarded_duration_from_back >= 0); SB_DCHECK(storage_type() == kSbMediaAudioFrameStorageTypeInterleaved); - const auto bytes_per_frame = GetBytesPerSample(sample_type()) * channels_; - auto discarded_frames_from_front = - AudioDurationToFrames(discarded_duration_from_front, sample_rate); + if (discarded_duration_from_front == 0 && discarded_duration_from_back == 0) { + return; + } - discarded_frames_from_front = std::min(discarded_frames_from_front, frames()); + const auto bytes_per_frame = GetBytesPerSample(sample_type()) * channels_; + int current_frames = frames(); + int discarded_frames_from_front = + (discarded_duration_from_front >= + AudioFramesToDuration(current_frames, sample_rate)) + ? current_frames + : AudioDurationToFrames(discarded_duration_from_front, sample_rate); offset_in_bytes_ += bytes_per_frame * discarded_frames_from_front; size_in_bytes_ -= bytes_per_frame * discarded_frames_from_front; - auto discarded_frames_from_back = - AudioDurationToFrames(discarded_duration_from_back, sample_rate); - discarded_frames_from_back = std::min(discarded_frames_from_back, frames()); + current_frames = frames(); + int discarded_frames_from_back = + (discarded_duration_from_back >= + AudioFramesToDuration(current_frames, sample_rate)) + ? current_frames + : AudioDurationToFrames(discarded_duration_from_back, sample_rate); size_in_bytes_ -= bytes_per_frame * discarded_frames_from_back; }
diff --git a/starboard/shared/starboard/player/filter/audio_frame_discarder.cc b/starboard/shared/starboard/player/filter/audio_frame_discarder.cc index b1ad4fb..b8f2c36 100644 --- a/starboard/shared/starboard/player/filter/audio_frame_discarder.cc +++ b/starboard/shared/starboard/player/filter/audio_frame_discarder.cc
@@ -43,8 +43,12 @@ void AudioFrameDiscarder::AdjustForDiscardedDurations( int sample_rate, scoped_refptr<DecodedAudio>* decoded_audio) { - SB_DCHECK(decoded_audio); - SB_DCHECK(*decoded_audio); + if (!decoded_audio || !*decoded_audio) { + SB_LOG(ERROR) << "No input buffer to adjust."; + SB_DCHECK(decoded_audio); + SB_DCHECK(*decoded_audio); + return; + } InputBufferInfo input_info; {
diff --git a/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc index 4a1713e..0816518 100644 --- a/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc +++ b/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -39,6 +39,9 @@ using std::placeholders::_1; using std::placeholders::_2; +typedef shared::starboard::player::PlayerWorker::Handler::HandlerResult + HandlerResult; + // TODO: Make this configurable inside SbPlayerCreate(). const SbTimeMonotonic kUpdateInterval = 200 * kSbTimeMillisecond; @@ -89,7 +92,7 @@ update_job_ = std::bind(&FilterBasedPlayerWorkerHandler::Update, this); } -bool FilterBasedPlayerWorkerHandler::Init( +HandlerResult FilterBasedPlayerWorkerHandler::Init( SbPlayer player, UpdateMediaInfoCB update_media_info_cb, GetPlayerStateCB get_player_state_cb, @@ -126,11 +129,10 @@ SB_LOG(ERROR) << "Audio channels requested " << required_audio_channels << ", but currently supported less than or equal to " << supported_audio_channels; - OnError( - kSbPlayerErrorCapabilityChanged, + std::string error_message = FormatString("Required channel %d is greater than maximum channel %d", - required_audio_channels, supported_audio_channels)); - return false; + required_audio_channels, supported_audio_channels); + return HandlerResult{false, error_message}; } } @@ -140,14 +142,14 @@ { ::starboard::ScopedLock lock(player_components_existence_mutex_); - std::string error_message; - player_components_ = - factory->CreateComponents(creation_parameters, &error_message); + std::string components_error_message; + player_components_ = factory->CreateComponents(creation_parameters, + &components_error_message); if (!player_components_) { - OnError(kSbPlayerErrorDecode, - FormatString("Failed to create player components with error: %s", - error_message.c_str())); - return false; + std::string error_message = + FormatString("Failed to create player components with error: %s.", + components_error_message.c_str()); + return HandlerResult{false, error_message}; } media_time_provider_ = player_components_->GetMediaTimeProvider(); audio_renderer_ = player_components_->GetAudioRenderer(); @@ -187,16 +189,17 @@ update_job_token_ = Schedule(update_job_, kUpdateInterval); - return true; + return HandlerResult{true}; } -bool FilterBasedPlayerWorkerHandler::Seek(SbTime seek_to_time, int ticket) { +HandlerResult FilterBasedPlayerWorkerHandler::Seek(SbTime seek_to_time, + int ticket) { SB_DCHECK(BelongsToCurrentThread()); SB_LOG(INFO) << "Seek to " << seek_to_time << ", and media time provider is " << media_time_provider_; if (!media_time_provider_) { - return false; + return HandlerResult{false, "Invalid media time provider"}; } if (seek_to_time < 0) { @@ -211,10 +214,10 @@ media_time_provider_->Seek(seek_to_time); audio_prerolled_ = false; video_prerolled_ = false; - return true; + return HandlerResult{true}; } -bool FilterBasedPlayerWorkerHandler::WriteSamples( +HandlerResult FilterBasedPlayerWorkerHandler::WriteSamples( const InputBuffers& input_buffers, int* samples_written) { SB_DCHECK(!input_buffers.empty()); @@ -227,19 +230,19 @@ *samples_written = 0; if (input_buffers.front()->sample_type() == kSbMediaTypeAudio) { if (!audio_renderer_) { - return false; + return HandlerResult{false, "Invalid audio renderer."}; } if (audio_renderer_->IsEndOfStreamWritten()) { SB_LOG(WARNING) << "Try to write audio sample after EOS is reached"; } else { if (!audio_renderer_->CanAcceptMoreData()) { - return true; + return HandlerResult{true}; } for (const auto& input_buffer : input_buffers) { if (input_buffer->drm_info()) { if (!SbDrmSystemIsValid(drm_system_)) { - return false; + return HandlerResult{false, "Invalid DRM system."}; } DumpInputHash(input_buffer); SbDrmSystemPrivate::DecryptStatus decrypt_status = @@ -250,10 +253,10 @@ InputBuffers(input_buffers.begin(), input_buffers.begin() + *samples_written)); } - return true; + return HandlerResult{true}; } if (decrypt_status == SbDrmSystemPrivate::kFailure) { - return false; + return HandlerResult{false, "Sample decryption failure."}; } } DumpInputHash(input_buffer); @@ -265,19 +268,19 @@ SB_DCHECK(input_buffers.front()->sample_type() == kSbMediaTypeVideo); if (!video_renderer_) { - return false; + return HandlerResult{false, "Invalid video renderer."}; } if (video_renderer_->IsEndOfStreamWritten()) { SB_LOG(WARNING) << "Try to write video sample after EOS is reached"; } else { if (!video_renderer_->CanAcceptMoreData()) { - return true; + return HandlerResult{true}; } for (const auto& input_buffer : input_buffers) { if (input_buffer->drm_info()) { if (!SbDrmSystemIsValid(drm_system_)) { - return false; + return HandlerResult{false, "Invalid DRM system."}; } DumpInputHash(input_buffer); SbDrmSystemPrivate::DecryptStatus decrypt_status = @@ -288,10 +291,10 @@ InputBuffers(input_buffers.begin(), input_buffers.begin() + *samples_written)); } - return true; + return HandlerResult{true}; } if (decrypt_status == SbDrmSystemPrivate::kFailure) { - return false; + return HandlerResult{false, "Sample decryption failure."}; } } DumpInputHash(input_buffer); @@ -301,16 +304,17 @@ } } - return true; + return HandlerResult{true}; } -bool FilterBasedPlayerWorkerHandler::WriteEndOfStream(SbMediaType sample_type) { +HandlerResult FilterBasedPlayerWorkerHandler::WriteEndOfStream( + SbMediaType sample_type) { SB_DCHECK(BelongsToCurrentThread()); if (sample_type == kSbMediaTypeAudio) { if (!audio_renderer_) { SB_LOG(INFO) << "Audio EOS enqueued when renderer is NULL."; - return false; + return HandlerResult{false, "Audio EOS enqueued when renderer is NULL."}; } if (audio_renderer_->IsEndOfStreamWritten()) { SB_LOG(WARNING) << "Try to write audio EOS after EOS is enqueued"; @@ -321,7 +325,7 @@ } else { if (!video_renderer_) { SB_LOG(INFO) << "Video EOS enqueued when renderer is NULL."; - return false; + return HandlerResult{false, "Video EOS enqueued when renderer is NULL."}; } if (video_renderer_->IsEndOfStreamWritten()) { SB_LOG(WARNING) << "Try to write video EOS after EOS is enqueued"; @@ -331,17 +335,17 @@ } } - return true; + return HandlerResult{true}; } -bool FilterBasedPlayerWorkerHandler::SetPause(bool pause) { +HandlerResult FilterBasedPlayerWorkerHandler::SetPause(bool pause) { SB_DCHECK(BelongsToCurrentThread()); SB_LOG(INFO) << "Set pause from " << paused_ << " to " << pause << ", and media time provider is " << media_time_provider_; if (!media_time_provider_) { - return false; + return HandlerResult{false, "Invalid media time provider."}; } paused_ = pause; @@ -351,11 +355,12 @@ } else { media_time_provider_->Play(); } - - return true; + Update(); + return HandlerResult{true}; } -bool FilterBasedPlayerWorkerHandler::SetPlaybackRate(double playback_rate) { +HandlerResult FilterBasedPlayerWorkerHandler::SetPlaybackRate( + double playback_rate) { SB_DCHECK(BelongsToCurrentThread()); SB_LOG(INFO) << "Set playback rate from " << playback_rate_ << " to " @@ -365,11 +370,12 @@ playback_rate_ = playback_rate; if (!media_time_provider_) { - return false; + return HandlerResult{false, "Invalid media time provider."}; } media_time_provider_->SetPlaybackRate(playback_rate_); - return true; + Update(); + return HandlerResult{true}; } void FilterBasedPlayerWorkerHandler::SetVolume(double volume) { @@ -384,7 +390,7 @@ } } -bool FilterBasedPlayerWorkerHandler::SetBounds(const Bounds& bounds) { +HandlerResult FilterBasedPlayerWorkerHandler::SetBounds(const Bounds& bounds) { SB_DCHECK(BelongsToCurrentThread()); if (memcmp(&bounds_, &bounds, sizeof(bounds_)) != 0) { @@ -407,7 +413,7 @@ } } - return true; + return HandlerResult{true}; } void FilterBasedPlayerWorkerHandler::OnError(SbPlayerError error, @@ -500,6 +506,7 @@ update_media_info_cb_(media_time, dropped_frames, !is_underflow); } + RemoveJobByToken(update_job_token_); update_job_token_ = Schedule(update_job_, kUpdateInterval); }
diff --git a/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h b/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h index 6f3ed97..e553774 100644 --- a/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h +++ b/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
@@ -48,19 +48,19 @@ SbDecodeTargetGraphicsContextProvider* provider); private: - bool Init(SbPlayer player, - UpdateMediaInfoCB update_media_info_cb, - GetPlayerStateCB get_player_state_cb, - UpdatePlayerStateCB update_player_state_cb, - UpdatePlayerErrorCB update_player_error_cb) override; - bool Seek(SbTime seek_to_time, int ticket) override; - bool WriteSamples(const InputBuffers& input_buffers, - int* samples_written) override; - bool WriteEndOfStream(SbMediaType sample_type) override; - bool SetPause(bool pause) override; - bool SetPlaybackRate(double playback_rate) override; + HandlerResult Init(SbPlayer player, + UpdateMediaInfoCB update_media_info_cb, + GetPlayerStateCB get_player_state_cb, + UpdatePlayerStateCB update_player_state_cb, + UpdatePlayerErrorCB update_player_error_cb) override; + HandlerResult Seek(SbTime seek_to_time, int ticket) override; + HandlerResult WriteSamples(const InputBuffers& input_buffers, + int* samples_written) override; + HandlerResult WriteEndOfStream(SbMediaType sample_type) override; + HandlerResult SetPause(bool pause) override; + HandlerResult SetPlaybackRate(double playback_rate) override; void SetVolume(double volume) override; - bool SetBounds(const Bounds& bounds) override; + HandlerResult SetBounds(const Bounds& bounds) override; void Stop() override; void Update();
diff --git a/starboard/shared/starboard/player/filter/stub_audio_decoder.cc b/starboard/shared/starboard/player/filter/stub_audio_decoder.cc index 2f4a7b4..39b5017 100644 --- a/starboard/shared/starboard/player/filter/stub_audio_decoder.cc +++ b/starboard/shared/starboard/player/filter/stub_audio_decoder.cc
@@ -158,11 +158,12 @@ for (const auto& input_buffer : input_buffers) { DecodeOneBuffer(input_buffer); } - decoder_thread_->job_queue()->Schedule(consumed_cb); + Schedule(consumed_cb); } void StubAudioDecoder::DecodeOneBuffer( const scoped_refptr<InputBuffer>& input_buffer) { + SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread()); const int kMaxInputBeforeMultipleDecodedAudios = 4; if (last_input_buffer_) { @@ -189,7 +190,7 @@ if (total_input_count_ % kMaxInputBeforeMultipleDecodedAudios != 0) { ScopedLock lock(decoded_audios_mutex_); decoded_audios_.push(decoded_audio); - decoder_thread_->job_queue()->Schedule(output_cb_); + Schedule(output_cb_); } else { // Divide the content of `decoded_audio` as multiple DecodedAudio objects // to ensure that the user of AudioDecoders works with output in @@ -230,7 +231,7 @@ ScopedLock lock(decoded_audios_mutex_); decoded_audios_.push(current_decoded_audio); - decoder_thread_->job_queue()->Schedule(output_cb_); + Schedule(output_cb_); } } } @@ -274,11 +275,11 @@ ScopedLock lock(decoded_audios_mutex_); decoded_audios_.push(decoded_audio); - decoder_thread_->job_queue()->Schedule(output_cb_); + Schedule(output_cb_); } ScopedLock lock(decoded_audios_mutex_); decoded_audios_.push(new DecodedAudio()); - decoder_thread_->job_queue()->Schedule(output_cb_); + Schedule(output_cb_); } } // namespace filter
diff --git a/starboard/shared/starboard/player/filter/testing/BUILD.gn b/starboard/shared/starboard/player/filter/testing/BUILD.gn index 9d6f6b9..d1cb5be 100644 --- a/starboard/shared/starboard/player/filter/testing/BUILD.gn +++ b/starboard/shared/starboard/player/filter/testing/BUILD.gn
@@ -49,41 +49,41 @@ data_deps = [ "//starboard/shared/starboard/player:player_download_test_data" ] } -} -if (host_os != "win" && current_toolchain == starboard_toolchain) { - target(final_executable_type, "player_filter_benchmarks") { + if (host_os != "win") { + target(final_executable_type, "player_filter_benchmarks") { + testonly = true + + sources = [ + "//starboard/common/benchmark_main.cc", + "audio_decoder_benchmark.cc", + ] + + public_deps = [ + ":test_util", + "//third_party/google_benchmark", + ] + + deps = cobalt_platform_dependencies + } + } + + static_library("test_util") { testonly = true sources = [ - "//starboard/common/benchmark_main.cc", - "audio_decoder_benchmark.cc", + "test_util.cc", + "test_util.h", ] + public_configs = [ "//starboard/build/config:starboard_implementation" ] + public_deps = [ - ":test_util", - "//third_party/google_benchmark", + "//starboard", + "//starboard/shared/starboard/media:media_util", + "//starboard/shared/starboard/player:player_download_test_data", + "//starboard/shared/starboard/player:video_dmp", + "//testing/gtest", ] - - deps = cobalt_platform_dependencies } } - -static_library("test_util") { - testonly = true - - sources = [ - "test_util.cc", - "test_util.h", - ] - - public_configs = [ "//starboard/build/config:starboard_implementation" ] - - public_deps = [ - "//starboard", - "//starboard/shared/starboard/media:media_util", - "//starboard/shared/starboard/player:player_download_test_data", - "//starboard/shared/starboard/player:video_dmp", - "//testing/gtest", - ] -}
diff --git a/starboard/shared/starboard/player/filter/testing/test_util.cc b/starboard/shared/starboard/player/filter/testing/test_util.cc index 25ad281..39ae17d 100644 --- a/starboard/shared/starboard/player/filter/testing/test_util.cc +++ b/starboard/shared/starboard/player/filter/testing/test_util.cc
@@ -181,14 +181,32 @@ const auto& video_stream_info = dmp_reader.video_stream_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(), - video_mime.size() > 0 ? &video_mime_type : nullptr, -1, -1, 8, - kSbMediaPrimaryIdUnspecified, kSbMediaTransferIdUnspecified, - kSbMediaMatrixIdUnspecified, video_stream_info.frame_width, - video_stream_info.frame_height, dmp_reader.video_bitrate(), - dmp_reader.video_fps(), false)) { - test_params.push_back(std::make_tuple(filename, output_mode)); + // SbMediaIsVideoSupported may return false for gpu based decoder that in + // fact supports av1 or/and vp9 because the system can make async + // initialization at startup. + // To minimize probability of false negative we check result few times + static bool decoder_has_been_checked_once = false; + int counter = 5; + const SbMediaVideoCodec video_codec = dmp_reader.video_codec(); + bool need_to_check_with_wait = video_codec == kSbMediaVideoCodecAv1 || + video_codec == kSbMediaVideoCodecVp9; + do { + if (SbMediaIsVideoSupported( + video_codec, video_mime.size() > 0 ? &video_mime_type : nullptr, + -1, -1, 8, kSbMediaPrimaryIdUnspecified, + kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified, + video_stream_info.frame_width, video_stream_info.frame_height, + dmp_reader.video_bitrate(), dmp_reader.video_fps(), false)) { + test_params.push_back(std::make_tuple(filename, output_mode)); + break; + } else if (need_to_check_with_wait && !decoder_has_been_checked_once) { + SbThreadSleep(kSbTimeSecond); + } else { + break; + } + } while (--counter); + if (need_to_check_with_wait) { + decoder_has_been_checked_once = true; } } }
diff --git a/starboard/shared/starboard/player/player_worker.cc b/starboard/shared/starboard/player/player_worker.cc index 5ee9bc0..09947e8 100644 --- a/starboard/shared/starboard/player/player_worker.cc +++ b/starboard/shared/starboard/player/player_worker.cc
@@ -33,6 +33,9 @@ using std::placeholders::_2; using std::placeholders::_3; +typedef shared::starboard::player::PlayerWorker::Handler::HandlerResult + HandlerResult; + #ifdef SB_MEDIA_PLAYER_THREAD_STACK_SIZE const int kPlayerStackSize = SB_MEDIA_PLAYER_THREAD_STACK_SIZE; #else // SB_MEDIA_PLAYER_THREAD_STACK_SIZE @@ -158,9 +161,16 @@ } void PlayerWorker::UpdatePlayerError(SbPlayerError error, + HandlerResult result, const std::string& error_message) { + SB_DCHECK(!result.success); + std::string complete_error_message = error_message; + if (!result.error_message.empty()) { + complete_error_message += " Error: " + result.error_message; + } + SB_LOG(WARNING) << "Encountered player error " << error - << " with message: " << error_message; + << " with message: " << complete_error_message; // Only report the first error. if (error_occurred_.exchange(true)) { return; @@ -168,7 +178,7 @@ if (!player_error_func_) { return; } - player_error_func_(player_, context_, error, error_message.c_str()); + player_error_func_(player_, context_, error, complete_error_message.c_str()); } // static @@ -197,17 +207,18 @@ SB_DCHECK(job_queue_->BelongsToCurrentThread()); Handler::UpdatePlayerErrorCB update_player_error_cb; - update_player_error_cb = - std::bind(&PlayerWorker::UpdatePlayerError, this, _1, _2); - if (handler_->Init( - player_, std::bind(&PlayerWorker::UpdateMediaInfo, this, _1, _2, _3), - std::bind(&PlayerWorker::player_state, this), - std::bind(&PlayerWorker::UpdatePlayerState, this, _1), - update_player_error_cb)) { + update_player_error_cb = std::bind(&PlayerWorker::UpdatePlayerError, this, _1, + HandlerResult{false}, _2); + HandlerResult result = handler_->Init( + player_, std::bind(&PlayerWorker::UpdateMediaInfo, this, _1, _2, _3), + std::bind(&PlayerWorker::player_state, this), + std::bind(&PlayerWorker::UpdatePlayerState, this, _1), + update_player_error_cb); + if (result.success) { UpdatePlayerState(kSbPlayerStateInitialized); } else { - UpdatePlayerError(kSbPlayerErrorDecode, - "Failed to initialize PlayerWorker with unknown error."); + UpdatePlayerError(kSbPlayerErrorDecode, result, + "Failed to initialize PlayerWorker."); } } @@ -232,8 +243,9 @@ pending_audio_buffers_.clear(); pending_video_buffers_.clear(); - if (!handler_->Seek(seek_to_time, ticket)) { - UpdatePlayerError(kSbPlayerErrorDecode, "Failed seek."); + HandlerResult result = handler_->Seek(seek_to_time, ticket); + if (!result.success) { + UpdatePlayerError(kSbPlayerErrorDecode, result, "Failed seek."); return; } @@ -273,9 +285,10 @@ SB_DCHECK(pending_video_buffers_.empty()); } int samples_written; - bool result = handler_->WriteSamples(input_buffers, &samples_written); - if (!result) { - UpdatePlayerError(kSbPlayerErrorDecode, "Failed to write sample."); + HandlerResult result = + handler_->WriteSamples(input_buffers, &samples_written); + if (!result.success) { + UpdatePlayerError(kSbPlayerErrorDecode, result, "Failed to write sample."); return; } if (samples_written == input_buffers.size()) { @@ -341,31 +354,37 @@ SB_DCHECK(pending_video_buffers_.empty()); } - if (!handler_->WriteEndOfStream(sample_type)) { - UpdatePlayerError(kSbPlayerErrorDecode, "Failed to write end of stream."); + HandlerResult result = handler_->WriteEndOfStream(sample_type); + if (!result.success) { + UpdatePlayerError(kSbPlayerErrorDecode, result, + "Failed to write end of stream."); } } void PlayerWorker::DoSetBounds(Bounds bounds) { SB_DCHECK(job_queue_->BelongsToCurrentThread()); - if (!handler_->SetBounds(bounds)) { - UpdatePlayerError(kSbPlayerErrorDecode, "Failed to set bounds"); + HandlerResult result = handler_->SetBounds(bounds); + if (!result.success) { + UpdatePlayerError(kSbPlayerErrorDecode, result, "Failed to set bounds."); } } void PlayerWorker::DoSetPause(bool pause) { SB_DCHECK(job_queue_->BelongsToCurrentThread()); - if (!handler_->SetPause(pause)) { - UpdatePlayerError(kSbPlayerErrorDecode, "Failed to set pause."); + HandlerResult result = handler_->SetPause(pause); + if (!result.success) { + UpdatePlayerError(kSbPlayerErrorDecode, result, "Failed to set pause."); } } void PlayerWorker::DoSetPlaybackRate(double playback_rate) { SB_DCHECK(job_queue_->BelongsToCurrentThread()); - if (!handler_->SetPlaybackRate(playback_rate)) { - UpdatePlayerError(kSbPlayerErrorDecode, "Failed to set playback rate."); + HandlerResult result = handler_->SetPlaybackRate(playback_rate); + if (!result.success) { + UpdatePlayerError(kSbPlayerErrorDecode, result, + "Failed to set playback rate."); } }
diff --git a/starboard/shared/starboard/player/player_worker.h b/starboard/shared/starboard/player/player_worker.h index cf27fb3..96bae3d 100644 --- a/starboard/shared/starboard/player/player_worker.h +++ b/starboard/shared/starboard/player/player_worker.h
@@ -63,6 +63,13 @@ // All functions of this class will be called from the JobQueue thread. class Handler { public: + // Stores the success status of Handler operations. If |success| is false, + // |error_message| may be set with details of the error. + struct HandlerResult { + bool success; + std::string error_message; + }; + typedef PlayerWorker::Bounds Bounds; typedef ::starboard::shared::starboard::player::InputBuffer InputBuffer; typedef ::starboard::shared::starboard::player::InputBuffers InputBuffers; @@ -79,22 +86,23 @@ Handler() = default; virtual ~Handler() {} - // All the following functions return false to signal a fatal error. The - // event processing loop in PlayerWorker will terminate in this case. - virtual bool Init(SbPlayer player, - UpdateMediaInfoCB update_media_info_cb, - GetPlayerStateCB get_player_state_cb, - UpdatePlayerStateCB update_player_state_cb, - UpdatePlayerErrorCB update_player_error_cb) = 0; - virtual bool Seek(SbTime seek_to_time, int ticket) = 0; - virtual bool WriteSamples(const InputBuffers& input_buffers, - int* samples_written) = 0; - virtual bool WriteEndOfStream(SbMediaType sample_type) = 0; - virtual bool SetPause(bool pause) = 0; - virtual bool SetPlaybackRate(double playback_rate) = 0; + // All the following functions set |HandlerResult.success| to false to + // signal a fatal error. The event processing loop in PlayerWorker will + // terminate in this case. + virtual HandlerResult Init(SbPlayer player, + UpdateMediaInfoCB update_media_info_cb, + GetPlayerStateCB get_player_state_cb, + UpdatePlayerStateCB update_player_state_cb, + UpdatePlayerErrorCB update_player_error_cb) = 0; + virtual HandlerResult Seek(SbTime seek_to_time, int ticket) = 0; + virtual HandlerResult WriteSamples(const InputBuffers& input_buffers, + int* samples_written) = 0; + virtual HandlerResult WriteEndOfStream(SbMediaType sample_type) = 0; + virtual HandlerResult SetPause(bool pause) = 0; + virtual HandlerResult SetPlaybackRate(double playback_rate) = 0; virtual void SetVolume(double volume) = 0; - virtual bool SetBounds(const Bounds& bounds) = 0; + virtual HandlerResult SetBounds(const Bounds& bounds) = 0; // Once this function returns, all processing on the Handler and related // objects has to be stopped. The JobQueue will be destroyed immediately @@ -187,7 +195,9 @@ SbPlayerState player_state() const { return player_state_; } void UpdatePlayerState(SbPlayerState player_state); - void UpdatePlayerError(SbPlayerError error, const std::string& message); + void UpdatePlayerError(SbPlayerError error, + Handler::HandlerResult result, + const std::string& message); static void* ThreadEntryPoint(void* context); void RunLoop();
diff --git a/starboard/shared/uwp/application_uwp.cc b/starboard/shared/uwp/application_uwp.cc index c1192f5..b0f18dc 100644 --- a/starboard/shared/uwp/application_uwp.cc +++ b/starboard/shared/uwp/application_uwp.cc
@@ -660,7 +660,8 @@ TryAddCommandArgsFromStarboardFile(&args_); CommandLine cmd_line(args_); if (cmd_line.HasSwitch(kNetArgsCommandSwitchWait)) { - SbTime timeout = kSbTimeSecond * 2; + // Wait for net args is flaky and needs extended wait time on Xbox. + SbTime timeout = kSbTimeSecond * 30; std::string val = cmd_line.GetSwitchValue(kNetArgsCommandSwitchWait); if (!val.empty()) { timeout = atoi(val.c_str());
diff --git a/starboard/shared/uwp/extended_resources_manager.cc b/starboard/shared/uwp/extended_resources_manager.cc index 5b0c78b..21d2821 100644 --- a/starboard/shared/uwp/extended_resources_manager.cc +++ b/starboard/shared/uwp/extended_resources_manager.cc
@@ -26,7 +26,7 @@ #include "starboard/time.h" #include "starboard/xb1/shared/internal_shims.h" #if defined(INTERNAL_BUILD) -#include "internal/starboard/xb1/av1_video_decoder.h" +#include "internal/starboard/xb1/dav1d_video_decoder.h" #include "internal/starboard/xb1/vpx_video_decoder.h" #include "third_party/internal/libvpx_xb1/libvpx/d3dx12.h" #endif // defined(INTERNAL_BUILD) @@ -41,12 +41,36 @@ using ::starboard::shared::starboard::media::MimeSupportabilityCache; using Windows::Foundation::Metadata::ApiInformation; #if defined(INTERNAL_BUILD) -using ::starboard::xb1::shared::Av1VideoDecoder; +using ::starboard::xb1::shared::Dav1dVideoDecoder; +using ::starboard::xb1::shared::GpuVideoDecoderBase; using ::starboard::xb1::shared::VpxVideoDecoder; #endif // defined(INTERNAL_BUILD) const SbTime kReleaseTimeout = kSbTimeSecond; +// kFrameBuffersPoolMemorySize is the size of gpu memory heap for common use +// by vpx & av1 sw decoders. +// This value must be greater then max(av1_min_value, vpx_min_value), where +// av1_min_value & vpx_min_value are minimal required memory size for sw av1 & +// vpx decoders. +// +// Vpx sw decoder needs 13 internal frame buffers for work and at least +// 8 buffers for preroll. +// The size of fb is 13762560 for 4K SDR and 12976128 for 2K HDR +// So, vpx decoder needs minimum 13762560 * (13 + preroll_size) = 289013760 +// bytes. +// +// Av1 sw decoder needs 13 internal buffers and 8 buffers for preroll. +// The size of fb is 5996544 for 2K SDR and 11993088 for 2K HDR +// av1 decoder needs minimum 11993088 * (13 + preroll_size) = 251854848 bytes. +// +// So, the value 289013760 is minimal for reliable decoders working. +// +// To make playback more smooth it is better to increase the output queue size +// up to 30-50 frames, but it should not exceed memory budgetd. +// So, the value of 440 Mb looks as compromise. +const uint64_t kFrameBuffersPoolMemorySize = 440 * 1024 * 1024; + bool IsExtendedResourceModeRequired() { if (!::starboard::xb1::shared::CanAcquire()) { return false; @@ -150,6 +174,7 @@ bool ExtendedResourcesManager::GetD3D12Objects( Microsoft::WRL::ComPtr<ID3D12Device>* device, + Microsoft::WRL::ComPtr<ID3D12Heap>* buffer_heap, void** command_queue) { if (HasNonrecoverableFailure()) { SB_LOG(WARNING) << "The D3D12 device has encountered a nonrecoverable " @@ -184,8 +209,8 @@ D3D12_HEAP_PROPERTIES prop = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(1024 * 1024); HRESULT result = d3d12device_->CreateCommittedResource( - &prop, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST, - nullptr, IID_PPV_ARGS(&res)); + &prop, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr, + IID_PPV_ARGS(&res)); if (result != S_OK) { SB_LOG(WARNING) << "The D3D12 device is not in a good state, can not use " "GPU based decoders."; @@ -196,11 +221,25 @@ *device = d3d12device_; *command_queue = d3d12queue_.Get(); + *buffer_heap = d3d12FrameBuffersHeap_.Get(); return true; } bool ExtendedResourcesManager::GetD3D12ObjectsInternal() { if (!d3d12device_) { + UINT dxgiFactoryFlags = 0; +#if defined(_DEBUG) + { + // This can help to debug DX issues. If something goes wrong in DX, + // Debug Layer outputs detailed log + ComPtr<ID3D12Debug> debugController; + HRESULT hr = D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)); + if (SUCCEEDED(hr)) { + debugController->EnableDebugLayer(); + } + } +#endif + if (FAILED(D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&d3d12device_)))) { // GPU based vp9 decoding will be temporarily disabled. @@ -221,8 +260,26 @@ } SB_DCHECK(d3d12queue_); } + if (!d3d12FrameBuffersHeap_) { + D3D12_HEAP_DESC heap_desc; + heap_desc.SizeInBytes = kFrameBuffersPoolMemorySize; + heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; + heap_desc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heap_desc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heap_desc.Properties.CreationNodeMask = 0; + heap_desc.Properties.VisibleNodeMask = 0; + heap_desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + heap_desc.Flags = D3D12_HEAP_FLAG_NONE; - return d3d12device_ && d3d12queue_; + if (FAILED(d3d12device_->CreateHeap( + &heap_desc, IID_PPV_ARGS(&d3d12FrameBuffersHeap_)))) { + SB_LOG(WARNING) << "Failed to create d3d12 buffer."; + return false; + } + SB_DCHECK(d3d12FrameBuffersHeap_); + } + + return d3d12device_ && d3d12queue_ && d3d12FrameBuffersHeap_; } bool ExtendedResourcesManager::AcquireExtendedResourcesInternal() { @@ -335,7 +392,7 @@ "shader compile."; return; } - if (Av1VideoDecoder::CompileShaders(d3d12device_, d3d12queue_.Get())) { + if (Dav1dVideoDecoder::CompileShaders(d3d12device_)) { is_av1_shader_compiled_ = true; SB_LOG(INFO) << "Gpu based AV1 decoder finished compiling its shaders."; } else { @@ -352,7 +409,8 @@ return; } - if (VpxVideoDecoder::CompileShaders(d3d12device_, d3d12queue_.Get())) { + if (VpxVideoDecoder::CompileShaders(d3d12device_, d3d12FrameBuffersHeap_, + d3d12queue_.Get())) { is_vp9_shader_compiled_ = true; SB_LOG(INFO) << "Gpu based VP9 decoder finished compiling its shaders."; } else { @@ -372,10 +430,6 @@ void ExtendedResourcesManager::ReleaseExtendedResourcesInternal() { SB_DCHECK(thread_checker_.CalledOnValidThread()); -#if defined(INTERNAL_BUILD) - Av1VideoDecoder::ClearFrameBufferPool(); -#endif // defined(INTERNAL_BUILD) - ScopedLock scoped_lock(mutex_); if (!is_extended_resources_acquired_.load()) { SB_LOG(INFO) << "Extended resources hasn't been acquired," @@ -410,7 +464,7 @@ SB_LOG(INFO) << "CreateEvent() failed with " << GetLastError(); } #if defined(INTERNAL_BUILD) - Av1VideoDecoder::ReleaseShaders(); + Dav1dVideoDecoder::ReleaseShaders(); VpxVideoDecoder::ReleaseShaders(); #endif // #if defined(INTERNAL_BUILD) is_av1_shader_compiled_ = false; @@ -418,27 +472,41 @@ } else { SB_LOG(INFO) << "CreateFence() failed with " << hr; } +#if defined(INTERNAL_BUILD) + // Clear frame buffers used for rendering queue + GpuVideoDecoderBase::ClearFrameBuffersPool(); +#endif // #if defined(INTERNAL_BUILD) } if (d3d12queue_) { #if !defined(COBALT_BUILD_TYPE_GOLD) d3d12queue_->AddRef(); ULONG reference_count = d3d12queue_->Release(); - SB_DLOG(INFO) << "Reference count of |d3d12queue_| is " - << reference_count; + SB_LOG(INFO) << "Reference count of |d3d12queue_| is " << reference_count; #endif d3d12queue_.Reset(); } + if (d3d12FrameBuffersHeap_) { +#if !defined(COBALT_BUILD_TYPE_GOLD) + d3d12FrameBuffersHeap_->AddRef(); + ULONG reference_count = d3d12FrameBuffersHeap_->Release(); + SB_LOG(INFO) << "Reference count of |d3d12FrameBuffersHeap_| is " + << reference_count; +#endif + d3d12FrameBuffersHeap_.Reset(); + } + if (d3d12device_) { #if !defined(COBALT_BUILD_TYPE_GOLD) d3d12device_->AddRef(); ULONG reference_count = d3d12device_->Release(); - SB_DLOG(INFO) << "Reference count of |d3d12device_| is " - << reference_count; + SB_LOG(INFO) << "Reference count of |d3d12device_| is " + << reference_count; #endif d3d12device_.Reset(); } + } catch (const std::exception& e) { SB_LOG(ERROR) << "Exception on releasing extended resources: " << e.what(); OnNonrecoverableFailure();
diff --git a/starboard/shared/uwp/extended_resources_manager.h b/starboard/shared/uwp/extended_resources_manager.h index fbd8a5b..54b966f 100644 --- a/starboard/shared/uwp/extended_resources_manager.h +++ b/starboard/shared/uwp/extended_resources_manager.h
@@ -47,8 +47,10 @@ void ReleaseExtendedResources(); void Quit(); - // Returns true when the d3d12 device and command queue can be used. + // Returns true when the d3d12 device, buffer heap + // and command queue can be used. bool GetD3D12Objects(Microsoft::WRL::ComPtr<ID3D12Device>* device, + Microsoft::WRL::ComPtr<ID3D12Heap>* buffer_heap, void** command_queue); bool IsGpuDecoderReady() const { @@ -91,6 +93,8 @@ Queue<Event> event_queue_; Microsoft::WRL::ComPtr<ID3D12Device> d3d12device_; Microsoft::WRL::ComPtr<ID3D12CommandQueue> d3d12queue_; + // heap for frame buffers (for the decoder and output queue) memory allocation + Microsoft::WRL::ComPtr<ID3D12Heap> d3d12FrameBuffersHeap_; // This is set to true when a release of extended resources is requested. // Anything delaying the release should be expedited when this is set.
diff --git a/starboard/shared/uwp/player_components_factory.cc b/starboard/shared/uwp/player_components_factory.cc index 6963151..c8511be 100644 --- a/starboard/shared/uwp/player_components_factory.cc +++ b/starboard/shared/uwp/player_components_factory.cc
@@ -43,7 +43,7 @@ #include "starboard/xb1/shared/video_decoder_uwp.h" #if defined(INTERNAL_BUILD) -#include "internal/starboard/xb1/av1_video_decoder.h" +#include "internal/starboard/xb1/dav1d_video_decoder.h" #include "internal/starboard/xb1/vpx_video_decoder.h" #endif // defined(INTERNAL_BUILD) @@ -236,9 +236,10 @@ SB_DCHECK(output_mode == kSbPlayerOutputModeDecodeToTexture); Microsoft::WRL::ComPtr<ID3D12Device> d3d12device; + Microsoft::WRL::ComPtr<ID3D12Heap> d3d12buffer_heap; void* d3d12queue = nullptr; if (!uwp::ExtendedResourcesManager::GetInstance()->GetD3D12Objects( - &d3d12device, &d3d12queue)) { + &d3d12device, &d3d12buffer_heap, &d3d12queue)) { // Somehow extended resources get lost. Returns directly to trigger an // error to the player. *error_message = @@ -248,24 +249,25 @@ return false; } SB_DCHECK(d3d12device); + SB_DCHECK(d3d12buffer_heap); SB_DCHECK(d3d12queue); #if defined(INTERNAL_BUILD) using GpuVp9VideoDecoder = ::starboard::xb1::shared::VpxVideoDecoder; - using GpuAv1VideoDecoder = ::starboard::xb1::shared::Av1VideoDecoder; + using GpuAv1VideoDecoder = ::starboard::xb1::shared::Dav1dVideoDecoder; if (video_codec == kSbMediaVideoCodecVp9) { video_decoder->reset(new GpuVp9VideoDecoder( creation_parameters.decode_target_graphics_context_provider(), creation_parameters.video_stream_info(), is_hdr_video, d3d12device, - d3d12queue)); + d3d12buffer_heap, d3d12queue)); } if (video_codec == kSbMediaVideoCodecAv1) { video_decoder->reset(new GpuAv1VideoDecoder( creation_parameters.decode_target_graphics_context_provider(), creation_parameters.video_stream_info(), is_hdr_video, d3d12device, - d3d12queue)); + d3d12buffer_heap, d3d12queue)); } #endif // defined(INTERNAL_BUILD)
diff --git a/starboard/shared/uwp/time_zone_get_name.cc b/starboard/shared/uwp/time_zone_get_name.cc new file mode 100644 index 0000000..58fc8b9 --- /dev/null +++ b/starboard/shared/uwp/time_zone_get_name.cc
@@ -0,0 +1,45 @@ +// Copyright 2023 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/time_zone.h" + +#include <Windows.h> +#include <string> + +#include "starboard/once.h" +#include "starboard/shared/win32/wchar_utils.h" + +namespace { +class TimeZoneString { + public: + static TimeZoneString* Get(); + const char* value() const { return value_.c_str(); } + + private: + TimeZoneString() { + Windows::Globalization::Calendar ^ calendar = + ref new Windows::Globalization::Calendar(); + Platform::String ^ time_zone = calendar->GetTimeZone(); + value_ = starboard::shared::win32::platformStringToString(time_zone); + } + std::string value_; +}; + +SB_ONCE_INITIALIZE_FUNCTION(TimeZoneString, TimeZoneString::Get); +} // namespace. + +const char* SbTimeZoneGetName() { + const char* output = TimeZoneString::Get()->value(); + return output; +}
diff --git a/starboard/shared/uwp/wasapi_audio_sink.cc b/starboard/shared/uwp/wasapi_audio_sink.cc index 2beffa1..d8f5990 100644 --- a/starboard/shared/uwp/wasapi_audio_sink.cc +++ b/starboard/shared/uwp/wasapi_audio_sink.cc
@@ -97,9 +97,9 @@ return false; } - hr = device_->Activate( - IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, - reinterpret_cast<void**>(audio_endpoint_volume_.GetAddressOf())); + hr = audio_client_->GetService( + IID_ISimpleAudioVolume, + reinterpret_cast<void**>(audio_volume_.GetAddressOf())); if (hr != S_OK) { SB_LOG(ERROR) << "Failed to initialize volume handler, error code: " << std::hex << hr; @@ -294,7 +294,7 @@ } double volume = volume_.load(); if (current_volume_ != volume) { - hr = audio_endpoint_volume_->SetMasterVolumeLevelScalar(volume, NULL); + hr = audio_volume_->SetMasterVolume(volume, NULL); CHECK_HRESULT_OK(hr); current_volume_ = volume; }
diff --git a/starboard/shared/uwp/wasapi_audio_sink.h b/starboard/shared/uwp/wasapi_audio_sink.h index 86863a4..3d94f53 100644 --- a/starboard/shared/uwp/wasapi_audio_sink.h +++ b/starboard/shared/uwp/wasapi_audio_sink.h
@@ -16,11 +16,9 @@ #define STARBOARD_SHARED_UWP_WASAPI_AUDIO_SINK_H_ #include <Audioclient.h> -#include <endpointvolume.h> #include <mmdeviceapi.h> #include <wrl\client.h> -#include <atomic> #include <functional> #include <queue> @@ -119,7 +117,6 @@ }; const IID IID_IAudioClock = __uuidof(IAudioClock); -const IID IID_IAudioEndpointVolume = __uuidof(IAudioEndpointVolume); const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); const IID IID_ISimpleAudioVolume = __uuidof(ISimpleAudioVolume); @@ -168,7 +165,7 @@ Microsoft::WRL::ComPtr<IMMDevice> device_; Microsoft::WRL::ComPtr<IAudioClient3> audio_client_; Microsoft::WRL::ComPtr<IAudioRenderClient> render_client_; - Microsoft::WRL::ComPtr<IAudioEndpointVolume> audio_endpoint_volume_; + Microsoft::WRL::ComPtr<ISimpleAudioVolume> audio_volume_; Mutex audio_clock_mutex_; Microsoft::WRL::ComPtr<IAudioClock> audio_clock_;
diff --git a/starboard/shared/widevine/drm_system_widevine.cc b/starboard/shared/widevine/drm_system_widevine.cc index 08729a8..3320eba 100644 --- a/starboard/shared/widevine/drm_system_widevine.cc +++ b/starboard/shared/widevine/drm_system_widevine.cc
@@ -157,9 +157,10 @@ SB_ONCE_INITIALIZE_FUNCTION(Mutex, GetInitializationMutex); void EnsureWidevineCdmIsInitialized(const std::string& company_name, - const std::string& model_name) { + const std::string& model_name, + WidevineStorage* storage) { + SB_DCHECK(storage) << "|storage| is NULL."; static WidevineClock s_clock; - static WidevineStorage s_storage(GetWidevineStoragePath()); static WidevineTimer s_timer; static bool s_initialized = false; @@ -190,7 +191,7 @@ log_level = wv3cdm::kSilent; #endif // COBALT_BUILD_TYPE_GOLD wv3cdm::Status status = - wv3cdm::initialize(wv3cdm::kNoSecureOutput, client_info, &s_storage, + wv3cdm::initialize(wv3cdm::kNoSecureOutput, client_info, storage, &s_clock, &s_timer, log_level); SB_DCHECK(status == wv3cdm::kSuccess); s_initialized = true; @@ -235,9 +236,10 @@ } #endif // !defined(COBALT_BUILD_TYPE_GOLD) - EnsureWidevineCdmIsInitialized(company_name, model_name); + static WidevineStorage s_storage(GetWidevineStoragePath()); + EnsureWidevineCdmIsInitialized(company_name, model_name, &s_storage); const bool kEnablePrivacyMode = true; - cdm_.reset(wv3cdm::create(this, NULL, kEnablePrivacyMode)); + cdm_.reset(wv3cdm::create(this, &s_storage, kEnablePrivacyMode)); SB_DCHECK(cdm_); // Get cert scope and pass to widevine.
diff --git a/starboard/shared/win32/dx_context_video_decoder.cc b/starboard/shared/win32/dx_context_video_decoder.cc index 1ca7403..b576659 100644 --- a/starboard/shared/win32/dx_context_video_decoder.cc +++ b/starboard/shared/win32/dx_context_video_decoder.cc
@@ -45,7 +45,7 @@ query_display(display, EGL_DEVICE_EXT, &egl_device); SB_DCHECK(egl_device != 0); - intptr_t device; + intptr_t device = 0; query_device(reinterpret_cast<EGLDeviceEXT>(egl_device), EGL_D3D11_DEVICE_ANGLE, &device);
diff --git a/starboard/shared/win32/test_filters.py b/starboard/shared/win32/test_filters.py index 2619466..352adbb 100644 --- a/starboard/shared/win32/test_filters.py +++ b/starboard/shared/win32/test_filters.py
@@ -15,7 +15,15 @@ from starboard.tools.testing import test_filter -_FILTERED_TESTS = {} +_FILTERED_TESTS = { + 'nplb': [ + # Windows uses a special time zone format that ICU accepts, so we don't + # enforce IANA. + # TODO(b/304335954): Re-enable the test for UWP after fixing DST + # implementation. + 'SbTimeZoneGetNameTest.IsIANAFormat', + ], +} class TestFilters(object):
diff --git a/starboard/shared/win32/time_zone_get_name.cc b/starboard/shared/win32/time_zone_get_name.cc index bc92e3b..9c31522 100644 --- a/starboard/shared/win32/time_zone_get_name.cc +++ b/starboard/shared/win32/time_zone_get_name.cc
@@ -28,6 +28,10 @@ const char* value() const { return value_.c_str(); } private: + // Returns a string representing a time zone name, e.g. "EST" for Eastern + // Standard Time or "PDT" for Pacific Daylight Time. There isn't a native way + // to convert these to IANA name format on Windows without UWP, so we're + // making use of GetDynamicTimeZoneInformation for now. TimeZoneString() { DYNAMIC_TIME_ZONE_INFORMATION time_zone_info; DWORD zone_id = GetDynamicTimeZoneInformation(&time_zone_info);
diff --git a/starboard/shared/win32/video_decoder.cc b/starboard/shared/win32/video_decoder.cc index b69cdaf..f889019 100644 --- a/starboard/shared/win32/video_decoder.cc +++ b/starboard/shared/win32/video_decoder.cc
@@ -194,6 +194,10 @@ HardwareDecoderContext hardware_context = GetDirectXForHardwareDecoding(); d3d_device_ = hardware_context.dx_device_out; device_manager_ = hardware_context.dxgi_device_manager_out; + if (!d3d_device_ || !device_manager_) { + return; + } + HRESULT hr = d3d_device_.As(&video_device_); if (FAILED(hr)) { return;
diff --git a/starboard/time_zone.h b/starboard/time_zone.h index ce9e424..d140338 100644 --- a/starboard/time_zone.h +++ b/starboard/time_zone.h
@@ -29,18 +29,17 @@ // The number of minutes west of the Greenwich Prime Meridian, NOT including // Daylight Savings Time adjustments. // -// For example: PST/PDT is 480 minutes (28800 seconds, 8 hours). +// For example: America/Los_Angeles is 480 minutes (28800 seconds, 8 hours). typedef int SbTimeZone; // Gets the system's current SbTimeZone in minutes. SB_EXPORT SbTimeZone SbTimeZoneGetCurrent(); -// Gets a string representation of the current timezone. Note that the string -// representation can either be standard or daylight saving time. The output -// can be of the form: -// 1) A three-letter abbreviation such as "PST" or "PDT" (preferred). -// 2) A time zone identifier such as "America/Los_Angeles" -// 3) An un-abbreviated name such as "Pacific Standard Time". +// Gets a string representation of the current timezone. The format should be +// in the IANA format https://data.iana.org/time-zones/theory.html#naming . +// Names normally have the form AREA/LOCATION, where AREA is a continent or +// ocean, and LOCATION is a specific location within the area. +// Typical names are 'Africa/Cairo', 'America/New_York', and 'Pacific/Honolulu'. SB_EXPORT const char* SbTimeZoneGetName(); #ifdef __cplusplus
diff --git a/starboard/tools/abstract_launcher.py b/starboard/tools/abstract_launcher.py index 9928039..d57589e 100644 --- a/starboard/tools/abstract_launcher.py +++ b/starboard/tools/abstract_launcher.py
@@ -18,6 +18,7 @@ import abc import os import sys +from enum import IntEnum from starboard.tools import build from starboard.tools import paths @@ -78,7 +79,7 @@ RuntimeError: The platform does not exist, or there is no project root. """ - # Creates launcher for provided platform if the platform has a valid port + # Creates launcher for provided platform if the platform has a valid port. launcher_module = _GetLauncherForPlatform(platform_name) if not launcher_module: @@ -97,8 +98,51 @@ **kwargs) +class TargetStatus(IntEnum): + """Represents status of the target run and its return code availability.""" + + # Target exited normally. Return code is available. + OK = 0 + + # Target exited normally. Return code is not available. + NA = 1 + + # Target crashed. + CRASH = 2 + + # Target not started. + NOT_STARTED = 3 + + @classmethod + def ToString(cls, status): + return [ + "SUCCEEDED", "SUCCEEDED", "FAILED (CRASHED)", "FAILED (NOT STARTED)" + ][status] + + class AbstractLauncher(object): - """Class that specifies all required behavior for Cobalt app launchers.""" + """ + Class that specifies all required behavior for Cobalt app launchers. + The following is definition of extended interface. Implementation is optional. + Functions: + - InitDevice: a function to be called before any other performance + on the target device. + - RebootDevice: a function, used to reboot the target device. + - Run2: a function to run a target on device. Must be implemented + to support extended interface. The target must have been deployed + on the device, if required. + Returns: + a tuple of ReturnCodeStatus and, target return code if available, + else 0. + - Deploy: creates a package (if required) and deploys it on the target + device. + - CheckPackageIsDeployed: a function, which returns True, if the target + is deployed on the device, False otherwise. Must be implemented, + if Deploy is implemented. + - Kill: request the target OS to kill the target process. Must be + implemented. + - SendStop: sends stop signal to the launcher's executable. + """ __metaclass__ = abc.ABCMeta @@ -143,30 +187,32 @@ self.test_result_xml_path = kwargs.get("test_result_xml_path", None) + def HasExtendedInterface(self) -> bool: + return hasattr(self, "Run2") + @abc.abstractmethod def Run(self): - """Runs the launcher's executable. + """Runs an underlying application. Supports launching target + executable on target device, or target executable directly. + Implementation is platform specific. Must be implemented in subclasses. Returns: - The return code from the launcher's executable. + The return code from the underlying application, if available, + else, 0 in case of normal completion of the underlying application, + otherwise 1. """ pass @abc.abstractmethod - def Kill(self): - """Kills the launcher. Must be implemented in subclasses.""" - pass - - @abc.abstractmethod def GetDeviceIp(self): """Gets the device IP. Must be implemented in subclasses.""" pass @abc.abstractmethod def GetDeviceOutputPath(self): - """Writable path where test targets can output files""" + """Writable path where test targets can output files.""" pass def SupportsSuspendResume(self): @@ -219,14 +265,6 @@ """ raise RuntimeError("Freeze not supported for this platform.") - def SendStop(self): - """sends stop signal to the launcher's executable. - - Raises: - RuntimeError: Stop signal not supported on platform. - """ - raise RuntimeError("Stop not supported for this platform.") - def SupportsDeepLink(self): return False @@ -290,7 +328,7 @@ The default path returned by this method takes the form of: - "/path/to/out/<platform>_<config>/target_name" + "/path/to/out/<platform>_<config>/target_name". Returns: The path to an executable target. @@ -302,7 +340,7 @@ The default path returned by this method takes the form of: - "/path/to/out/<platform>_<config>/install/target_name" + "/path/to/out/<platform>_<config>/install/target_name". Returns: The path to an executable target.
diff --git a/starboard/tools/testing/test_filter.py b/starboard/tools/testing/test_filter.py index 6c2af98..6a787b0 100644 --- a/starboard/tools/testing/test_filter.py +++ b/starboard/tools/testing/test_filter.py
@@ -19,6 +19,13 @@ FILTER_ALL = 'FILTER_ALL' DISABLE_TESTING = 'DISABLE_TESTING' +EVERGREEN_ONLY_TESTS = { + 'elf_loader_test': {FILTER_ALL}, + 'installation_manager_test': {FILTER_ALL}, + 'reset_evergreen_update_test': {FILTER_ALL}, + 'slot_management_test': {FILTER_ALL}, +} + class TestFilter(object): """Container for data used to filter out a unit test.
diff --git a/starboard/tools/testing/test_runner.py b/starboard/tools/testing/test_runner.py index 8ae6f49..0fb343a 100755 --- a/starboard/tools/testing/test_runner.py +++ b/starboard/tools/testing/test_runner.py
@@ -29,6 +29,7 @@ from six.moves import cStringIO as StringIO from starboard.build import clang from starboard.tools import abstract_launcher +from starboard.tools.abstract_launcher import TargetStatus from starboard.tools import build from starboard.tools import command_line from starboard.tools import paths @@ -160,14 +161,33 @@ communicate, and for the main thread to shut them down. """ - def __init__(self, launcher): + def __init__(self, launcher, skip_init): self.launcher = launcher - self.runner_thread = threading.Thread(target=self._Run) self.return_code_lock = threading.Lock() self.return_code = 1 + self.run_test = None + self.skip_init = skip_init def Start(self): + if self.launcher.HasExtendedInterface(): + if not self.skip_init: + if hasattr(self.launcher, "InitDevice"): + self.launcher.InitDevice() + if hasattr(self.launcher, "Deploy"): + assert hasattr(self.launcher, "CheckPackageIsDeployed") + if abstract_launcher.ARG_NOINSTALL not in self.launcher.launcher_args: + self.launcher.Deploy() + if not self.launcher.CheckPackageIsDeployed(): + raise IOError( + "The target application is not installed on the device.") + + self.run_test = self.launcher.Run2 + + else: + self.run_test = lambda: (TargetStatus.OK, self.launcher.Run()) + + self.runner_thread = threading.Thread(target=self._Run) self.runner_thread.start() def Kill(self): @@ -183,12 +203,12 @@ def Join(self): self.runner_thread.join() - def _Run(self): - """Runs the launcher, and assigns a return code.""" - return_code = 1 + def _Run(self) -> None: + """Runs the launcher, and assigns a status and a return code.""" + return_code = TargetStatus.NOT_STARTED, 0 try: logging.info("Running launcher") - return_code = self.launcher.Run() + return_code = self.run_test() logging.info("Finished running launcher") except Exception: # pylint: disable=broad-except sys.stderr.write(f"Error while running {self.launcher.target_name}:\n") @@ -259,6 +279,7 @@ self.xml_output_dir = xml_output_dir self.log_xml_results = log_xml_results self.threads = [] + self.is_initialized = False _EnsureBuildDirectoryExists(self.out_directory) _VerifyConfig(self._platform_config, @@ -273,6 +294,7 @@ # If a particular test binary has been provided, configure only that one. logging.info("Getting test targets") + if specified_targets: self.test_targets = self._GetSpecifiedTestTargets(specified_targets) else: @@ -392,11 +414,11 @@ env_variables[test] = test_env return env_variables - def _RunTest(self, - target_name, - test_name=None, - shard_index=None, - shard_count=None): + def RunTest(self, + target_name, + test_name=None, + shard_index=None, + shard_count=None): """Runs a specific target or test and collects the output. Args: @@ -497,7 +519,7 @@ logging.info("Launcher initialized") test_reader = TestLineReader(read_pipe) - test_launcher = TestLauncher(launcher) + test_launcher = TestLauncher(launcher, self.is_initialized) self.threads.append(test_launcher) self.threads.append(test_reader) @@ -524,6 +546,7 @@ # Wait for the launcher to exit then close the write pipe, which will # cause the reader to exit. test_launcher.Join() + self.is_initialized = True write_pipe.close() # Only after closing the write pipe, wait for the reader to exit. @@ -533,10 +556,10 @@ output = test_reader.GetLines() self.threads = [] - return self._CollectTestResults(output, target_name, - test_launcher.GetReturnCode()) + return (target_name, *self._CollectTestResults(output), + *test_launcher.GetReturnCode()) - def _CollectTestResults(self, results, target_name, return_code): + def _CollectTestResults(self, results): """Collects passing and failing tests for one test binary. Args: @@ -570,8 +593,7 @@ # Descriptions of all failed tests appear after this line failed_tests = self._CollectFailedTests(results[idx + 1:]) - return (target_name, total_count, passed_count, failed_count, failed_tests, - return_code) + return (total_count, passed_count, failed_count, failed_tests) def _CollectFailedTests(self, lines): """Collects the names of all failed tests. @@ -630,7 +652,8 @@ passed_count = result_set[2] failed_count = result_set[3] failed_tests = result_set[4] - return_code = result_set[5] + return_code_status = result_set[5] + return_code = result_set[6] actual_failed_tests = [] flaky_failed_tests = [] filtered_tests = self._GetFilteredTestList(target_name) @@ -660,7 +683,7 @@ for retry in range(_FLAKY_RETRY_LIMIT): # Sometimes the returned test "name" includes information about the # parameter that was passed to it. This needs to be stripped off. - retry_result = self._RunTest(target_name, test_case.split(",")[0]) + retry_result = self.RunTest(target_name, test_case.split(",")[0]) print() # Explicit print for empty formatting line. if retry_result[2] == 1: flaky_passed_tests.append(test_case) @@ -676,24 +699,26 @@ else: logging.info("") # formatting newline. - test_status = "SUCCEEDED" + test_status = TargetStatus.ToString(return_code_status) all_flaky_tests_succeeded = initial_flaky_failed_count == len( flaky_passed_tests) and initial_flaky_failed_count != 0 # Always mark as FAILED if we have a non-zero return code, or failing # test. - if ((return_code != 0 and not all_flaky_tests_succeeded) or - actual_failed_count > 0 or flaky_failed_count > 0): + if ((return_code_status == TargetStatus.OK and return_code != 0 and + not all_flaky_tests_succeeded) or actual_failed_count > 0 or + flaky_failed_count > 0): error = True - test_status = "FAILED" failed_test_groups.append(target_name) # Be specific about the cause of failure if it was caused due to crash # upon exit. Normal Gtest failures have return_code = 1; test crashes # yield different return codes (e.g. segfault has return_code = 11). if (return_code != 1 and actual_failed_count == 0 and flaky_failed_count == 0): - test_status = "FAILED (CRASHED)" + test_status = "FAILED (ISSUE)" + else: + test_status = "FAILED" logging.info("%s: %s.", target_name, test_status) if return_code != 0 and run_count == 0 and filtered_count == 0: @@ -808,12 +833,12 @@ if run_action == ShardingTestConfig.RUN_FULL_TEST: logging.info("SHARD %d RUNS TEST %s (full)", self.shard_index, test_target) - results.append(self._RunTest(test_target)) + results.append(self.RunTest(test_target)) elif run_action == ShardingTestConfig.RUN_PARTIAL_TEST: logging.info("SHARD %d RUNS TEST %s (%d of %d)", self.shard_index, test_target, sub_shard_index + 1, sub_shard_count) results.append( - self._RunTest( + self.RunTest( test_target, shard_index=sub_shard_index, shard_count=sub_shard_count)) @@ -822,7 +847,7 @@ logging.info("SHARD %d SKIP TEST %s", self.shard_index, test_target) else: # Run all tests and cases serially. No sharding enabled. - results.append(self._RunTest(test_target)) + results.append(self.RunTest(test_target)) return self._ProcessAllTestResults(results) def GenerateCoverageReport(self): @@ -957,6 +982,7 @@ launcher_args.append(abstract_launcher.ARG_DRYRUN) logging.info("Initializing test runner") + runner = TestRunner(args.platform, args.config, args.loader_platform, args.loader_config, args.device_id, args.target_name, target_params, args.out_directory, @@ -991,7 +1017,16 @@ return 1 if args.run: - run_success = runner.RunAllTests() + if isinstance(args.target_name, list) and len(args.target_name) == 1: + r = runner.RunTest(args.target_name[0]) + if r[5] == TargetStatus.OK: + return r[6] + elif r[5] == TargetStatus.NA: + return 0 + else: + return 1 + else: + run_success = runner.RunAllTests() runner.GenerateCoverageReport()
diff --git a/starboard/win/shared/BUILD.gn b/starboard/win/shared/BUILD.gn index 9501d8f..a99e7e8 100644 --- a/starboard/win/shared/BUILD.gn +++ b/starboard/win/shared/BUILD.gn
@@ -30,16 +30,18 @@ cflags = [ "/EHsc", # C++ exceptions (required with /ZW) - ] - - cflags += [ "/FU${msvc_path}/lib/x86/store/references/platform.winmd", "/FU${windows_sdk_path}/References/$wdk_version/Windows.Foundation.FoundationContract/4.0.0.0/Windows.Foundation.FoundationContract.winmd", "/FU${windows_sdk_path}/References/$wdk_version/Windows.Foundation.UniversalApiContract/14.0.0.0/Windows.Foundation.UniversalApiContract.winmd", "/FU${windows_sdk_path}/References/$wdk_version/Windows.UI.ViewManagement.ViewManagementViewScalingContract/1.0.0.0/Windows.UI.ViewManagement.ViewManagementViewScalingContract.winmd", - "/FU${windows_sdk_path}/References/$wdk_version/Windows.Xbox.ApplicationResourcesContract/2.0.0.0/Windows.Xbox.ApplicationResourcesContract.winmd", - "/FU${windows_sdk_path}/References/$wdk_version/Windows.Xbox.Security.ApplicationSpecificDeviceAuthenticationContract/1.0.0.0/Windows.Xbox.Security.ApplicationSpecificDeviceAuthenticationContract.winmd", ] + + if (is_internal_build) { + cflags += [ + "/FU${windows_sdk_path}/References/$wdk_version/Windows.Xbox.ApplicationResourcesContract/2.0.0.0/Windows.Xbox.ApplicationResourcesContract.winmd", + "/FU${windows_sdk_path}/References/$wdk_version/Windows.Xbox.Security.ApplicationSpecificDeviceAuthenticationContract/1.0.0.0/Windows.Xbox.Security.ApplicationSpecificDeviceAuthenticationContract.winmd", + ] + } } static_library("starboard_platform") { @@ -304,7 +306,6 @@ "//starboard/shared/win32/time_get_now.cc", "//starboard/shared/win32/time_utils.h", "//starboard/shared/win32/time_zone_get_current.cc", - "//starboard/shared/win32/time_zone_get_name.cc", "//starboard/shared/win32/video_decoder.cc", "//starboard/shared/win32/video_decoder.h", "//starboard/shared/win32/wasapi_include.h",
diff --git a/starboard/win/win32/BUILD.gn b/starboard/win/win32/BUILD.gn index e458622..746db84 100644 --- a/starboard/win/win32/BUILD.gn +++ b/starboard/win/win32/BUILD.gn
@@ -59,6 +59,7 @@ "//starboard/shared/win32/system_get_used_cpu_memory.cc", "//starboard/shared/win32/system_raise_platform_error.cc", "//starboard/shared/win32/system_symbolize.cc", + "//starboard/shared/win32/time_zone_get_name.cc", "//starboard/shared/win32/window_create.cc", "//starboard/shared/win32/window_destroy.cc", "//starboard/shared/win32/window_get_platform_handle.cc",
diff --git a/starboard/win/win32/test_filters.py b/starboard/win/win32/test_filters.py index cf8621f..98be41f 100644 --- a/starboard/win/win32/test_filters.py +++ b/starboard/win/win32/test_filters.py
@@ -87,6 +87,7 @@ return [test_filter.DISABLE_TESTING] else: filters = super().GetTestFilters() + _FILTERED_TESTS.update(test_filter.EVERGREEN_ONLY_TESTS) for target, tests in _FILTERED_TESTS.items(): filters.extend(test_filter.TestFilter(target, test) for test in tests) if os.environ.get('EXPERIMENTAL_CI', '0') == '1':
diff --git a/starboard/xb1/BUILD.gn b/starboard/xb1/BUILD.gn index 5a54c16..e4137bc 100644 --- a/starboard/xb1/BUILD.gn +++ b/starboard/xb1/BUILD.gn
@@ -176,6 +176,10 @@ "//starboard/shared/win32/media_get_max_buffer_capacity.cc", "//starboard/shared/win32/media_transform.cc", "//starboard/shared/win32/media_transform.h", + + # TODO (b/304335954): Use uwp implementation for correct IANA name once + # daylight savings offset is fixed. + "//starboard/shared/win32/time_zone_get_name.cc", "//starboard/shared/win32/video_decoder.cc", "//starboard/shared/win32/video_decoder.h", "//starboard/shared/win32/win32_audio_decoder.cc", @@ -213,8 +217,8 @@ if (is_internal_build) { sources += [ "//internal/starboard/shared/uwp/keys.cc", - "//internal/starboard/xb1/av1_video_decoder.cc", - "//internal/starboard/xb1/av1_video_decoder.h", + "//internal/starboard/xb1/dav1d_video_decoder.cc", + "//internal/starboard/xb1/dav1d_video_decoder.h", "//internal/starboard/xb1/drm_create_system.cc", "//internal/starboard/xb1/internal_shims.cc", "//internal/starboard/xb1/media_is_supported.cc", @@ -234,7 +238,7 @@ "//starboard/shared/widevine:oemcrypto", "//third_party/internal/ce_cdm/cdm:widevine_cdm_core", "//third_party/internal/ce_cdm/cdm:widevine_ce_cdm_static", - "//third_party/internal/libav1_xb1", + "//third_party/internal/dav1d_gpu/xb1:dav1d_xb1", "//third_party/internal/libvpx_xb1", ] } else {
diff --git a/starboard/xb1/launcher.py b/starboard/xb1/launcher.py index 9be3550..583aa3b 100644 --- a/starboard/xb1/launcher.py +++ b/starboard/xb1/launcher.py
@@ -62,12 +62,7 @@ # All other functions are automatically delegated using this function. def __getattr__(self, fname): - - def method(*args): - f = getattr(self.delegate, fname) - return f(*args) - - return method + return getattr(self.delegate, fname) def GetDeviceIp(self): """Gets the device IP. TODO: Implement."""
diff --git a/starboard/xb1/platform_configuration/BUILD.gn b/starboard/xb1/platform_configuration/BUILD.gn index 68b7862..db2a0b2 100644 --- a/starboard/xb1/platform_configuration/BUILD.gn +++ b/starboard/xb1/platform_configuration/BUILD.gn
@@ -59,7 +59,9 @@ ] } - ldflags += [ "/DEBUG:FASTLINK" ] + if (is_qa || is_gold || !cobalt_fastbuild) { + ldflags += [ "/DEBUG:FASTLINK" ] + } ldflags += [ "/NODEFAULTLIB" ] arflags += [ "/NODEFAULTLIB" ]
diff --git a/starboard/xb1/shared/gpu_base_video_decoder.cc b/starboard/xb1/shared/gpu_base_video_decoder.cc index 32749b1..84bab91 100644 --- a/starboard/xb1/shared/gpu_base_video_decoder.cc +++ b/starboard/xb1/shared/gpu_base_video_decoder.cc
@@ -16,7 +16,9 @@ #include <d3d11_1.h> #include <wrl/client.h> +#include <algorithm> +#include "starboard/once.h" #include "starboard/shared/uwp/application_uwp.h" #include "starboard/shared/uwp/async_utils.h" #include "starboard/shared/uwp/decoder_utils.h" @@ -53,11 +55,78 @@ constexpr int kMaxNumberOfPendingBuffers = 8; // Limit the cached presenting images. constexpr int kNumberOfCachedPresentingImage = 3; +// The number of frame buffers in decoder +constexpr int kNumOutputFrameBuffers = 7; const char kDecoderThreadName[] = "gpu_video_decoder_thread"; - } // namespace +class GpuFrameBufferPool { + public: + HRESULT AllocateFrameBuffers( + uint16_t width, + uint16_t height, + DXGI_FORMAT dxgi_format, + Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device, + Microsoft::WRL::ComPtr<ID3D12Device> d3d12_device) { + HRESULT hr; + uint16_t number_of_buffers = kNumOutputFrameBuffers; + if (!frame_buffers_.empty()) { + auto& buffer = frame_buffers_.front(); + D3D11_TEXTURE2D_DESC desc; + buffer->texture(0)->GetDesc(&desc); + if (desc.Format != dxgi_format || buffer->width() < width || + buffer->height() < height || + d3d11_device.Get() != buffer->device11().Get() || + d3d12_device.Get() != buffer->device12().Get()) { + frame_buffers_.clear(); + } + } + if (frame_buffers_.empty()) { + frame_buffers_.reserve(number_of_buffers); + while (number_of_buffers--) { + GpuVideoDecoderBase::GpuFrameBuffer* gpu_fb = + new GpuVideoDecoderBase::GpuFrameBuffer(width, height, dxgi_format, + d3d11_device, d3d12_device); + hr = gpu_fb->CreateTextures(); + if (FAILED(hr)) { + frame_buffers_.clear(); + return hr; + } + frame_buffers_.emplace_back(gpu_fb); + } + } + return S_OK; + } + + GpuVideoDecoderBase::GpuFrameBuffer* GetFreeBuffer() { + SB_DCHECK(!frame_buffers_.empty()); + auto iter = std::find_if( + frame_buffers_.begin(), frame_buffers_.end(), + [](const auto& frame_buffer) { return frame_buffer->HasOneRef(); }); + if (iter == frame_buffers_.end()) + return nullptr; + else + return iter->get(); + } + + bool CheckIfAllBuffersAreReleased() { + for (auto&& frame_buffer : frame_buffers_) { + if (!frame_buffer->HasOneRef()) + return false; + } + return true; + } + + void Clear() { frame_buffers_.clear(); } + + private: + std::vector<scoped_refptr<GpuVideoDecoderBase::GpuFrameBuffer>> + frame_buffers_; +}; + +SB_ONCE_INITIALIZE_FUNCTION(GpuFrameBufferPool, GetGpuFrameBufferPool); + class GpuVideoDecoderBase::GPUDecodeTargetPrivate : public SbDecodeTargetPrivate { public: @@ -85,7 +154,6 @@ info.is_opaque = true; info.width = image->width(); info.height = image->height(); - GLuint gl_textures_yuv[kNumberOfPlanes] = {}; glGenTextures(kNumberOfPlanes, gl_textures_yuv); SB_DCHECK(glGetError() == GL_NO_ERROR); @@ -160,19 +228,109 @@ void* egl_config_; }; +GpuVideoDecoderBase::GpuFrameBuffer::GpuFrameBuffer( + uint16_t width, + uint16_t height, + DXGI_FORMAT dxgi_format, + Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device, + Microsoft::WRL::ComPtr<ID3D12Device> d3d12_device) + : d3d11_device_(d3d11_device), d3d12_device_(d3d12_device) { + SB_DCHECK(d3d11_device_); + SB_DCHECK(d3d12_device_); + + texture_desc_.Format = dxgi_format; + texture_desc_.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + texture_desc_.DepthOrArraySize = 1; + texture_desc_.MipLevels = 1; + texture_desc_.SampleDesc.Count = 1; + texture_desc_.SampleDesc.Quality = 0; + texture_desc_.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + texture_desc_.Layout = D3D12_TEXTURE_LAYOUT_64KB_UNDEFINED_SWIZZLE; + + width_ = width; + height_ = height; +} + +HRESULT GpuVideoDecoderBase::GpuFrameBuffer::CreateTextures() { + const D3D12_HEAP_PROPERTIES kHeapPropertyTypeDefault = { + D3D12_HEAP_TYPE_DEFAULT, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + D3D12_MEMORY_POOL_UNKNOWN, 1, 1}; + HRESULT hr = E_FAIL; + for (unsigned int i = 0; i < kNumberOfPlanes; i++) { + const int subsampling = i > 0; + const int plane_width = + (texture_desc_.Format == DXGI_FORMAT_R10G10B10A2_UNORM) + ? (((width_ + subsampling) >> subsampling) + 2) / 3 + : ((width_ + subsampling) >> subsampling); + const int plane_height = (height_ + subsampling) >> subsampling; + + // Create interop resources. + texture_desc_.Width = plane_width; + texture_desc_.Height = plane_height; + hr = d3d12_device_->CreateCommittedResource( + &kHeapPropertyTypeDefault, D3D12_HEAP_FLAG_SHARED, &texture_desc_, + D3D12_RESOURCE_STATE_RENDER_TARGET, 0, + IID_PPV_ARGS(&d3d12_resources_[i])); + SB_DCHECK(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + + // Lowering the priority of texture reduces the amount of texture + // thrashing when the Xbox attempts to transfer textures to faster + // memory as it become more reluctant to be moved. + Microsoft::WRL::ComPtr<ID3D12Device1> d3d12_device1; + if (SUCCEEDED(d3d12_device_.As(&d3d12_device1)) && d3d12_device1) { + Microsoft::WRL::ComPtr<ID3D12Pageable> d3d12_pageable; + if (SUCCEEDED(d3d12_resources_[i].As(&d3d12_pageable)) && + d3d12_pageable) { + D3D12_RESIDENCY_PRIORITY priority = D3D12_RESIDENCY_PRIORITY_LOW; + hr = d3d12_device1->SetResidencyPriority( + 1, d3d12_pageable.GetAddressOf(), &priority); + SB_DCHECK(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + } + } + + HANDLE interop_handle = 0; + hr = d3d12_device_->CreateSharedHandle(d3d12_resources_[i].Get(), 0, + GENERIC_ALL, NULL, &interop_handle); + SB_DCHECK(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + hr = d3d11_device_->OpenSharedResource1(interop_handle, + IID_PPV_ARGS(&d3d11_textures_[i])); + SB_DCHECK(SUCCEEDED(hr)); + if (FAILED(hr)) { + return hr; + } + CloseHandle(interop_handle); + } + return S_OK; +} + GpuVideoDecoderBase::GpuVideoDecoderBase( SbDecodeTargetGraphicsContextProvider* decode_target_graphics_context_provider, const VideoStreamInfo& video_stream_info, bool is_hdr_video, + bool is_10x3_preferred, const ComPtr<ID3D12Device>& d3d12_device, + const ComPtr<ID3D12Heap> d3d12OutputPoolBufferHeap, void* d3d12_queue) : decode_target_context_runner_(decode_target_graphics_context_provider), is_hdr_video_(is_hdr_video), + is_10x3_preferred_(is_10x3_preferred), d3d12_device_(d3d12_device), - d3d12_queue_(d3d12_queue) { + d3d12_queue_(d3d12_queue), + d3d12FrameBuffersHeap_(d3d12OutputPoolBufferHeap), + frame_buffers_condition_(frame_buffers_mutex_) { SB_DCHECK(d3d12_device_); SB_DCHECK(d3d12_queue_); + SB_DCHECK(d3d12FrameBuffersHeap_); egl_display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); EGLint attribute_list[] = {EGL_SURFACE_TYPE, // this must be first @@ -215,6 +373,7 @@ SB_DCHECK(written_inputs_.empty()); SB_DCHECK(output_queue_.empty()); SB_DCHECK(decoder_behavior_.load() == kDecodingStopped); + SB_DCHECK(GetGpuFrameBufferPool()->CheckIfAllBuffersAreReleased()); // All presenting decode targets should be released. SB_DCHECK(presenting_decode_targets_.empty()); @@ -236,9 +395,18 @@ error_cb_ = error_cb; } +size_t GpuVideoDecoderBase::GetPrerollFrameCount() const { + // The underlying decoder has its own output queue. We notify the underlying + // decoder to preroll frames once we receive first needed frame. Then the + // underlying decoder will delay outputs until it has enough prerolled frames. + // When we receive the second output frame, the underlying decoder should + // already have enough prerolled frames in its own output queue. So, we always + // return 2 here. + return 2; +} + size_t GpuVideoDecoderBase::GetMaxNumberOfCachedFrames() const { - return GetMaxNumberOfCachedFramesInternal() - - number_of_presenting_decode_targets_; + return GetMaxNumberOfCachedFramesInternal() + kNumOutputFrameBuffers; } void GpuVideoDecoderBase::WriteInputBuffers(const InputBuffers& input_buffers) { @@ -265,6 +433,7 @@ { ScopedLock pending_inputs_lock(pending_inputs_mutex_); pending_inputs_.push_back(input_buffer); + needs_more_input = pending_inputs_.size() < kMaxNumberOfPendingBuffers; } decoder_behavior_.store(kDecodingFrames); @@ -309,7 +478,10 @@ decoder_thread_.reset(); } pending_inputs_.clear(); - written_inputs_.clear(); + { + ScopedLock input_queue_lock(written_inputs_mutex_); + written_inputs_.clear(); + } // Release all frames after decoder thread is destroyed. decoder_status_cb_(kReleaseAllFrames, nullptr); { @@ -369,30 +541,26 @@ return decoder_thread_->job_queue()->BelongsToCurrentThread(); } -void GpuVideoDecoderBase::OnOutputRetrieved( +int GpuVideoDecoderBase::OnOutputRetrieved( const scoped_refptr<DecodedImage>& image) { SB_DCHECK(decoder_thread_); SB_DCHECK(decoder_status_cb_); SB_DCHECK(image); if (decoder_behavior_.load() == kResettingDecoder || error_occured_) { - return; - } - - if (!BelongsToDecoderThread()) { - decoder_thread_->job_queue()->Schedule( - std::bind(&GpuVideoDecoderBase::OnOutputRetrieved, this, image)); - return; + return 0; } SbTime timestamp = image->timestamp(); - const auto iter = FindByTimestamp(written_inputs_, timestamp); - SB_DCHECK(iter != written_inputs_.cend()); - if (is_hdr_video_) { - image->AttachColorMetadata((*iter)->video_stream_info().color_metadata); + { + ScopedLock input_queue_lock(written_inputs_mutex_); + const auto iter = FindByTimestamp(written_inputs_, timestamp); + SB_DCHECK(iter != written_inputs_.cend()); + if (is_hdr_video_) { + image->AttachColorMetadata((*iter)->video_stream_info().color_metadata); + } + written_inputs_.erase(iter); } - written_inputs_.erase(iter); - scoped_refptr<VideoFrameImpl> frame(new VideoFrameImpl( timestamp, std::bind(&GpuVideoDecoderBase::DeleteVideoFrame, this, std::placeholders::_1))); @@ -400,10 +568,21 @@ decoder_behavior_.load() == kEndingStream ? kBufferFull : kNeedMoreInput, frame); + // The underlying decoder relies on the return value of OnOutputRetrieved() to + // determine stream preroll status. The underlying decoder will start + // prorolling at the first time it receives 1 from OnOutputRetrieved(). In + // other words, if OnOutputRetrieved() returns 1, the underlying decoder will + // delay next output until it has enough prerolled frames inside the + // underlying decoder. if (!frame->HasOneRef()) { ScopedLock output_queue_lock(output_queue_mutex_); output_queue_.push_back(image); + if (is_waiting_frame_after_drain_) { + is_waiting_frame_after_drain_ = false; + return 1; + } } + return 0; } void GpuVideoDecoderBase::OnDecoderDrained() { @@ -412,9 +591,7 @@ SB_DCHECK(decoder_behavior_.load() == kEndingStream || decoder_behavior_.load() == kResettingDecoder); - if (decoder_behavior_.load() == kResettingDecoder || error_occured_) { - return; - } + is_waiting_frame_after_drain_ = true; if (!BelongsToDecoderThread()) { decoder_thread_->job_queue()->Schedule( @@ -422,7 +599,6 @@ return; } - SB_DCHECK(written_inputs_.empty()); if (decoder_behavior_.load() == kEndingStream) { decoder_status_cb_(kBufferFull, VideoFrame::CreateEOSFrame()); } @@ -445,16 +621,6 @@ } } -bool GpuVideoDecoderBase::IsCacheFull() { - SB_DCHECK(decoder_thread_); - SB_DCHECK(BelongsToDecoderThread()); - - ScopedLock output_queue_lock(output_queue_mutex_); - return written_inputs_.size() + output_queue_.size() + - number_of_presenting_decode_targets_ >= - GetMaxNumberOfCachedFramesInternal(); -} - void GpuVideoDecoderBase::DecodeOneBuffer() { SB_DCHECK(decoder_thread_); SB_DCHECK(BelongsToDecoderThread()); @@ -463,20 +629,28 @@ return; } - if (IsCacheFull()) { - decoder_thread_->job_queue()->Schedule( - std::bind(&GpuVideoDecoderBase::DecodeOneBuffer, this), - kSbTimeMillisecond); - return; - } - + // Both decoders av1 & vp9 return decoded frames in separate thread, + // so there isn't danger of deadlock in DecodeOneBuffer() and there isn't + // necessity of IsCacheFull call + scoped_refptr<InputBuffer> input = 0; + bool needs_more_input = false; { ScopedLock pending_inputs_lock(pending_inputs_mutex_); SB_DCHECK(!pending_inputs_.empty()); - written_inputs_.push_back(pending_inputs_.front()); + input = pending_inputs_.front(); pending_inputs_.pop_front(); + if (pending_inputs_.size() < kMaxNumberOfPendingBuffers) { + needs_more_input = true; + } } - DecodeInternal(written_inputs_.back()); + { + ScopedLock input_queue_lock(written_inputs_mutex_); + written_inputs_.push_back(input); + } + if (needs_more_input) { + decoder_status_cb_(kNeedMoreInput, nullptr); + } + DecodeInternal(input); } void GpuVideoDecoderBase::DecodeEndOfStream() { @@ -509,6 +683,9 @@ if (!is_drain_decoder_called_) { is_drain_decoder_called_ = true; DrainDecoderInternal(); + // DrainDecoderInternal is sync command, after it finished, we can be sure + // that drain really completed. + OnDecoderDrained(); } } @@ -558,6 +735,62 @@ number_of_presenting_decode_targets_ = 0; } +HRESULT GpuVideoDecoderBase::AllocateFrameBuffers(uint16_t width, + uint16_t height) { + HRESULT hr = S_OK; + DXGI_FORMAT dxgi_format = + is_hdr_video_ ? (is_10x3_preferred_ ? DXGI_FORMAT_R10G10B10A2_UNORM + : DXGI_FORMAT_R16_UNORM) + : DXGI_FORMAT_R8_UNORM; + return GetGpuFrameBufferPool()->AllocateFrameBuffers( + width, height, dxgi_format, d3d11_device_, d3d12_device_); +} + +void GpuVideoDecoderBase::ReleaseFrameBuffer(GpuFrameBuffer* frame_buffer) { + SB_DCHECK(frame_buffer); + ScopedLock lock(frame_buffers_mutex_); + frame_buffer->Release(); + SB_DCHECK(frame_buffer->HasOneRef()); + frame_buffers_condition_.Signal(); +} + +void GpuVideoDecoderBase::ClearFrameBuffersPool() { + GetGpuFrameBufferPool()->Clear(); +} + +GpuVideoDecoderBase::GpuFrameBuffer* +GpuVideoDecoderBase::GetAvailableFrameBuffer(uint16_t width, uint16_t height) { + if (decoder_behavior_.load() == kResettingDecoder) { + return nullptr; + } + + GpuFrameBuffer* frame_buffer = nullptr; + bool is_resetting = false; + while (!frame_buffer) { + ScopedLock lock(frame_buffers_mutex_); + frame_buffer = GetGpuFrameBufferPool()->GetFreeBuffer(); + // Wait until we get next free frame buffer. + if (!frame_buffer) { + if (is_resetting) { + // We should have enough free frame buffers during resetting. If that + // error happens it means that the frames are not released properly by + // either GpuVideoDecoderBase or VideoRenderer. + SB_NOTREACHED(); + ReportError(kSbPlayerErrorDecode, + "Timed out on waiting for available frame buffer."); + return nullptr; + } + is_resetting = decoder_behavior_.load() == kResettingDecoder; + frame_buffers_condition_.WaitTimed(50 * kSbTimeMillisecond); + continue; + } + } + + // Increment the refcount for |frame_buffer| so that its data buffer + // persists until ReleaseFrameBuffer is called. + frame_buffer->AddRef(); + return frame_buffer; +} } // namespace shared } // namespace xb1 } // namespace starboard
diff --git a/starboard/xb1/shared/gpu_base_video_decoder.h b/starboard/xb1/shared/gpu_base_video_decoder.h index e6d2eae..34d4533 100644 --- a/starboard/xb1/shared/gpu_base_video_decoder.h +++ b/starboard/xb1/shared/gpu_base_video_decoder.h
@@ -51,6 +51,54 @@ ~GpuVideoDecoderBase() override; + class GpuFrameBuffer : public RefCountedThreadSafe<GpuFrameBuffer> { + public: + GpuFrameBuffer(uint16_t width, + uint16_t height, + DXGI_FORMAT dxgi_format, + Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device, + Microsoft::WRL::ComPtr<ID3D12Device> d3d12_device); + + HRESULT CreateTextures(); + + const Microsoft::WRL::ComPtr<ID3D12Resource>& resource(int index) const { + SB_DCHECK(index < kNumberOfPlanes); + SB_DCHECK(d3d12_resources_[index] != nullptr); + return d3d12_resources_[index]; + } + + const Microsoft::WRL::ComPtr<ID3D11Texture2D>& texture(int index) const { + SB_DCHECK(index < kNumberOfPlanes); + SB_DCHECK(d3d11_textures_[index] != nullptr); + return d3d11_textures_[index]; + } + + uint16_t width() const { return width_; } + uint16_t height() const { return height_; } + const Microsoft::WRL::ComPtr<ID3D11Device1>& device11() { + return d3d11_device_; + } + const Microsoft::WRL::ComPtr<ID3D12Device>& device12() { + return d3d12_device_; + } + + private: + uint16_t width_ = 0; + uint16_t height_ = 0; + D3D12_RESOURCE_DESC texture_desc_ = {0}; + + Microsoft::WRL::ComPtr<ID3D12Resource> d3d12_resources_[kNumberOfPlanes]; + Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_textures_[kNumberOfPlanes]; + + const Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device_; + const Microsoft::WRL::ComPtr<ID3D12Device> d3d12_device_; + }; + void ReleaseFrameBuffer(GpuFrameBuffer* frame_buffer); + int GetWidth() { return frame_width_; } + int GetHeight() { return frame_height_; } + bool IsHdrVideo() { return is_hdr_video_; } + static void ClearFrameBuffersPool(); + protected: typedef ::starboard::shared::starboard::media::VideoStreamInfo VideoStreamInfo; @@ -116,16 +164,20 @@ kEndingStream }; - GpuVideoDecoderBase(SbDecodeTargetGraphicsContextProvider* - decode_target_graphics_context_provider, - const VideoStreamInfo& video_stream_info, - bool is_hdr_video, - const Microsoft::WRL::ComPtr<ID3D12Device>& d3d12_device, - void* d3d12_queue); + GpuVideoDecoderBase( + SbDecodeTargetGraphicsContextProvider* + decode_target_graphics_context_provider, + const VideoStreamInfo& video_stream_info, + bool is_hdr_video, + bool is_10x3_preferred, + const Microsoft::WRL::ComPtr<ID3D12Device>& d3d12_device, + const Microsoft::WRL::ComPtr<ID3D12Heap> d3d12OutputPoolBufferHeap, + void* d3d12_queue); // VideoDecoder methods void Initialize(const DecoderStatusCB& decoder_status_cb, const ErrorCB& error_cb) final; + size_t GetPrerollFrameCount() const final; SbTime GetPrerollTimeout() const final { return kSbTimeMax; } size_t GetMaxNumberOfCachedFrames() const override; @@ -135,19 +187,23 @@ SbDecodeTarget GetCurrentDecodeTarget() final; // Methods for inherited classes to implement. - virtual size_t GetMaxNumberOfCachedFramesInternal() const = 0; virtual void InitializeCodecIfNeededInternal() = 0; virtual void DecodeInternal( const scoped_refptr<InputBuffer>& input_buffer) = 0; virtual void DrainDecoderInternal() = 0; + virtual size_t GetMaxNumberOfCachedFramesInternal() const = 0; bool BelongsToDecoderThread() const; - void OnOutputRetrieved(const scoped_refptr<DecodedImage>& image); void OnDecoderDrained(); void ClearCachedImages(); void ReportError(const SbPlayerError error, const std::string& error_message); + int OnOutputRetrieved(const scoped_refptr<DecodedImage>& image); + HRESULT AllocateFrameBuffers(uint16_t width, uint16_t height); + GpuVideoDecoderBase::GpuFrameBuffer* GetAvailableFrameBuffer(uint16_t width, + uint16_t height); const bool is_hdr_video_; + const bool is_10x3_preferred_; int frame_width_; int frame_height_; atomic_integral<RetrievingBehavior> decoder_behavior_{kDecodingStopped}; @@ -156,12 +212,16 @@ // These are platform-specific objects required to create and use a codec. Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device_; Microsoft::WRL::ComPtr<ID3D12Device> d3d12_device_; + Microsoft::WRL::ComPtr<ID3D12Heap> + d3d12FrameBuffersHeap_; // output buffers queue memory void* d3d12_queue_ = nullptr; + Mutex frame_buffers_mutex_; + ConditionVariable frame_buffers_condition_; + private: class GPUDecodeTargetPrivate; - bool IsCacheFull(); void DecodeOneBuffer(); void DecodeEndOfStream(); void DrainDecoder(); @@ -183,7 +243,9 @@ // |pending_inputs_| is shared between player main thread and decoder thread. Mutex pending_inputs_mutex_; std::deque<scoped_refptr<InputBuffer>> pending_inputs_; - // |written_inputs_| is only accessed on decoder thread. + // |written_inputs_| is shared between decoder thread and underlying decoder + // output thread. + Mutex written_inputs_mutex_; std::vector<scoped_refptr<InputBuffer>> written_inputs_; // |output_queue_| is shared between decoder thread and render thread. Mutex output_queue_mutex_; @@ -195,6 +257,7 @@ SbMediaColorMetadata last_presented_color_metadata_ = {}; bool is_drain_decoder_called_ = false; + bool is_waiting_frame_after_drain_ = false; bool needs_hdr_metadata_update_ = true; };
diff --git a/starboard/xb1/shared/internal_shims.h b/starboard/xb1/shared/internal_shims.h index 9cd7153..877ed8b 100644 --- a/starboard/xb1/shared/internal_shims.h +++ b/starboard/xb1/shared/internal_shims.h
@@ -31,7 +31,7 @@ Platform::String ^ GetCertScope(); -void GetSignature(Windows::Storage::Streams::IBuffer ^ message_buffer, +bool GetSignature(Windows::Storage::Streams::IBuffer ^ message_buffer, Windows::Storage::Streams::IBuffer ^ *signature); } // namespace shared
diff --git a/starboard/xb1/shared/internal_stubs.cc b/starboard/xb1/shared/internal_stubs.cc index a8f6ba5..b243b4b 100644 --- a/starboard/xb1/shared/internal_stubs.cc +++ b/starboard/xb1/shared/internal_stubs.cc
@@ -33,8 +33,10 @@ Platform::String ^ GetCertScope() { return ""; } -void GetSignature(Windows::Storage::Streams::IBuffer ^ message_buffer, - Windows::Storage::Streams::IBuffer ^ *signature) {} +bool GetSignature(Windows::Storage::Streams::IBuffer ^ message_buffer, + Windows::Storage::Streams::IBuffer ^ *signature) { + return false; +} // clang-format on } // namespace shared
diff --git a/starboard/xb1/templates/AppxManifest.xml.template b/starboard/xb1/templates/AppxManifest.xml.template index 85b4a30..5a2d9f2 100644 --- a/starboard/xb1/templates/AppxManifest.xml.template +++ b/starboard/xb1/templates/AppxManifest.xml.template
@@ -24,10 +24,10 @@ </Properties> <Dependencies> <TargetDeviceFamily - {% if __CONFIG__ == 'gold' %} - Name="Windows.Xbox" - {% else %} + {% if __CONFIG__ == 'debug' or __CONFIG__ == 'devel' %} Name="Windows.Universal" + {% else %} + Name="Windows.Xbox" {% endif %} MinVersion="10.0.22000.0" MaxVersionTested="10.0.22000.0" />
diff --git a/starboard/xb1/test_filters.py b/starboard/xb1/test_filters.py index e545d5b..6bb45b5 100644 --- a/starboard/xb1/test_filters.py +++ b/starboard/xb1/test_filters.py
@@ -59,6 +59,7 @@ return [test_filter.DISABLE_TESTING] filters = super().GetTestFilters() + _FILTERED_TESTS.update(test_filter.EVERGREEN_ONLY_TESTS) for target, tests in _FILTERED_TESTS.items(): filters.extend(test_filter.TestFilter(target, test) for test in tests) return filters
diff --git a/starboard/xb1/tools/packager.py b/starboard/xb1/tools/packager.py index 9503f20..a5bcf60 100644 --- a/starboard/xb1/tools/packager.py +++ b/starboard/xb1/tools/packager.py
@@ -52,7 +52,8 @@ 'youtubetv': _INTERNAL_CERT_PATH, } _DEFAULT_SDK_BIN_DIR = 'C:\\Program Files (x86)\\Windows Kits\\10\\bin' -_DEFAULT_WIN_SDK_VERSION = '10.0.22000.0' +_DEFAULT_WIN_SDK_VERSION = '10.0.22621.0' +_BACKUP_WIN_SDK_VERSION = '10.0.22000.0' _SOURCE_SPLASH_SCREEN_SUB_PATH = os.path.join('internal', 'cobalt', 'browser', 'splash_screen') # The splash screen file referenced in starboard/xb1/shared/configuration.cc @@ -67,7 +68,7 @@ } -def _SelectBestPath(os_var_name, path): +def _SelectBestPath(os_var_name: str, path: str) -> str: if os_var_name in os.environ: return os.environ[os_var_name] if os.path.exists(path): @@ -86,6 +87,18 @@ return os.path.join(src_dir, _SOURCE_SPLASH_SCREEN_SUB_PATH) +def GetWinToolsPath() -> str: + windows_sdk_bin_dir = _SelectBestPath('WindowsSdkBinPath', + _DEFAULT_SDK_BIN_DIR) + + # This check can be removed once it's confirmed our builders are using the new + # version of the win sdk. + path = os.path.join(windows_sdk_bin_dir, _DEFAULT_WIN_SDK_VERSION, 'x64') + if os.path.exists(path): + return path + return os.path.join(windows_sdk_bin_dir, _BACKUP_WIN_SDK_VERSION, 'x64') + + class Package(package.PackageBase): """A class representing an installable UWP Appx package.""" @@ -137,6 +150,15 @@ return shutil.copy(src_splash_screen_file, splash_screen_dir) + def _CopyAppxData(self): + appx_data_output_dir = os.path.join(self.appx_folder_location, 'content', + 'data') + source_dir = os.path.join(self.source_dir, 'appx', 'content', 'data') + if not os.path.exists(source_dir): + logging.error('Failed to find source content in: %s', source_dir) + return + shutil.copytree(source_dir, appx_data_output_dir) + @classmethod def SupportedPlatforms(cls): if platform.system() == 'Windows': @@ -145,10 +167,7 @@ return [] def __init__(self, publisher, product, **kwargs): - windows_sdk_bin_dir = _SelectBestPath('WindowsSdkBinPath', - _DEFAULT_SDK_BIN_DIR) - self.windows_sdk_host_tools = os.path.join(windows_sdk_bin_dir, - _DEFAULT_WIN_SDK_VERSION, 'x64') + self.windows_sdk_host_tools = GetWinToolsPath() self.publisher = publisher self.product = product super().__init__(**kwargs) @@ -161,6 +180,11 @@ if self.publisher: self._UpdateAppxManifestPublisher(publisher) + # For YouTubeTV and MainAppBeta move the appx data content to the correct + # appx directory. + if (self.product in ['youtubetv', 'mainappbeta']): + self._CopyAppxData() + # Remove any previous splash screen from content. self._CleanSplashScreenDir() # Copy the correct splash screen into content.
diff --git a/starboard/xb1/tools/xb1_launcher.py b/starboard/xb1/tools/xb1_launcher.py index 969a5ae..b83a9e6 100644 --- a/starboard/xb1/tools/xb1_launcher.py +++ b/starboard/xb1/tools/xb1_launcher.py
@@ -80,6 +80,7 @@ from starboard.shared.win32 import mini_dump_printer from starboard.tools import abstract_launcher +from starboard.tools.abstract_launcher import TargetStatus from starboard.tools import net_args from starboard.tools import net_log from starboard.xb1.tools import packager @@ -88,13 +89,29 @@ _ARGS_DIRECTORY = 'content/data/arguments' _STARBOARD_ARGUMENTS_FILE = 'starboard_arguments.txt' _DEFAULT_PACKAGE_NAME = 'GoogleInc.YouTube' +_STUB_PACKAGE_NAME = 'Microsoft.Title.StubApp' +_DEBUG_VC_LIBS_PACKAGE_NAME = 'Microsoft.VCLibs.140.00.Debug' _DEFAULT_APPX_NAME = 'cobalt.appx' _DEFAULT_STAGING_APP_NAME = 'appx' +_EXTENSION_SDK_DIR = os.path.realpath( + os.path.expandvars('%ProgramFiles(x86)%\\Microsoft SDKs' + '\\Windows Kits\\10\\ExtensionSDKs')) +_DEBUG_VC_LIBS_PATH = os.path.join(_EXTENSION_SDK_DIR, 'Microsoft.VCLibs', + '14.0', 'Appx', 'Debug', 'x64', + 'Microsoft.VCLibs.x64.Debug.14.00.appx') _XB1_LOG_FILE_PARAM = 'xb1_log_file' _XB1_PORT = 11443 _XB1_NET_LOG_PORT = 49353 _XB1_NET_ARG_PORT = 49355 +# Number of times a test will try or retry. +_TEST_MAX_TRIES = 4 +# Seconds to wait between retries (scales with backoff factor). +_TEST_RETRY_WAIT = 8 +# Amount to multiply retry time with each failed attempt (i.e. 2 doubles the +# amount of time to wait between retries). +_TEST_RETRY_BACKOFF_FACTOR = 2 + _PROCESS_TIMEOUT = 60 * 5.0 _PROCESS_KILL_TIMEOUT_SECONDS = 5.0 @@ -363,10 +380,12 @@ self._network_api.SetXboxLiveSignedInUserState(users[0]['EmailAddress'], True) - def WinAppDeployCmd(self, command): + def WinAppDeployCmd(self, command: str): try: - out = subprocess.check_output('WinAppDeployCmd ' + command + ' -ip ' + - self.GetDeviceIp()).decode() + exe_path = os.path.join(packager.GetWinToolsPath(), 'WinAppDeployCmd.exe') + command_str = f'{exe_path} {command} -ip {self.GetDeviceIp()}' + self._LogLn('Running: ' + command_str) + out = subprocess.check_output(command_str).decode() except subprocess.CalledProcessError as e: self._LogLn(e.output) raise e @@ -392,7 +411,9 @@ for package in packages: try: package_full_name = package['PackageFullName'] - if package_full_name.find(_DEFAULT_PACKAGE_NAME) != -1: + if package_full_name.find( + _DEFAULT_PACKAGE_NAME) != -1 or package_full_name.find( + _STUB_PACKAGE_NAME) != -1: if package_full_name not in uninstalled_packages: self._LogLn('Existing YouTube app found on device. Uninstalling: ' + package_full_name) @@ -404,6 +425,9 @@ except subprocess.CalledProcessError as err: self._LogLn(err.output) + def DeleteLooseApps(self): + self._network_api.ClearLooseAppFiles() + def Deploy(self): # starboard_arguments.txt is packaged with the appx. It instructs the app # to wait for the NetArgs thread to send command-line args via the socket. @@ -415,53 +439,85 @@ raise IOError('Packaged appx not found in package directory. Perhaps ' 'package_cobalt script did not complete successfully.') - existing_package = self.CheckPackageIsDeployed() + existing_package = self.CheckPackageIsDeployed(_DEFAULT_PACKAGE_NAME) if existing_package: self._LogLn('Existing YouTube app found on device. Uninstalling.') self.WinAppDeployCmd('uninstall -package ' + existing_package) + if not self.CheckPackageIsDeployed(_DEBUG_VC_LIBS_PACKAGE_NAME): + self._LogLn('Required dependency missing. Attempting to install.') + self.WinAppDeployCmd(f'install -file "{_DEBUG_VC_LIBS_PATH}"') + self._LogLn('Deleting temporary files') self._network_api.ClearTempFiles() try: - self._LogLn('Installing appx file ' + appx_package_file) - self.WinAppDeployCmd('install -file ' + appx_package_file) + self.WinAppDeployCmd(f'install -file {appx_package_file}') except subprocess.CalledProcessError: # Install exited with non-zero status code, clear everything out, restart, # and attempt another install. self._LogLn('Error installing appx. Attempting a clean install...') self.UninstallSubPackages() + self.DeleteLooseApps() self.RestartDevkit() - self.WinAppDeployCmd('install -file ' + appx_package_file) + self.WinAppDeployCmd(f'install -file {appx_package_file}') # Cleanup starboard arguments file. self.InstallStarboardArgument(None) # Validate that app was installed correctly by checking to make sure # that the full package name can now be found. - def CheckPackageIsDeployed(self): + def CheckPackageIsDeployed(self, package_name=_DEFAULT_PACKAGE_NAME): package_list = self.WinAppDeployCmd('list') - package_index = package_list.find(_DEFAULT_PACKAGE_NAME) + package_index = package_list.find(package_name) if package_index == -1: return False return package_list[package_index:].split('\n')[0].strip() + def RunTest(self, appx_name: str): + self.net_args_thread = None + attempt_num = 0 + retry_wait_s = _TEST_RETRY_WAIT + while attempt_num < _TEST_MAX_TRIES: + if not self.net_args_thread or not self.net_args_thread.is_alive(): + # This thread must start before the app executes or else it is possible + # the app will hang at _network_api.ExecuteBinary() + self.net_args_thread = net_args.NetArgsThread(self.device_id, + _XB1_NET_ARG_PORT, + self._target_args) + if self._network_api.ExecuteBinary(_DEFAULT_PACKAGE_NAME, appx_name): + break + + if not self.net_args_thread.ArgsSent(): + self._LogLn( + 'Net Args were not sent to the test! This will likely cause ' + 'the test to fail!') + attempt_num += 1 + self._LogLn(f'Retry attempt {attempt_num}.') + time.sleep(retry_wait_s) + retry_wait_s *= _TEST_RETRY_BACKOFF_FACTOR + if hasattr(self, 'net_args_thread'): + self.net_args_thread.join() + + def InitDevice(self): + if not self._network_api.IsInDevMode(): + raise IOError('\n\n**** Please set the XBOX at ' + self._device_id + + ' to dev mode!!!! ****\n') + self.SignIn() + def Run(self): # Only upload and install Appx on the first run. if FirstRun(): if self._do_restart: self.RestartDevkit() - if not self._network_api.IsInDevMode(): - raise IOError('\n\n**** Please set the XBOX at ' + self._device_id + - ' to dev mode!!!! ****\n') - self.SignIn() + self.InitDevice() if self._do_deploy: self.Deploy() else: self._LogLn('Skipping deploy step.') - if not self.CheckPackageIsDeployed(): + if not self.CheckPackageIsDeployed(_DEFAULT_PACKAGE_NAME): raise IOError('Could not resolve ' + _DEFAULT_PACKAGE_NAME + ' to\n' + 'it\'s full package name after install! This means that' + '\n the package is not deployed correctly!\n\n') @@ -470,14 +526,15 @@ self._LogLn('Skipping running step.') return 0 + status, _ = self.Run2() + if status == TargetStatus.CRASH: + return 1 + else: + return 0 + + def Run2(self): try: self.Kill() # Kill existing running app. - - # These threads must start before the app executes or else it is possible - # the app will hang at _network_api.ExecuteBinary() - self.net_args_thread = net_args.NetArgsThread(self.device_id, - _XB1_NET_ARG_PORT, - self._target_args) # While binary is running, extract the net log and stream it to # the output. self.net_log_thread = net_log.NetLogThread(self.device_id, @@ -485,7 +542,7 @@ appx_name = ToAppxFriendlyName(self.target_name) - self._network_api.ExecuteBinary(_DEFAULT_PACKAGE_NAME, appx_name) + self.RunTest(appx_name) while self._network_api.IsBinaryRunning(self.target_name): self._Log(self.net_log_thread.GetLog()) @@ -511,8 +568,10 @@ self.Kill() self._LogLn('Finished running...') - crashed = self._DetectAndHandleAnyCrashes(self.target_name) - return crashed + if self._DetectAndHandleAnyCrashes(self.target_name): + return TargetStatus.CRASH, 0 + else: + return TargetStatus.NA, 0 def _DetectAndHandleAnyCrashes(self, target_name): crashes_detected = False
diff --git a/starboard/xb1/tools/xb1_network_api.py b/starboard/xb1/tools/xb1_network_api.py index 093218e..fac5872 100644 --- a/starboard/xb1/tools/xb1_network_api.py +++ b/starboard/xb1/tools/xb1_network_api.py
@@ -48,6 +48,7 @@ _APPX_RELATIVE_PATH = 'appx' _DEVELOPMENT_FILES = 'DevelopmentFiles' +_LOOSE_APPS = 'LooseApps' _LOCAL_APP_DATA = 'LocalAppData' _LOCAL_CACHE_FOLDERNAME = r'\\LocalCache' _TEMP_FILE_FOLDERNAME = r'\\WdpTempWebFolder' @@ -421,7 +422,8 @@ return None return None - def ExecuteBinary(self, partial_package_name, app_alias_name): + def ExecuteBinary(self, partial_package_name: str, + app_alias_name: str) -> bool: default_relative_name = self._GetDefaultRelativeId(partial_package_name) if not default_relative_name or not '!' in default_relative_name: raise IOError('Could not resolve package name "' + partial_package_name + @@ -432,33 +434,25 @@ appid_64 = base64.b64encode(package_relative_id.encode('UTF-8')) package_64 = base64.b64encode(default_relative_name.encode('UTF-8')) - retry_count = 4 - # Time to wait between tries. - retry_wait_s = 8 try: - while retry_count > 0: - self.LogLn('Executing: ' + package_relative_id) - response = self._DoJsonRequest( - 'POST', - _TASKMANAGER_ENDPOINT, - params={ - 'appid': appid_64, - 'package': package_64 - }, - raise_on_failure=False) - if not response or response == requests.codes.OK: - self.LogLn('Execution successful') - break - self.LogLn('Execution not successful: ' + str(response)) - self.LogLn('Retrying with ' + str(retry_count) + ' attempts remaining.') - time.sleep(retry_wait_s) - retry_count -= 1 - # Double the wait time until the next attempt. - retry_wait_s *= 2 + self.LogLn('Executing: ' + package_relative_id) + response = self._DoJsonRequest( + 'POST', + _TASKMANAGER_ENDPOINT, + params={ + 'appid': appid_64, + 'package': package_64 + }, + raise_on_failure=False) + if not response or response == requests.codes.OK: + self.LogLn('Execution successful') + return True + self.LogLn('Execution not successful: ' + str(response)) except Exception as err: err_msg = '\n Failed to run:\n ' + package_relative_id + \ '\n because of:\n' + str(err) raise IOError(err_msg) from err + return False # Given a package name, return all files + directories. # Throws IOError if the app is locked. @@ -568,17 +562,23 @@ 'path': path }) - def ClearTempFiles(self): + def ClearDevFiles(self, path): file_listing = self._DoJsonRequest( 'GET', _GET_FILES_ENDPOINT, params={ 'knownfolderid': _DEVELOPMENT_FILES, - 'path': _TEMP_FILE_FOLDERNAME + 'path': path }) for file in file_listing['Items']: self.DeleteFile(_DEVELOPMENT_FILES, _TEMP_FILE_FOLDERNAME, file['Name']) + def ClearTempFiles(self): + self.ClearDevFiles(_TEMP_FILE_FOLDERNAME) + + def ClearLooseAppFiles(self): + self.ClearDevFiles(_LOOSE_APPS) + def FindPackage(self, package_name): all_packages = self.GetInstalledPackages()
diff --git a/testing/android/AndroidManifest.xml b/testing/android/AndroidManifest.xml index 283ebb9..6faeeb4 100644 --- a/testing/android/AndroidManifest.xml +++ b/testing/android/AndroidManifest.xml
@@ -10,7 +10,7 @@ android:versionCode="1" android:versionName="1.0"> - <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" /> + <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34" /> <application android:label="ChromeNativeTests"> <activity android:name=".ChromeNativeTestActivity"
diff --git a/third_party/angle/include/GLSLANG/ShaderVars.h b/third_party/angle/include/GLSLANG/ShaderVars.h index 52f6ad0..5d2d146 100644 --- a/third_party/angle/include/GLSLANG/ShaderVars.h +++ b/third_party/angle/include/GLSLANG/ShaderVars.h
@@ -12,6 +12,7 @@ #include <algorithm> #include <array> +#include <cstdint> #include <string> #include <vector>
diff --git a/third_party/angle/src/common/angleutils.h b/third_party/angle/src/common/angleutils.h index 3a1391e..c8d36f5 100644 --- a/third_party/angle/src/common/angleutils.h +++ b/third_party/angle/src/common/angleutils.h
@@ -14,6 +14,7 @@ #include <climits> #include <cstdarg> #include <cstddef> +#include <cstdint> #include <set> #include <sstream> #include <string>
diff --git a/third_party/chromium/media/filters/chunk_demuxer.cc b/third_party/chromium/media/filters/chunk_demuxer.cc index 2b729a4..e24189d 100644 --- a/third_party/chromium/media/filters/chunk_demuxer.cc +++ b/third_party/chromium/media/filters/chunk_demuxer.cc
@@ -235,6 +235,19 @@ return write_head_; } +size_t ChunkDemuxerStream::GetStreamMemoryLimit() { + DCHECK(stream_); + base::AutoLock auto_lock(lock_); + return stream_->memory_limit(); +} + +void ChunkDemuxerStream::SetStreamMemoryLimitOverride(size_t memory_limit) { + DCHECK(stream_); + base::AutoLock auto_lock(lock_); + stream_->set_memory_limit_override(memory_limit); +} + + #endif // defined(STARBOARD) void ChunkDemuxerStream::OnMemoryPressure( @@ -1110,6 +1123,22 @@ return iter->second[0]->GetWriteHead(); } +void ChunkDemuxer::SetSourceBufferStreamMemoryLimit(const std::string& id, + size_t limit) { + base::AutoLock auto_lock(lock_); + DCHECK(source_state_map_.find(id) != source_state_map_.end()); + source_state_map_[id]->SetSourceBufferStreamMemoryLimit(limit); +} + +size_t ChunkDemuxer::GetSourceBufferStreamMemoryLimit(const std::string& id) { + + base::AutoLock auto_lock(lock_); + if (source_state_map_.find(id) == source_state_map_.end()) { + return 0; + } + return source_state_map_[id]->GetSourceBufferStreamMemoryLimit(); +} + #endif // defined(STARBOARD) bool ChunkDemuxer::AppendData(const std::string& id,
diff --git a/third_party/chromium/media/filters/chunk_demuxer.h b/third_party/chromium/media/filters/chunk_demuxer.h index 2e71291..76a01f4 100644 --- a/third_party/chromium/media/filters/chunk_demuxer.h +++ b/third_party/chromium/media/filters/chunk_demuxer.h
@@ -132,6 +132,8 @@ // DemuxerStream methods. #if defined(STARBOARD) std::string mime_type() const override { return mime_type_; } + size_t GetStreamMemoryLimit(); + void SetStreamMemoryLimitOverride(size_t memory_limit); #endif // defined (STARBOARD) #if defined(STARBOARD) @@ -392,6 +394,9 @@ #if defined(STARBOARD) base::TimeDelta GetWriteHead(const std::string& id) const; + + void SetSourceBufferStreamMemoryLimit(const std::string& guid, size_t limit); + size_t GetSourceBufferStreamMemoryLimit(const std::string& guid); #endif // defined(STARBOARD) void OnMemoryPressure(
diff --git a/third_party/chromium/media/filters/source_buffer_state.cc b/third_party/chromium/media/filters/source_buffer_state.cc index 8951bc8..955dcf8 100644 --- a/third_party/chromium/media/filters/source_buffer_state.cc +++ b/third_party/chromium/media/filters/source_buffer_state.cc
@@ -915,9 +915,41 @@ return success; } +#if defined(STARBOARD) +void SourceBufferState::SetSourceBufferStreamMemoryLimit(size_t limit) { + LOG(INFO) << "Setting SourceBuffferStream MemoryLimit Override: " << limit; + stream_memory_limit_override_ = limit; + SetStreamMemoryLimits(); +} + +size_t SourceBufferState::GetSourceBufferStreamMemoryLimit() { + // a source buffer can be backed my multiple Demuxer streams, although at + // YouTube we usually only have a single one. If we've set an override then + // all values will be the same, but we shouldn't assume that, so instead we'll + // return the largest here if there are multiple. + size_t memory_limit = 0; + for (const auto& it : audio_streams_) { + memory_limit = std::max(memory_limit, it.second->GetStreamMemoryLimit()); + } + for (const auto& it : video_streams_) { + memory_limit = std::max(memory_limit, it.second->GetStreamMemoryLimit()); + } + return memory_limit; +} +#endif // defined(STARBOARD) + void SourceBufferState::SetStreamMemoryLimits() { #if defined(STARBOARD) - // Cobalt doesn't get stream memory limits from the command line. + LOG(INFO) << "Custom SourceBuffer memory limit=" + << stream_memory_limit_override_; + if (stream_memory_limit_override_) { + for (const auto& it : audio_streams_) { + it.second->SetStreamMemoryLimitOverride(stream_memory_limit_override_); + } + for (const auto& it : video_streams_) { + it.second->SetStreamMemoryLimitOverride(stream_memory_limit_override_); + } + } #else // defined(STARBOARD) size_t audio_buf_size_limit = GetMSEBufferSizeLimitIfExists(switches::kMSEAudioBufferSizeLimitMb);
diff --git a/third_party/chromium/media/filters/source_buffer_state.h b/third_party/chromium/media/filters/source_buffer_state.h index 31f5bab..62ee4f7 100644 --- a/third_party/chromium/media/filters/source_buffer_state.h +++ b/third_party/chromium/media/filters/source_buffer_state.h
@@ -159,6 +159,11 @@ void SetParseWarningCallback(SourceBufferParseWarningCB parse_warning_cb); +#if defined(STARBOARD) + void SetSourceBufferStreamMemoryLimit(size_t limit); + size_t GetSourceBufferStreamMemoryLimit(); + size_t stream_memory_limit_override_ = 0; +#endif // defined(STARBOARD) private: // State advances through this list to PARSER_INITIALIZED. // The intent is to ensure at least one config is received prior to parser
diff --git a/third_party/chromium/media/filters/source_buffer_stream.cc b/third_party/chromium/media/filters/source_buffer_stream.cc index a1e9e2e..6d6789f 100644 --- a/third_party/chromium/media/filters/source_buffer_stream.cc +++ b/third_party/chromium/media/filters/source_buffer_stream.cc
@@ -1900,11 +1900,14 @@ video_configs_[append_config_index_] = config; #if defined(STARBOARD) - // Dynamically increase |memory_limit_| when video resolution goes up. - memory_limit_ = std::max( - memory_limit_, - GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes::kChunkDemuxer, - &config, mime_type_)); + // Dynamically increase |memory_limit_| when video resolution goes up as long + // as we haven't set a manual override. + if (!memory_override_) { + memory_limit_ = std::max( + memory_limit_, + GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes::kChunkDemuxer, + &config, mime_type_)); + } #endif // defined(STARBOARD) return true; }
diff --git a/third_party/chromium/media/filters/source_buffer_stream.h b/third_party/chromium/media/filters/source_buffer_stream.h index 356db45..248b38e 100644 --- a/third_party/chromium/media/filters/source_buffer_stream.h +++ b/third_party/chromium/media/filters/source_buffer_stream.h
@@ -188,6 +188,16 @@ memory_limit_ = memory_limit; } +#if defined(STARBOARD) + size_t memory_limit() const { + return memory_limit_; + } + void set_memory_limit_override(size_t memory_limit) { + memory_limit_ = memory_limit; + memory_override_ = true; + } +#endif // defined (STARBOARD) + private: friend class SourceBufferStreamTest; @@ -405,6 +415,7 @@ base::TimeDelta GetBufferedDurationForGarbageCollection() const; const std::string mime_type_; + bool memory_override_ = false; #endif // defined (STARBOARD) // Used to report log messages that can help the web developer figure out what
diff --git a/third_party/crashpad/build/BUILD.gn b/third_party/crashpad/build/BUILD.gn index 6e6ccd6..e80f9b5 100644 --- a/third_party/crashpad/build/BUILD.gn +++ b/third_party/crashpad/build/BUILD.gn
@@ -66,3 +66,12 @@ } } } + +if (crashpad_is_in_native_target_build) { + config("native_target_executable_config") { + # This is to undo the "main=StarboardMain" define added for all targets + # when final_executable_type == "shared_library", in + # starboard/build/config/BUILD.gn, which itself is admittedly a hack. + defines = [ "StarboardMain=main" ] + } +}
diff --git a/third_party/crashpad/handler/BUILD.gn b/third_party/crashpad/handler/BUILD.gn index b8ce718..2b9d2e4 100644 --- a/third_party/crashpad/handler/BUILD.gn +++ b/third_party/crashpad/handler/BUILD.gn
@@ -169,15 +169,6 @@ } } - if (crashpad_is_in_native_target_build) { - config("crashpad_handler_native_target_config") { - # This is to undo the "main=StarboardMain" define added for all targets - # when final_executable_type == "shared_library", in - # starboard/build/config/BUILD.gn, which itself is admittedly a hack. - defines = [ "StarboardMain=main" ] - } - } - crashpad_executable("crashpad_handler") { if (crashpad_is_in_starboard) { install_target = !crashpad_is_android @@ -212,7 +203,7 @@ } if (crashpad_is_in_native_target_build) { - configs += [ ":crashpad_handler_native_target_config" ] + configs += [ "../build:native_target_executable_config" ] } } }
diff --git a/third_party/crashpad/tools/BUILD.gn b/third_party/crashpad/tools/BUILD.gn index 402f04a..76d6d28 100644 --- a/third_party/crashpad/tools/BUILD.gn +++ b/third_party/crashpad/tools/BUILD.gn
@@ -31,9 +31,7 @@ } } -# TODO(b/251521595): resolve GN error and enable this target to be built for -# android. -if (!crashpad_is_ios && !crashpad_is_android) { +if (!crashpad_is_ios) { crashpad_executable("crashpad_database_util") { check_includes = !crashpad_is_in_starboard && !crashpad_is_in_native_target_build @@ -53,6 +51,10 @@ "//starboard", ] } + + if (crashpad_is_in_native_target_build) { + configs = [ "../build:native_target_executable_config" ] + } } if (!crashpad_is_in_starboard && !crashpad_is_in_native_target_build) {
diff --git a/third_party/crashpad/util/misc/reinterpret_bytes.cc b/third_party/crashpad/util/misc/reinterpret_bytes.cc index 65ec33f..3fee722 100644 --- a/third_party/crashpad/util/misc/reinterpret_bytes.cc +++ b/third_party/crashpad/util/misc/reinterpret_bytes.cc
@@ -17,6 +17,7 @@ #include <string.h> #include <algorithm> +#include <cstdint> #include "base/logging.h"
diff --git a/third_party/devtools/front_end/cobalt/cobalt.js b/third_party/devtools/front_end/cobalt/cobalt.js index c623f69..444d9fe 100644 --- a/third_party/devtools/front_end/cobalt/cobalt.js +++ b/third_party/devtools/front_end/cobalt/cobalt.js
@@ -9,6 +9,7 @@ ['Trace', 'console_trace.json'], ['Timed Trace', 'timed_trace.json'] ]; + const timed_trace_durations = ['5', '10', '20', '60']; super(true, false); SDK.targetManager.observeTargets(this); @@ -33,6 +34,16 @@ this.run(`(function() { window.h5vcc.traceEvent.stop();})()`); console.log("Stopped Trace"); })); + traceContainer.appendChild(UI.createLabel('Navigate Timed Trace:')); + timed_trace_durations.forEach((duration) => { + traceContainer.appendChild(UI.createTextButton(Common.UIString(duration + 's'), event => { + console.log("Request Navigate Timed Trace. " + duration); + this._cobaltAgent.invoke_sendConsoleCommand({ + command: 'navigate_timed_trace', message: duration + }); + console.log("Requested Navigate Timed Trace."); + })); + }); trace_files.forEach((file) => { traceContainer.appendChild(UI.createTextButton(Common.UIString('Download ' + file[0]), event => { console.log("Download Trace"); @@ -47,6 +58,32 @@ }); })); }); + + const netLogContainer = this.element.createChild('div', 'netlog-container'); + netLogContainer.appendChild(UI.createTextButton(Common.UIString('Start NetLog'), event => { + console.log("Start NetLog"); + this.run(`(function() { window.h5vcc.netLog.start();})()`); + console.log("Started NetLog"); + })); + netLogContainer.appendChild(UI.createTextButton(Common.UIString('Stop NetLog'), event => { + console.log("Stop NetLog"); + this.run(`(function() { window.h5vcc.netLog.stop();})()`); + console.log("Stopped NetLog"); + })); + netLogContainer.appendChild(UI.createTextButton(Common.UIString('Download NetLog'), event => { + console.log("Download Trace"); + this.run(`(function() { return window.h5vcc.netLog.stopAndRead();})()`).then(function (result) { + const netlog_file = 'net_log.json'; + download_element.setAttribute('href', 'data:text/plain;charset=utf-8,' + + encodeURIComponent(result.result.value)); + download_element.setAttribute('download', netlog_file); + console.log("Downloaded NetLog"); + download_element.click(); + download_element.setAttribute('href', undefined); + }); + })); + + const debugLogContainer = this.element.createChild('div', 'debug-log-container'); debugLogContainer.appendChild(UI.createTextButton(Common.UIString('DebugLog On'), event => { this._cobaltAgent.invoke_sendConsoleCommand({ @@ -58,6 +95,7 @@ command: 'debug_log', message: 'off' }); })); + const lifecycleContainer = this.element.createChild('div', 'lifecycle-container'); lifecycleContainer.appendChild(UI.createTextButton(Common.UIString('Blur'), event => { this._cobaltAgent.invoke_sendConsoleCommand({ command: 'blur' }); @@ -77,6 +115,7 @@ lifecycleContainer.appendChild(UI.createTextButton(Common.UIString('Quit'), event => { this._cobaltAgent.invoke_sendConsoleCommand({ command: 'quit' }); })); + const consoleContainer = this.element.createChild('div', 'console-container'); consoleContainer.appendChild(UI.createTextButton(Common.UIString('DebugCommand'), event => { const outputElement = document.getElementsByClassName('console-output')[0];
diff --git a/third_party/googletest/src/googletest/src/gtest.cc b/third_party/googletest/src/googletest/src/gtest.cc index 9b84415..2465a48 100644 --- a/third_party/googletest/src/googletest/src/gtest.cc +++ b/third_party/googletest/src/googletest/src/gtest.cc
@@ -1132,6 +1132,21 @@ } // A helper class for measuring elapsed times. +#if GTEST_OS_STARBOARD +class Timer { + public: + Timer() : start_(GetTimeInMillis()) { + } + + // Return time elapsed in milliseconds since the timer was created. + TimeInMillis Elapsed() { + return (GetTimeInMillis() - start_); + } + + private: + TimeInMillis start_; +}; +#else // GTEST_OS_STARBOARD class Timer { public: Timer() : start_(std::chrono::steady_clock::now()) {} @@ -1146,6 +1161,7 @@ private: std::chrono::steady_clock::time_point start_; }; +#endif // GTEST_OS_STARBOARD // Returns a timestamp as milliseconds since the epoch. Note this time may jump // around subject to adjustments by the system, to measure elapsed time use
diff --git a/third_party/musl/arch/aarch64/bits/alltypes.h b/third_party/musl/arch/aarch64/bits/alltypes.h index 04d8c0b..77ff768 100644 --- a/third_party/musl/arch/aarch64/bits/alltypes.h +++ b/third_party/musl/arch/aarch64/bits/alltypes.h
@@ -11,10 +11,14 @@ #define __LONG_MAX 0x7fffffffffffffffL #ifndef __cplusplus +#if defined(USE_COBALT_CUSTOMIZATIONS) +typedef __WCHAR_TYPE__ wchar_t; +#else #if defined(__NEED_wchar_t) && !defined(__DEFINED_wchar_t) typedef unsigned wchar_t; #define __DEFINED_wchar_t #endif +#endif // defined(USE_COBALT_CUSTOMIZATIONS) #endif #if defined(__NEED_wint_t) && !defined(__DEFINED_wint_t)
diff --git a/third_party/musl/arch/arm/bits/alltypes.h b/third_party/musl/arch/arm/bits/alltypes.h index 2bc88cd..3993b1e 100644 --- a/third_party/musl/arch/arm/bits/alltypes.h +++ b/third_party/musl/arch/arm/bits/alltypes.h
@@ -12,10 +12,14 @@ #define __LONG_MAX 0x7fffffffL #ifndef __cplusplus +#if defined(USE_COBALT_CUSTOMIZATIONS) +typedef __WCHAR_TYPE__ wchar_t; +#else #if defined(__NEED_wchar_t) && !defined(__DEFINED_wchar_t) typedef unsigned wchar_t; #define __DEFINED_wchar_t #endif +#endif // defined(USE_COBALT_CUSTOMIZATIONS) #endif
diff --git a/third_party/musl/arch/x86_64/bits/alltypes.h b/third_party/musl/arch/x86_64/bits/alltypes.h index b5d6f52..2e3401f 100644 --- a/third_party/musl/arch/x86_64/bits/alltypes.h +++ b/third_party/musl/arch/x86_64/bits/alltypes.h
@@ -1,16 +1,18 @@ #define _Addr long #define _Int64 long #define _Reg long - #define __BYTE_ORDER 1234 #define __LONG_MAX 0x7fffffffffffffffL #ifndef __cplusplus +#if defined(USE_COBALT_CUSTOMIZATIONS) +typedef __WCHAR_TYPE__ wchar_t; +#else #if defined(__NEED_wchar_t) && !defined(__DEFINED_wchar_t) typedef int wchar_t; #define __DEFINED_wchar_t #endif - +#endif // defined(USE_COBALT_CUSTOMIZATIONS) #endif #if defined(__FLT_EVAL_METHOD__) && __FLT_EVAL_METHOD__ == 2
diff --git a/third_party/v8/src/base/logging.h b/third_party/v8/src/base/logging.h index fe39f98..dbe1305 100644 --- a/third_party/v8/src/base/logging.h +++ b/third_party/v8/src/base/logging.h
@@ -5,6 +5,7 @@ #ifndef V8_BASE_LOGGING_H_ #define V8_BASE_LOGGING_H_ +#include <cstdint> #include <cstring> #include <sstream> #include <string>
diff --git a/third_party/v8/src/base/macros.h b/third_party/v8/src/base/macros.h index 515a9e3..a7cd7c0 100644 --- a/third_party/v8/src/base/macros.h +++ b/third_party/v8/src/base/macros.h
@@ -5,6 +5,7 @@ #ifndef V8_BASE_MACROS_H_ #define V8_BASE_MACROS_H_ +#include <cstdint> #include <limits> #include <type_traits>
diff --git a/third_party/v8/src/inspector/v8-string-conversions.h b/third_party/v8/src/inspector/v8-string-conversions.h index c1d69c1..eb33c68 100644 --- a/third_party/v8/src/inspector/v8-string-conversions.h +++ b/third_party/v8/src/inspector/v8-string-conversions.h
@@ -5,6 +5,7 @@ #ifndef V8_INSPECTOR_V8_STRING_CONVERSIONS_H_ #define V8_INSPECTOR_V8_STRING_CONVERSIONS_H_ +#include <cstdint> #include <string> // Conversion routines between UT8 and UTF16, used by string-16.{h,cc}. You may
diff --git a/third_party/v8/src/wasm/wasm-code-manager.cc b/third_party/v8/src/wasm/wasm-code-manager.cc index 579c094..fd83a60 100644 --- a/third_party/v8/src/wasm/wasm-code-manager.cc +++ b/third_party/v8/src/wasm/wasm-code-manager.cc
@@ -1123,6 +1123,10 @@ // The caller must hold the {allocation_mutex_}, thus we fail to lock it here. DCHECK(!allocation_mutex_.TryLock()); + // Add the code to the surrounding code ref scope, so the returned pointer is + // guaranteed to be valid. + WasmCodeRefScope::AddRef(code.get()); + if (!code->IsAnonymous() && code->index() >= module_->num_imported_functions) { DCHECK_LT(code->index(), num_functions()); @@ -1159,17 +1163,21 @@ WasmCodeRefScope::AddRef(prior_code); // The code is added to the current {WasmCodeRefScope}, hence the ref // count cannot drop to zero here. - CHECK(!prior_code->DecRef()); + prior_code->DecRefOnLiveCode(); } PatchJumpTablesLocked(slot_idx, code->instruction_start()); + } else { + // The code tables does not hold a reference to the code, hence decrement + // the initial ref count of 1. The code was added to the + // {WasmCodeRefScope} though, so it cannot die here. + code->DecRefOnLiveCode(); } if (!code->for_debugging() && tiering_state_ == kTieredDown && code->tier() == ExecutionTier::kTurbofan) { liftoff_bailout_count_.fetch_add(1); } } - WasmCodeRefScope::AddRef(code.get()); WasmCode* result = code.get(); owned_code_.emplace(result->instruction_start(), std::move(code)); return result;
diff --git a/third_party/v8/src/wasm/wasm-code-manager.h b/third_party/v8/src/wasm/wasm-code-manager.h index 26a9030..da8e2cc 100644 --- a/third_party/v8/src/wasm/wasm-code-manager.h +++ b/third_party/v8/src/wasm/wasm-code-manager.h
@@ -226,6 +226,14 @@ } } + // Decrement the ref count on code that is known to be in use (i.e. the ref + // count cannot drop to zero here). + void DecRefOnLiveCode() { + int old_count = ref_count_.fetch_sub(1, std::memory_order_acq_rel); + DCHECK_LE(2, old_count); + USE(old_count); + } + // Decrement the ref count on code that is known to be dead, even though there // might still be C++ references. Returns whether this drops the last // reference and the code needs to be freed.
diff --git a/tools/copy_and_filter_out_dir.py b/tools/copy_and_filter_out_dir.py index b9f08ec..9c398cd 100755 --- a/tools/copy_and_filter_out_dir.py +++ b/tools/copy_and_filter_out_dir.py
@@ -102,6 +102,12 @@ return 0 _IterateAndFilter(source_out_dir, dest_out_dir, copies) + + # Add build_info json to the out directory root. + source_build_info = os.path.join(source_out_dir, 'gen', 'build_info.json') + dest_build_info = os.path.join(dest_out_dir, 'build_info.json') + copies[source_build_info] = dest_build_info + for source, dest in copies.items(): dirname = os.path.dirname(dest) if not os.path.exists(dirname):
diff --git a/tools/lz4_compress/lz4_compress.cc b/tools/lz4_compress/lz4_compress.cc index 26421dc..08a79f5 100644 --- a/tools/lz4_compress/lz4_compress.cc +++ b/tools/lz4_compress/lz4_compress.cc
@@ -22,6 +22,7 @@ #include <stdio.h> +#include <cstdint> #include <iostream> #include <vector>
diff --git a/tools/metrics/actions/cobalt/actions.xml b/tools/metrics/actions/cobalt/actions.xml new file mode 100644 index 0000000..16e1580 --- /dev/null +++ b/tools/metrics/actions/cobalt/actions.xml
@@ -0,0 +1,43 @@ +<!-- +Copyright 2023 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. +--> + +<!-- +This file is a comprehensive list of Cobalt user actions along with the owner +and description for each user action. + +For best practices on writing user action descriptions, see +https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/actions/README.md +For details on how to modify this file to add your description, keep reading. + +If an action is not user triggered, specify it using not_user_triggered="true" +as an attribute to the <action> tag. + +An example entry looks like: + + <action name="ClickedFooButton"> + <owner>buttonengineer@google.com</owner> + <description>User clicked the Foo button.</description> + </action> + +Please run ../extract_actions.py to add new actions and pretty-print this file. + +If a user action is not being used any more, put an <obsolete> tag under +the <action> tag with necessary explanation. Don't delete things in this file. +--> + +<actions> + +</actions>
diff --git a/tools/metrics/histograms/metadata/cobalt/enums.xml b/tools/metrics/histograms/metadata/cobalt/enums.xml new file mode 100644 index 0000000..feb372a --- /dev/null +++ b/tools/metrics/histograms/metadata/cobalt/enums.xml
@@ -0,0 +1,74 @@ +<!-- +Copyright 2023 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. +--> + +<!-- + +This file describes the enumerations referenced by entries in histograms.xml for +this directory. + +For best practices on writing enumerations descriptions, see +https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histograms/README.md#Enum-Histograms +--> + +<histogram-configuration> + +<!-- Enum types --> + +<enums> + +<enum name="Boolean"> + <summary> + A "True/False" enum for boolean histograms. + </summary> + <int value="0" label="False"/> + <int value="1" label="True"/> +</enum> + + +<enum name="PipelineStatus"> + <summary> + Possible status values reported by the Media Pipeline + </summary> + <int value="0" label="PIPELINE_OK"/> + <int value="1" label="PIPELINE_ERROR_URL_NOT_FOUND"/> + <int value="2" label="PIPELINE_ERROR_NETWORK"/> + <int value="3" label="PIPELINE_ERROR_DECODE"/> + <int value="5" label="PIPELINE_ERROR_ABORT"/> + <int value="6" label="PIPELINE_ERROR_INITIALIZATION_FAILED"/> + <int value="7" label="PIPELINE_ERROR_REQUIRED_FILTER_MISSING"/> + <int value="8" label="PIPELINE_ERROR_COULD_NOT_RENDER"/> + <int value="9" label="PIPELINE_ERROR_READ"/> + <int value="10" label="PIPELINE_ERROR_OPERATION_PENDING"/> + <int value="11" label="PIPELINE_ERROR_INVALID_STATE"/> + <int value="12" label="DEMUXER_ERROR_COULD_NOT_OPEN"/> + <int value="13" label="DEMUXER_ERROR_COULD_NOT_PARSE"/> + <int value="14" label="DEMUXER_ERROR_NO_SUPPORTED_STREAMS"/> + <int value="15" label="DECODER_ERROR_NOT_SUPPORTED"/> + <int value="16" label="CHUNK_DEMUXER_ERROR_APPEND_FAILED"/> + <int value="17" label="CHUNK_DEMUXER_ERROR_EOS_STATUS_DECODE_ERROR"/> + <int value="18" label="CHUNK_DEMUXER_ERROR_EOS_STATUS_NETWORK_ERROR"/> + <int value="19" label="AUDIO_RENDERER_ERROR"/> + <int value="20" label="AUDIO_RENDERER_ERROR_SPLICE_FAILED"/> + <int value="21" label="PIPELINE_ERROR_EXTERNAL_RENDERER_FAILED"/> + <int value="22" label="DEMUXER_ERROR_DETECTED_HLS"/> + <int value="23" label="PIPELINE_ERROR_HARDWARE_CONTEXT_RESET"/> + <int value="24" label="PLAYBACK_CAPABILITY_CHANGED"/> +</enum> + + +</enums> + +</histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/cobalt/histograms.xml b/tools/metrics/histograms/metadata/cobalt/histograms.xml new file mode 100644 index 0000000..7a5547a --- /dev/null +++ b/tools/metrics/histograms/metadata/cobalt/histograms.xml
@@ -0,0 +1,169 @@ +<!-- +Copyright 2023 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. +--> + +<!-- +This file is used to generate a comprehensive list of Cobalt histograms +along with a detailed description for each histogram. See go/cobalt-telemetry +for details on how to modify this file. + +For best practices on writing histogram descriptions, see +https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histograms/README.md + +Always run the pretty print utility on this file after editing: + + ./tools/metrics/histograms/pretty_print.py tools/metrics/histograms/metadata/cobalt/histograms.xml +--> + +<histogram-configuration> + +<histograms> + +<histogram name="Cobalt.DOM.CSS.Link.ParseTimeMicrosPerKB" units="microseconds" + expires_after="never"> +<!-- expires-never: Needed for long-term tracking of CSS parse performance. --> + + <owner>joeltine@google.com</owner> + <summary> + A normalized ratio of time to KB of CSS parsing for link tags. Only logs a + sample for links loaded through HTTP/HTTPS and greater than 1KB. See + go/cobalt-js-css-parsing-metrics for the full design. + </summary> +</histogram> + +<histogram name="Cobalt.DOM.CSS.Style.ParseTimeMicrosPerKB" + units="microseconds" expires_after="never"> +<!-- expires-never: Needed for long-term tracking of CSS parse performance. --> + + <owner>joeltine@google.com</owner> + <summary> + A normalized ratio of time to KB of CSS parsing for style tags. Only logs a + sample for HTMLStyleElements with CSS greater than 1KB. See + go/cobalt-js-css-parsing-metrics for the full design. + </summary> +</histogram> + +<histogram name="Cobalt.Media.HasEverPlayed" enum="PipelineStatus" + expires_after="never"> +<!-- expires-never: Needed for baseline Media pipeline health metric. --> + + <owner>sideboard@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + Status of the media pipeline at the end of its lifecycle for audio-video + streams with an unknown video codec. + </summary> +</histogram> + +<histogram name="Cobalt.Media.PipelineStatus.AudioOnly" enum="PipelineStatus" + expires_after="never"> +<!-- expires-never: Needed for baseline Media pipeline health metric. --> + + <owner>sideboard@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + Status of the media pipeline at the end of its lifecycle for audio only + streams. + </summary> +</histogram> + +<histogram name="Cobalt.Media.PipelineStatus.AudioVideo.AV1" + enum="PipelineStatus" expires_after="never"> +<!-- expires-never: Needed for baseline Media pipeline health metric. --> + + <owner>sideboard@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + Status of the media pipeline at the end of its lifecycle for audio-video + streams with AV1 video codec. + </summary> +</histogram> + +<histogram name="Cobalt.Media.PipelineStatus.AudioVideo.H264" + enum="PipelineStatus" expires_after="never"> +<!-- expires-never: Needed for baseline Media pipeline health metric. --> + + <owner>sideboard@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + Status of the media pipeline at the end of its lifecycle for audio-video + streams with H264 video codec. + </summary> +</histogram> + +<histogram name="Cobalt.Media.PipelineStatus.AudioVideo.Other" + enum="PipelineStatus" expires_after="never"> +<!-- expires-never: Needed for baseline Media pipeline health metric. --> + + <owner>sideboard@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + Status of the media pipeline at the end of its lifecycle for audio-video + streams with an unknown video codec. + </summary> +</histogram> + +<histogram name="Cobalt.Media.PipelineStatus.AudioVideo.VP9" + enum="PipelineStatus" expires_after="never"> +<!-- expires-never: Needed for baseline Media pipeline health metric. --> + + <owner>sideboard@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + Status of the media pipeline at the end of its lifecycle for audio-video + streams with VP9 video codec. + </summary> +</histogram> + +<histogram name="Cobalt.Media.PipelineStatus.Unsupported" enum="PipelineStatus" + expires_after="never"> +<!-- expires-never: Needed for baseline Media pipeline health metric. --> + + <owner>sideboard@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + Status of the media pipeline at the end of its lifecycle for audio-video + streams with an unknown video codec. + </summary> +</histogram> + +<histogram name="Cobalt.Media.PipelineStatus.VideoOnly" enum="PipelineStatus" + expires_after="never"> +<!-- expires-never: Needed for baseline Media pipeline health metric. --> + + <owner>sideboard@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + Status of the media pipeline at the end of its lifecycle for video only + streams. + </summary> +</histogram> + +<histogram name="Cobalt.MediaDevices.MicCreationSucceeded" enum="Boolean" + expires_after="never"> +<!-- expires-never: Needed for long-term tracking of device mic support. --> + + <owner>loganmann@google.com</owner> + <owner>yt-magma-eng@google.com</owner> + <owner>cobalt-team@google.com</owner> + <summary> + A boolean representing the success or failure of an attempted mic creation + event via mediaDevices.getUserMedia(). + </summary> +</histogram> + +</histograms> + +</histogram-configuration> \ No newline at end of file