diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index bce4ed78eda15..8012e0c5ac572 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -33,12 +33,13 @@ #include +#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 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& callback) { + mDeviceCallback = callback; + } + + sp 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(mAudioRecordClass.get()), + javaAudioRecordFields.postNativeEventInJava, + mAudioRecordWeakRef.get(), static_cast(event), arg, 0, nullptr); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } + + // Mutation of this object is protected using Java concurrency constructs + sp mDeviceCallback; + const GlobalRef mAudioRecordClass; + const GlobalRef mAudioRecordWeakRef; +}; // ---------------------------------------------------------------------------- @@ -84,104 +137,10 @@ static SortedVector 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 getJniDeviceCallback(JNIEnv* env, jobject thiz) -{ - Mutex::Autolock l(sLock); - JNIDeviceCallback* const cb = - (JNIDeviceCallback*)env->GetLongField(thiz, - javaAudioRecordFields.nativeDeviceCallback); - return sp(cb); -} - -static sp setJniDeviceCallback(JNIEnv* env, - jobject thiz, - const sp& cb) -{ - Mutex::Autolock l(sLock); - sp 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 getAudioRecord(JNIEnv* env, jobject thiz) { - Mutex::Autolock l(sLock); - AudioRecord* const ar = - (AudioRecord*)env->GetLongField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj); - return sp(ar); -} - -static sp setAudioRecord(JNIEnv* env, jobject thiz, const sp& ar) -{ - Mutex::Autolock l(sLock); - sp 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(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 lpRecorder = 0; - audiorecord_callback_cookie *lpCallbackData = NULL; - + sp lpRecorder; + sp 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::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::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{}, javaAudioRecordFields.nativeRecorderInJavaObj); + setFieldSp(env, thiz, sp{}, 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 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{}, javaAudioRecordFields.nativeRecorderInJavaObj); + setFieldSp(env, thiz, sp{}, 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 lpRecorder = getAudioRecord(env, thiz); - if (lpRecorder == 0) { + if (lpRecorder == nullptr) { return; } - sp 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(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::make(env, thiz, pJniStorage->getAudioTrackWeakRef(), + javaAudioRecordFields.postNativeEventInJava)); + lpRecorder->addAudioDeviceCallback(pJniStorage->getDeviceCallback()); } static void android_media_AudioRecord_disableDeviceCallback( JNIEnv *env, jobject thiz) { - sp lpRecorder = getAudioRecord(env, thiz); - if (lpRecorder == 0) { + if (lpRecorder == nullptr) { return; } - sp cb = setJniDeviceCallback(env, thiz, 0); - if (cb != 0) { - lpRecorder->removeAudioDeviceCallback(cb); + const auto pJniStorage = + getFieldSp(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"); diff --git a/core/jni/android_media_JNIUtils.h b/core/jni/android_media_JNIUtils.h index 9da5fa37a8f39..a413d096421c5 100644 --- a/core/jni/android_media_JNIUtils.h +++ b/core/jni/android_media_JNIUtils.h @@ -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 diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 52838898146df..8be1bcdc94795 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -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 {