Loading core/jni/android_media_AudioRecord.cpp +103 −193 Original line number Diff line number Diff line Loading @@ -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" // ---------------------------------------------------------------------------- Loading @@ -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 { Loading @@ -66,122 +66,81 @@ 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; // ---------------------------------------------------------------------------- #define AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT (-16) #define AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK (-17) #define AUDIORECORD_ERROR_SETUP_INVALIDFORMAT (-18) #define AUDIORECORD_ERROR_SETUP_INVALIDSOURCE (-19) #define AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED (-20) 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. }; // ---------------------------------------------------------------------------- static void recorderCallback(int event, void* user, void *info) { public: AudioRecordJNIStorage(jclass audioRecordClass, jobject audioRecordWeakRef) : mAudioRecordClass(audioRecordClass), mAudioRecordWeakRef(audioRecordWeakRef) {} AudioRecordJNIStorage(const AudioRecordJNIStorage &) = delete; AudioRecordJNIStorage& operator=(const AudioRecordJNIStorage &) = delete; audiorecord_callback_cookie *callbackInfo = (audiorecord_callback_cookie *)user; { Mutex::Autolock l(sLock); if (sAudioRecordCallBackCookies.indexOf(callbackInfo) < 0) { return; } callbackInfo->busy = true; void onMarker(uint32_t) override { postEvent(EventType::EVENT_MARKER); } 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(); void onNewPos(uint32_t) override { postEvent(EventType::EVENT_NEW_POS); } void setDeviceCallback(const sp<JNIDeviceCallback>& callback) { mDeviceCallback = callback; } } break; case AudioRecord::EVENT_NEW_POS: { JNIEnv *env = AndroidRuntime::getJNIEnv(); if (user != NULL && env != NULL) { 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( callbackInfo->audioRecord_class, static_cast<jclass>(mAudioRecordClass.get()), javaAudioRecordFields.postNativeEventInJava, callbackInfo->audioRecord_ref, event, 0,0, NULL); mAudioRecordWeakRef.get(), static_cast<int>(event), arg, 0, nullptr); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } } } break; } { Mutex::Autolock l(sLock); callbackInfo->busy = false; callbackInfo->cond.broadcast(); } } // Mutation of this object is protected using Java concurrency constructs sp<JNIDeviceCallback> mDeviceCallback; const GlobalRef mAudioRecordClass; const GlobalRef mAudioRecordWeakRef; }; 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; } #define AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT (-16) #define AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK (-17) #define AUDIORECORD_ERROR_SETUP_INVALIDFORMAT (-18) #define AUDIORECORD_ERROR_SETUP_INVALIDSOURCE (-19) #define AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED (-20) // ---------------------------------------------------------------------------- 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); } // ---------------------------------------------------------------------------- Loading Loading @@ -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); Loading Loading @@ -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, Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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); } // ---------------------------------------------------------------------------- Loading Loading @@ -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 Loading @@ -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"); Loading core/jni/android_media_JNIUtils.h +34 −0 Original line number Diff line number Diff line Loading @@ -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 media/java/android/media/AudioRecord.java +5 −14 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -2131,7 +2122,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * @hide */ public int getPortId() { if (mNativeRecorderInJavaObj == 0) { if (mNativeAudioRecordHandle == 0) { return 0; } try { Loading Loading
core/jni/android_media_AudioRecord.cpp +103 −193 Original line number Diff line number Diff line Loading @@ -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" // ---------------------------------------------------------------------------- Loading @@ -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 { Loading @@ -66,122 +66,81 @@ 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; // ---------------------------------------------------------------------------- #define AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT (-16) #define AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK (-17) #define AUDIORECORD_ERROR_SETUP_INVALIDFORMAT (-18) #define AUDIORECORD_ERROR_SETUP_INVALIDSOURCE (-19) #define AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED (-20) 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. }; // ---------------------------------------------------------------------------- static void recorderCallback(int event, void* user, void *info) { public: AudioRecordJNIStorage(jclass audioRecordClass, jobject audioRecordWeakRef) : mAudioRecordClass(audioRecordClass), mAudioRecordWeakRef(audioRecordWeakRef) {} AudioRecordJNIStorage(const AudioRecordJNIStorage &) = delete; AudioRecordJNIStorage& operator=(const AudioRecordJNIStorage &) = delete; audiorecord_callback_cookie *callbackInfo = (audiorecord_callback_cookie *)user; { Mutex::Autolock l(sLock); if (sAudioRecordCallBackCookies.indexOf(callbackInfo) < 0) { return; } callbackInfo->busy = true; void onMarker(uint32_t) override { postEvent(EventType::EVENT_MARKER); } 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(); void onNewPos(uint32_t) override { postEvent(EventType::EVENT_NEW_POS); } void setDeviceCallback(const sp<JNIDeviceCallback>& callback) { mDeviceCallback = callback; } } break; case AudioRecord::EVENT_NEW_POS: { JNIEnv *env = AndroidRuntime::getJNIEnv(); if (user != NULL && env != NULL) { 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( callbackInfo->audioRecord_class, static_cast<jclass>(mAudioRecordClass.get()), javaAudioRecordFields.postNativeEventInJava, callbackInfo->audioRecord_ref, event, 0,0, NULL); mAudioRecordWeakRef.get(), static_cast<int>(event), arg, 0, nullptr); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } } } break; } { Mutex::Autolock l(sLock); callbackInfo->busy = false; callbackInfo->cond.broadcast(); } } // Mutation of this object is protected using Java concurrency constructs sp<JNIDeviceCallback> mDeviceCallback; const GlobalRef mAudioRecordClass; const GlobalRef mAudioRecordWeakRef; }; 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; } #define AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT (-16) #define AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK (-17) #define AUDIORECORD_ERROR_SETUP_INVALIDFORMAT (-18) #define AUDIORECORD_ERROR_SETUP_INVALIDSOURCE (-19) #define AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED (-20) // ---------------------------------------------------------------------------- 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); } // ---------------------------------------------------------------------------- Loading Loading @@ -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); Loading Loading @@ -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, Loading Loading @@ -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); Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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); } // ---------------------------------------------------------------------------- Loading Loading @@ -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 Loading @@ -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"); Loading
core/jni/android_media_JNIUtils.h +34 −0 Original line number Diff line number Diff line Loading @@ -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
media/java/android/media/AudioRecord.java +5 −14 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -2131,7 +2122,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * @hide */ public int getPortId() { if (mNativeRecorderInJavaObj == 0) { if (mNativeAudioRecordHandle == 0) { return 0; } try { Loading