Move common JNI code out of AudioTrack

Consolidate JNI utility code and common patterns.
- sp to avoid lifetime races on destruction

Bug: 218351957
Test: atest AudioTrack
Change-Id: I9e70224c724e2cb8e3bbb8f4d772a0b83eb2b949
This commit is contained in:
Atneya Nair
2022-03-28 14:16:39 -04:00
parent bc16160052
commit 26845c3dfd
2 changed files with 76 additions and 39 deletions

View File

@@ -37,6 +37,7 @@
#include "android_media_AudioFormat.h"
#include "android_media_AudioTrackCallback.h"
#include "android_media_DeviceCallback.h"
#include "android_media_JNIUtils.h"
#include "android_media_MediaMetricsJNI.h"
#include "android_media_PlaybackParams.h"
#include "android_media_VolumeShaper.h"
@@ -82,7 +83,7 @@ class AudioTrackCallbackImpl : public AudioTrack::IAudioTrackCallback {
AudioTrackCallbackImpl(jclass audioTrackClass, jobject audioTrackWeakRef, bool isOffload)
: mIsOffload(isOffload)
{
const auto env = getJNIEnv();
const auto env = getJNIEnvOrDie();
mAudioTrackClass = (jclass)env->NewGlobalRef(audioTrackClass);
// we use a weak reference so the AudioTrack object can be garbage collected.
mAudioTrackWeakRef = env->NewGlobalRef(audioTrackWeakRef);
@@ -92,7 +93,7 @@ class AudioTrackCallbackImpl : public AudioTrack::IAudioTrackCallback {
AudioTrackCallbackImpl(const AudioTrackCallbackImpl&) = delete;
AudioTrackCallbackImpl& operator=(const AudioTrackCallbackImpl&) = delete;
~AudioTrackCallbackImpl() {
const auto env = getJNIEnv();
const auto env = getJNIEnvOrDie();
env->DeleteGlobalRef(mAudioTrackClass);
env->DeleteGlobalRef(mAudioTrackWeakRef);
}
@@ -129,16 +130,9 @@ class AudioTrackCallbackImpl : public AudioTrack::IAudioTrackCallback {
protected:
jobject mAudioTrackWeakRef;
private:
JNIEnv* getJNIEnv() {
auto jni = AndroidRuntime::getJNIEnv();
if (jni == nullptr) {
LOG_ALWAYS_FATAL("AudioTrackCallback thread JNI reference is null");
}
return jni;
}
void postEvent(int event, int arg = 0) {
auto env = getJNIEnv();
auto env = getJNIEnvOrDie();
env->CallStaticVoidMethod(
mAudioTrackClass,
javaAudioTrackFields.postNativeEventInJava,
@@ -216,7 +210,6 @@ public:
static void initCheckOrDie(JNIEnv *env) { (void)getIds(env); }
};
static Mutex sLock;
// ----------------------------------------------------------------------------
#define DEFAULT_OUTPUT_SAMPLE_RATE 44100
@@ -235,32 +228,6 @@ sp<IMemory> allocSharedMem(int sizeInBytes) {
}
return sp<MemoryBase>::make(heap, 0, sizeInBytes);
}
// TODO(b/218351957) move somewhere?
template<typename T>
sp<T> getFieldSp(JNIEnv* env, jobject thiz, jfieldID id)
{
// make these fields atomic longs on the java side
Mutex::Autolock l(sLock);
return sp<T>::fromExisting(reinterpret_cast<T*>(env->GetLongField(thiz, id)));
}
// This (semantically) should only be called on AudioTrack creation and release
template <typename T>
sp<T> setFieldSp(JNIEnv* env, jobject thiz, const sp<T>& at, jfieldID id)
{
Mutex::Autolock l(sLock);
// I don't think this synchronization actually prevents a race
// We can still invalidate under our feet in release
sp<T> old = sp<T>::fromExisting(reinterpret_cast<T*>(env->GetLongField(thiz, id)));
if (at.get()) {
at->incStrong((void*)setFieldSp<T>);
}
if (old != 0) {
old->decStrong((void*)setFieldSp<T>);
}
env->SetLongField(thiz, id, (jlong)at.get());
return old;
}
sp<AudioTrack> getAudioTrack(JNIEnv* env, jobject thiz) {
return getFieldSp<AudioTrack>(env, thiz, javaAudioTrackFields.nativeTrackInJavaObj);
@@ -268,8 +235,9 @@ sp<AudioTrack> getAudioTrack(JNIEnv* env, jobject thiz) {
} // anonymous
// ----------------------------------------------------------------------------
// For MediaSync
sp<AudioTrack> android_media_AudioTrack_getAudioTrack(JNIEnv* env, jobject audioTrackObj) {
return getFieldSp<AudioTrack>(env, audioTrackObj, javaAudioTrackFields.nativeTrackInJavaObj);
return getAudioTrack(env, audioTrackObj);
}
// ----------------------------------------------------------------------------
@@ -524,8 +492,9 @@ native_init_failure:
if (nSession != NULL) {
env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
}
env->SetLongField(thiz, javaAudioTrackFields.jniData, 0);
setFieldSp(env, thiz, sp<AudioTrack>{}, javaAudioTrackFields.nativeTrackInJavaObj);
setFieldSp(env, thiz, sp<AudioTrackJniStorage>{}, javaAudioTrackFields.jniData);
// lpTrack goes out of scope, so reference count drops to zero
return (jint) AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED;
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* 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.
*/
#pragma once
#include <jni.h>
#include <mutex>
#include <android_runtime/AndroidRuntime.h>
#include <log/log_main.h>
#include <utils/RefBase.h>
namespace android {
// TODO (b/218351957) remove static lock
// This lock is intentionally static, it is local to each TU which is
// responsible for a single Java class.
static std::mutex sFieldSpLock;
// getFieldSp and setFieldSp are used to logically represent an owning reference
// to a native object from across the JNI. The data (underlying ptr) is stored
// in a field in the Java object as a long. Setting a Java field to a sp
// involves incrementing the reference count to prevent object destruction.
// Resetting the field decrements the reference count to avoid memory leaks.
template <typename T>
sp<T> getFieldSp(JNIEnv* env, jobject thiz, jfieldID id) {
const std::lock_guard l{sFieldSpLock};
return sp<T>::fromExisting(reinterpret_cast<T*>(env->GetLongField(thiz, id)));
}
template <typename T>
sp<T> setFieldSp(JNIEnv* env, jobject thiz, const sp<T>& newSp, jfieldID id) {
const std::lock_guard l{sFieldSpLock};
sp<T> old = sp<T>::fromExisting(reinterpret_cast<T*>(env->GetLongField(thiz, id)));
if (newSp) {
newSp->incStrong((void*)setFieldSp<T>);
}
if (old) {
old->decStrong((void*)setFieldSp<T>);
}
env->SetLongField(thiz, id, (jlong)newSp.get());
return old;
}
inline JNIEnv* getJNIEnvOrDie() {
const auto env = AndroidRuntime::getJNIEnv();
LOG_ALWAYS_FATAL_IF(env == nullptr,
"Thread JNI reference is null. Thread not prepared for Java.");
return env;
}
} // namespace android