Merge "Refactor AudioTrack JNI stub"

This commit is contained in:
Atneya Nair
2022-02-14 17:19:30 +00:00
committed by Android (Google) Code Review
2 changed files with 162 additions and 179 deletions

View File

@@ -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);
}
// 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);
return sp<T>::fromExisting(reinterpret_cast<T*>(env->GetLongField(thiz, id)));
}
// ----------------------------------------------------------------------------
static sp<AudioTrack> getAudioTrack(JNIEnv* env, jobject thiz)
// 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);
AudioTrack* const at =
(AudioTrack*)env->GetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj);
return sp<AudioTrack>(at);
}
static sp<AudioTrack> setAudioTrack(JNIEnv* env, jobject thiz, const sp<AudioTrack>& at)
{
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);

View File

@@ -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);