Import Cobalt 21.lts.4.301329
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md index b201080..dc2d88d 100644 --- a/src/cobalt/CHANGELOG.md +++ b/src/cobalt/CHANGELOG.md
@@ -137,6 +137,11 @@ rendering; the web app does not know about the custom transform so may not layout elements appropriately. + - **Added support for javascript code caching.** + + Platforms can provide javascript code caching by implementing + CobaltExtensionJavaScriptCacheApi. + ## Version 20 - **Support for QUIC and SPDY is now enabled.**
diff --git a/src/cobalt/audio/audio_destination_node.cc b/src/cobalt/audio/audio_destination_node.cc index e35f1f9..6f82ba8 100644 --- a/src/cobalt/audio/audio_destination_node.cc +++ b/src/cobalt/audio/audio_destination_node.cc
@@ -59,6 +59,7 @@ if (!audio_device_) { audio_device_.reset( new AudioDevice(static_cast<int>(channel_count(NULL)), this)); + SB_LOG(INFO) << "Created audio device " << audio_device_.get() << '.'; context()->PreventGarbageCollection(); } audio_device_to_delete_ = NULL; @@ -73,7 +74,10 @@ DCHECK_EQ(number_of_inputs(), 1u); bool all_finished = true; Input(0)->FillAudioBus(audio_bus, silence, &all_finished); - if (all_consumed && all_finished) { + if (all_consumed && all_finished && + audio_device_to_delete_ != audio_device_.get()) { + SB_LOG(INFO) << "Schedule to destroy audio device " << audio_device_.get() + << '.'; audio_device_to_delete_ = audio_device_.get(); message_loop_->task_runner()->PostTask( FROM_HERE, base::Bind(&AudioDestinationNode::DestroyAudioDevice, @@ -82,7 +86,12 @@ } void AudioDestinationNode::DestroyAudioDevice() { + AudioLock::AutoLock lock(audio_lock()); + if (!audio_device_.get()) { + return; + } if (audio_device_.get() == audio_device_to_delete_) { + SB_LOG(INFO) << "Destroying audio device " << audio_device_.get() << '.'; audio_device_.reset(); context()->AllowGarbageCollection(); }
diff --git a/src/cobalt/base/message_queue.h b/src/cobalt/base/message_queue.h index 60e72ea..be36eea 100644 --- a/src/cobalt/base/message_queue.h +++ b/src/cobalt/base/message_queue.h
@@ -58,6 +58,14 @@ } } + // Clear all the messages in the queue. + void ClearAll() { + TRACE_EVENT0("cobalt::base", "MessageQueue::ClearAll()"); + base::AutoLock lock(mutex_); + std::queue<base::Closure> empty_queue; + empty_queue.swap(queue_); + } + private: base::Lock mutex_; std::queue<base::Closure> queue_;
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc index f2ba949..27fab77 100644 --- a/src/cobalt/browser/browser_module.cc +++ b/src/cobalt/browser/browser_module.cc
@@ -765,7 +765,13 @@ TRACE_EVENT0("cobalt::browser", "BrowserModule::ProcessRenderTreeSubmissionQueue()"); DCHECK_EQ(base::MessageLoop::current(), self_message_loop_); - render_tree_submission_queue_.ProcessAll(); + // If the app is preloaded, clear the render tree queue to avoid unnecessary + // rendering overhead. + if (application_state_ == base::kApplicationStatePreloading) { + render_tree_submission_queue_.ClearAll(); + } else { + render_tree_submission_queue_.ProcessAll(); + } } void BrowserModule::QueueOnRenderTreeProduced(
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id index 4021ee4..189d70d 100644 --- a/src/cobalt/build/build.id +++ b/src/cobalt/build/build.id
@@ -1 +1 @@ -300899 \ No newline at end of file +301329 \ No newline at end of file
diff --git a/src/cobalt/cssom/viewport_size.h b/src/cobalt/cssom/viewport_size.h index 6efe128..040d414 100644 --- a/src/cobalt/cssom/viewport_size.h +++ b/src/cobalt/cssom/viewport_size.h
@@ -53,7 +53,7 @@ // Ratio of CSS pixels per device pixel, matching the devicePixelRatio // attribute. - // https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-window-devicepixelratio + // https://www.w3.org/TR/cssom-view-1/#dom-window-devicepixelratio float device_pixel_ratio_ = 1.0f; };
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc index 1bbc6c8..989ccac 100644 --- a/src/cobalt/extension/extension_test.cc +++ b/src/cobalt/extension/extension_test.cc
@@ -19,6 +19,7 @@ #include "cobalt/extension/font.h" #include "cobalt/extension/graphics.h" #include "cobalt/extension/installation_manager.h" +#include "cobalt/extension/javascript_cache.h" #include "cobalt/extension/platform_service.h" #include "starboard/system.h" #include "testing/gtest/include/gtest/gtest.h" @@ -219,6 +220,29 @@ EXPECT_EQ(second_extension_api, extension_api) << "Extension struct should be a singleton"; } + +TEST(ExtensionTest, JavaScriptCache) { + typedef CobaltExtensionJavaScriptCacheApi ExtensionApi; + const char* kExtensionName = kCobaltExtensionJavaScriptCacheName; + + 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->GetCachedScript, nullptr); + EXPECT_NE(extension_api->ReleaseCachedScriptData, nullptr); + EXPECT_NE(extension_api->StoreCachedScript, 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 cobalt #endif // SB_API_VERSION >= 11
diff --git a/src/cobalt/extension/javascript_cache.h b/src/cobalt/extension/javascript_cache.h new file mode 100644 index 0000000..660c578 --- /dev/null +++ b/src/cobalt/extension/javascript_cache.h
@@ -0,0 +1,62 @@ +// Copyright 2021 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COBALT_EXTENSION_JAVASCRIPT_CACHE_H_ +#define COBALT_EXTENSION_JAVASCRIPT_CACHE_H_ + +#include <stdbool.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define kCobaltExtensionJavaScriptCacheName \ + "dev.cobalt.extension.JavaScriptCache" + +// The implementation must be thread-safe as the extension would +// be called from different threads. Also all storage management +// is delegated to the platform. +typedef struct CobaltExtensionJavaScriptCacheApi { + // Name should be the string |kCobaltExtensionJavaScriptCacheName|. + // 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. + + // Retrieves the cached data for a script using |key|. The |source_length| + // provides the actual size of the script source in bytes. The cached script + // bytes will be returned in |cache_data_out|. After the |cache_data| is + // processed the memory should be released by calling + // |ReleaseScriptCacheData|. + bool (*GetCachedScript)(uint32_t key, int source_length, + const uint8_t** cache_data_out, + int* cache_data_length); + + // Releases the memory allocated for the |cache_data| by |GetCachedScript|. + void (*ReleaseCachedScriptData)(const uint8_t* cache_data); + + // Stores the cached data for |key|. + bool (*StoreCachedScript)(uint32_t key, int source_length, + const uint8_t* cache_data, int cache_data_length); +} CobaltExtensionJavaScriptCacheApi; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // COBALT_EXTENSION_JAVASCRIPT_CACHE_H_
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl index 1a36b31..9a77757 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
@@ -30,6 +30,5 @@ } vec4 untransformed_color = vec4(y_value, uv_value.r, uv_value.g, 1.0); - vec4 color = untransformed_color * to_rgb_color_matrix; - gl_FragColor = color; + gl_FragColor = untransformed_color * to_rgb_color_matrix; }
diff --git a/src/cobalt/script/v8c/v8c_global_environment.cc b/src/cobalt/script/v8c/v8c_global_environment.cc index b19174c..044ea64 100644 --- a/src/cobalt/script/v8c/v8c_global_environment.cc +++ b/src/cobalt/script/v8c/v8c_global_environment.cc
@@ -25,6 +25,7 @@ #include "base/strings/stringprintf.h" #include "base/trace_event/trace_event.h" #include "cobalt/base/polymorphic_downcast.h" +#include "cobalt/script/javascript_engine.h" #include "cobalt/script/v8c/embedded_resources.h" #include "cobalt/script/v8c/entry_scope.h" #include "cobalt/script/v8c/v8c_script_value_factory.h" @@ -32,6 +33,8 @@ #include "cobalt/script/v8c/v8c_user_object_holder.h" #include "cobalt/script/v8c/v8c_value_handle.h" #include "nb/memory_scope.h" +#include "starboard/common/murmurhash2.h" + namespace cobalt { namespace script { @@ -71,6 +74,18 @@ return *v8::String::Utf8Value(isolate, value.As<v8::String>()); } +uint32_t CreateJavaScriptCacheKey(const std::string& javascript_engine_version, + uint32_t cached_data_version_tag, + const std::string& source, + const std::string& origin) { + uint32_t res = starboard::MurmurHash2_32(javascript_engine_version.c_str(), + javascript_engine_version.size(), + cached_data_version_tag); + res = starboard::MurmurHash2_32(source.c_str(), source.size(), res); + res = starboard::MurmurHash2_32(origin.c_str(), origin.size(), res); + return res; +} + } // namespace V8cGlobalEnvironment::V8cGlobalEnvironment(v8::Isolate* isolate) @@ -229,7 +244,11 @@ } void V8cGlobalEnvironment::RemoveRoot(Traceable* traceable) { - V8cEngine::GetFromIsolate(isolate_)->heap_tracer()->RemoveRoot(traceable); + CHECK(isolate_); + V8cEngine* v8c_engine = V8cEngine::GetFromIsolate(isolate_); + CHECK(v8c_engine); + CHECK(v8c_engine->heap_tracer()); + v8c_engine->heap_tracer()->RemoveRoot(traceable); } void V8cGlobalEnvironment::PreventGarbageCollection( @@ -415,7 +434,29 @@ v8::Local<v8::Context> context = isolate_->GetCurrentContext(); v8::Local<v8::Script> script; - { + + bool used_cache = false; +#if SB_API_VERSION >= 11 + const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension = + static_cast<const CobaltExtensionJavaScriptCacheApi*>( + SbSystemGetExtension(kCobaltExtensionJavaScriptCacheName)); + if (javascript_cache_extension && + SbStringCompareAll(javascript_cache_extension->name, + kCobaltExtensionJavaScriptCacheName) == 0 && + javascript_cache_extension->version >= 1) { + TRACE_EVENT0("cobalt::script", + "V8cGlobalEnvironment::CompileWithCaching()"); + if (CompileWithCaching(javascript_cache_extension, context, v8c_source_code, + source, &script_origin) + .ToLocal(&script)) { + used_cache = true; + } else { + LOG(WARNING) << "Failed to compile script."; + return {}; + } + } +#endif + if (!used_cache) { TRACE_EVENT0("cobalt::script", "v8::Script::Compile()"); if (!v8::Script::Compile(context, source, &script_origin) .ToLocal(&script)) { @@ -436,6 +477,78 @@ return result; } +v8::MaybeLocal<v8::Script> V8cGlobalEnvironment::CompileWithCaching( + const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension, + v8::Local<v8::Context> context, V8cSourceCode* v8c_source_code, + v8::Local<v8::String> source, v8::ScriptOrigin* script_origin) { + const base::SourceLocation& source_location = v8c_source_code->location(); + + DLOG(INFO) << "CompileWithCaching: " << source_location.file_path.c_str() + << " " << v8c_source_code->location().file_path; + + bool successful_cache = false; + + std::string javascript_engine_version = + script::GetJavaScriptEngineNameAndVersion(); + uint32_t javascript_cache_key = CreateJavaScriptCacheKey( + javascript_engine_version, v8::ScriptCompiler::CachedDataVersionTag(), + v8c_source_code->source_utf8(), source_location.file_path); + const uint8_t* cache_data_buf = nullptr; + int cache_data_size = -1; + v8::Local<v8::Script> script; + if (javascript_cache_extension->GetCachedScript( + javascript_cache_key, v8c_source_code->source_utf8().size(), + &cache_data_buf, &cache_data_size)) { + DLOG(INFO) << "CompileWithCaching: Using cached resource"; + v8::ScriptCompiler::CachedData* cached_code = + new v8::ScriptCompiler::CachedData( + cache_data_buf, cache_data_size, + v8::ScriptCompiler::CachedData::BufferNotOwned); + // The script_source owns the cached_code object. + v8::ScriptCompiler::Source script_source(source, *script_origin, + cached_code); + + { + TRACE_EVENT0("cobalt::script", "v8::Script::Compile()"); + successful_cache = + v8::ScriptCompiler::Compile(context, &script_source, + v8::ScriptCompiler::kConsumeCodeCache) + .ToLocal(&script) && + !cached_code->rejected; + if (!successful_cache) { + LOG(WARNING) + << "CompileWithCaching: Failed to reuse the cached script rejected=" + << cached_code->rejected; + } + } + } + + if (cache_data_buf != nullptr) { + javascript_cache_extension->ReleaseCachedScriptData(cache_data_buf); + cache_data_buf = nullptr; + } + + if (!successful_cache) { + DLOG(INFO) << "CompileWithCaching: compile"; + { + TRACE_EVENT0("cobalt::script", "v8::Script::Compile()"); + if (!v8::Script::Compile(context, source, script_origin) + .ToLocal(&script)) { + LOG(WARNING) << "CompileWithCaching: Failed to compile script."; + return {}; + } + } + std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data( + v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript())); + if (!javascript_cache_extension->StoreCachedScript( + javascript_cache_key, v8c_source_code->source_utf8().size(), + cached_data->data, cached_data->length)) { + LOG(WARNING) << "CompileWithCaching: Failed to store cached script"; + } + } + return script; +} + void V8cGlobalEnvironment::EvaluateEmbeddedScript(const unsigned char* data, size_t size, const char* filename) {
diff --git a/src/cobalt/script/v8c/v8c_global_environment.h b/src/cobalt/script/v8c/v8c_global_environment.h index c131a8d..6d6c9c5 100644 --- a/src/cobalt/script/v8c/v8c_global_environment.h +++ b/src/cobalt/script/v8c/v8c_global_environment.h
@@ -26,9 +26,11 @@ #include "base/optional.h" #include "base/stl_util.h" #include "base/threading/thread_checker.h" +#include "cobalt/extension/javascript_cache.h" #include "cobalt/script/global_environment.h" #include "cobalt/script/javascript_engine.h" #include "cobalt/script/v8c/v8c_heap_tracer.h" +#include "cobalt/script/v8c/v8c_source_code.h" #include "cobalt/script/v8c/wrapper_factory.h" #include "v8/include/libplatform/libplatform.h" #include "v8/include/v8.h" @@ -145,6 +147,11 @@ v8::MaybeLocal<v8::Value> EvaluateScriptInternal( const scoped_refptr<SourceCode>& source_code); + v8::MaybeLocal<v8::Script> CompileWithCaching( + const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension, + v8::Local<v8::Context> context, V8cSourceCode* v8c_source_code, + v8::Local<v8::String> source, v8::ScriptOrigin* script_origin); + void EvaluateEmbeddedScript(const unsigned char* data, size_t size, const char* filename);
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java index 048ecfa..ac387f5 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -74,6 +74,9 @@ int preferredBufferSizeInBytes, boolean enableAudioRouting, int tunnelModeAudioSessionId) { + // TODO: Re-enable audio routing when all related bugs are fixed. + enableAudioRouting = false; + tunnelModeEnabled = tunnelModeAudioSessionId != -1; int channelConfig; switch (channelCount) { @@ -166,6 +169,7 @@ preferredBufferSizeInBytes, AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType))); if (audioTrack != null && enableAudioRouting && Build.VERSION.SDK_INT >= 24) { + Log.i(TAG, "Audio routing enabled."); currentRoutedDevice = audioTrack.getRoutedDevice(); onRoutingChangedListener = new AudioRouting.OnRoutingChangedListener() { @@ -186,6 +190,8 @@ } }; audioTrack.addOnRoutingChangedListener(onRoutingChangedListener, null); + } else { + Log.i(TAG, "Audio routing disabled."); } } @@ -195,7 +201,7 @@ public void release() { if (audioTrack != null) { - if (Build.VERSION.SDK_INT >= 24) { + if (Build.VERSION.SDK_INT >= 24 && onRoutingChangedListener != null) { audioTrack.removeOnRoutingChangedListener(onRoutingChangedListener); } audioTrack.release();
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java index 5318110..cdbbf9a 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -120,6 +120,7 @@ // On the emulator it fails with the log: "storeMetaDataInBuffers failed w/ err -1010" codecBlackList.add("OMX.google.vp9.decoder"); + vp9WhiteList.put("Amazon", new HashSet<String>()); vp9WhiteList.put("Amlogic", new HashSet<String>()); vp9WhiteList.put("Arcadyan", new HashSet<String>()); vp9WhiteList.put("arcelik", new HashSet<String>()); @@ -159,6 +160,7 @@ vp9WhiteList.put("Xiaomi", new HashSet<String>()); vp9WhiteList.put("ZTE TV", new HashSet<String>()); + vp9WhiteList.get("Amazon").add("AFTS"); vp9WhiteList.get("Amlogic").add("p212"); vp9WhiteList.get("Arcadyan").add("Bouygtel4K"); vp9WhiteList.get("Arcadyan").add("HMB2213PW22TS");
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java index c49cacb..9ec58e5 100644 --- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java +++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
@@ -15,7 +15,6 @@ package dev.cobalt.util; import android.content.Context; -import android.content.res.Resources; import android.util.DisplayMetrics; import android.util.Size; import android.util.SizeF; @@ -134,7 +133,6 @@ private static DisplayMetrics cachedDisplayMetrics = null; private static DisplayMetrics getDisplayMetrics() { - Resources.getSystem().getDisplayMetrics(); if (cachedDisplayMetrics == null) { cachedDisplayMetrics = new DisplayMetrics(); Display display = getDefaultDisplay();
diff --git a/src/starboard/android/apk/build.id b/src/starboard/android/apk/build.id new file mode 100644 index 0000000..13984bc --- /dev/null +++ b/src/starboard/android/apk/build.id
@@ -0,0 +1 @@ +301279 \ No newline at end of file
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc index e28eabe..1c7d413 100644 --- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc +++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -14,6 +14,7 @@ #include "starboard/android/shared/audio_sink_min_required_frames_tester.h" +#include <string> #include <vector> #include "starboard/android/shared/audio_track_audio_sink_type.h" @@ -177,7 +178,9 @@ // static void MinRequiredFramesTester::ErrorFunc(bool capability_changed, + 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();
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h index e689e96..669217a 100644 --- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h +++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
@@ -17,6 +17,7 @@ #include <atomic> #include <functional> +#include <string> #include <vector> #include "starboard/common/condition_variable.h" @@ -85,7 +86,9 @@ static void ConsumeFramesFunc(int frames_consumed, SbTime frames_consumed_at, void* context); - static void ErrorFunc(bool capability_changed, void* context); + static void ErrorFunc(bool capability_changed, + const std::string& error_message, + void* context); void UpdateSourceStatus(int* frames_in_buffer, int* offset_in_frames, bool* is_playing,
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc index 3f5b5b8..4c24e5b 100644 --- a/src/starboard/android/shared/audio_track_audio_sink_type.cc +++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -15,8 +15,10 @@ #include "starboard/android/shared/audio_track_audio_sink_type.h" #include <algorithm> +#include <string> #include <vector> +#include "starboard/common/string.h" #include "starboard/shared/starboard/player/filter/common.h" namespace { @@ -246,7 +248,8 @@ j_audio_track_bridge_, "getAndResetHasNewAudioDeviceAdded", "()Z"); if (new_audio_device_added) { SB_LOG(INFO) << "New audio device added."; - error_func_(kSbPlayerErrorCapabilityChanged, context_); + error_func_(kSbPlayerErrorCapabilityChanged, "New audio device added.", + context_); break; } @@ -395,10 +398,12 @@ // Take all |frames_in_audio_track| as consumed since audio track could be // dead. consume_frames_func_(frames_in_audio_track, now, context_); - error_func_(written_frames == kAudioTrackErrorDeadObject - ? kSbPlayerErrorCapabilityChanged - : kSbPlayerErrorDecode, - context_); + + bool capabilities_changed = written_frames == kAudioTrackErrorDeadObject; + error_func_( + capabilities_changed, + FormatString("Error while writing frames: %d", written_frames), + context_); break; } else if (written_frames > 0) { last_written_succeeded_at = now;
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h index 5e1b078..7caf57b 100644 --- a/src/starboard/android/shared/player_components_factory.h +++ b/src/starboard/android/shared/player_components_factory.h
@@ -49,6 +49,11 @@ using starboard::shared::starboard::media::MimeType; +// Tunnel mode has to be enabled explicitly by the web app via mime attributes +// "tunnelmode", set the following variable to true to force enabling tunnel +// mode on all playbacks. +constexpr bool kForceTunnelMode = false; + // On some platforms tunnel mode is only supported in the secure pipeline. Set // the following variable to true to force creating a secure pipeline in tunnel // mode, even for clear content. @@ -113,7 +118,8 @@ SB_DCHECK(frames_consumed == 0); } - void OnError(bool capability_changed) override { + void OnError(bool capability_changed, + const std::string& error_message) override { error_occurred_.store(true); } @@ -196,6 +202,12 @@ << ". Tunnel mode is disabled."; } + if (kForceTunnelMode && !enable_tunnel_mode) { + SB_LOG(INFO) << "`kForceTunnelMode` is set to true, force enabling tunnel" + << " mode."; + enable_tunnel_mode = true; + } + bool force_secure_pipeline_under_tunnel_mode = false; if (enable_tunnel_mode && IsTunnelModeSupported(creation_parameters,
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi index 60e8129..97371fe 100644 --- a/src/starboard/android/shared/starboard_platform.gypi +++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -164,6 +164,8 @@ 'trace_util.h', 'video_decoder.cc', 'video_decoder.h', + 'video_frame_tracker.cc', + 'video_frame_tracker.h', 'video_render_algorithm.cc', 'video_render_algorithm.h', 'video_window.cc',
diff --git a/src/starboard/android/shared/starboard_platform_tests.gypi b/src/starboard/android/shared/starboard_platform_tests.gypi index 5b40e63..5b8fe50 100644 --- a/src/starboard/android/shared/starboard_platform_tests.gypi +++ b/src/starboard/android/shared/starboard_platform_tests.gypi
@@ -23,6 +23,7 @@ '<(DEPTH)/starboard/common/test_main.cc', '<@(media_tests_sources)', 'jni_env_ext_test.cc', + 'video_frame_tracker_test.cc', ], 'defines': [ # This allows the tests to include internal only header files.
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc index ff53ef1..267285d 100644 --- a/src/starboard/android/shared/video_decoder.cc +++ b/src/starboard/android/shared/video_decoder.cc
@@ -20,7 +20,6 @@ #include <cmath> #include <functional> #include <list> -#include <vector> #include "starboard/android/shared/application_android.h" #include "starboard/android/shared/decode_target_create.h" @@ -36,7 +35,6 @@ #include "starboard/drm.h" #include "starboard/memory.h" #include "starboard/shared/starboard/player/filter/video_frame_internal.h" -#include "starboard/shared/starboard/thread_checker.h" #include "starboard/string.h" #include "starboard/thread.h" @@ -154,125 +152,7 @@ } // namespace -// TODO: Move into a separate file and write unit tests for it. -class VideoFrameTracker { - public: - SbTime seek_to_time() const { return seek_to_time_; } - - void OnInputBuffer(SbTime timestamp) { - SB_DCHECK(thread_checker_.CalledOnValidThread()); - - if (frames_to_be_rendered_.empty()) { - frames_to_be_rendered_.push_back(timestamp); - return; - } - - if (frames_to_be_rendered_.size() > kMaxPendingWorkSize * 2) { - // OnFrameRendered() is only available after API level 23. Cap the size - // of |frames_to_be_rendered_| in case OnFrameRendered() is not available. - frames_to_be_rendered_.pop_front(); - } - - // Sort by |timestamp|, because |timestamp| won't be monotonic if there are - // B frames. - for (auto it = frames_to_be_rendered_.end(); - it != frames_to_be_rendered_.begin();) { - it--; - if (*it < timestamp) { - frames_to_be_rendered_.emplace(++it, timestamp); - return; - } else if (*it == timestamp) { - SB_LOG(WARNING) << "feed video AU with same time stamp " << timestamp; - return; - } - } - - frames_to_be_rendered_.emplace_front(timestamp); - } - - void OnFrameRendered(int64_t frame_timestamp) { - ScopedLock lock(rendered_frames_mutex_); - rendered_frames_on_decoder_thread_.push_back(frame_timestamp); - } - - void Seek(SbTime seek_to_time) { - SB_DCHECK(thread_checker_.CalledOnValidThread()); - - // Ensure that all dropped frames before seeking are captured. - UpdateDroppedFrames(); - - frames_to_be_rendered_.clear(); - seek_to_time_ = seek_to_time; - } - - int UpdateAndGetDroppedFrames() { - SB_DCHECK(thread_checker_.CalledOnValidThread()); - UpdateDroppedFrames(); - return dropped_frames_; - } - - private: - // TODO: - // * It is possible that the initial frame rendered time is before the seek to - // time, when the platform decides to render a frame earlier than the seek - // to time during preroll. This shouldn't be an issue after we align seek - // time to the next video key frame. - // * The reported frame rendering time is the real time the frame is rendered. - // It can be slightly different than the timestamp associated with the - // InputBuffer. For example, the frame with timestamp 120000 may be - // rendered at 120020. We have to account for this difference, as otherwise - // lots of frames will be reported as being dropped. - void UpdateDroppedFrames() { - SB_DCHECK(thread_checker_.CalledOnValidThread()); - - { - ScopedLock lock(rendered_frames_mutex_); - rendered_frames_on_tracker_thread_.swap( - rendered_frames_on_decoder_thread_); - } - - // TODO: Refine the algorithm, and consider using std::set<> for - // |frames_to_be_rendered_|. - for (auto timestamp : rendered_frames_on_tracker_thread_) { - auto it = frames_to_be_rendered_.begin(); - while (it != frames_to_be_rendered_.end()) { - if (*it > timestamp) { - break; - } - - if (*it < seek_to_time_) { - it = frames_to_be_rendered_.erase(it); - } else if (*it < timestamp) { - SB_LOG(WARNING) << "Video frame dropped:" << *it - << ", current frame timestamp:" << timestamp - << ", frames in the backlog:" - << frames_to_be_rendered_.size(); - ++dropped_frames_; - it = frames_to_be_rendered_.erase(it); - } else if (*it == timestamp) { - it = frames_to_be_rendered_.erase(it); - } else { - ++it; - } - } - } - - rendered_frames_on_tracker_thread_.clear(); - } - - ::starboard::shared::starboard::ThreadChecker thread_checker_; - - std::list<SbTime> frames_to_be_rendered_; - - int dropped_frames_ = 0; - SbTime seek_to_time_ = 0; - - std::vector<SbTime> rendered_frames_on_tracker_thread_; - Mutex rendered_frames_mutex_; - std::vector<SbTime> rendered_frames_on_decoder_thread_; -}; - -// TODO: Merge this with VideoFrameTracker +// TODO: Merge this with VideoFrameTracker, maybe? class VideoRenderAlgorithmTunneled : public VideoRenderAlgorithmBase { public: explicit VideoRenderAlgorithmTunneled(VideoFrameTracker* frame_tracker) @@ -352,7 +232,7 @@ SB_DCHECK(error_message); if (tunnel_mode_audio_session_id != -1) { - video_frame_tracker_.reset(new VideoFrameTracker); + video_frame_tracker_.reset(new VideoFrameTracker(kMaxPendingWorkSize * 2)); } if (force_secure_pipeline_under_tunnel_mode) { SB_DCHECK(tunnel_mode_audio_session_id != -1);
diff --git a/src/starboard/android/shared/video_decoder.h b/src/starboard/android/shared/video_decoder.h index 42d6901..5297bb5 100644 --- a/src/starboard/android/shared/video_decoder.h +++ b/src/starboard/android/shared/video_decoder.h
@@ -22,6 +22,7 @@ #include "starboard/android/shared/drm_system.h" #include "starboard/android/shared/media_codec_bridge.h" #include "starboard/android/shared/media_decoder.h" +#include "starboard/android/shared/video_frame_tracker.h" #include "starboard/android/shared/video_window.h" #include "starboard/atomic.h" #include "starboard/common/condition_variable.h" @@ -41,8 +42,6 @@ namespace android { namespace shared { -class VideoFrameTracker; - class VideoDecoder : public ::starboard::shared::starboard::player::filter::VideoDecoder, private MediaDecoder::Host,
diff --git a/src/starboard/android/shared/video_frame_tracker.cc b/src/starboard/android/shared/video_frame_tracker.cc new file mode 100644 index 0000000..3e3349f --- /dev/null +++ b/src/starboard/android/shared/video_frame_tracker.cc
@@ -0,0 +1,144 @@ +// Copyright 2021 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/android/shared/video_frame_tracker.h" + +#include <cmath> +#include <cstdint> +#include <cstdlib> +#include <vector> + +#include "starboard/common/log.h" +#include "starboard/common/mutex.h" +#include "starboard/time.h" + +namespace starboard { +namespace android { +namespace shared { +namespace { + +const SbTime kMaxAllowedSkew = 5000; + +} // namespace + +SbTime VideoFrameTracker::seek_to_time() const { + return seek_to_time_; +} + +void VideoFrameTracker::OnInputBuffer(SbTime timestamp) { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + if (frames_to_be_rendered_.empty()) { + frames_to_be_rendered_.push_back(timestamp); + return; + } + + if (frames_to_be_rendered_.size() > max_pending_frames_size_) { + // OnFrameRendered() is only available after API level 23. Cap the size + // of |frames_to_be_rendered_| in case OnFrameRendered() is not available. + frames_to_be_rendered_.pop_front(); + } + + // Sort by |timestamp|, because |timestamp| won't be monotonic if there are + // B frames. + for (auto it = frames_to_be_rendered_.end(); + it != frames_to_be_rendered_.begin();) { + it--; + if (*it < timestamp) { + frames_to_be_rendered_.emplace(++it, timestamp); + return; + } else if (*it == timestamp) { + SB_LOG(WARNING) << "feed video AU with same time stamp " << timestamp; + return; + } + } + + frames_to_be_rendered_.emplace_front(timestamp); +} + +void VideoFrameTracker::OnFrameRendered(int64_t frame_timestamp) { + ScopedLock lock(rendered_frames_mutex_); + rendered_frames_on_decoder_thread_.push_back(frame_timestamp); +} + +void VideoFrameTracker::Seek(SbTime seek_to_time) { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + // Ensure that all dropped frames before seeking are captured. + UpdateDroppedFrames(); + + frames_to_be_rendered_.clear(); + seek_to_time_ = seek_to_time; +} + +int VideoFrameTracker::UpdateAndGetDroppedFrames() { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + UpdateDroppedFrames(); + return dropped_frames_; +} + +void VideoFrameTracker::UpdateDroppedFrames() { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + { + ScopedLock lock(rendered_frames_mutex_); + rendered_frames_on_tracker_thread_.swap(rendered_frames_on_decoder_thread_); + } + + while (frames_to_be_rendered_.front() < seek_to_time_) { + // It is possible that the initial frame rendered time is before the + // seek to time, when the platform decides to render a frame earlier + // than the seek to time during preroll. This shouldn't be an issue + // after we align seek time to the next video key frame. + frames_to_be_rendered_.pop_front(); + } + + // Loop over all timestamps from OnFrameRendered and compare against ones from + // OnInputBuffer. + for (auto rendered_timestamp : rendered_frames_on_tracker_thread_) { + auto to_render_timestamp = frames_to_be_rendered_.begin(); + // Loop over all frames to render until we've caught up to the timestamp of + // the last rendered frame. + while (to_render_timestamp != frames_to_be_rendered_.end() && + !(*to_render_timestamp - rendered_timestamp > kMaxAllowedSkew)) { + if (std::abs(*to_render_timestamp - rendered_timestamp) <= + kMaxAllowedSkew) { + // This frame was rendered, remove it from frames_to_be_rendered_. + to_render_timestamp = frames_to_be_rendered_.erase(to_render_timestamp); + } else if (rendered_timestamp - *to_render_timestamp > kMaxAllowedSkew) { + // The rendered frame is too far ahead. The to_render_timestamp frame + // was dropped. + SB_LOG(WARNING) << "Video frame dropped:" << *to_render_timestamp + << ", current frame timestamp:" << rendered_timestamp + << ", frames in the backlog:" + << frames_to_be_rendered_.size(); + ++dropped_frames_; + to_render_timestamp = frames_to_be_rendered_.erase(to_render_timestamp); + } else { + // The rendered frame is too early to match the next frame to render. + // This could happen if a frame is reported to be rendered twice or if + // it is rendered more than kMaxAllowedSkew early. In the latter + // scenario the frame will be reported dropped in the next iteration of + // the outer loop. + ++to_render_timestamp; + } + } + } + + rendered_frames_on_tracker_thread_.clear(); +} + +} // namespace shared +} // namespace android +} // namespace starboard
diff --git a/src/starboard/android/shared/video_frame_tracker.h b/src/starboard/android/shared/video_frame_tracker.h new file mode 100644 index 0000000..8b88f59 --- /dev/null +++ b/src/starboard/android/shared/video_frame_tracker.h
@@ -0,0 +1,64 @@ +// Copyright 2021 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_ANDROID_SHARED_VIDEO_FRAME_TRACKER_H_ +#define STARBOARD_ANDROID_SHARED_VIDEO_FRAME_TRACKER_H_ + +#include <list> +#include <vector> + +#include "starboard/common/mutex.h" +#include "starboard/shared/starboard/thread_checker.h" +#include "starboard/time.h" + +namespace starboard { +namespace android { +namespace shared { + +class VideoFrameTracker { + public: + explicit VideoFrameTracker(int max_pending_frames_size) + : max_pending_frames_size_(max_pending_frames_size) {} + + SbTime seek_to_time() const; + + void OnInputBuffer(SbTime timestamp); + + void OnFrameRendered(int64_t frame_timestamp); + + void Seek(SbTime seek_to_time); + + int UpdateAndGetDroppedFrames(); + + private: + void UpdateDroppedFrames(); + + ::starboard::shared::starboard::ThreadChecker thread_checker_; + + std::list<SbTime> frames_to_be_rendered_; + + const int max_pending_frames_size_; + int dropped_frames_ = 0; + SbTime seek_to_time_ = 0; + + Mutex rendered_frames_mutex_; + std::vector<SbTime> rendered_frames_on_tracker_thread_; + std::vector<SbTime> rendered_frames_on_decoder_thread_; +}; + +} // namespace shared +} // namespace android +} // namespace starboard + +#endif // STARBOARD_ANDROID_SHARED_VIDEO_FRAME_TRACKER_H_
diff --git a/src/starboard/android/shared/video_frame_tracker_test.cc b/src/starboard/android/shared/video_frame_tracker_test.cc new file mode 100644 index 0000000..5e61b00 --- /dev/null +++ b/src/starboard/android/shared/video_frame_tracker_test.cc
@@ -0,0 +1,167 @@ +// Copyright 2021 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <string> + +#include "starboard/android/shared/video_frame_tracker.h" +#include "starboard/time.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace android { +namespace shared { +namespace { + +TEST(VideoFrameTrackerTest, DroppedFrameCountIsCumulative) { + VideoFrameTracker video_frame_tracker(100); + + video_frame_tracker.OnInputBuffer(10000); + video_frame_tracker.OnInputBuffer(20000); + video_frame_tracker.OnInputBuffer(30000); + video_frame_tracker.OnInputBuffer(40000); + video_frame_tracker.OnInputBuffer(50000); + video_frame_tracker.OnInputBuffer(60000); + video_frame_tracker.OnInputBuffer(70000); + video_frame_tracker.OnInputBuffer(80000); + + video_frame_tracker.OnFrameRendered(10000); + video_frame_tracker.OnFrameRendered(20000); + // Frame with timestamp 30000 was dropped + video_frame_tracker.OnFrameRendered(40000); + + EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 1); + video_frame_tracker.OnFrameRendered(50000); + // Frame with timestamp 60000 was dropped + video_frame_tracker.OnFrameRendered(70000); + // Frame with timestamp 80000 is not rendered and not dropped. + + EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 2); +} + +TEST(VideoFrameTrackerTest, OnlySkewOverMaxAllowedShouldResultInDroppedFrame) { + VideoFrameTracker video_frame_tracker(100); + + video_frame_tracker.OnInputBuffer(100000); + video_frame_tracker.OnInputBuffer(200000); + video_frame_tracker.OnInputBuffer(300000); + video_frame_tracker.OnInputBuffer(400000); + video_frame_tracker.OnInputBuffer(500000); + video_frame_tracker.OnInputBuffer(600000); + + video_frame_tracker.OnFrameRendered(104999); // 4999us late -> not dropped + video_frame_tracker.OnFrameRendered(195001); // 4999us early -> not dropped + video_frame_tracker.OnFrameRendered(300000); + + EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 0); + + video_frame_tracker.OnFrameRendered(394999); // 5001us early -> dropped + video_frame_tracker.OnFrameRendered(505001); // 5001us late -> dropped + video_frame_tracker.OnFrameRendered(600000); + + EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 2); +} + +TEST(VideoFrameTrackerTest, MultipleFramesDroppedAreReportedCorrectly) { + VideoFrameTracker video_frame_tracker(100); + + video_frame_tracker.OnInputBuffer(10000); + video_frame_tracker.OnInputBuffer(20000); + video_frame_tracker.OnInputBuffer(30000); + video_frame_tracker.OnInputBuffer(40000); + video_frame_tracker.OnInputBuffer(50000); + video_frame_tracker.OnInputBuffer(60000); + video_frame_tracker.OnInputBuffer(70000); + video_frame_tracker.OnInputBuffer(80000); + video_frame_tracker.OnInputBuffer(90000); + + video_frame_tracker.OnFrameRendered(10000); + video_frame_tracker.OnFrameRendered(20000); + // Frame with timestamp 30000 was dropped. + video_frame_tracker.OnFrameRendered(40000); + // Frames with timestamp 50000, 60000, 70000, 80000 were dropped. + video_frame_tracker.OnFrameRendered(90000); + + EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 5); +} + +TEST(VideoFrameTrackerTest, RenderQueueIsClearedOnSeek) { + VideoFrameTracker video_frame_tracker(100); + + video_frame_tracker.OnInputBuffer(10000); + video_frame_tracker.OnInputBuffer(20000); + video_frame_tracker.OnInputBuffer(30000); + + // These frames are not rendered but due to the seek below should not count as + // dropped. + video_frame_tracker.OnInputBuffer(40000); + video_frame_tracker.OnInputBuffer(50000); + video_frame_tracker.OnInputBuffer(60000); + + video_frame_tracker.OnFrameRendered(10000); + // Frame with timestamp 20000 was dropped and should be registered. + video_frame_tracker.OnFrameRendered(30000); + // Frames 40000, 50000, 60000 were never rendered. + + const SbTime kSeekToTime = 90000; + video_frame_tracker.Seek(kSeekToTime); + ASSERT_EQ(kSeekToTime, video_frame_tracker.seek_to_time()); + + video_frame_tracker.OnInputBuffer(90000); + video_frame_tracker.OnInputBuffer(100000); + video_frame_tracker.OnInputBuffer(110000); + video_frame_tracker.OnInputBuffer(120000); + + video_frame_tracker.OnFrameRendered(90000); + video_frame_tracker.OnFrameRendered(100000); + + EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 1); +} + +TEST(VideoFrameTrackerTest, UnorderedInputFramesAreHandled) { + VideoFrameTracker video_frame_tracker(100); + + // Order of input frames should not matter as long as they are before the + // render timestamp. + video_frame_tracker.OnInputBuffer(20000); + video_frame_tracker.OnInputBuffer(30000); + video_frame_tracker.OnInputBuffer(10000); + video_frame_tracker.OnInputBuffer(40000); + video_frame_tracker.OnInputBuffer(60000); + video_frame_tracker.OnInputBuffer(80000); + + video_frame_tracker.OnFrameRendered(10000); + video_frame_tracker.OnFrameRendered(20000); + video_frame_tracker.OnFrameRendered(30000); + video_frame_tracker.OnFrameRendered(40000); + + // Add more input frames after the first frames are rendered. + video_frame_tracker.OnInputBuffer(50000); + video_frame_tracker.OnInputBuffer(70000); + video_frame_tracker.OnInputBuffer(90000); + + EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 0); + + video_frame_tracker.OnFrameRendered(50000); + video_frame_tracker.OnFrameRendered(60000); + video_frame_tracker.OnFrameRendered(70000); + video_frame_tracker.OnFrameRendered(80000); + + EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 0); +} + +} // namespace +} // namespace shared +} // namespace android +} // namespace starboard
diff --git a/src/starboard/evergreen/testing/linux/start_cobalt.sh b/src/starboard/evergreen/testing/linux/start_cobalt.sh index e4d3823..b060bbc 100755 --- a/src/starboard/evergreen/testing/linux/start_cobalt.sh +++ b/src/starboard/evergreen/testing/linux/start_cobalt.sh
@@ -37,7 +37,6 @@ log "info" " Starting Cobalt with:" log "info" " --url=${URL}" - log "info" " --content=${CONTENT}" for arg in $ARGS; do log "info" " ${arg}" @@ -45,7 +44,7 @@ log "info" " Logs will be output to '${LOG_PATH}/${LOG}'" - eval "${OUT}/loader_app --url=\"\"${URL}\"\" --content=${CONTENT} ${ARGS} 2>&1 | tee \"${LOG_PATH}/${LOG}\"" & + eval "${OUT}/loader_app --url=\"\"${URL}\"\" ${ARGS} 2>&1 | tee \"${LOG_PATH}/${LOG}\"" & eval $__LOADER=$!
diff --git a/src/starboard/evergreen/testing/raspi/start_cobalt.sh b/src/starboard/evergreen/testing/raspi/start_cobalt.sh index ae370d8..df4efb4 100755 --- a/src/starboard/evergreen/testing/raspi/start_cobalt.sh +++ b/src/starboard/evergreen/testing/raspi/start_cobalt.sh
@@ -37,7 +37,6 @@ log "info" " Starting Cobalt with:" log "info" " --url=${URL}" - log "info" " --content=${CONTENT}" for arg in $ARGS; do log "info" " ${arg}" @@ -45,7 +44,7 @@ log "info" " Logs will be output to '${LOG_PATH}/${LOG}'" - eval "${SSH}\"/home/pi/coeg/loader_app --url=\"\"${URL}\"\" --content=${CONTENT} ${ARGS} \" 2>&1 | tee \"${LOG_PATH}/${LOG}\"" & + eval "${SSH}\"/home/pi/coeg/loader_app --url=\"\"${URL}\"\" ${ARGS} \" 2>&1 | tee \"${LOG_PATH}/${LOG}\"" & eval $__LOADER=$!
diff --git a/src/starboard/evergreen/testing/tests/alternative_content_test.sh b/src/starboard/evergreen/testing/tests/alternative_content_test.sh index 84601d9..f3f6bb8 100755 --- a/src/starboard/evergreen/testing/tests/alternative_content_test.sh +++ b/src/starboard/evergreen/testing/tests/alternative_content_test.sh
@@ -32,7 +32,7 @@ return 1 fi - cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.1.log" "App is up to date" "--content=${CONTENT}" + cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.1.log" "App is up to date" "--content=${STORAGE_DIR}/installation_1/content" if [[ $? -ne 0 ]]; then log "error" "Failed to find 'App is up to date' indicating failure"
diff --git a/src/starboard/loader_app/app_key_internal.cc b/src/starboard/loader_app/app_key_internal.cc index 4fcd641..12e2f62 100644 --- a/src/starboard/loader_app/app_key_internal.cc +++ b/src/starboard/loader_app/app_key_internal.cc
@@ -115,6 +115,11 @@ output.resize(output_size); + // Replace the '+' and '/' characters with '-' and '_' so that they are safe + // to use in a URL and with the filesystem. + std::replace(output.begin(), output.end(), '+', '-'); + std::replace(output.begin(), output.end(), '/', '_'); + return output; }
diff --git a/src/starboard/loader_app/app_key_test.cc b/src/starboard/loader_app/app_key_test.cc index 8207ee4..af3d5e1 100644 --- a/src/starboard/loader_app/app_key_test.cc +++ b/src/starboard/loader_app/app_key_test.cc
@@ -673,5 +673,18 @@ } } +TEST(AppKeyTest, SunnyDayExtractAppKeySanitizesResult) { + // This magic is 11111011 11110000 in binary and thus begins with '+' and '/' + // when base64 encoded. + uint8_t magic[3] = {0xFB, 0xF0, 0x00}; + + const std::string actual = + ExtractAppKey(reinterpret_cast<const char*>(magic)); + + // Check that the '+' and '/' characters were replaced with '-' and '_'. + EXPECT_EQ("\xFB\xF0", actual); + EXPECT_EQ("-_A=", EncodeAppKey(actual)); +} + } // namespace loader_app } // namespace starboard
diff --git a/src/starboard/sabi/README.md b/src/starboard/sabi/README.md new file mode 100644 index 0000000..7becc8b --- /dev/null +++ b/src/starboard/sabi/README.md
@@ -0,0 +1,4 @@ +**IMPORTANT:** None of the files in this directory or any subdirectory should be +modified. These files are used to define the Starboard ABI for a particular +platform, and any changes can cause the binaries generated using those changes +to no longer be compatible with any Evergreen binaries.
diff --git a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h index c1cf847..fce1821 100644 --- a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h +++ b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
@@ -18,6 +18,7 @@ #include "starboard/audio_sink.h" #include <functional> +#include <string> #include "starboard/configuration.h" #include "starboard/shared/internal_only.h" @@ -27,8 +28,9 @@ // When |capability_changed| is true, it hints that the error is caused by a // a transisent capability on the platform. The app should retry playback to // recover from the error. - // TODO: Allow to pass an error message. - typedef void (*ErrorFunc)(bool capability_changed, void* context); + typedef void (*ErrorFunc)(bool capability_changed, + const std::string& error_message, + void* context); #endif // SB_API_VERSION >= 12 typedef std::function<
diff --git a/src/starboard/shared/starboard/media/avc_util.cc b/src/starboard/shared/starboard/media/avc_util.cc index d5b9620..15b5ba9 100644 --- a/src/starboard/shared/starboard/media/avc_util.cc +++ b/src/starboard/shared/starboard/media/avc_util.cc
@@ -117,10 +117,12 @@ } parameter_sets_.push_back(nalu); combined_size_in_bytes_ += nalu.size(); - } else { + } else if (nalu[kAnnexBHeaderSizeInBytes] == kIdrStartCode) { break; } } + SB_LOG_IF(ERROR, first_sps_index_ == -1 || first_pps_index_ == -1) + << "AVC parameter set NALUs not found."; } AvcParameterSets AvcParameterSets::ConvertTo(Format new_format) const {
diff --git a/src/starboard/shared/starboard/media/avc_util.h b/src/starboard/shared/starboard/media/avc_util.h index d603aac..c9d7931 100644 --- a/src/starboard/shared/starboard/media/avc_util.h +++ b/src/starboard/shared/starboard/media/avc_util.h
@@ -45,6 +45,7 @@ }; static const size_t kAnnexBHeaderSizeInBytes = 4; + static const uint8_t kIdrStartCode = 0x65; static const uint8_t kSpsStartCode = 0x67; static const uint8_t kPpsStartCode = 0x68;
diff --git a/src/starboard/shared/starboard/media/avc_util_test.cc b/src/starboard/shared/starboard/media/avc_util_test.cc index 09345cb..a6b954e 100644 --- a/src/starboard/shared/starboard/media/avc_util_test.cc +++ b/src/starboard/shared/starboard/media/avc_util_test.cc
@@ -33,7 +33,7 @@ const auto kAnnexBHeaderSizeInBytes = AvcParameterSets::kAnnexBHeaderSizeInBytes; const uint8_t kSliceStartCode = 0x61; -const uint8_t kIdrStartCode = 0x65; +const uint8_t kIdrStartCode = AvcParameterSets::kIdrStartCode; const uint8_t kSpsStartCode = AvcParameterSets::kSpsStartCode; const uint8_t kPpsStartCode = AvcParameterSets::kPpsStartCode; @@ -323,7 +323,7 @@ } } -TEST(AvcParameterSetsTest, SpsAfterPayload) { +TEST(AvcParameterSetsTest, SpsAfterIdr) { auto parameter_sets_in_annex_b = kSpsInAnnexB + kPpsInAnnexB; auto nalus_in_annex_b = parameter_sets_in_annex_b + kIdrInAnnexB + kSpsInAnnexB; @@ -334,7 +334,7 @@ HasEqualParameterSets(parameter_sets_in_annex_b, nalus_in_annex_b)); } -TEST(AvcParameterSetsTest, PpsAfterPayload) { +TEST(AvcParameterSetsTest, PpsAfterIdr) { auto parameter_sets_in_annex_b = kSpsInAnnexB + kPpsInAnnexB; auto nalus_in_annex_b = parameter_sets_in_annex_b + kIdrInAnnexB + kPpsInAnnexB; @@ -345,7 +345,7 @@ HasEqualParameterSets(parameter_sets_in_annex_b, nalus_in_annex_b)); } -TEST(AvcParameterSetsTest, SpsAndPpsAfterPayloadWithoutSpsAndPps) { +TEST(AvcParameterSetsTest, SpsAndPpsAfterIdrWithoutSpsAndPps) { auto parameter_sets_in_annex_b = kSpsInAnnexB + kPpsInAnnexB; auto nalus_in_annex_b = kIdrInAnnexB + kPpsInAnnexB + parameter_sets_in_annex_b;
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.cc index 1e3bb83..12ce929 100644 --- a/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.cc +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.cc
@@ -15,6 +15,7 @@ #include "starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h" #include <algorithm> +#include <string> #include "starboard/memory.h" #include "starboard/shared/starboard/media/media_util.h" @@ -449,15 +450,16 @@ } } -void AudioRendererImpl::OnError(bool capability_changed) { +void AudioRendererImpl::OnError(bool capability_changed, + const std::string& error_message) { SB_DCHECK(error_cb_); if (capability_changed) { - error_cb_(kSbPlayerErrorCapabilityChanged, "failed to start audio sink"); + error_cb_(kSbPlayerErrorCapabilityChanged, error_message); } else { // Send |kSbPlayerErrorDecode| on fatal audio sink error. The error code // will be mapped into MediaError eventually, and there is no corresponding // error code in MediaError for audio sink error anyway. - error_cb_(kSbPlayerErrorDecode, "failed to start audio sink"); + error_cb_(kSbPlayerErrorDecode, error_message); } }
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h b/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h index 90e9a1b..1129b9d 100644 --- a/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h
@@ -16,6 +16,7 @@ #define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_INTERNAL_IMPL_H_ #include <functional> +#include <string> #include <vector> #include "starboard/atomic.h" @@ -138,7 +139,8 @@ bool* is_playing, bool* is_eos_reached) override; void ConsumeFrames(int frames_consumed, SbTime frames_consumed_at) override; - void OnError(bool capability_changed) override; + void OnError(bool capability_changed, + const std::string& error_message) override; void UpdateVariablesOnSinkThread_Locked(SbTime system_time_on_consume_frames);
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_sink.h b/src/starboard/shared/starboard/player/filter/audio_renderer_sink.h index 7142d0a..1859e7c 100644 --- a/src/starboard/shared/starboard/player/filter/audio_renderer_sink.h +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_sink.h
@@ -15,6 +15,8 @@ #ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_SINK_H_ #define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_SINK_H_ +#include <string> + #include "starboard/audio_sink.h" #include "starboard/shared/internal_only.h" #include "starboard/time.h" @@ -40,7 +42,8 @@ // When |capability_changed| is true, it hints that the error is caused by a // a transisent capability on the platform. The app should retry playback // to recover from the error. - virtual void OnError(bool capability_changed) = 0; + virtual void OnError(bool capability_changed, + const std::string& error_message) = 0; protected: ~RenderCallback() {}
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.cc index 61a48db..4970a5b 100644 --- a/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.cc +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.cc
@@ -14,6 +14,8 @@ #include "starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h" +#include <string> + #include "starboard/common/log.h" #include "starboard/configuration_constants.h" #include "starboard/shared/starboard/thread_checker.h" @@ -187,13 +189,16 @@ } // static -void AudioRendererSinkImpl::ErrorFunc(bool capability_changed, void* context) { +void AudioRendererSinkImpl::ErrorFunc(bool capability_changed, + const std::string& error_message, + void* context) { AudioRendererSinkImpl* audio_renderer_sink = static_cast<AudioRendererSinkImpl*>(context); SB_DCHECK(audio_renderer_sink); SB_DCHECK(audio_renderer_sink->render_callback_); - audio_renderer_sink->render_callback_->OnError(capability_changed); + audio_renderer_sink->render_callback_->OnError(capability_changed, + error_message); } } // namespace filter
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h b/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h index 239180f..319cb32 100644 --- a/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h
@@ -16,6 +16,7 @@ #define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_SINK_IMPL_H_ #include <functional> +#include <string> #include "starboard/audio_sink.h" #include "starboard/shared/internal_only.h" @@ -57,8 +58,8 @@ SbMediaAudioSampleType audio_sample_type) const override; bool IsAudioFrameStorageTypeSupported( SbMediaAudioFrameStorageType audio_frame_storage_type) const override; - int GetNearestSupportedSampleFrequency(int sampling_frequency_hz) const - override; + int GetNearestSupportedSampleFrequency( + int sampling_frequency_hz) const override; bool HasStarted() const override; void Start(SbTime media_start_time, @@ -83,7 +84,9 @@ static void ConsumeFramesFunc(int frames_consumed, SbTime frames_consumed_at, void* context); - static void ErrorFunc(bool capability_changed, void* context); + static void ErrorFunc(bool capability_changed, + const std::string& error_message, + void* context); ThreadChecker thread_checker_; const CreateAudioSinkFunc create_audio_sink_func_;
diff --git a/src/starboard/shared/starboard/player/player_create.cc b/src/starboard/shared/starboard/player/player_create.cc index 5e7bb68..9a55ac8 100644 --- a/src/starboard/shared/starboard/player/player_create.cc +++ b/src/starboard/shared/starboard/player/player_create.cc
@@ -88,7 +88,7 @@ &creation_param->audio_sample_info; const auto output_mode = creation_param->output_mode; -#else // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT) +#else // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT) SbPlayer SbPlayerCreate(SbWindow window, SbMediaVideoCodec video_codec, @@ -169,8 +169,11 @@ if (audio_sample_info && audio_sample_info->number_of_channels > SbAudioSinkGetMaxChannels()) { - SB_LOG(ERROR) << "audio_sample_info->number_of_channels exceeds the maximum" - << " number of audio channels supported by this platform."; + SB_LOG(ERROR) << "audio_sample_info->number_of_channels (" + << audio_sample_info->number_of_channels + << ") exceeds the maximum" + << " number of audio channels supported by this platform (" + << SbAudioSinkGetMaxChannels() << ")."; return kSbPlayerInvalid; }
diff --git a/src/starboard/stub/javascript_cache.cc b/src/starboard/stub/javascript_cache.cc new file mode 100644 index 0000000..82e8bdd --- /dev/null +++ b/src/starboard/stub/javascript_cache.cc
@@ -0,0 +1,60 @@ +// Copyright 2021 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/stub/javascript_cache.h" + +#include <functional> +#include <string> + +#include "cobalt/extension/javascript_cache.h" +#include "starboard/common/log.h" +#include "starboard/file.h" + +namespace starboard { +namespace stub { + +namespace { + +bool GetCachedScript(uint32_t key, + int source_length, + const uint8_t** cache_data_out, + int* cache_data_length) { + return false; +} + +void ReleaseCachedScriptData(const uint8_t* cache_data) {} + +bool StoreCachedScript(uint32_t key, + int source_length, + const uint8_t* cache_data, + int cache_data_length) { + return false; +} + +const CobaltExtensionJavaScriptCacheApi kJavaScriptCacheApi = { + kCobaltExtensionJavaScriptCacheName, + 1, // API version that's implemented. + &GetCachedScript, + &ReleaseCachedScriptData, + &StoreCachedScript, +}; + +} // namespace + +const void* GetJavaScriptCacheApi() { + return &kJavaScriptCacheApi; +} + +} // namespace stub +} // namespace starboard
diff --git a/src/starboard/stub/javascript_cache.h b/src/starboard/stub/javascript_cache.h new file mode 100644 index 0000000..e0e04d6 --- /dev/null +++ b/src/starboard/stub/javascript_cache.h
@@ -0,0 +1,26 @@ +// Copyright 2021 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_STUB_JAVASCRIPT_CACHE_H_ +#define STARBOARD_STUB_JAVASCRIPT_CACHE_H_ + +namespace starboard { +namespace stub { + +const void* GetJavaScriptCacheApi(); + +} // namespace stub +} // namespace starboard + +#endif // STARBOARD_STUB_JAVASCRIPT_CACHE_H_
diff --git a/src/starboard/stub/starboard_platform.gyp b/src/starboard/stub/starboard_platform.gyp index 717849d..62cceef 100644 --- a/src/starboard/stub/starboard_platform.gyp +++ b/src/starboard/stub/starboard_platform.gyp
@@ -29,6 +29,8 @@ 'configuration_constants.cc', 'font.cc', 'font.h', + 'javascript_cache.h', + 'javascript_cache.cc', 'main.cc', 'system_get_extensions.cc', 'thread_types_public.h',
diff --git a/src/starboard/stub/system_get_extensions.cc b/src/starboard/stub/system_get_extensions.cc index 22e8777..f3f9783 100644 --- a/src/starboard/stub/system_get_extensions.cc +++ b/src/starboard/stub/system_get_extensions.cc
@@ -16,9 +16,11 @@ #include "cobalt/extension/configuration.h" #include "cobalt/extension/font.h" +#include "cobalt/extension/javascript_cache.h" #include "starboard/common/string.h" #include "starboard/stub/configuration.h" #include "starboard/stub/font.h" +#include "starboard/stub/javascript_cache.h" const void* SbSystemGetExtension(const char* name) { if (SbStringCompareAll(name, kCobaltExtensionConfigurationName) == 0) { @@ -27,5 +29,8 @@ if (SbStringCompareAll(name, kCobaltExtensionFontName) == 0) { return starboard::stub::GetFontApi(); } + if (SbStringCompareAll(name, kCobaltExtensionJavaScriptCacheName) == 0) { + return starboard::stub::GetJavaScriptCacheApi(); + } return NULL; }
diff --git a/src/third_party/mozjs-45/js/src/gc/StoreBuffer.h b/src/third_party/mozjs-45/js/src/gc/StoreBuffer.h index c343d6b..8ff3a80 100644 --- a/src/third_party/mozjs-45/js/src/gc/StoreBuffer.h +++ b/src/third_party/mozjs-45/js/src/gc/StoreBuffer.h
@@ -296,7 +296,7 @@ explicit operator bool() const { return objectAndKind_ != 0; } - typedef struct { + typedef struct Hasher { typedef SlotsEdge Lookup; static HashNumber hash(const Lookup& l) { return l.objectAndKind_ ^ l.start_ ^ l.count_; } static bool match(const SlotsEdge& k, const Lookup& l) { return k == l; }
diff --git a/src/tools/gyp/pylib/gyp/win_tool.py b/src/tools/gyp/pylib/gyp/win_tool.py index 6b950b7..c79ffd7 100755 --- a/src/tools/gyp/pylib/gyp/win_tool.py +++ b/src/tools/gyp/pylib/gyp/win_tool.py
@@ -69,6 +69,14 @@ shutil.copytree(source, dest, ignore=shutil.ignore_patterns(r'.git')) else: shutil.copy2(source, dest) + # Hack to make target look 2 seconds newer, to prevent ninja erroneously re-trigger + # copy rules on nanosecond differences. Risk of getting stale content files due to this + # is almost non-existent + OFFSET_SECONDS = 2 + stat = os.stat(source) + mtime = stat.st_mtime + OFFSET_SECONDS + atime = stat.st_atime + OFFSET_SECONDS + os.utime(dest, (atime, mtime)) if platform.system() == 'Windows': def ExecLinkWrapper(self, arch, *args):