diff options
| -rw-r--r-- | core/jni/android_media_AudioTrack.cpp | 327 | ||||
| -rw-r--r-- | core/jni/android_media_AudioTrack.h | 8 |
2 files changed, 159 insertions, 176 deletions
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 73d2d8d949cd..8c33e0719f73 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -63,12 +63,94 @@ static audio_track_fields_t javaAudioTrackFields; static PlaybackParams::fields_t gPlaybackParamsFields; static VolumeShaperHelper::fields_t gVolumeShaperFields; -struct audiotrack_callback_cookie { - jclass audioTrack_class; - jobject audioTrack_ref; - bool busy; - Condition cond; - bool isOffload; +class AudioTrackCallbackImpl : public AudioTrack::IAudioTrackCallback { + public: + enum event_type { + // Keep in sync with java + EVENT_MORE_DATA = 0, + EVENT_UNDERRUN = 1, + EVENT_LOOP_END = 2, + EVENT_MARKER = 3, + EVENT_NEW_POS = 4, + EVENT_BUFFER_END = 5, + EVENT_NEW_IAUDIOTRACK = 6, + EVENT_STREAM_END = 7, + // 8 is reserved for future use + EVENT_CAN_WRITE_MORE_DATA = 9 + }; + + AudioTrackCallbackImpl(jclass audioTrackClass, jobject audioTrackWeakRef, bool isOffload) + : mIsOffload(isOffload) + { + const auto env = getJNIEnv(); + mAudioTrackClass = (jclass)env->NewGlobalRef(audioTrackClass); + // we use a weak reference so the AudioTrack object can be garbage collected. + mAudioTrackWeakRef = env->NewGlobalRef(audioTrackWeakRef); + + } + + AudioTrackCallbackImpl(const AudioTrackCallbackImpl&) = delete; + AudioTrackCallbackImpl& operator=(const AudioTrackCallbackImpl&) = delete; + ~AudioTrackCallbackImpl() { + const auto env = getJNIEnv(); + env->DeleteGlobalRef(mAudioTrackClass); + env->DeleteGlobalRef(mAudioTrackWeakRef); + } + + size_t onCanWriteMoreData(const AudioTrack::Buffer& buffer) override { + if (!mIsOffload) { + LOG_FATAL("Received canWrite callback for non-offload track"); + return 0; + } + const size_t availableForWrite = buffer.size(); + const int arg = availableForWrite > INT32_MAX ? INT32_MAX : (int) availableForWrite; + postEvent(EVENT_CAN_WRITE_MORE_DATA, arg); + return 0; + } + + void onMarker([[maybe_unused]] uint32_t markerPosition) override { + postEvent(EVENT_MARKER); + } + void onNewPos([[maybe_unused]] uint32_t newPos) override { + postEvent(EVENT_NEW_POS); + } + + + void onNewIAudioTrack() override { + if (!mIsOffload) return; + postEvent(EVENT_NEW_IAUDIOTRACK); + } + + void onStreamEnd() override { + if (!mIsOffload) return; + postEvent(EVENT_STREAM_END); + } + + 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(); + env->CallStaticVoidMethod( + mAudioTrackClass, + javaAudioTrackFields.postNativeEventInJava, + mAudioTrackWeakRef, event, arg, 0, NULL); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } + + jclass mAudioTrackClass; + const bool mIsOffload; }; // keep these values in sync with AudioTrack.java @@ -76,22 +158,21 @@ struct audiotrack_callback_cookie { #define MODE_STREAM 1 // ---------------------------------------------------------------------------- -class AudioTrackJniStorage { +class AudioTrackJniStorage : public virtual RefBase, + public AudioTrackCallbackImpl +{ public: - sp<MemoryHeapBase> mMemHeap; - sp<MemoryBase> mMemBase; - audiotrack_callback_cookie mCallbackData{}; + // TODO do we always want to initialize the callback implementation? + AudioTrackJniStorage(jclass audioTrackClass, jobject audioTrackRef, bool isOffload = false) + : AudioTrackCallbackImpl(audioTrackClass, audioTrackRef, isOffload) {} + sp<JNIDeviceCallback> mDeviceCallback; sp<JNIAudioTrackCallback> mAudioTrackCallback; - bool allocSharedMem(int sizeInBytes) { - mMemHeap = new MemoryHeapBase(sizeInBytes, 0, "AudioTrack Heap Base"); - if (mMemHeap->getHeapID() < 0) { - return false; - } - mMemBase = new MemoryBase(mMemHeap, 0, sizeInBytes); - return true; + jobject getAudioTrackWeakRef() const { + return mAudioTrackWeakRef; } + }; class TunerConfigurationHelper { @@ -136,7 +217,6 @@ public: }; static Mutex sLock; -static SortedVector <audiotrack_callback_cookie *> sAudioTrackCallBackCookies; // ---------------------------------------------------------------------------- #define DEFAULT_OUTPUT_SAMPLE_RATE 44100 @@ -147,102 +227,49 @@ static SortedVector <audiotrack_callback_cookie *> sAudioTrackCallBackCookies; #define AUDIOTRACK_ERROR_SETUP_INVALIDSTREAMTYPE (-19) #define AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED (-20) -// ---------------------------------------------------------------------------- -static void audioCallback(int event, void* user, void *info) { - - audiotrack_callback_cookie *callbackInfo = (audiotrack_callback_cookie *)user; - { - Mutex::Autolock l(sLock); - if (sAudioTrackCallBackCookies.indexOf(callbackInfo) < 0) { - return; - } - callbackInfo->busy = true; - } - - // used as default argument when event callback doesn't have any, or number of - // frames for EVENT_CAN_WRITE_MORE_DATA - int arg = 0; - bool postEvent = false; - switch (event) { - // Offload only events - case AudioTrack::EVENT_CAN_WRITE_MORE_DATA: - // this event will read the info return parameter of the callback: - // for JNI offload, use the returned size to indicate: - // 1/ no data is returned through callback, as it's all done through write() - // 2/ do not wait as AudioTrack does when it receives 0 bytes - if (callbackInfo->isOffload) { - AudioTrack::Buffer* pBuffer = (AudioTrack::Buffer*) info; - const size_t availableForWrite = pBuffer->size; - arg = availableForWrite > INT32_MAX ? INT32_MAX : (int) availableForWrite; - pBuffer->size = 0; - } - FALLTHROUGH_INTENDED; - case AudioTrack::EVENT_STREAM_END: - case AudioTrack::EVENT_NEW_IAUDIOTRACK: // a.k.a. tear down - if (callbackInfo->isOffload) { - postEvent = true; - } - break; - - // PCM and offload events - case AudioTrack::EVENT_MARKER: - case AudioTrack::EVENT_NEW_POS: - postEvent = true; - break; - default: - // event will not be posted - break; - } - - if (postEvent) { - JNIEnv *env = AndroidRuntime::getJNIEnv(); - if (env != NULL) { - env->CallStaticVoidMethod( - callbackInfo->audioTrack_class, - javaAudioTrackFields.postNativeEventInJava, - callbackInfo->audioTrack_ref, event, arg, 0, NULL); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - } - } - - { - Mutex::Autolock l(sLock); - callbackInfo->busy = false; - callbackInfo->cond.broadcast(); +namespace { +sp<IMemory> allocSharedMem(int sizeInBytes) { + const auto heap = sp<MemoryHeapBase>::make(sizeInBytes, 0, "AudioTrack Heap Base"); + if (heap->getBase() == MAP_FAILED || heap->getBase() == nullptr) { + return nullptr; } + return sp<MemoryBase>::make(heap, 0, sizeInBytes); } - - -// ---------------------------------------------------------------------------- -static sp<AudioTrack> getAudioTrack(JNIEnv* env, jobject thiz) +// 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); - AudioTrack* const at = - (AudioTrack*)env->GetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj); - return sp<AudioTrack>(at); + return sp<T>::fromExisting(reinterpret_cast<T*>(env->GetLongField(thiz, id))); } -static sp<AudioTrack> setAudioTrack(JNIEnv* env, jobject thiz, const sp<AudioTrack>& at) +// 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); - sp<AudioTrack> old = - (AudioTrack*)env->GetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj); + // 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*)setAudioTrack); + at->incStrong((void*)setFieldSp<T>); } if (old != 0) { - old->decStrong((void*)setAudioTrack); + old->decStrong((void*)setFieldSp<T>); } - env->SetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (jlong)at.get()); + env->SetLongField(thiz, id, (jlong)at.get()); return old; } +sp<AudioTrack> getAudioTrack(JNIEnv* env, jobject thiz) { + return getFieldSp<AudioTrack>(env, thiz, javaAudioTrackFields.nativeTrackInJavaObj); +} + +} // anonymous // ---------------------------------------------------------------------------- sp<AudioTrack> android_media_AudioTrack_getAudioTrack(JNIEnv* env, jobject audioTrackObj) { - return getAudioTrack(env, audioTrackObj); + return getFieldSp<AudioTrack>(env, audioTrackObj, javaAudioTrackFields.nativeTrackInJavaObj); } // ---------------------------------------------------------------------------- @@ -274,7 +301,6 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); nSession = NULL; - AudioTrackJniStorage* lpJniStorage = NULL; jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { @@ -284,6 +310,7 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we // if we pass in an existing *Native* AudioTrack, we don't need to create/initialize one. sp<AudioTrack> lpTrack; + const auto lpJniStorage = sp<AudioTrackJniStorage>::make(clazz, weak_this, offload); if (nativeAudioTrack == 0) { if (jaa == 0) { ALOGE("Error creating AudioTrack: invalid audio attributes"); @@ -332,7 +359,7 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we AttributionSourceState attributionSource; attributionSource.packageName = std::string(opPackageNameStr.c_str()); attributionSource.token = sp<BBinder>::make(); - lpTrack = new AudioTrack(attributionSource); + lpTrack = sp<AudioTrack>::make(attributionSource); // read the AudioAttributes values auto paa = JNIAudioAttributeHelper::makeUnique(); @@ -345,13 +372,6 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we // initialize the callback information: // this data will be passed with every AudioTrack callback - lpJniStorage = new AudioTrackJniStorage(); - lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz); - // we use a weak reference so the AudioTrack object can be garbage collected. - lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this); - lpJniStorage->mCallbackData.isOffload = offload; - lpJniStorage->mCallbackData.busy = false; - audio_offload_info_t offloadInfo; if (offload == JNI_TRUE) { offloadInfo = AUDIO_INFO_INITIALIZER; @@ -385,8 +405,7 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we nativeChannelMask, offload ? 0 : frameCount, offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD : AUDIO_OUTPUT_FLAG_NONE, - audioCallback, - &(lpJniStorage->mCallbackData), // callback, callback data (user) + lpJniStorage, 0, // notificationFrames == 0 since not using EVENT_MORE_DATA // to feed the AudioTrack 0, // shared mem @@ -400,9 +419,10 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we break; case MODE_STATIC: + { // AudioTrack is using shared memory - - if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) { + const auto iMem = allocSharedMem(buffSizeInBytes); + if (iMem == nullptr) { ALOGE("Error creating AudioTrack in static mode: error creating mem heap base"); goto native_init_failure; } @@ -412,19 +432,18 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we sampleRateInHertz, format, // word length, PCM nativeChannelMask, frameCount, AUDIO_OUTPUT_FLAG_NONE, - audioCallback, - &(lpJniStorage->mCallbackData), // callback, callback data (user) + lpJniStorage, 0, // notificationFrames == 0 since not using EVENT_MORE_DATA // to feed the AudioTrack - lpJniStorage->mMemBase, // shared mem + iMem, // shared mem true, // thread can call Java sessionId, // audio session ID AudioTrack::TRANSFER_SHARED, - NULL, // default offloadInfo + nullptr , // default offloadInfo AttributionSourceState(), // default uid, pid values paa.get()); break; - + } default: ALOGE("Unknown mode %d", memoryMode); goto native_init_failure; @@ -438,7 +457,7 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we // MediaMetricsConstants.h: AMEDIAMETRICS_PROP_CALLERNAME_VALUE_JAVA lpTrack->setCallerName("java"); } else { // end if (nativeAudioTrack == 0) - lpTrack = (AudioTrack*)nativeAudioTrack; + lpTrack = sp<AudioTrack>::fromExisting(reinterpret_cast<AudioTrack*>(nativeAudioTrack)); // TODO: We need to find out which members of the Java AudioTrack might // need to be initialized from the Native AudioTrack // these are directly returned from getters: @@ -456,15 +475,19 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we // initialize the callback information: // this data will be passed with every AudioTrack callback - lpJniStorage = new AudioTrackJniStorage(); + + // TODO this callback information is useless, it isn't passed to the + // native AudioTrack object + /* lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz); // we use a weak reference so the AudioTrack object can be garbage collected. lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this); lpJniStorage->mCallbackData.busy = false; + */ } lpJniStorage->mAudioTrackCallback = - new JNIAudioTrackCallback(env, thiz, lpJniStorage->mCallbackData.audioTrack_ref, - javaAudioTrackFields.postNativeEventInJava); + sp<JNIAudioTrackCallback>::make(env, thiz, lpJniStorage->getAudioTrackWeakRef(), + javaAudioTrackFields.postNativeEventInJava); lpTrack->setAudioTrackCallback(lpJniStorage->mAudioTrackCallback); nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL); @@ -482,17 +505,13 @@ static jint android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject we env->SetIntArrayRegion(jSampleRate, 0, 1, elements); } - { // scope for the lock - Mutex::Autolock l(sLock); - sAudioTrackCallBackCookies.add(&lpJniStorage->mCallbackData); - } // save our newly created C++ AudioTrack in the "nativeTrackInJavaObj" field // of the Java object (in mNativeTrackInJavaObj) - setAudioTrack(env, thiz, lpTrack); + setFieldSp(env, thiz, lpTrack, javaAudioTrackFields.nativeTrackInJavaObj); // save the JNI resources so we can free them later //ALOGV("storing lpJniStorage: %x\n", (long)lpJniStorage); - env->SetLongField(thiz, javaAudioTrackFields.jniData, (jlong)lpJniStorage); + setFieldSp(env, thiz, lpJniStorage, javaAudioTrackFields.jniData); // since we had audio attributes, the stream type was derived from them during the // creation of the native AudioTrack: push the same value to the Java object @@ -505,9 +524,6 @@ native_init_failure: if (nSession != NULL) { env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); } - env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioTrack_class); - env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioTrack_ref); - delete lpJniStorage; env->SetLongField(thiz, javaAudioTrackFields.jniData, 0); // lpTrack goes out of scope, so reference count drops to zero @@ -607,38 +623,9 @@ android_media_AudioTrack_set_volume(JNIEnv *env, jobject thiz, jfloat leftVol, j // ---------------------------------------------------------------------------- -#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000 static void android_media_AudioTrack_release(JNIEnv *env, jobject thiz) { - sp<AudioTrack> lpTrack = setAudioTrack(env, thiz, 0); - if (lpTrack == NULL) { - return; - } - //ALOGV("deleting lpTrack: %x\n", (int)lpTrack); - - // delete the JNI data - AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( - thiz, javaAudioTrackFields.jniData); - // reset the native resources in the Java object so any attempt to access - // them after a call to release fails. - env->SetLongField(thiz, javaAudioTrackFields.jniData, 0); - - if (pJniStorage) { - Mutex::Autolock l(sLock); - audiotrack_callback_cookie *lpCookie = &pJniStorage->mCallbackData; - //ALOGV("deleting pJniStorage: %x\n", (int)pJniStorage); - while (lpCookie->busy) { - if (lpCookie->cond.waitRelative(sLock, - milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) != - NO_ERROR) { - break; - } - } - sAudioTrackCallBackCookies.remove(lpCookie); - // delete global refs created in native_setup - env->DeleteGlobalRef(lpCookie->audioTrack_class); - env->DeleteGlobalRef(lpCookie->audioTrack_ref); - delete pJniStorage; - } + setFieldSp(env, thiz, sp<AudioTrack>(nullptr), javaAudioTrackFields.nativeTrackInJavaObj); + setFieldSp(env, thiz, sp<AudioTrackJniStorage>(nullptr), javaAudioTrackFields.jniData); } @@ -1249,17 +1236,18 @@ static void android_media_AudioTrack_enableDeviceCallback( JNIEnv *env, jobject thiz) { sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); - if (lpTrack == NULL) { + if (lpTrack == nullptr) { return; } - AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( - thiz, javaAudioTrackFields.jniData); - if (pJniStorage == NULL || pJniStorage->mDeviceCallback != 0) { + const auto pJniStorage = + getFieldSp<AudioTrackJniStorage>(env, thiz, javaAudioTrackFields.jniData); + if (pJniStorage == nullptr || pJniStorage->mDeviceCallback != nullptr) { return; } + pJniStorage->mDeviceCallback = - new JNIDeviceCallback(env, thiz, pJniStorage->mCallbackData.audioTrack_ref, - javaAudioTrackFields.postNativeEventInJava); + sp<JNIDeviceCallback>::make(env, thiz, pJniStorage->getAudioTrackWeakRef(), + javaAudioTrackFields.postNativeEventInJava); lpTrack->addAudioDeviceCallback(pJniStorage->mDeviceCallback); } @@ -1267,12 +1255,13 @@ static void android_media_AudioTrack_disableDeviceCallback( JNIEnv *env, jobject thiz) { sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); - if (lpTrack == NULL) { + if (lpTrack == nullptr) { return; } - AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( - thiz, javaAudioTrackFields.jniData); - if (pJniStorage == NULL || pJniStorage->mDeviceCallback == 0) { + const auto pJniStorage = + getFieldSp<AudioTrackJniStorage>(env, thiz, javaAudioTrackFields.jniData); + + if (pJniStorage == nullptr || pJniStorage->mDeviceCallback == nullptr) { return; } lpTrack->removeAudioDeviceCallback(pJniStorage->mDeviceCallback); diff --git a/core/jni/android_media_AudioTrack.h b/core/jni/android_media_AudioTrack.h index ef2aa669db26..d5b858c89c27 100644 --- a/core/jni/android_media_AudioTrack.h +++ b/core/jni/android_media_AudioTrack.h @@ -18,15 +18,9 @@ #define ANDROID_MEDIA_AUDIOTRACK_H #include "jni.h" - +#include <media/AudioTrack.h> #include <utils/StrongPointer.h> -namespace android { - -class AudioTrack; - -}; // namespace android - /* Gets the underlying AudioTrack from an AudioTrack Java object. */ extern android::sp<android::AudioTrack> android_media_AudioTrack_getAudioTrack( JNIEnv* env, jobject audioTrackObj); |