Skip to content

Commit

Permalink
[android] Asynchronously destroy MediaCodecBridge (#3186)
Browse files Browse the repository at this point in the history
b/331215721

Co-authored-by: Colin Liang <[email protected]>
  • Loading branch information
zhongqiliang and Colin Liang authored Jun 12, 2024
1 parent 66936f4 commit 5e8fa2d
Show file tree
Hide file tree
Showing 10 changed files with 378 additions and 1 deletion.
14 changes: 14 additions & 0 deletions cobalt/media/media_module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "base/synchronization/waitable_event.h"
#include "cobalt/media/base/format_support_query_metrics.h"
#include "media/base/mime_util.h"
#include "starboard/extension/media_settings.h"
#include "starboard/media.h"
#include "starboard/window.h"

Expand Down Expand Up @@ -218,6 +219,19 @@ bool MediaModule::SetConfiguration(const std::string& name, int32 value) {
<< (value ? "true" : "false");
return true;
}
} else if (name == "AsyncReleaseMediaCodecBridge") {
const StarboardExtensionMediaSettingsApi* media_settings_api =
static_cast<const StarboardExtensionMediaSettingsApi*>(
SbSystemGetExtension(kStarboardExtensionMediaSettingsName));
if (media_settings_api &&
strcmp(media_settings_api->name,
kStarboardExtensionMediaSettingsName) == 0 &&
media_settings_api->version >= 1) {
media_settings_api->EnableAsyncReleaseMediaCodecBridge(value);
LOG(INFO) << "Set AsyncReleaseMediaCodecBridge to "
<< (value ? "true" : "false");
return true;
}
}

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -836,12 +836,20 @@ private int flush() {
return MEDIA_CODEC_OK;
}

// It is required to reset mNativeMediaCodecBridge when the native media_codec_bridge object is
// destroyed.
@SuppressWarnings("unused")
@UsedByNative
private void stop() {
private void resetNativeMediaCodecBridge() {
synchronized (this) {
mNativeMediaCodecBridge = 0;
}
}

@SuppressWarnings("unused")
@UsedByNative
private void stop() {
resetNativeMediaCodecBridge();
try {
mMediaCodec.stop();
} catch (Exception e) {
Expand Down
4 changes: 4 additions & 0 deletions starboard/android/shared/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,8 @@ static_library("starboard_platform") {
"media_capabilities_cache.h",
"media_codec_bridge.cc",
"media_codec_bridge.h",
"media_codec_bridge_eradicator.cc",
"media_codec_bridge_eradicator.h",
"media_common.h",
"media_decoder.cc",
"media_decoder.h",
Expand All @@ -367,6 +369,8 @@ static_library("starboard_platform") {
"media_is_buffer_pool_allocate_on_demand.cc",
"media_is_supported.cc",
"media_is_video_supported.cc",
"media_settings_api.cc",
"media_settings_api.h",
"microphone_impl.cc",
"network_status_impl.cc",
"platform_info.cc",
Expand Down
20 changes: 20 additions & 0 deletions starboard/android/shared/media_codec_bridge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "starboard/android/shared/media_codec_bridge.h"

#include "starboard/android/shared/media_capabilities_cache.h"
#include "starboard/android/shared/media_codec_bridge_eradicator.h"
#include "starboard/common/string.h"

namespace starboard {
Expand Down Expand Up @@ -177,6 +178,11 @@ scoped_ptr<MediaCodecBridge> MediaCodecBridge::CreateAudioMediaCodecBridge(
return scoped_ptr<MediaCodecBridge>(NULL);
}

if (MediaCodecBridgeEradicator::GetInstance()->IsEnabled()) {
// block if the old MediaCodecBridge instances haven't been destroyed yet
MediaCodecBridgeEradicator::GetInstance()->WaitForPendingDestructions();
}

JniEnvExt* env = JniEnvExt::Get();
ScopedLocalJavaRef<jbyteArray> configuration_data;
if (audio_stream_info.codec == kSbMediaAudioCodecOpus &&
Expand Down Expand Up @@ -271,6 +277,11 @@ scoped_ptr<MediaCodecBridge> MediaCodecBridge::CreateVideoMediaCodecBridge(
return scoped_ptr<MediaCodecBridge>(NULL);
}

if (MediaCodecBridgeEradicator::GetInstance()->IsEnabled()) {
// block if the old MediaCodecBridge instances haven't been destroyed yet
MediaCodecBridgeEradicator::GetInstance()->WaitForPendingDestructions();
}

JniEnvExt* env = JniEnvExt::Get();
ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
ScopedLocalJavaRef<jstring> j_decoder_name(
Expand Down Expand Up @@ -348,6 +359,15 @@ MediaCodecBridge::~MediaCodecBridge() {
return;
}

if (MediaCodecBridgeEradicator::GetInstance()->IsEnabled()) {
if (MediaCodecBridgeEradicator::GetInstance()->Destroy(
j_media_codec_bridge_, j_reused_get_output_format_result_)) {
return;
}
SB_LOG(WARNING)
<< "MediaCodecBridge destructor fallback into none eradicator mode.";
}

JniEnvExt* env = JniEnvExt::Get();

env->CallVoidMethodOrAbort(j_media_codec_bridge_, "stop", "()V");
Expand Down
131 changes: 131 additions & 0 deletions starboard/android/shared/media_codec_bridge_eradicator.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2024 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_codec_bridge_eradicator.h"

#include <pthread.h>

#include "starboard/android/shared/jni_utils.h"
#include "starboard/common/log.h"
#include "starboard/common/once.h"

namespace starboard {
namespace android {
namespace shared {

SB_ONCE_INITIALIZE_FUNCTION(MediaCodecBridgeEradicator,
MediaCodecBridgeEradicator::GetInstance);

namespace {

struct EradicateParam {
EradicateParam(MediaCodecBridgeEradicator* eradicator,
jobject j_media_codec_bridge,
jobject j_reused_get_output_format_result)
: eradicator(eradicator),
j_media_codec_bridge(j_media_codec_bridge),
j_reused_get_output_format_result(j_reused_get_output_format_result) {}

MediaCodecBridgeEradicator* eradicator;
jobject j_media_codec_bridge;
jobject j_reused_get_output_format_result;
};

} // namespace

void MediaCodecBridgeEradicator::WaitForPendingDestructions() {
ScopedLock scoped_lock(mutex_);
while (!j_media_codec_bridge_set_.empty()) {
condition_variable_.Wait();
}
}

bool MediaCodecBridgeEradicator::Destroy(
jobject j_media_codec_bridge,
jobject j_reused_get_output_format_result) {
// Since the native media_codec_bridge object is about to be destroyed, remove
// its reference from the Java MediaCodecBridge object to prevent further
// access. Otherwise it could lead to an application crash.
// The de-reference has to happen before the java MediaCodecBridge object is
// destroyed.
JniEnvExt* env = JniEnvExt::Get();
env->CallVoidMethodOrAbort(j_media_codec_bridge,
"resetNativeMediaCodecBridge", "()V");

{
// add the j_media_codec_bridge to the set before the work is started
ScopedLock scoped_lock(mutex_);
j_media_codec_bridge_set_.insert(j_media_codec_bridge);
}

EradicateParam* param = new EradicateParam(this, j_media_codec_bridge,
j_reused_get_output_format_result);
pthread_t thread_id;
int result = pthread_create(
&thread_id, nullptr, &MediaCodecBridgeEradicator::DestroyMediaCodecBridge,
param);

if (result == 0) {
pthread_detach(thread_id);
} else {
// If it fails to create the thread
delete param;

ScopedLock scoped_lock(mutex_);
j_media_codec_bridge_set_.erase(j_media_codec_bridge);
if (j_media_codec_bridge_set_.empty()) {
condition_variable_.Signal();
}
}

return result == 0;
}

void* MediaCodecBridgeEradicator::DestroyMediaCodecBridge(void* context) {
EradicateParam* param = static_cast<EradicateParam*>(context);
SB_DCHECK(param != nullptr);

MediaCodecBridgeEradicator* eradicator = param->eradicator;
jobject j_media_codec_bridge = param->j_media_codec_bridge;
jobject j_reused_get_output_format_result =
param->j_reused_get_output_format_result;

delete param;
param = NULL;

JniEnvExt* env = JniEnvExt::Get();

env->CallVoidMethodOrAbort(j_media_codec_bridge, "stop", "()V");
env->CallVoidMethodOrAbort(j_media_codec_bridge, "release", "()V");
env->DeleteGlobalRef(j_media_codec_bridge);

SB_DCHECK(j_reused_get_output_format_result);
env->DeleteGlobalRef(j_reused_get_output_format_result);

{
// the work is finished, delete the j_media_codec_bridge from the set
ScopedLock scoped_lock(eradicator->mutex_);
eradicator->j_media_codec_bridge_set_.erase(j_media_codec_bridge);

if (eradicator->j_media_codec_bridge_set_.empty()) {
eradicator->condition_variable_.Signal();
}
}

return NULL;
}

} // namespace shared
} // namespace android
} // namespace starboard
60 changes: 60 additions & 0 deletions starboard/android/shared/media_codec_bridge_eradicator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2024 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_CODEC_BRIDGE_ERADICATOR_H_
#define STARBOARD_ANDROID_SHARED_MEDIA_CODEC_BRIDGE_ERADICATOR_H_

#include <jni.h>
#include <set>

#include "starboard/android/shared/jni_env_ext.h"
#include "starboard/common/atomic.h"
#include "starboard/common/condition_variable.h"
#include "starboard/common/mutex.h"

namespace starboard {
namespace android {
namespace shared {

/**
* This class is a singleton utility that runs the MediaCodecBridge destructor
* on a separate thread to prevent Application Not Responding (ANR) errors in
* Android.
*/
class MediaCodecBridgeEradicator {
public:
static MediaCodecBridgeEradicator* GetInstance();

~MediaCodecBridgeEradicator() { WaitForPendingDestructions(); }

bool Destroy(jobject j_media_codec_bridge,
jobject j_reused_get_output_format_result);
void WaitForPendingDestructions();
bool IsEnabled() const { return is_enabled_.load(); }
void SetEnabled(bool enabled) { is_enabled_.store(enabled); }

private:
static void* DestroyMediaCodecBridge(void* context);

atomic_bool is_enabled_; // false by default
Mutex mutex_;
ConditionVariable condition_variable_{mutex_};
std::set<jobject> j_media_codec_bridge_set_;
};

} // namespace shared
} // namespace android
} // namespace starboard

#endif // STARBOARD_ANDROID_SHARED_MEDIA_CODEC_BRIDGE_ERADICATOR_H_
46 changes: 46 additions & 0 deletions starboard/android/shared/media_settings_api.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2024 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_settings_api.h"
#include "starboard/android/shared/media_codec_bridge_eradicator.h"
#include "starboard/extension/media_settings.h"

namespace starboard {
namespace android {
namespace shared {

namespace {

// Definitions of any functions included as components in the extension
// are added here.

void EnableAsyncReleaseMediaCodecBridge(bool value) {
MediaCodecBridgeEradicator::GetInstance()->SetEnabled(value);
}

const StarboardExtensionMediaSettingsApi kMediaSettingsApi = {
kStarboardExtensionMediaSettingsName,
1, // API version that's implemented.
&EnableAsyncReleaseMediaCodecBridge,
};

} // namespace

const void* GetMediaSettingsApi() {
return &kMediaSettingsApi;
}

} // namespace shared
} // namespace android
} // namespace starboard
28 changes: 28 additions & 0 deletions starboard/android/shared/media_settings_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2024 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_SETTINGS_API_H_
#define STARBOARD_ANDROID_SHARED_MEDIA_SETTINGS_API_H_

namespace starboard {
namespace android {
namespace shared {

const void* GetMediaSettingsApi();

} // namespace shared
} // namespace android
} // namespace starboard

#endif // STARBOARD_ANDROID_SHARED_MEDIA_SETTINGS_API_H_
Loading

0 comments on commit 5e8fa2d

Please sign in to comment.