From b6c18827cda7cfef61859940cb03dbd022ee40ba Mon Sep 17 00:00:00 2001 From: Xiaoming Shi Date: Tue, 28 Jul 2026 17:54:49 +0000 Subject: [PATCH 1/3] Cherry pick PR #11549: starboard: Fix integer overflow in AudioTrack sink Refer to original PR: #11549 Update accumulated_written_frames, last_playback_head_position, and playback_head_position to use 64-bit integers in AudioTrackAudioSink. At a 48kHz sampling rate, 32-bit signed integers overflow after approximately 12.4 hours of continuous playback, which can cause incorrect playback head reporting and audio sync issues. Also update GetFramesDurationUs to accept int64_t to maintain type consistency and prevent narrowing. Note that this doesn't resolve all integer size issues across the audio pipeline as the Java AudioTrackBridge is still tracking the playback position using 32 bits integer, which will be resolve in b/539224216. Bug: 539224216 (cherry picked from commit 97d99449fb8ce259e371b4959135f6780934555d) --- .github/AUTOROLL | 2 +- starboard/android/shared/audio_track_audio_sink_type.cc | 8 ++++---- starboard/android/shared/audio_track_audio_sink_type.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/AUTOROLL b/.github/AUTOROLL index 8e9439e0e5d2..632231bab5c9 100644 --- a/.github/AUTOROLL +++ b/.github/AUTOROLL @@ -1 +1 @@ -5f4930b006c0b13e4610d565da281c05a08a42cf +97d99449fb8ce259e371b4959135f6780934555d diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc index d9d180478b44..5f62b3b76d66 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.cc +++ b/starboard/android/shared/audio_track_audio_sink_type.cc @@ -261,10 +261,10 @@ void AudioTrackAudioSink::AudioThreadFunc() { SB_LOG(INFO) << "AudioTrackAudioSink thread started."; - int accumulated_written_frames = 0; + int64_t accumulated_written_frames = 0; int64_t last_playback_head_event_at = -1; // microseconds - int last_playback_head_position = 0; + int64_t last_playback_head_position = 0; bool release_frames_after_audio_starts = features::FeatureList::IsEnabled( features::kReleaseVideoFramesAfterAudioStarts); @@ -284,7 +284,7 @@ void AudioTrackAudioSink::AudioThreadFunc() { continue; } - int playback_head_position = 0; + int64_t playback_head_position = 0; int64_t frames_consumed_at = 0; if (audio_track_->GetAndResetHasAudioDeviceChanged()) { SB_LOG(INFO) << "Audio device changed, raising a capability changed " @@ -515,7 +515,7 @@ void AudioTrackAudioSink::ReportError(bool capability_changed, } } -int64_t AudioTrackAudioSink::GetFramesDurationUs(int frames) const { +int64_t AudioTrackAudioSink::GetFramesDurationUs(int64_t frames) const { return frames * 1'000'000LL / sampling_frequency_hz_; } diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h index 5bfb2ccfe692..2344278fef83 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.h +++ b/starboard/android/shared/audio_track_audio_sink_type.h @@ -169,7 +169,7 @@ class AudioTrackAudioSink : public SbAudioSinkImpl { void ReportError(bool capability_changed, const std::string& error_message); - int64_t GetFramesDurationUs(int frames) const; + int64_t GetFramesDurationUs(int64_t frames) const; const raw_ptr type_; const int channels_; From ea183c498495b58926af9ab1bcdddeabeb7ec933 Mon Sep 17 00:00:00 2001 From: Xiaoming Shi Date: Tue, 28 Jul 2026 18:44:48 +0000 Subject: [PATCH 2/3] Cherry pick PR #11550: media: Add page alignment and lockless decommit Refer to original PR: #11550 Add support for a page alignment flag in DecoderBufferAllocator to control OS block allocation alignment. This allows for more granular memory management when using the configurable decommit strategy. Implement a lockless optimization during suspend by checking an atomic flag before acquiring the allocator mutex. This prevents unnecessary blocking of the UI thread when decommit on suspend is not enabled. Additionally, simplify pointer arithmetic in StarboardMemoryAllocator using uint8_t pointers and update H5vcc settings to expose the new alignment configuration bit. Bug: 454441375 (cherry picked from commit 8ac74bddcde16727852bcac7b28eedc3a526aadd) --- .github/AUTOROLL | 2 +- ...al_fit_decoder_buffer_allocator_strategy.h | 9 ++-- media/starboard/decoder_buffer_allocator.cc | 23 ++++++--- media/starboard/decoder_buffer_allocator.h | 5 +- ..._pool_decoder_buffer_allocator_strategy.cc | 3 +- media/starboard/starboard_memory_allocator.h | 28 ++++++----- .../starboard_memory_allocator_test.cc | 8 ++-- .../cobalt/h5vcc_settings/h_5_vcc_settings.cc | 47 ++++++++++++------- 8 files changed, 80 insertions(+), 45 deletions(-) diff --git a/.github/AUTOROLL b/.github/AUTOROLL index 632231bab5c9..78687765b7aa 100644 --- a/.github/AUTOROLL +++ b/.github/AUTOROLL @@ -1 +1 @@ -97d99449fb8ce259e371b4959135f6780934555d +8ac74bddcde16727852bcac7b28eedc3a526aadd diff --git a/media/starboard/bidirectional_fit_decoder_buffer_allocator_strategy.h b/media/starboard/bidirectional_fit_decoder_buffer_allocator_strategy.h index 7678881bea44..9b45f38147ce 100644 --- a/media/starboard/bidirectional_fit_decoder_buffer_allocator_strategy.h +++ b/media/starboard/bidirectional_fit_decoder_buffer_allocator_strategy.h @@ -30,7 +30,8 @@ class BidirectionalFitDecoderBufferAllocatorStrategy public: BidirectionalFitDecoderBufferAllocatorStrategy(size_t initial_capacity, size_t allocation_increment) - : fallback_allocator_(/*enable_decommit_on_idle=*/false), + : fallback_allocator_(/*enable_decommit_on_idle=*/false, + /*enable_page_alignment=*/false), bidirectional_fit_allocator_(&fallback_allocator_, initial_capacity, kSmallAllocationThreshold, @@ -50,8 +51,10 @@ class BidirectionalFitDecoderBufferAllocatorStrategy bool enable_decommit_on_idle, size_t retain_blocks, size_t conservative_decommit_blocks, - bool aggressive_decommit_on_suspend = false) - : fallback_allocator_(enable_decommit_on_idle), + bool aggressive_decommit_on_suspend = false, + bool allocate_with_page_alignment = true) + : fallback_allocator_(enable_decommit_on_idle, + allocate_with_page_alignment), bidirectional_fit_allocator_(&fallback_allocator_, initial_capacity, kSmallAllocationThreshold, diff --git a/media/starboard/decoder_buffer_allocator.cc b/media/starboard/decoder_buffer_allocator.cc index 6ef18a033b02..2b7cb6c2848c 100644 --- a/media/starboard/decoder_buffer_allocator.cc +++ b/media/starboard/decoder_buffer_allocator.cc @@ -107,6 +107,9 @@ void DecoderBufferAllocator::ReleaseIdleMemory() { } void DecoderBufferAllocator::DecommitAllDecommitableBlocks() { + if (!decommit_on_suspend_enabled_.load(std::memory_order_relaxed)) { + return; + } base::AutoLock scoped_lock(mutex_); if (strategy_) { strategy_->DecommitAllDecommitableBlocks(); @@ -238,13 +241,19 @@ void DecoderBufferAllocator::EnableConfigurableDecommitStrategy( int block_size, int retain_blocks, int conservative_decommit_blocks, - bool aggressive_decommit_on_suspend) { + bool aggressive_decommit_on_suspend, + bool allocate_with_page_alignment) { auto* allocator = Get(); CHECK(allocator); + if (aggressive_decommit_on_suspend) { + // This optimization is enable only, and isn't expected to be disabled. + allocator->decommit_on_suspend_enabled_.store(true, + std::memory_order_relaxed); + } allocator->UpdateAllocatorStrategy(base::BindRepeating( [](int block_size, int retain_blocks, int conservative_decommit_blocks, - bool aggressive_decommit_on_suspend, int initial_capacity, - int allocation_unit) + bool aggressive_decommit_on_suspend, bool allocate_with_page_alignment, + int initial_capacity, int allocation_unit) -> std::unique_ptr { LOG(INFO) << "DecoderBufferAllocator is using " @@ -256,15 +265,17 @@ void DecoderBufferAllocator::EnableConfigurableDecommitStrategy( << "retain_blocks: " << retain_blocks << ", " << "conservative_decommit_blocks: " << conservative_decommit_blocks << ", aggressive_decommit_on_suspend: " - << ToString(aggressive_decommit_on_suspend); + << ToString(aggressive_decommit_on_suspend) + << ", allocate_with_page_alignment: " + << ToString(allocate_with_page_alignment); return std::make_unique( block_size, block_size, /*enable_decommit_on_idle=*/true, retain_blocks, conservative_decommit_blocks, - aggressive_decommit_on_suspend); + aggressive_decommit_on_suspend, allocate_with_page_alignment); }, block_size, retain_blocks, conservative_decommit_blocks, - aggressive_decommit_on_suspend)); + aggressive_decommit_on_suspend, allocate_with_page_alignment)); } // static diff --git a/media/starboard/decoder_buffer_allocator.h b/media/starboard/decoder_buffer_allocator.h index d28a7edb0ffa..1a3c2b3ef288 100644 --- a/media/starboard/decoder_buffer_allocator.h +++ b/media/starboard/decoder_buffer_allocator.h @@ -15,6 +15,7 @@ #ifndef MEDIA_STARBOARD_DECODER_BUFFER_ALLOCATOR_H_ #define MEDIA_STARBOARD_DECODER_BUFFER_ALLOCATOR_H_ +#include #include #include @@ -86,7 +87,8 @@ class DecoderBufferAllocator : public DecoderBuffer::Allocator, int block_size, int retain_blocks, int conservative_decommit_blocks, - bool aggressive_decommit_on_suspend); + bool aggressive_decommit_on_suspend, + bool allocate_with_page_alignment); static void EnableMediaBufferPoolStrategy(); static void EnableReleaseIdleMemory(); @@ -118,6 +120,7 @@ class DecoderBufferAllocator : public DecoderBuffer::Allocator, int pending_allocation_operations_count_ GUARDED_BY(mutex_) = 0; int allocation_operation_index_ GUARDED_BY(mutex_) = 0; #endif // !BUILDFLAG(COBALT_IS_RELEASE_BUILD) + std::atomic decommit_on_suspend_enabled_{false}; }; } // namespace media diff --git a/media/starboard/media_buffer_pool_decoder_buffer_allocator_strategy.cc b/media/starboard/media_buffer_pool_decoder_buffer_allocator_strategy.cc index 147bd43edf5b..350602a70b65 100644 --- a/media/starboard/media_buffer_pool_decoder_buffer_allocator_strategy.cc +++ b/media/starboard/media_buffer_pool_decoder_buffer_allocator_strategy.cc @@ -29,7 +29,8 @@ MediaBufferPoolDecoderBufferAllocatorStrategy:: : media_buffer_pool_(media_buffer_pool), video_buffer_initial_capacity_(video_buffer_initial_capacity), video_buffer_allocation_increment_(video_buffer_allocation_increment), - audio_fallback_allocator_(/*enable_decommit_on_idle=*/false), + audio_fallback_allocator_(/*enable_decommit_on_idle=*/false, + /*enable_page_alignment=*/false), audio_allocator_( &audio_fallback_allocator_, // Pre-allocate sufficient capacity for audio buffers to diff --git a/media/starboard/starboard_memory_allocator.h b/media/starboard/starboard_memory_allocator.h index 1442bb40e919..af29c10bc4e7 100644 --- a/media/starboard/starboard_memory_allocator.h +++ b/media/starboard/starboard_memory_allocator.h @@ -41,11 +41,14 @@ using starboard::AlignUp; // using posix_memalign() and free(). class StarboardMemoryAllocator : public starboard::Allocator { public: - explicit StarboardMemoryAllocator(bool enable_decommit) + StarboardMemoryAllocator(bool enable_decommit, bool enable_page_alignment) : enable_decommit_(enable_decommit), + enable_page_alignment_(enable_page_alignment), page_size_(static_cast(sysconf(_SC_PAGESIZE))) { LOG(INFO) << "StarboardMemoryAllocator: decommit is " - << (enable_decommit_ ? "enabled" : "disabled") << "."; + << (enable_decommit_ ? "enabled" : "disabled") + << ", page alignment is " + << (enable_page_alignment_ ? "enabled" : "disabled") << "."; } void* Allocate(size_t size) override { @@ -57,7 +60,7 @@ class StarboardMemoryAllocator : public starboard::Allocator { } void* AllocateForAlignment(size_t* size, size_t alignment) override { - if (enable_decommit_) { + if (enable_page_alignment_) { alignment = std::max(alignment, page_size_); *size = AlignUp(*size, page_size_); } @@ -69,23 +72,23 @@ class StarboardMemoryAllocator : public starboard::Allocator { void Decommit(void* memory, size_t size, bool conservative) override { #if !BUILDFLAG(COBALT_IS_RELEASE_BUILD) - uintptr_t start = reinterpret_cast(memory); CHECK(enable_decommit_); - CHECK_EQ(start % page_size_, 0U); - CHECK_EQ(size % page_size_, 0U); #endif // !BUILDFLAG(COBALT_IS_RELEASE_BUILD) - // Align down the size to prevent decommitting past the end just in case - // the user explicitly passes an altered, unaligned size block. - size_t aligned_size = AlignDown(size, page_size_); + uint8_t* start = static_cast(memory); + uint8_t* end = start + size; + uint8_t* aligned_start = AlignUp(start, page_size_); + uint8_t* aligned_end = AlignDown(end, page_size_); - if (aligned_size > 0) { + if (aligned_start < aligned_end) { + size_t aligned_size = aligned_end - aligned_start; // MADV_FREE is not supported on all kernel versions/configurations. If it // fails, fallback to MADV_DONTNEED to ensure memory is still decommitted. - if (conservative && madvise(memory, aligned_size, MADV_FREE) == 0) { + if (conservative && + madvise(aligned_start, aligned_size, MADV_FREE) == 0) { return; } - madvise(memory, aligned_size, MADV_DONTNEED); + madvise(aligned_start, aligned_size, MADV_DONTNEED); } } @@ -102,6 +105,7 @@ class StarboardMemoryAllocator : public starboard::Allocator { private: const bool enable_decommit_; + const bool enable_page_alignment_; const size_t page_size_; }; diff --git a/media/starboard/starboard_memory_allocator_test.cc b/media/starboard/starboard_memory_allocator_test.cc index 29ee21b9aea3..add5e6de4714 100644 --- a/media/starboard/starboard_memory_allocator_test.cc +++ b/media/starboard/starboard_memory_allocator_test.cc @@ -24,7 +24,7 @@ namespace { class StarboardMemoryAllocatorTest : public ::testing::TestWithParam { protected: - StarboardMemoryAllocatorTest() : allocator_(GetParam()) {} + StarboardMemoryAllocatorTest() : allocator_(GetParam(), GetParam()) {} StarboardMemoryAllocator allocator_; }; @@ -78,7 +78,8 @@ INSTANTIATE_TEST_SUITE_P(EnableDecommit, TEST(StarboardMemoryAllocatorDecommitSpecificTest, AllocateForAlignmentUpdatesSizeWhenDecommitEnabled) { - StarboardMemoryAllocator allocator(/*enable_decommit=*/true); + StarboardMemoryAllocator allocator(/*enable_decommit=*/true, + /*enable_page_alignment=*/true); const size_t page_size = sysconf(_SC_PAGESIZE); const size_t initial_size = page_size / 2; // A size smaller than page size @@ -95,7 +96,8 @@ TEST(StarboardMemoryAllocatorDecommitSpecificTest, TEST(StarboardMemoryAllocatorDecommitSpecificTest, AllocateForAlignmentDoesNotUpdateSizeWhenDecommitDisabled) { - StarboardMemoryAllocator allocator(/*enable_decommit=*/false); + StarboardMemoryAllocator allocator(/*enable_decommit=*/false, + /*enable_page_alignment=*/false); const size_t page_size = sysconf(_SC_PAGESIZE); const size_t initial_size = page_size / 2; diff --git a/third_party/blink/renderer/modules/cobalt/h5vcc_settings/h_5_vcc_settings.cc b/third_party/blink/renderer/modules/cobalt/h5vcc_settings/h_5_vcc_settings.cc index 6bd719736f83..71d98bebde47 100644 --- a/third_party/blink/renderer/modules/cobalt/h5vcc_settings/h_5_vcc_settings.cc +++ b/third_party/blink/renderer/modules/cobalt/h5vcc_settings/h_5_vcc_settings.cc @@ -149,23 +149,27 @@ ScriptPromise H5vccSettings::set( if (name == "DecoderBuffer.EnableConfigurableDecommitStrategy") { // The value is a 32-bit integer encoding four parts: - // aggressive_decommit_on_suspend (flag), block_size (in MB), retain_blocks - // (count), and conservative_decommit_blocks (count). For example, + // flags (8 bits), block_size (in MB), retain_blocks (count), and + // conservative_decommit_blocks (count). For example, // 0x01040402 sets aggressive_decommit_on_suspend to true, 4 MB block size, // 4 retain blocks, and 2 conservative decommit blocks respectively. // Passing multiple parameters encoded within a single integer is not // ideal, but it simplifies experiment setup in the current framework. // - // - aggressive_decommit_on_suspend: When non-zero, enables aggressive - // MADV_DONTNEED decommit on all idle - // blocks when app suspends/hides. - // - block_size: Specifies both the initial pool capacity and the fallback - // allocation increment. - // - retain_blocks: The first `retain_blocks` blocks of the pool are kept - // fully committed. - // - conservative_decommit_blocks: The next `conservative_decommit_blocks` - // blocks are conservatively decommitted - // (e.g. using MADV_FREE). + // - flags (8 bits): + // - aggressive_decommit_on_suspend (bit 24): When set (LSB is non-zero), + // enables aggressive MADV_DONTNEED decommit on all idle blocks when + // app suspends/hides. + // - allocate_with_page_alignment (bit 25): When set (second bit is + // non-zero), forces new OS block allocations to be page-aligned and + // rounded up to page sizes. Default is true. + // - block_size (8 bits): Specifies both the initial pool capacity and the + // fallback allocation increment. + // - retain_blocks (8 bits): Specifies the first `retain_blocks` blocks of + // the pool are kept fully committed. + // - conservative_decommit_blocks (8 bits): Specifies the next + // `conservative_decommit_blocks` blocks are conservatively decommitted + // (e.g. using MADV_FREE). // Any remaining memory beyond these two windows is aggressively decommitted // (e.g. MADV_DONTNEED). // For example, if 128 MB is allocated for the memory pool, and @@ -179,18 +183,25 @@ ScriptPromise H5vccSettings::set( // (returned // to the OS immediately, with virtual memory address range retained). // - // Note: The values passed in are assumed to be valid positive integers. A - // value of 0 will not enable this feature as it is not a positive integer. - return ProcessSettingAsPositiveInt( + // Note: A value of 0 or less will not enable this feature (handled as a + // placebo). + return ProcessSettingAs( script_state, exception_context, name, *value, [](int value) { - bool aggressive_decommit_on_suspend = ((value >> 24) & 0xFF) != 0; + if (value <= 0) { + // Explicitly allow non-positive values as placebo. + return base::ok(); + } + + bool aggressive_decommit_on_suspend = ((value >> 24) & 0x01) != 0; + bool allocate_with_page_alignment = ((value >> 24) & 0x02) != 0; int block_size_mb = (value >> 16) & 0xFF; int retain_blocks = (value >> 8) & 0xFF; int conservative_decommit_blocks = value & 0xFF; ::media::DecoderBufferAllocator::EnableConfigurableDecommitStrategy( block_size_mb * 1024 * 1024, retain_blocks, - conservative_decommit_blocks, aggressive_decommit_on_suspend); - return true; + conservative_decommit_blocks, aggressive_decommit_on_suspend, + allocate_with_page_alignment); + return base::ok(); }); } if (name == "DecoderBuffer.EnableMediaBufferPoolAllocatorStrategy") { From 42fac34a0debfabcec2a73ee92a2b14ddf3e0816 Mon Sep 17 00:00:00 2001 From: Colin Liang Date: Tue, 28 Jul 2026 11:49:34 -0700 Subject: [PATCH 3/3] Cherry pick PR #11564: Revert "android: Wait for media resources during shutdown (#11376)" Refer to original PR: #11564 This reverts commit 25cef17bcbc5fd78632de691466c450b50de6a8d. This reverts commit 42cace2a48b60466780fba649a259d02c08ee365. Issue: 534798847 (cherry picked from commit 00d5d7bd741b67377b635a199fb366a14cdce7c3) --- .github/AUTOROLL | 2 +- .../dev/cobalt/coat/BaseStarboardBridge.java | 3 +- .../dev/cobalt/media/MediaCodecBridge.java | 58 ++-- .../java/dev/cobalt/media/MediaDrmBridge.java | 57 ++-- .../java/dev/cobalt/util/JavaSwitches.java | 77 ++--- .../src/dev/cobalt/shell/ShellManager.java | 312 +++++++++--------- starboard/android/shared/BUILD.gn | 3 - .../android/shared/audio_track_bridge.cc | 6 +- .../android/shared/base_starboard_bridge.cc | 22 -- starboard/android/shared/media_drm_bridge.cc | 6 +- .../android/shared/media_resource_tracker.cc | 66 ---- .../android/shared/media_resource_tracker.h | 54 --- .../shared/media_resource_tracker_test.cc | 59 ---- starboard/android/shared/player_create.cc | 2 - starboard/android/shared/player_destroy.cc | 2 - starboard/extension/feature_config.h | 6 - 16 files changed, 240 insertions(+), 495 deletions(-) delete mode 100644 starboard/android/shared/media_resource_tracker.cc delete mode 100644 starboard/android/shared/media_resource_tracker.h delete mode 100644 starboard/android/shared/media_resource_tracker_test.cc diff --git a/.github/AUTOROLL b/.github/AUTOROLL index 78687765b7aa..49c9e21e9eb7 100644 --- a/.github/AUTOROLL +++ b/.github/AUTOROLL @@ -1 +1 @@ -8ac74bddcde16727852bcac7b28eedc3a526aadd +00d5d7bd741b67377b635a199fb366a14cdce7c3 diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/BaseStarboardBridge.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/BaseStarboardBridge.java index a67df1e2ee29..03193f39fd12 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/BaseStarboardBridge.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/BaseStarboardBridge.java @@ -208,9 +208,10 @@ protected void onActivityDestroy(Activity activity) { if (mApplicationStopped) { // 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."); + BaseStarboardBridgeJni.get().closeNativeStarboard(mNativeApp); mTtsHelper.shutdown(); mAdvertisingId.shutdown(); - BaseStarboardBridgeJni.get().closeNativeStarboard(mNativeApp); + System.exit(0); } else { Log.i(TAG, "Activity destroyed without shutdown; app suspended in background."); } diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java index 5155d1823ae8..d7527a9336b0 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java @@ -41,8 +41,8 @@ import dev.cobalt.util.SynchronizedHolder; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; +import java.util.Locale; import org.jni_zero.CalledByNative; import org.jni_zero.JNINamespace; import org.jni_zero.NativeMethods; @@ -88,17 +88,14 @@ private boolean shouldSkipVideoFrame(long presentationTimeUs, boolean isDecodeOn return true; } // |mOperatingRate| won't be 0, but to be cautious we still add a check here. - if (!mSkipVideoFramesOver60Fps - || mOperatingRate <= kMaxAcceptedOperatingRate - || mOperatingRate == 0) { + if (!mSkipVideoFramesOver60Fps || mOperatingRate <= kMaxAcceptedOperatingRate || mOperatingRate == 0) { return false; } // Deterministically downsample to 60fps by picking one frame per 1/60s interval. // Some visual jitter may occur briefly when the playback rate changes. double frameIntervalUs = 1_000_000.0 / mOperatingRate; if (Math.floor(presentationTimeUs * kMaxAcceptedOperatingRate / 1_000_000.0) - == Math.floor( - (presentationTimeUs - frameIntervalUs) * kMaxAcceptedOperatingRate / 1_000_000.0)) { + == Math.floor((presentationTimeUs - frameIntervalUs) * kMaxAcceptedOperatingRate / 1_000_000.0)) { return true; } return false; @@ -127,17 +124,17 @@ int sizeOfActiveOutputBuffers() { return mActiveOutputBuffers.get(); } - /** Wraps a {@link MediaFormat} object to expose its properties to native code */ - // Copied from Chromium's MediaCodecBridge.java - // https://source.chromium.org/chromium/chromium/src/+/main:media/base/android/java/src/org/chromium/media/MediaCodecBridge.java;l=294-350;drc=6ac17d9d1b844a695209e865137466925fa1214f - // Here are changes made. - // - Exposes formatHasCropValues() to the native layer, which needs to call it. - // - Removes the methods that Cobalt do not use (e.g. colorStandrd). - // - Add @Nonnul annotation to mFormat. since it is not null. - // - Add safety checks for width() and height() to prevent a crash. Cobalt's native code - // accesses the format immediately in the onOutputFormatChanged callback, which can be - // before the dimension keys are available. - private static class MediaFormatWrapper { + /** Wraps a {@link MediaFormat} object to expose its properties to native code */ + // Copied from Chromium's MediaCodecBridge.java + // https://source.chromium.org/chromium/chromium/src/+/main:media/base/android/java/src/org/chromium/media/MediaCodecBridge.java;l=294-350;drc=6ac17d9d1b844a695209e865137466925fa1214f + // Here are changes made. + // - Exposes formatHasCropValues() to the native layer, which needs to call it. + // - Removes the methods that Cobalt do not use (e.g. colorStandrd). + // - Add @Nonnul annotation to mFormat. since it is not null. + // - Add safety checks for width() and height() to prevent a crash. Cobalt's native code + // accesses the format immediately in the onOutputFormatChanged callback, which can be + // before the dimension keys are available. + private static class MediaFormatWrapper { @NonNull private final MediaFormat mFormat; private MediaFormatWrapper(MediaFormat format) { @@ -147,9 +144,9 @@ private MediaFormatWrapper(MediaFormat format) { @CalledByNative("MediaFormatWrapper") private boolean formatHasCropValues() { return mFormat.containsKey(KEY_CROP_RIGHT) - && mFormat.containsKey(KEY_CROP_LEFT) - && mFormat.containsKey(KEY_CROP_BOTTOM) - && mFormat.containsKey(KEY_CROP_TOP); + && mFormat.containsKey(KEY_CROP_LEFT) + && mFormat.containsKey(KEY_CROP_BOTTOM) + && mFormat.containsKey(KEY_CROP_TOP); } @CalledByNative("MediaFormatWrapper") @@ -720,12 +717,11 @@ private int flush() { mFrameRateEstimator.reset(); } if (mEnableIgnoreCallbacksDuringFlushing) { - mMainHandler.post( - () -> { - synchronized (mNativeBridgeLock) { - mIsFlushing = false; - } - }); + mMainHandler.post(() -> { + synchronized (mNativeBridgeLock) { + mIsFlushing = false; + } + }); } } return MediaCodecStatus.OK; @@ -774,8 +770,8 @@ private MediaFormatWrapper getOutputFormat() { MediaFormat format = null; try { format = mMediaCodec.get().getOutputFormat(); - // Catches `RuntimeException` to handle any undocumented exceptions. - // See http://b/445694177#comment4 for details. + // Catches `RuntimeException` to handle any undocumented exceptions. + // See http://b/445694177#comment4 for details. } catch (RuntimeException e) { Log.e(TAG, "Failed to get output format", e); return null; @@ -785,7 +781,7 @@ private MediaFormatWrapper getOutputFormat() { // If the format is null, we crash the app for dev builds to enforce the API contract. // On release builds, we log the error and return null. if (format == null) { - assert (false); + assert(false); Log.e(TAG, "MediaCodec.getOutputFormat() returned null"); return null; } @@ -1091,9 +1087,7 @@ public void onFirstTunnelFrameReady(MediaCodec codec) { } }; if (mEnableIgnoreCallbacksDuringFlushing) { - mMediaCodec - .get() - .setOnFirstTunnelFrameReadyListener(mMainHandler, mFirstTunnelFrameReadyListener); + mMediaCodec.get().setOnFirstTunnelFrameReadyListener(mMainHandler, mFirstTunnelFrameReadyListener); } else { mMediaCodec.get().setOnFirstTunnelFrameReadyListener(null, mFirstTunnelFrameReadyListener); } diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java index 46eea5d734e8..224abd963595 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java @@ -125,14 +125,11 @@ public static OperationResult operationFailed(String errorMessage) { } public static OperationResult operationFailed(String errorMessage, Throwable e) { - return operationFailed( - String.format("%s StackTrace: %s", errorMessage, Log.getStackTraceString(e))); + return operationFailed(String.format("%s StackTrace: %s", errorMessage, Log.getStackTraceString(e))); } public static OperationResult notProvisioned(Throwable e) { - return new OperationResult( - DrmOperationStatus.NOT_PROVISIONED, - String.format("Device is not provisioned. StackTrace: %s", Log.getStackTraceString(e))); + return new OperationResult(DrmOperationStatus.NOT_PROVISIONED, String.format("Device is not provisioned. StackTrace: %s", Log.getStackTraceString(e))); } @CalledByNative("OperationResult") @@ -156,8 +153,7 @@ public boolean isSuccess() { * @param nativeMediaDrmBridge The native owner of this class. */ @CalledByNative - static MediaDrmBridge create( - String keySystem, boolean enableAppProvisioning, long nativeMediaDrmBridge) { + static MediaDrmBridge create(String keySystem, boolean enableAppProvisioning, long nativeMediaDrmBridge) { UUID cryptoScheme = WIDEVINE_UUID; if (!MediaDrm.isCryptoSchemeSupported(cryptoScheme)) { return null; @@ -165,8 +161,7 @@ static MediaDrmBridge create( MediaDrmBridge mediaDrmBridge = null; try { - mediaDrmBridge = - new MediaDrmBridge(keySystem, cryptoScheme, enableAppProvisioning, nativeMediaDrmBridge); + mediaDrmBridge = new MediaDrmBridge(keySystem, cryptoScheme, enableAppProvisioning, nativeMediaDrmBridge); Log.d(TAG, "MediaDrmBridge successfully created."); } catch (UnsupportedSchemeException e) { Log.e(TAG, "Unsupported DRM scheme", e); @@ -273,8 +268,7 @@ OperationResult createSessionWithAppProvisioning(int ticket, byte[] initData, St assert mEnableAppProvisioning; if (mMediaDrm == null) { Log.e(TAG, "createSessionWithAppProvisioning() called when MediaDrm is null."); - return OperationResult.operationFailed( - "createSessionWithAppProvisioning() called when MediaDrm is null."); + return OperationResult.operationFailed("createSessionWithAppProvisioning() called when MediaDrm is null."); } OperationResult result = createMediaCryptoSessionWithAppProvisioning(); @@ -333,12 +327,14 @@ OperationResult updateSession(int ticket, byte[] sessionId, byte[] response) { Log.d(TAG, "updateSession()"); if (mMediaDrm == null) { Log.e(TAG, "updateSession() called when MediaDrm is null."); - return OperationResult.operationFailed("Null MediaDrm object when calling updateSession()."); + return OperationResult.operationFailed( + "Null MediaDrm object when calling updateSession()."); } if (!sessionExists(sessionId)) { Log.e(TAG, "updateSession tried to update a session that does not exist."); - return OperationResult.operationFailed("Failed to update session because it does not exist."); + return OperationResult.operationFailed( + "Failed to update session because it does not exist."); } try { @@ -429,8 +425,7 @@ MediaCrypto getMediaCrypto() { return mMediaCrypto; } - private MediaDrmBridge( - String keySystem, UUID schemeUUID, boolean enableAppProvisioning, long nativeMediaDrmBridge) + private MediaDrmBridge(String keySystem, UUID schemeUUID, boolean enableAppProvisioning, long nativeMediaDrmBridge) throws android.media.UnsupportedSchemeException { mSchemeUUID = schemeUUID; mMediaDrm = new MediaDrm(schemeUUID); @@ -518,15 +513,12 @@ public void onKeyStatusChange( byte[] sessionId, List keyInformation, boolean hasNewUsableKey) { - MediaDrmBridgeJni.get() - .onKeyStatusChange( - mNativeMediaDrmBridge, - sessionId, - keyInformation.stream() - .map( - keyStatus -> - new KeyStatus(keyStatus.getKeyId(), keyStatus.getStatusCode())) - .toArray(KeyStatus[]::new)); + MediaDrmBridgeJni.get().onKeyStatusChange( + mNativeMediaDrmBridge, + sessionId, + keyInformation.stream() + .map(keyStatus -> new KeyStatus(keyStatus.getKeyId(), keyStatus.getStatusCode())) + .toArray(KeyStatus[]::new)); } }, null); @@ -568,10 +560,7 @@ private void handleKeyRequiredEventWithAppProvisioning(byte[] sessionId, byte[] } ByteBuffer sessionIdByteBuffer = ByteBuffer.wrap(sessionId); if (!mSessionIds.containsKey(sessionIdByteBuffer)) { - Log.e( - TAG, - "HandleKeyRequiredEventWithAppProvisioning failed: invalid session id=" - + bytesToString(sessionId)); + Log.e(TAG, "HandleKeyRequiredEventWithAppProvisioning failed: invalid session id=" + bytesToString(sessionId)); return; } @@ -599,8 +588,8 @@ private void onSessionMessage( int requestType = request.getRequestType(); - MediaDrmBridgeJni.get() - .onSessionMessage(mNativeMediaDrmBridge, ticket, sessionId, requestType, request.getData()); + MediaDrmBridgeJni.get().onSessionMessage( + mNativeMediaDrmBridge, ticket, sessionId, requestType, request.getData()); } /** @@ -692,6 +681,8 @@ private byte[] openSession() throws android.media.NotProvisionedException { // Throw NotProvisionedException so that we can attemptProvisioning(). throw e; } catch (MediaDrmException e) { + // Other MediaDrmExceptions (e.g. ResourceBusyException) are not + // recoverable. Log.e(TAG, "Cannot open a new session", e); release(); return null; @@ -968,7 +959,11 @@ private int getStatusCode() { @NativeMethods interface Natives { void onSessionMessage( - long nativeMediaDrmBridge, int ticket, byte[] sessionId, int requestType, byte[] message); + long nativeMediaDrmBridge, + int ticket, + byte[] sessionId, + int requestType, + byte[] message); void onKeyStatusChange( long nativeMediaDrmBridge, diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/util/JavaSwitches.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/util/JavaSwitches.java index 2b3e36611eee..5f1eef52aefa 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/util/JavaSwitches.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/util/JavaSwitches.java @@ -19,7 +19,9 @@ import java.util.Map; import java.util.StringJoiner; -/** Defines the constant names for feature switches used in Kimono. */ +/** + * Defines the constant names for feature switches used in Kimono. + */ public class JavaSwitches { public static final String ENABLE_QUIC = "EnableQUIC"; public static final String DISABLE_STARTUP_GUARD = "DisableStartupGuard"; @@ -53,15 +55,13 @@ public class JavaSwitches { public static final String RECLAIM_DELAY_IN_SECONDS = "ReclaimDelayInSeconds"; /** flag to disable GPU memory buffer compositor resources. */ - public static final String DISABLE_GPU_MEMORY_BUFFER_COMPOSITOR_RESOURCES = - "DisableGpuMemoryBufferCompositorResources"; + public static final String DISABLE_GPU_MEMORY_BUFFER_COMPOSITOR_RESOURCES = "DisableGpuMemoryBufferCompositorResources"; /** flag to limit GPU image cache items */ public static final String GPU_IMAGE_CACHE_LIMIT_ITEMS = "GpuImageCacheLimitItems"; /** flag to limit GPU image cache working set budget bytes */ - public static final String DECODED_IMAGE_WORKING_SET_BUDGET_BYTES = - "DecodedImageWorkingSetBudgetBytes"; + public static final String DECODED_IMAGE_WORKING_SET_BUDGET_BYTES = "DecodedImageWorkingSetBudgetBytes"; /** flag to allow scaling clipped images in GpuImageDecodeCache */ public static final String ENABLE_SCALING_CLIPPED_IMAGES = "EnableScalingClippedImages"; @@ -73,15 +73,14 @@ public class JavaSwitches { public static final String REDUCE_ANDROID_THREAD_STACK_SIZE = "ReduceAndroidThreadStackSize"; /** flag to enable dynamic mojo pipe sizing. */ - public static final String ENABLE_COBALT_DYNAMIC_MOJO_PIPE_SIZING = - "EnableCobaltDynamicMojoPipeSizing"; + public static final String ENABLE_COBALT_DYNAMIC_MOJO_PIPE_SIZING = "EnableCobaltDynamicMojoPipeSizing"; /** flag to tune cobalt dynamic mojo pipe sizing subresource size in bytes. */ - public static final String COBALT_DYNAMIC_MOJO_PIPE_SUBRESOURCE_SIZE = - "CobaltDynamicMojoPipeSubresourceSize"; + public static final String COBALT_DYNAMIC_MOJO_PIPE_SUBRESOURCE_SIZE = "CobaltDynamicMojoPipeSubresourceSize"; /** flag to tune cobalt dynamic mojo pipe sizing media size in bytes. */ - public static final String COBALT_DYNAMIC_MOJO_PIPE_MEDIA_SIZE = "CobaltDynamicMojoPipeMediaSize"; + public static final String COBALT_DYNAMIC_MOJO_PIPE_MEDIA_SIZE = + "CobaltDynamicMojoPipeMediaSize"; /** flag to disable FontSrcLocalMatching lookup table. */ public static final String DISABLE_FONT_SRC_LOCAL_MATCHING = "DisableFontSrcLocalMatching"; @@ -93,25 +92,19 @@ public class JavaSwitches { public static final String COBALT_BYPASS_BUFFERING_BYTES_CONSUMER = "CobaltBypassBufferingBytesConsumer"; - /** flag to wait for media resources during shutdown. */ - public static final String WAIT_FOR_MEDIA_RESOURCES_ON_SHUTDOWN = - "WaitForMediaResourcesOnShutdown"; - /** flag to aggressively flush v8 bytecode after a configurable old time. */ public static final String V8_SET_BYTECODE_OLD_TIME = "V8SetBytecodeOldTime"; public static List getExtraCommandLineArgs(Map javaSwitches) { List extraCommandLineArgs = new ArrayList<>(); StringJoiner jsFlags = new StringJoiner(";"); - StringJoiner enabledFeatures = new StringJoiner(","); - StringJoiner disabledFeatures = new StringJoiner(","); if (javaSwitches.containsKey(JavaSwitches.USE_IPV4_FOR_DNS)) { - enabledFeatures.add("UseIPv4ForDNS"); + extraCommandLineArgs.add("--enable-features=UseIPv4ForDNS"); } if (javaSwitches.containsKey(JavaSwitches.LOCAL_STORAGE_DELETE_LOCK_FILE)) { - enabledFeatures.add("LocalStorageDeleteLockFile"); + extraCommandLineArgs.add("--enable-features=LocalStorageDeleteLockFile"); } if (!javaSwitches.containsKey(JavaSwitches.ENABLE_QUIC)) { @@ -137,15 +130,11 @@ public static List getExtraCommandLineArgs(Map javaSwitc } if (javaSwitches.containsKey(JavaSwitches.GPU_IMAGE_CACHE_LIMIT_ITEMS)) { - String limit = - javaSwitches.get(JavaSwitches.GPU_IMAGE_CACHE_LIMIT_ITEMS).replaceAll("[^0-9]", ""); + String limit = javaSwitches.get(JavaSwitches.GPU_IMAGE_CACHE_LIMIT_ITEMS).replaceAll("[^0-9]", ""); extraCommandLineArgs.add("--cc-image-cache-limit-items=" + limit); } if (javaSwitches.containsKey(JavaSwitches.DECODED_IMAGE_WORKING_SET_BUDGET_BYTES)) { - String budget = - javaSwitches - .get(JavaSwitches.DECODED_IMAGE_WORKING_SET_BUDGET_BYTES) - .replaceAll("[^0-9]", ""); + String budget = javaSwitches.get(JavaSwitches.DECODED_IMAGE_WORKING_SET_BUDGET_BYTES).replaceAll("[^0-9]", ""); extraCommandLineArgs.add("--decoded-image-working-set-budget-bytes=" + budget); } @@ -154,8 +143,7 @@ public static List getExtraCommandLineArgs(Map javaSwitc } StringJoiner mojoPipeParams = new StringJoiner("/"); - String subresourceSize = - javaSwitches.get(JavaSwitches.COBALT_DYNAMIC_MOJO_PIPE_SUBRESOURCE_SIZE); + String subresourceSize = javaSwitches.get(JavaSwitches.COBALT_DYNAMIC_MOJO_PIPE_SUBRESOURCE_SIZE); if (subresourceSize != null) { String size = subresourceSize.replaceAll("[^0-9]", ""); if (!size.isEmpty()) { @@ -171,34 +159,32 @@ public static List getExtraCommandLineArgs(Map javaSwitc } } - if (javaSwitches.containsKey(JavaSwitches.ENABLE_COBALT_DYNAMIC_MOJO_PIPE_SIZING) - || mojoPipeParams.length() > 0) { + if (javaSwitches.containsKey(JavaSwitches.ENABLE_COBALT_DYNAMIC_MOJO_PIPE_SIZING) || mojoPipeParams.length() > 0) { if (mojoPipeParams.length() > 0) { - enabledFeatures.add("CobaltDynamicMojoPipeSizing:" + mojoPipeParams.toString()); + extraCommandLineArgs.add("--enable-features=CobaltDynamicMojoPipeSizing:" + mojoPipeParams.toString()); } else { - enabledFeatures.add("CobaltDynamicMojoPipeSizing"); + extraCommandLineArgs.add("--enable-features=CobaltDynamicMojoPipeSizing"); } } StringJoiner featureParams = new StringJoiner("/"); if (javaSwitches.containsKey(JavaSwitches.INTEREST_AREA_SIZE_IN_PIXELS)) { - String size = - javaSwitches.get(JavaSwitches.INTEREST_AREA_SIZE_IN_PIXELS).replaceAll("[^0-9]", ""); + String size = javaSwitches.get(JavaSwitches.INTEREST_AREA_SIZE_IN_PIXELS).replaceAll("[^0-9]", ""); featureParams.add("size_in_pixels/" + size); } if (javaSwitches.containsKey(JavaSwitches.RECLAIM_DELAY_IN_SECONDS)) { - String delay = - javaSwitches.get(JavaSwitches.RECLAIM_DELAY_IN_SECONDS).replaceAll("[^0-9]", ""); + String delay = javaSwitches.get(JavaSwitches.RECLAIM_DELAY_IN_SECONDS).replaceAll("[^0-9]", ""); featureParams.add("reclaim_delay_s/" + delay); } if (featureParams.length() > 0) { - enabledFeatures.add("SmallerInterestArea:" + featureParams.toString()); + extraCommandLineArgs.add( + "--enable-features=SmallerInterestArea:" + featureParams.toString()); } if (javaSwitches.containsKey(JavaSwitches.DISABLE_FONT_SRC_LOCAL_MATCHING)) { - disabledFeatures.add("FontSrcLocalMatching"); + extraCommandLineArgs.add("--disable-features=FontSrcLocalMatching"); } if (javaSwitches.containsKey(JavaSwitches.ENABLE_OPTIMIZED_FONT_LOADING)) { @@ -214,11 +200,11 @@ public static List getExtraCommandLineArgs(Map javaSwitc } if (javaSwitches.containsKey(JavaSwitches.REDUCE_STARBOARD_THREAD_STACK_SIZE)) { - enabledFeatures.add(JavaSwitches.REDUCE_STARBOARD_THREAD_STACK_SIZE); + extraCommandLineArgs.add("--enable-features=" + JavaSwitches.REDUCE_STARBOARD_THREAD_STACK_SIZE); } if (javaSwitches.containsKey(JavaSwitches.REDUCE_ANDROID_THREAD_STACK_SIZE)) { - enabledFeatures.add(JavaSwitches.REDUCE_ANDROID_THREAD_STACK_SIZE); + extraCommandLineArgs.add("--enable-features=" + JavaSwitches.REDUCE_ANDROID_THREAD_STACK_SIZE); } if (javaSwitches.containsKey(JavaSwitches.AVOID_CC_REUSE_RESOURCE)) { @@ -226,19 +212,8 @@ public static List getExtraCommandLineArgs(Map javaSwitc } if (javaSwitches.containsKey(JavaSwitches.COBALT_BYPASS_BUFFERING_BYTES_CONSUMER)) { - enabledFeatures.add(JavaSwitches.COBALT_BYPASS_BUFFERING_BYTES_CONSUMER); - } - - if (javaSwitches.containsKey(JavaSwitches.WAIT_FOR_MEDIA_RESOURCES_ON_SHUTDOWN)) { - enabledFeatures.add(JavaSwitches.WAIT_FOR_MEDIA_RESOURCES_ON_SHUTDOWN); - } - - if (enabledFeatures.length() > 0) { - extraCommandLineArgs.add("--enable-features=" + enabledFeatures.toString()); - } - - if (disabledFeatures.length() > 0) { - extraCommandLineArgs.add("--disable-features=" + disabledFeatures.toString()); + extraCommandLineArgs.add( + "--enable-features=" + JavaSwitches.COBALT_BYPASS_BUFFERING_BYTES_CONSUMER); } return extraCommandLineArgs; diff --git a/cobalt/shell/android/java/src/dev/cobalt/shell/ShellManager.java b/cobalt/shell/android/java/src/dev/cobalt/shell/ShellManager.java index 21eeb017b43f..c8b6cd198047 100644 --- a/cobalt/shell/android/java/src/dev/cobalt/shell/ShellManager.java +++ b/cobalt/shell/android/java/src/dev/cobalt/shell/ShellManager.java @@ -15,6 +15,7 @@ package dev.cobalt.shell; import android.content.Context; +import dev.cobalt.shell.ContentViewRenderView; import org.chromium.base.ThreadUtils; import org.chromium.build.annotations.Initializer; import org.chromium.build.annotations.NullMarked; @@ -27,188 +28,189 @@ import org.jni_zero.JNINamespace; import org.jni_zero.NativeMethods; -/** Copied from org.chromium.content_shell.ShellManager. Container and generator of ShellViews. */ +/** + * Copied from org.chromium.content_shell.ShellManager. + * Container and generator of ShellViews. + */ @JNINamespace("content") @NullMarked public class ShellManager { - private static final String TAG = "cobalt"; - private WindowAndroid mWindow; - private @Nullable Shell mActiveShell; + private static final String TAG = "cobalt"; + private WindowAndroid mWindow; + private @Nullable Shell mActiveShell; - private Shell.@Nullable OnWebContentsReadyListener mNextWebContentsReadyListener; + private Shell.@Nullable OnWebContentsReadyListener mNextWebContentsReadyListener; - // The target for all content rendering. - private @Nullable ContentViewRenderView mContentViewRenderView; + // The target for all content rendering. + private @Nullable ContentViewRenderView mContentViewRenderView; - private Context mContext; + private Context mContext; - private boolean mIsActivityVisible; + private boolean mIsActivityVisible; - /** Constructor for inflating via XML. */ - public ShellManager(final Context context) { - mContext = context; - if (sNatives == null) { - sNatives = ShellManagerJni.get(); + /** + * Constructor for inflating via XML. + */ + public ShellManager(final Context context) { + mContext = context; + if (sNatives == null) { + sNatives = ShellManagerJni.get(); + } + sNatives.init(this); } - sNatives.init(this); - } - public void onActivityVisible(boolean visible) { - mIsActivityVisible = visible; - if (mActiveShell != null) { - mActiveShell.onActivityVisible(visible); + public void onActivityVisible(boolean visible) { + mIsActivityVisible = visible; + if (mActiveShell != null) { + mActiveShell.onActivityVisible(visible); + } } - } - - public Context getContext() { - return mContext; - } - - /** - * @param window The window used to generate all shells. - */ - @Initializer - public void setWindow(WindowAndroid window) { - assert window != null; - mWindow = window; - mContentViewRenderView = new ContentViewRenderView(getContext()); - mContentViewRenderView.onNativeLibraryLoaded(window); - } - - /** - * @return The window used to generate all shells. - */ - public WindowAndroid getWindow() { - return mWindow; - } - - /** Get the ContentViewRenderView. */ - public @Nullable ContentViewRenderView getContentViewRenderView() { - return mContentViewRenderView; - } - - /** - * @return The currently visible shell view or null if one is not showing. - */ - public @Nullable Shell getActiveShell() { - return mActiveShell; - } - - /** - * Creates a new shell pointing to the specified URL. - * - * @param url The URL the shell should load upon creation. - */ - public void launchShell(String url) { - // Calls the overloaded method with a null listener. - launchShell(url, /* deepLinkUrl= */ "", /* listener= */ null); - } - - /** - * Creates a new shell pointing to the specified URL. - * - * @param url The URL the shell should load upon creation. - * @param deepLinkUrl The URL from the DeepLink URL. - * @param listener The listener to be notified when WebContents is ready. - */ - public void launchShell( - String url, String deepLinkUrl, Shell.OnWebContentsReadyListener listener) { - ThreadUtils.assertOnUiThread(); - mNextWebContentsReadyListener = listener; - Shell previousShell = mActiveShell; - sNatives.launchShell(url, deepLinkUrl); - if (previousShell != null) previousShell.close(); - } - - @CalledByNative - private Object createShell(long nativeShellPtr) { - if (mContentViewRenderView == null) { - mContentViewRenderView = new ContentViewRenderView(getContext()); - mContentViewRenderView.onNativeLibraryLoaded(mWindow); + + public Context getContext() { + return mContext; } - Shell shellView = new Shell(getContext()); - shellView.initialize(nativeShellPtr, mWindow); - shellView.onActivityVisible(mIsActivityVisible); - shellView.setWebContentsReadyListener(mNextWebContentsReadyListener); - mNextWebContentsReadyListener = null; + /** + * @param window The window used to generate all shells. + */ + @Initializer + public void setWindow(WindowAndroid window) { + assert window != null; + mWindow = window; + mContentViewRenderView = new ContentViewRenderView(getContext()); + mContentViewRenderView.onNativeLibraryLoaded(window); + } - // TODO(tedchoc): Allow switching back to these inactive shells. - if (mActiveShell != null) removeShell(mActiveShell); + /** + * @return The window used to generate all shells. + */ + public WindowAndroid getWindow() { + return mWindow; + } - showShell(shellView); - return shellView; - } + /** + * Get the ContentViewRenderView. + */ + public @Nullable ContentViewRenderView getContentViewRenderView() { + return mContentViewRenderView; + } - @RequiresNonNull("mContentViewRenderView") - private void showShell(Shell shellView) { - if (mActiveShell != null) { - mActiveShell.setContentViewRenderView(null); + /** + * @return The currently visible shell view or null if one is not showing. + */ + public @Nullable Shell getActiveShell() { + return mActiveShell; } - shellView.setContentViewRenderView(mContentViewRenderView); - mActiveShell = shellView; - WebContents webContents = mActiveShell.getWebContents(); - if (webContents != null) { - mContentViewRenderView.setCurrentWebContents(webContents); - if (mIsActivityVisible) { - webContents.updateWebContentsVisibility(Visibility.VISIBLE); - } + + /** + * Creates a new shell pointing to the specified URL. + * @param url The URL the shell should load upon creation. + */ + public void launchShell(String url) { + // Calls the overloaded method with a null listener. + launchShell(url, /* deepLinkUrl= */ "", /* listener= */ null); } - } - - @CalledByNative - private void removeShell(Shell shellView) { - if (shellView == mActiveShell) mActiveShell = null; - shellView.setContentViewRenderView(null); - } - - /** - * Destroys the Shell manager and associated components. Always called at activity exit, and - * potentially called by native in cases where we need to control the timing of - * mContentViewRenderView destruction. Must handle being called twice. - */ - @CalledByNative - public void destroy() { - // Remove active shell (Currently single shell support only available). - if (mActiveShell != null) { - removeShell(mActiveShell); + + /** + * Creates a new shell pointing to the specified URL. + * @param url The URL the shell should load upon creation. + * @param deepLinkUrl The URL from the DeepLink URL. + * @param listener The listener to be notified when WebContents is ready. + */ + public void launchShell(String url, String deepLinkUrl, Shell.OnWebContentsReadyListener listener) { + ThreadUtils.assertOnUiThread(); + mNextWebContentsReadyListener = listener; + Shell previousShell = mActiveShell; + sNatives.launchShell(url, deepLinkUrl); + if (previousShell != null) previousShell.close(); } - if (mContentViewRenderView != null) { - mContentViewRenderView.destroy(); - mContentViewRenderView = null; + + @CalledByNative + private Object createShell(long nativeShellPtr) { + if (mContentViewRenderView == null) { + mContentViewRenderView = new ContentViewRenderView(getContext()); + mContentViewRenderView.onNativeLibraryLoaded(mWindow); + } + + Shell shellView = new Shell(getContext()); + shellView.initialize(nativeShellPtr, mWindow); + shellView.onActivityVisible(mIsActivityVisible); + shellView.setWebContentsReadyListener(mNextWebContentsReadyListener); + mNextWebContentsReadyListener = null; + + // TODO(tedchoc): Allow switching back to these inactive shells. + if (mActiveShell != null) removeShell(mActiveShell); + + showShell(shellView); + return shellView; } - } - private static Natives sNatives; + @RequiresNonNull("mContentViewRenderView") + private void showShell(Shell shellView) { + if (mActiveShell != null) { + mActiveShell.setContentViewRenderView(null); + } + shellView.setContentViewRenderView(mContentViewRenderView); + mActiveShell = shellView; + WebContents webContents = mActiveShell.getWebContents(); + if (webContents != null) { + mContentViewRenderView.setCurrentWebContents(webContents); + if (mIsActivityVisible) { + webContents.updateWebContentsVisibility(Visibility.VISIBLE); + } + } + } - public static void setNativesForTesting(Natives natives) { - sNatives = natives; - } + @CalledByNative + private void removeShell(Shell shellView) { + if (shellView == mActiveShell) mActiveShell = null; + shellView.setContentViewRenderView(null); + } - /** Interface for the native implementation of ShellManager. */ - @NativeMethods - public interface Natives { /** - * Creates the native ShellManager object. - * - * @param shellManagerInstance The Java instance of the ShellManager. + * Destroys the Shell manager and associated components. + * Always called at activity exit, and potentially called by native in cases where we need to + * control the timing of mContentViewRenderView destruction. Must handle being called twice. */ - void init(Object shellManagerInstance); + @CalledByNative + public void destroy() { + // Remove active shell (Currently single shell support only available). + if (mActiveShell != null) { + removeShell(mActiveShell); + } + if (mContentViewRenderView != null) { + mContentViewRenderView.destroy(); + mContentViewRenderView = null; + } + } - /** - * Creates a new shell pointing to the specified URL. - * - * @param url The URL the shell should load upon creation. - * @param deepLinkUrl The topic URL from the DeepLink URL. - */ - void launchShell(String url, String deepLinkUrl); + private static Natives sNatives; + + public static void setNativesForTesting(Natives natives) { + sNatives = natives; + } /** - * Appends the migration status parameter to the given URL. - * - * @param url The URL to append the migration status to. - * @return The updated URL. + * Interface for the native implementation of ShellManager. */ - String appendMigrationStatus(String url); - } + @NativeMethods + public interface Natives { + /** + * Creates the native ShellManager object. + * @param shellManagerInstance The Java instance of the ShellManager. + */ + void init(Object shellManagerInstance); + /** + * Creates a new shell pointing to the specified URL. + * @param url The URL the shell should load upon creation. + * @param deepLinkUrl The topic URL from the DeepLink URL. + */ + void launchShell(String url, String deepLinkUrl); + /** + * Appends the migration status parameter to the given URL. + * @param url The URL to append the migration status to. + * @return The updated URL. + */ + String appendMigrationStatus(String url); + } } diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn index d47a0ef66939..985b38a75c6d 100644 --- a/starboard/android/shared/BUILD.gn +++ b/starboard/android/shared/BUILD.gn @@ -195,8 +195,6 @@ static_library("starboard_platform") { "media_is_buffer_pool_allocate_on_demand.cc", "media_is_key_system_supported.cc", "media_is_video_supported.cc", - "media_resource_tracker.cc", - "media_resource_tracker.h", "memfd_media_buffer_pool.cc", "memfd_media_buffer_pool.h", "microphone_impl.cc", @@ -445,7 +443,6 @@ test("starboard_platform_tests") { "media_codec_decoder_test.cc", "media_codec_video_decoder_helpers_test.cc", "media_codec_video_decoder_test.cc", - "media_resource_tracker_test.cc", "memfd_media_buffer_pool_test.cc", "mock_media_capabilities_cache.cc", "mock_media_capabilities_cache.h", diff --git a/starboard/android/shared/audio_track_bridge.cc b/starboard/android/shared/audio_track_bridge.cc index 1132010c54ed..3d21b4004169 100644 --- a/starboard/android/shared/audio_track_bridge.cc +++ b/starboard/android/shared/audio_track_bridge.cc @@ -19,7 +19,6 @@ #include "starboard/android/shared/audio_output_manager.h" #include "starboard/android/shared/media_common.h" -#include "starboard/android/shared/media_resource_tracker.h" #include "starboard/android/shared/safe_jni.h" #include "starboard/audio_sink.h" #include "starboard/common/check_op.h" @@ -124,9 +123,7 @@ AudioTrackBridge::AudioTrackBridge( const DataArray& j_audio_data) : max_samples_per_write_(max_samples_per_write), j_audio_track_bridge_(j_audio_track_bridge), - j_audio_data_(j_audio_data) { - MediaResourceTracker::GetInstance()->Increment(); -} + j_audio_data_(j_audio_data) {} AudioTrackBridge::~AudioTrackBridge() { if (!j_audio_track_bridge_.is_null()) { @@ -138,7 +135,6 @@ AudioTrackBridge::~AudioTrackBridge() { AudioOutputManager::GetInstance()->DestroyAudioTrackBridge( env, audio_track_bridge); } - MediaResourceTracker::GetInstance()->Decrement(); } void AudioTrackBridge::Play() { diff --git a/starboard/android/shared/base_starboard_bridge.cc b/starboard/android/shared/base_starboard_bridge.cc index 27b43935702b..348798dfa3f8 100644 --- a/starboard/android/shared/base_starboard_bridge.cc +++ b/starboard/android/shared/base_starboard_bridge.cc @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/strings/string_number_conversions.h" @@ -21,13 +19,11 @@ #include "starboard/android/shared/application_android.h" #include "starboard/android/shared/file_internal.h" #include "starboard/android/shared/log_internal.h" -#include "starboard/android/shared/media_resource_tracker.h" #include "starboard/android/shared/starboard_bridge.h" #include "starboard/common/command_line.h" #include "starboard/common/log.h" #include "starboard/common/time.h" #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h" -#include "starboard/shared/starboard/features.h" #include "third_party/jni_zero/jni_zero.h" // TODO(b/492704919): enable on AOSP when the layering violation is fixed. @@ -110,24 +106,6 @@ void JNI_BaseStarboardBridge_CloseNativeStarboard(JNIEnv* env, jlong nativeApp) { auto* app = reinterpret_cast(nativeApp); delete app; - - if (features::FeatureList::IsEnabled( - features::kWaitForMediaResourcesOnShutdown)) { - std::thread([]() { - constexpr int kTimeoutMs = 2'000; - int remaining_count = - MediaResourceTracker::GetInstance()->WaitUntilZero(kTimeoutMs); - if (remaining_count > 0) { - SB_LOG(WARNING) << "Timed out waiting for all media resources to be " - "destroyed. Active count: " - << remaining_count; - } - exit(0); - }).detach(); - return; - } - - exit(0); } void JNI_BaseStarboardBridge_InitializePlatformAudioSink(JNIEnv* env) { diff --git a/starboard/android/shared/media_drm_bridge.cc b/starboard/android/shared/media_drm_bridge.cc index f11de4692e3e..38e990d6aa7d 100644 --- a/starboard/android/shared/media_drm_bridge.cc +++ b/starboard/android/shared/media_drm_bridge.cc @@ -22,7 +22,6 @@ #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/memory/raw_ref.h" -#include "starboard/android/shared/media_resource_tracker.h" #include "starboard/common/check_op.h" #include "starboard/common/log.h" #include "third_party/jni_zero/jni_zero.h" @@ -152,15 +151,12 @@ std::unique_ptr MediaDrmBridge::Create( MediaDrmBridge::MediaDrmBridge(PassKey, base::raw_ref host) - : host_(host) { - MediaResourceTracker::GetInstance()->Increment(); -} + : host_(host) {} MediaDrmBridge::~MediaDrmBridge() { if (!j_media_drm_bridge_.is_null()) { Java_MediaDrmBridge_destroy(AttachCurrentThread(), j_media_drm_bridge_); } - MediaResourceTracker::GetInstance()->Decrement(); } void MediaDrmBridge::CreateSession(int ticket, diff --git a/starboard/android/shared/media_resource_tracker.cc b/starboard/android/shared/media_resource_tracker.cc deleted file mode 100644 index cd0fa0663186..000000000000 --- a/starboard/android/shared/media_resource_tracker.cc +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2026 The Cobalt Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/android/shared/media_resource_tracker.h" - -#include -#include - -#include "starboard/common/log.h" - -namespace starboard { - -// static -MediaResourceTracker* MediaResourceTracker::GetInstance() { - static starboard::NoDestructor instance; - return instance.get(); -} - -void MediaResourceTracker::Increment() { - active_media_resource_count_.fetch_add(1, std::memory_order_relaxed); -} - -void MediaResourceTracker::Decrement() { - int prev_count = - active_media_resource_count_.fetch_sub(1, std::memory_order_release); - if (prev_count <= 0) { - SB_LOG(WARNING) << "MediaResourceTracker::Decrement called when active " - "resource count was " - << prev_count << "."; - return; - } - if (prev_count == 1) { - SB_LOG(INFO) << "All media resources destroyed."; - std::lock_guard lock(mutex_); - cv_.notify_all(); - } -} - -int MediaResourceTracker::WaitUntilZero(int timeout_ms) { - if (active_media_resource_count_.load(std::memory_order_acquire) <= 0) { - SB_LOG(INFO) << "Active media resource count is already 0."; - return 0; - } - std::unique_lock lock(mutex_); - cv_.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { - return active_media_resource_count_.load(std::memory_order_acquire) <= 0; - }); - int remaining = active_media_resource_count_.load(std::memory_order_acquire); - if (remaining <= 0) { - SB_LOG(INFO) << "Finished waiting for all media resources to be destroyed."; - } - return remaining; -} - -} // namespace starboard diff --git a/starboard/android/shared/media_resource_tracker.h b/starboard/android/shared/media_resource_tracker.h deleted file mode 100644 index 183878cafeef..000000000000 --- a/starboard/android/shared/media_resource_tracker.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2026 The Cobalt Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef STARBOARD_ANDROID_SHARED_MEDIA_RESOURCE_TRACKER_H_ -#define STARBOARD_ANDROID_SHARED_MEDIA_RESOURCE_TRACKER_H_ - -#include -#include -#include -#include - -#include "starboard/common/no_destructor.h" - -namespace starboard { - -// MediaResourceTracker provides thread-safe accounting of active native media -// components (SbPlayer decoders, AudioTrack audio sinks, and MediaDrm crypto -// sessions) on Android. -class MediaResourceTracker { - public: - static MediaResourceTracker* GetInstance(); - - MediaResourceTracker(const MediaResourceTracker&) = delete; - MediaResourceTracker& operator=(const MediaResourceTracker&) = delete; - - void Increment(); - void Decrement(); - int WaitUntilZero(int timeout_ms); - - private: - friend class starboard::NoDestructor; - - MediaResourceTracker() = default; - ~MediaResourceTracker() = default; - - std::mutex mutex_; - std::condition_variable cv_; - std::atomic active_media_resource_count_ = 0; -}; - -} // namespace starboard - -#endif // STARBOARD_ANDROID_SHARED_MEDIA_RESOURCE_TRACKER_H_ diff --git a/starboard/android/shared/media_resource_tracker_test.cc b/starboard/android/shared/media_resource_tracker_test.cc deleted file mode 100644 index 83b268389cdd..000000000000 --- a/starboard/android/shared/media_resource_tracker_test.cc +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2026 The Cobalt Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/android/shared/media_resource_tracker.h" - -#include -#include - -#include "testing/gtest/include/gtest/gtest.h" - -namespace starboard { -namespace { - -TEST(MediaResourceTrackerTest, InitialCountIsZero) { - auto* tracker = MediaResourceTracker::GetInstance(); - EXPECT_EQ(0, tracker->WaitUntilZero(0)); -} - -TEST(MediaResourceTrackerTest, IncrementAndDecrement) { - auto* tracker = MediaResourceTracker::GetInstance(); - tracker->Increment(); - tracker->Increment(); - - std::thread worker([tracker]() { - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - tracker->Decrement(); - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - tracker->Decrement(); - }); - - EXPECT_EQ(0, tracker->WaitUntilZero(1000)); - worker.join(); -} - -TEST(MediaResourceTrackerTest, TimeoutWhenNotZero) { - auto* tracker = MediaResourceTracker::GetInstance(); - tracker->Increment(); - - // Should timeout and return remaining count of 1 - EXPECT_EQ(1, tracker->WaitUntilZero(50)); - - // Clean up counter state - tracker->Decrement(); - EXPECT_EQ(0, tracker->WaitUntilZero(0)); -} - -} // namespace -} // namespace starboard diff --git a/starboard/android/shared/player_create.cc b/starboard/android/shared/player_create.cc index f0af4ca0ebe1..000a71e06011 100644 --- a/starboard/android/shared/player_create.cc +++ b/starboard/android/shared/player_create.cc @@ -19,7 +19,6 @@ #include #include -#include "starboard/android/shared/media_resource_tracker.h" #include "starboard/android/shared/video_max_video_input_size.h" #include "starboard/android/shared/video_surface_view.h" #include "starboard/android/shared/video_window.h" @@ -223,6 +222,5 @@ SbPlayer SbPlayerCreate(SbWindow /*window*/, // don't matter. SbPlayerSetBounds(player.get(), 0, 0, 0, 0, 0); } - starboard::MediaResourceTracker::GetInstance()->Increment(); return player.release(); } diff --git a/starboard/android/shared/player_destroy.cc b/starboard/android/shared/player_destroy.cc index f94a29cfe7c7..6409862d1d8e 100644 --- a/starboard/android/shared/player_destroy.cc +++ b/starboard/android/shared/player_destroy.cc @@ -16,7 +16,6 @@ #include "starboard/player.h" // clang-format on -#include "starboard/android/shared/media_resource_tracker.h" #include "starboard/shared/starboard/player/player_internal.h" void SbPlayerDestroy(SbPlayer player) { @@ -25,5 +24,4 @@ void SbPlayerDestroy(SbPlayer player) { } delete player; - starboard::MediaResourceTracker::GetInstance()->Decrement(); } diff --git a/starboard/extension/feature_config.h b/starboard/extension/feature_config.h index 6075aeb75c2b..703bb6db41be 100644 --- a/starboard/extension/feature_config.h +++ b/starboard/extension/feature_config.h @@ -161,12 +161,6 @@ STARBOARD_FEATURE(kUseStubVideoDecoder, "UseStubVideoDecoder", false) STARBOARD_FEATURE(kVideoDecoderDelayUsecOverride, "VideoDecoderDelayUsecOverride", false) - -// Set the following variable to true to wait for media resources (MediaCodec, -// AudioTrack, MediaDrm) during shutdown before deleting the application. -STARBOARD_FEATURE(kWaitForMediaResourcesOnShutdown, - "WaitForMediaResourcesOnShutdown", - false) // keep-sorted end #endif // BUILDFLAG(IS_ANDROID) && (SB_API_VERSION >= 17)