Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 17fc60d0 authored by Atneya Nair's avatar Atneya Nair
Browse files

Refactor AudioTrack JNI stub

- Use new callback/buffer interface
- Manage callback interface object lifetime with sp<> and ref-counting
- Combine JNI storage into a ref-counted object, with lifetime tied
to AudioTrack.java

Test: atest AudioTrackTest
Bug: 199156212
Bug: 216175830
Change-Id: I0ff9c90fa19f9f38287d827eaebdf58fa02aec5b
parent 12d66274
Loading
Loading
Loading
Loading
+158 −169
Original line number Diff line number Diff line
@@ -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;
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;
    }
        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;
    return sp<MemoryBase>::make(heap, 0, sizeInBytes);
}
        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();
    }
}


// ----------------------------------------------------------------------------
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,14 +475,18 @@ 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,
            sp<JNIAudioTrackCallback>::make(env, thiz, lpJniStorage->getAudioTrackWeakRef(),
                                            javaAudioTrackFields.postNativeEventInJava);
    lpTrack->setAudioTrackCallback(lpJniStorage->mAudioTrackCallback);

@@ -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,16 +1236,17 @@ 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,
            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);
+1 −7
Original line number Diff line number Diff line
@@ -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);