Merge "Update AudioRecord JNI callback and cleanup" into tm-dev

This commit is contained in:
Atneya Nair
2022-04-14 00:28:48 +00:00
committed by Android (Google) Code Review
3 changed files with 143 additions and 208 deletions

View File

@@ -33,12 +33,13 @@
#include <nativehelper/ScopedUtfChars.h>
#include "android_media_AudioAttributes.h"
#include "android_media_AudioFormat.h"
#include "android_media_AudioErrors.h"
#include "android_media_DeviceCallback.h"
#include "android_media_JNIUtils.h"
#include "android_media_MediaMetricsJNI.h"
#include "android_media_MicrophoneInfo.h"
#include "android_media_AudioAttributes.h"
// ----------------------------------------------------------------------------
@@ -57,8 +58,7 @@ struct audio_record_fields_t {
// these fields provide access from C++ to the...
jmethodID postNativeEventInJava; //... event post callback method
jfieldID nativeRecorderInJavaObj; // provides access to the C++ AudioRecord object
jfieldID nativeCallbackCookie; // provides access to the AudioRecord callback data
jfieldID nativeDeviceCallback; // provides access to the JNIDeviceCallback instance
jfieldID jniData; // provides access to AudioRecord JNI Handle
};
static audio_record_fields_t javaAudioRecordFields;
static struct {
@@ -66,15 +66,68 @@ static struct {
jfieldID fieldNanoTime; // AudioTimestamp.nanoTime
} javaAudioTimestampFields;
struct audiorecord_callback_cookie {
jclass audioRecord_class;
jobject audioRecord_ref;
bool busy;
Condition cond;
};
static Mutex sLock;
static SortedVector <audiorecord_callback_cookie *> sAudioRecordCallBackCookies;
class AudioRecordJNIStorage : public AudioRecord::IAudioRecordCallback {
private:
// Keep in sync with frameworks/base/media/java/android/media/AudioRecord.java NATIVE_EVENT_*.
enum class EventType {
EVENT_MORE_DATA = 0, // Request to read available data from buffer.
// If this event is delivered but the callback handler
// does not want to read the available data, the handler must
// explicitly ignore the event by setting frameCount to zero.
EVENT_OVERRUN = 1, // Buffer overrun occurred.
EVENT_MARKER = 2, // Record head is at the specified marker position
// (See setMarkerPosition()).
EVENT_NEW_POS = 3, // Record head is at a new position
// (See setPositionUpdatePeriod()).
EVENT_NEW_IAUDIORECORD = 4, // IAudioRecord was re-created, either due to re-routing and
// voluntary invalidation by mediaserver, or mediaserver crash.
};
public:
AudioRecordJNIStorage(jclass audioRecordClass, jobject audioRecordWeakRef)
: mAudioRecordClass(audioRecordClass), mAudioRecordWeakRef(audioRecordWeakRef) {}
AudioRecordJNIStorage(const AudioRecordJNIStorage &) = delete;
AudioRecordJNIStorage& operator=(const AudioRecordJNIStorage &) = delete;
void onMarker(uint32_t) override {
postEvent(EventType::EVENT_MARKER);
}
void onNewPos(uint32_t) override {
postEvent(EventType::EVENT_NEW_POS);
}
void setDeviceCallback(const sp<JNIDeviceCallback>& callback) {
mDeviceCallback = callback;
}
sp<JNIDeviceCallback> getDeviceCallback() const { return mDeviceCallback; }
jobject getAudioTrackWeakRef() const & { return mAudioRecordWeakRef.get(); }
// If we attempt to get a jobject from a rvalue, it will soon go out of
// scope, and the reference count can drop to zero, which is unsafe.
jobject getAudioTrackWeakRef() const && = delete;
private:
void postEvent(EventType event, int arg = 0) const {
JNIEnv *env = getJNIEnvOrDie();
env->CallStaticVoidMethod(
static_cast<jclass>(mAudioRecordClass.get()),
javaAudioRecordFields.postNativeEventInJava,
mAudioRecordWeakRef.get(), static_cast<int>(event), arg, 0, nullptr);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
}
// Mutation of this object is protected using Java concurrency constructs
sp<JNIDeviceCallback> mDeviceCallback;
const GlobalRef mAudioRecordClass;
const GlobalRef mAudioRecordWeakRef;
};
// ----------------------------------------------------------------------------
@@ -84,104 +137,10 @@ static SortedVector <audiorecord_callback_cookie *> sAudioRecordCallBackCookies;
#define AUDIORECORD_ERROR_SETUP_INVALIDSOURCE (-19)
#define AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED (-20)
// ----------------------------------------------------------------------------
static void recorderCallback(int event, void* user, void *info) {
audiorecord_callback_cookie *callbackInfo = (audiorecord_callback_cookie *)user;
{
Mutex::Autolock l(sLock);
if (sAudioRecordCallBackCookies.indexOf(callbackInfo) < 0) {
return;
}
callbackInfo->busy = true;
}
switch (event) {
case AudioRecord::EVENT_MARKER: {
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (user != NULL && env != NULL) {
env->CallStaticVoidMethod(
callbackInfo->audioRecord_class,
javaAudioRecordFields.postNativeEventInJava,
callbackInfo->audioRecord_ref, event, 0,0, NULL);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
}
} break;
case AudioRecord::EVENT_NEW_POS: {
JNIEnv *env = AndroidRuntime::getJNIEnv();
if (user != NULL && env != NULL) {
env->CallStaticVoidMethod(
callbackInfo->audioRecord_class,
javaAudioRecordFields.postNativeEventInJava,
callbackInfo->audioRecord_ref, event, 0,0, NULL);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
}
} break;
}
{
Mutex::Autolock l(sLock);
callbackInfo->busy = false;
callbackInfo->cond.broadcast();
}
}
static sp<JNIDeviceCallback> getJniDeviceCallback(JNIEnv* env, jobject thiz)
{
Mutex::Autolock l(sLock);
JNIDeviceCallback* const cb =
(JNIDeviceCallback*)env->GetLongField(thiz,
javaAudioRecordFields.nativeDeviceCallback);
return sp<JNIDeviceCallback>(cb);
}
static sp<JNIDeviceCallback> setJniDeviceCallback(JNIEnv* env,
jobject thiz,
const sp<JNIDeviceCallback>& cb)
{
Mutex::Autolock l(sLock);
sp<JNIDeviceCallback> old =
(JNIDeviceCallback*)env->GetLongField(thiz,
javaAudioRecordFields.nativeDeviceCallback);
if (cb.get()) {
cb->incStrong((void*)setJniDeviceCallback);
}
if (old != 0) {
old->decStrong((void*)setJniDeviceCallback);
}
env->SetLongField(thiz, javaAudioRecordFields.nativeDeviceCallback, (jlong)cb.get());
return old;
}
// ----------------------------------------------------------------------------
static sp<AudioRecord> getAudioRecord(JNIEnv* env, jobject thiz)
{
Mutex::Autolock l(sLock);
AudioRecord* const ar =
(AudioRecord*)env->GetLongField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
return sp<AudioRecord>(ar);
}
static sp<AudioRecord> setAudioRecord(JNIEnv* env, jobject thiz, const sp<AudioRecord>& ar)
{
Mutex::Autolock l(sLock);
sp<AudioRecord> old =
(AudioRecord*)env->GetLongField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
if (ar.get()) {
ar->incStrong((void*)setAudioRecord);
}
if (old != 0) {
old->decStrong((void*)setAudioRecord);
}
env->SetLongField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj, (jlong)ar.get());
return old;
return getFieldSp<AudioRecord>(env, thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
}
// ----------------------------------------------------------------------------
@@ -211,9 +170,8 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
nSession = NULL;
sp<AudioRecord> lpRecorder = 0;
audiorecord_callback_cookie *lpCallbackData = NULL;
sp<AudioRecord> lpRecorder;
sp<AudioRecordJNIStorage> callbackData;
jclass clazz = env->GetObjectClass(thiz);
if (clazz == NULL) {
ALOGE("Can't find %s when setting up callback.", kClassPathName);
@@ -287,18 +245,14 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
}
// create the callback information:
// this data will be passed with every AudioRecord callback
lpCallbackData = new audiorecord_callback_cookie;
lpCallbackData->audioRecord_class = (jclass)env->NewGlobalRef(clazz);
// we use a weak reference so the AudioRecord object can be garbage collected.
lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this);
lpCallbackData->busy = false;
callbackData = sp<AudioRecordJNIStorage>::make(clazz, weak_this);
const status_t status =
lpRecorder->set(paa->source, sampleRateInHertz,
format, // word length, PCM
localChanMask, frameCount,
recorderCallback, // callback_t
lpCallbackData, // void* user
callbackData, // callback
0, // notificationFrames,
true, // threadCanCallJava
sessionId, AudioRecord::TRANSFER_DEFAULT, flags, -1,
@@ -330,11 +284,8 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
// create the callback information:
// this data will be passed with every AudioRecord callback
lpCallbackData = new audiorecord_callback_cookie;
lpCallbackData->audioRecord_class = (jclass)env->NewGlobalRef(clazz);
// we use a weak reference so the AudioRecord object can be garbage collected.
lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this);
lpCallbackData->busy = false;
// This next line makes little sense
// callbackData = sp<AudioRecordJNIStorage>::make(clazz, weak_this);
}
nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
@@ -352,26 +303,20 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
env->SetIntArrayRegion(jSampleRate, 0, 1, elements);
}
{ // scope for the lock
Mutex::Autolock l(sLock);
sAudioRecordCallBackCookies.add(lpCallbackData);
}
// save our newly created C++ AudioRecord in the "nativeRecorderInJavaObj" field
// of the Java object
setAudioRecord(env, thiz, lpRecorder);
setFieldSp(env, thiz, lpRecorder, javaAudioRecordFields.nativeRecorderInJavaObj);
// save our newly created callback information in the "nativeCallbackCookie" field
// of the Java object (in mNativeCallbackCookie) so we can free the memory in finalize()
env->SetLongField(thiz, javaAudioRecordFields.nativeCallbackCookie, (jlong)lpCallbackData);
// save our newly created callback information in the "jniData" field
// of the Java object (in mNativeJNIDataHandle) so we can free the memory in finalize()
setFieldSp(env, thiz, callbackData, javaAudioRecordFields.jniData);
return (jint) AUDIO_JAVA_SUCCESS;
// failure:
native_init_failure:
env->DeleteGlobalRef(lpCallbackData->audioRecord_class);
env->DeleteGlobalRef(lpCallbackData->audioRecord_ref);
delete lpCallbackData;
env->SetLongField(thiz, javaAudioRecordFields.nativeCallbackCookie, 0);
setFieldSp(env, thiz, sp<AudioRecord>{}, javaAudioRecordFields.nativeRecorderInJavaObj);
setFieldSp(env, thiz, sp<AudioRecordJNIStorage>{}, javaAudioRecordFields.jniData);
// lpRecorder goes out of scope, so reference count drops to zero
return (jint) AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED;
@@ -411,36 +356,9 @@ android_media_AudioRecord_stop(JNIEnv *env, jobject thiz)
#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
static void android_media_AudioRecord_release(JNIEnv *env, jobject thiz) {
sp<AudioRecord> lpRecorder = setAudioRecord(env, thiz, 0);
if (lpRecorder == NULL) {
return;
}
ALOGV("About to delete lpRecorder: %p", lpRecorder.get());
lpRecorder->stop();
audiorecord_callback_cookie *lpCookie = (audiorecord_callback_cookie *)env->GetLongField(
thiz, javaAudioRecordFields.nativeCallbackCookie);
// reset the native resources in the Java object so any attempt to access
// them after a call to release fails.
env->SetLongField(thiz, javaAudioRecordFields.nativeCallbackCookie, 0);
// delete the callback information
if (lpCookie) {
Mutex::Autolock l(sLock);
ALOGV("deleting lpCookie: %p", lpCookie);
while (lpCookie->busy) {
if (lpCookie->cond.waitRelative(sLock,
milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
NO_ERROR) {
break;
}
}
sAudioRecordCallBackCookies.remove(lpCookie);
env->DeleteGlobalRef(lpCookie->audioRecord_class);
env->DeleteGlobalRef(lpCookie->audioRecord_ref);
delete lpCookie;
}
setFieldSp(env, thiz, sp<AudioRecord>{}, javaAudioRecordFields.nativeRecorderInJavaObj);
setFieldSp(env, thiz, sp<AudioRecordJNIStorage>{}, javaAudioRecordFields.jniData);
}
@@ -685,43 +603,40 @@ static jint android_media_AudioRecord_getRoutedDeviceId(
return (jint)lpRecorder->getRoutedDeviceId();
}
// Enable and Disable Callback methods are synchronized on the Java side
static void android_media_AudioRecord_enableDeviceCallback(
JNIEnv *env, jobject thiz) {
sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
if (lpRecorder == 0) {
if (lpRecorder == nullptr) {
return;
}
sp<JNIDeviceCallback> cb = getJniDeviceCallback(env, thiz);
if (cb != 0) {
return;
}
audiorecord_callback_cookie *cookie =
(audiorecord_callback_cookie *)env->GetLongField(thiz,
javaAudioRecordFields.nativeCallbackCookie);
if (cookie == NULL) {
const auto pJniStorage =
getFieldSp<AudioRecordJNIStorage>(env, thiz, javaAudioRecordFields.jniData);
if (pJniStorage == nullptr || pJniStorage->getDeviceCallback() != nullptr) {
return;
}
cb = new JNIDeviceCallback(env, thiz, cookie->audioRecord_ref,
javaAudioRecordFields.postNativeEventInJava);
status_t status = lpRecorder->addAudioDeviceCallback(cb);
if (status == NO_ERROR) {
setJniDeviceCallback(env, thiz, cb);
}
pJniStorage->setDeviceCallback(
sp<JNIDeviceCallback>::make(env, thiz, pJniStorage->getAudioTrackWeakRef(),
javaAudioRecordFields.postNativeEventInJava));
lpRecorder->addAudioDeviceCallback(pJniStorage->getDeviceCallback());
}
static void android_media_AudioRecord_disableDeviceCallback(
JNIEnv *env, jobject thiz) {
sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
if (lpRecorder == 0) {
if (lpRecorder == nullptr) {
return;
}
sp<JNIDeviceCallback> cb = setJniDeviceCallback(env, thiz, 0);
if (cb != 0) {
lpRecorder->removeAudioDeviceCallback(cb);
const auto pJniStorage =
getFieldSp<AudioRecordJNIStorage>(env, thiz, javaAudioRecordFields.jniData);
if (pJniStorage == nullptr || pJniStorage->getDeviceCallback() == nullptr) {
return;
}
lpRecorder->removeAudioDeviceCallback(pJniStorage->getDeviceCallback());
pJniStorage->setDeviceCallback(nullptr);
}
// ----------------------------------------------------------------------------
@@ -962,17 +877,15 @@ static const JNINativeMethod gMethods[] = {
// field names found in android/media/AudioRecord.java
#define JAVA_POSTEVENT_CALLBACK_NAME "postEventFromNative"
#define JAVA_NATIVERECORDERINJAVAOBJ_FIELD_NAME "mNativeRecorderInJavaObj"
#define JAVA_NATIVECALLBACKINFO_FIELD_NAME "mNativeCallbackCookie"
#define JAVA_NATIVEDEVICECALLBACK_FIELD_NAME "mNativeDeviceCallback"
#define JAVA_NATIVEAUDIORECORDERHANDLE_FIELD_NAME "mNativeAudioRecordHandle"
#define JAVA_NATIVEJNIDATAHANDLE_FIELD_NAME "mNativeJNIDataHandle"
// ----------------------------------------------------------------------------
int register_android_media_AudioRecord(JNIEnv *env)
{
javaAudioRecordFields.postNativeEventInJava = NULL;
javaAudioRecordFields.nativeRecorderInJavaObj = NULL;
javaAudioRecordFields.nativeCallbackCookie = NULL;
javaAudioRecordFields.nativeDeviceCallback = NULL;
javaAudioRecordFields.jniData = NULL;
// Get the AudioRecord class
@@ -983,15 +896,12 @@ int register_android_media_AudioRecord(JNIEnv *env)
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
// Get the variables
// mNativeRecorderInJavaObj
// mNativeAudioRecordHandle
javaAudioRecordFields.nativeRecorderInJavaObj = GetFieldIDOrDie(env,
audioRecordClass, JAVA_NATIVERECORDERINJAVAOBJ_FIELD_NAME, "J");
// mNativeCallbackCookie
javaAudioRecordFields.nativeCallbackCookie = GetFieldIDOrDie(env,
audioRecordClass, JAVA_NATIVECALLBACKINFO_FIELD_NAME, "J");
javaAudioRecordFields.nativeDeviceCallback = GetFieldIDOrDie(env,
audioRecordClass, JAVA_NATIVEDEVICECALLBACK_FIELD_NAME, "J");
audioRecordClass, JAVA_NATIVEAUDIORECORDERHANDLE_FIELD_NAME, "J");
// mNativeJNIDataHandle
javaAudioRecordFields.jniData = GetFieldIDOrDie(env,
audioRecordClass, JAVA_NATIVEJNIDATAHANDLE_FIELD_NAME, "J");
// Get the RecordTimestamp class and fields
jclass audioTimestampClass = FindClassOrDie(env, "android/media/AudioTimestamp");

View File

@@ -64,5 +64,39 @@ inline JNIEnv* getJNIEnvOrDie() {
return env;
}
class GlobalRef {
public:
GlobalRef(jobject object) : GlobalRef(object, AndroidRuntime::getJNIEnv()) {}
GlobalRef(jobject object, JNIEnv* env) {
LOG_ALWAYS_FATAL_IF(env == nullptr, "Invalid JNIEnv when attempting to create a GlobalRef");
mGlobalRef = env->NewGlobalRef(object);
LOG_ALWAYS_FATAL_IF(env->IsSameObject(object, nullptr) == JNI_TRUE,
"Creating GlobalRef from null object");
}
GlobalRef(const GlobalRef& other) : GlobalRef(other.mGlobalRef) {}
GlobalRef(GlobalRef&& other) : mGlobalRef(other.mGlobalRef) { other.mGlobalRef = nullptr; }
// Logically const
GlobalRef& operator=(const GlobalRef& other) = delete;
GlobalRef& operator=(GlobalRef&& other) = delete;
~GlobalRef() {
if (mGlobalRef == nullptr) return; // No reference to decrement
getJNIEnvOrDie()->DeleteGlobalRef(mGlobalRef);
}
// Valid as long as this wrapper is in scope.
jobject get() const {
return mGlobalRef;
}
private:
// Logically const. Not actually const so we can move from GlobalRef
jobject mGlobalRef;
};
} // namespace android

View File

@@ -187,22 +187,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
*/
@SuppressWarnings("unused")
@UnsupportedAppUsage
private long mNativeRecorderInJavaObj;
private long mNativeAudioRecordHandle;
/**
* Accessed by native methods: provides access to the callback data.
*/
@SuppressWarnings("unused")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private long mNativeCallbackCookie;
/**
* Accessed by native methods: provides access to the JNIDeviceCallback instance.
*/
@SuppressWarnings("unused")
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private long mNativeDeviceCallback;
private long mNativeJNIDataHandle;
//---------------------------------------------------------
// Member variables
@@ -492,9 +484,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
* value here as no error checking is or can be done.
*/
/*package*/ AudioRecord(long nativeRecordInJavaObj) {
mNativeRecorderInJavaObj = 0;
mNativeCallbackCookie = 0;
mNativeDeviceCallback = 0;
mNativeAudioRecordHandle = 0;
mNativeJNIDataHandle = 0;
// other initialization...
if (nativeRecordInJavaObj != 0) {
@@ -2131,7 +2122,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
* @hide
*/
public int getPortId() {
if (mNativeRecorderInJavaObj == 0) {
if (mNativeAudioRecordHandle == 0) {
return 0;
}
try {