2828#include < pthread.h>
2929#include " helpers.h"
3030
31+ using DeviceIdVector = std::vector<int >;
3132extern void *libaudioclient_handle;
3233extern void *libpermission_handle;
3334extern void *libandroid_runtime_handle;
@@ -94,16 +95,17 @@ class MyCallback;
9495struct track_holder {
9596 explicit track_holder (JNIEnv* env) {
9697 int err = env->GetJavaVM (&vm);
97- if (err != 0 ) {
98+ if (err != JNI_OK ) {
9899 ALOGE (" could not get JavaVM: %d. aborting!" , err);
99100 abort ();
100101 }
101102 }
102103 void * track = nullptr ;
103104 MyCallback* callback = nullptr ;
105+ jobject thiz = nullptr ;
106+ jmethodID onAudioDeviceUpdate = nullptr ;
104107 void * ats = nullptr ;
105108 bool deathEmulation = false ;
106- int ignoredDeaths = 0 ;
107109 bool died = false ;
108110 JavaVM* vm = nullptr ;
109111};
@@ -166,10 +168,6 @@ class MyCallback : public virtual android::AudioTrack::IAudioTrackCallback {
166168 }
167169 void onNewIAudioTrack () override {
168170 if (!mCallback || mHolder ->died ) return ;
169- if (mHolder ->ignoredDeaths > 0 ) {
170- mHolder ->ignoredDeaths --;
171- return ; // do not call callback, and do not emulate death.
172- }
173171 if (mHolder ->deathEmulation ) {
174172 // block any further callbacks, and access to track object other than dtor
175173 mHolder ->died = true ;
@@ -251,7 +249,11 @@ class MyCallback : public virtual android::AudioTrack::IAudioTrackCallback {
251249 char *name;
252250 jobject group;
253251 } attachArgs = {.version = JNI_VERSION_1_6, .name = &buf[0 ], .group = nullptr };
254- mHolder ->vm ->AttachCurrentThread (&env, &attachArgs);
252+ ret = mHolder ->vm ->AttachCurrentThread (&env, &attachArgs);
253+ if (ret != JNI_OK) {
254+ ALOGE (" failed to attach jni thread %d" , ret);
255+ return false ;
256+ }
255257 static pthread_once_t initialized = PTHREAD_ONCE_INIT;
256258 static pthread_key_t jni_env_key;
257259 pthread_once (&initialized, [] {
@@ -280,6 +282,43 @@ class MyCallback : public virtual android::AudioTrack::IAudioTrackCallback {
280282 }
281283};
282284
285+ // TODO wire this up
286+ static void callOnAudioDeviceUpdate (track_holder* holder, int audioIo, const DeviceIdVector& deviceIds) {
287+ if (!holder->thiz || holder->died || !holder->onAudioDeviceUpdate ) return ;
288+ JNIEnv* env;
289+ int ret = holder->vm ->GetEnv ((void **)&env, JNI_VERSION_1_6);
290+ if (ret == JNI_EDETACHED) {
291+ char buf[50 ] = {' \0 ' };
292+ snprintf (buf, 50 , " audio_cb_tmp_thread" );
293+ struct {
294+ jint version;
295+ char *name;
296+ jobject group;
297+ } attachArgs = {.version = JNI_VERSION_1_6, .name = &buf[0 ], .group = nullptr };
298+ int ret2 = holder->vm ->AttachCurrentThread (&env, &attachArgs);
299+ if (ret2 != JNI_OK) {
300+ ALOGE (" failed to attach jni thread %d, dropping callOnAudioDeviceUpdate" , ret);
301+ return ;
302+ }
303+ } else if (ret != JNI_OK) {
304+ ALOGE (" failed to get jni env %d, dropping callOnAudioDeviceUpdate" , ret);
305+ return ;
306+ }
307+ jintArray deviceIdsJni;
308+ deviceIdsJni = env->NewIntArray ((int32_t )deviceIds.size ());
309+ if (deviceIdsJni == nullptr ) {
310+ ALOGE (" Out of memory, dropping onAudioDeviceUpdate" );
311+ env->ExceptionClear ();
312+ return ;
313+ }
314+ env->SetIntArrayRegion (deviceIdsJni, 0 , (int32_t )deviceIds.size (), &deviceIds[0 ]);
315+ env->CallVoidMethod (holder->thiz , holder->onAudioDeviceUpdate , audioIo, deviceIdsJni);
316+ env->DeleteLocalRef (deviceIdsJni);
317+ if (ret == JNI_EDETACHED) {
318+ myJniDetach (holder->vm );
319+ }
320+ }
321+
283322static void callbackAdapter (int event, void * userptr, void * info) {
284323 auto user = ((track_holder*) userptr)->callback ;
285324 if (user == nullptr ) {
@@ -289,6 +328,7 @@ static void callbackAdapter(int event, void* userptr, void* info) {
289328 }
290329 return ;
291330 }
331+ // TODO caf L/M event 9 is adsp error? we should make sure this doesn't conflict
292332 switch (event) {
293333 case 0 /* EVENT_MORE_DATA */ :
294334 ((android::AudioTrack::Buffer*)info)->mSize =
@@ -402,6 +442,10 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_create(
402442 } else {
403443 ZN7android10AudioTrackC1Ev (theTrack);
404444 }
445+ holder->thiz = env->NewGlobalRef (thiz);
446+ jclass clazz = env->GetObjectClass (thiz);
447+ holder->onAudioDeviceUpdate = env->GetMethodID (clazz, " onAudioDeviceUpdate" , " (I[I)V" );
448+ env->DeleteLocalRef (clazz);
405449 auto callback = new MyCallback (*holder, env, thiz);
406450 callback->incStrong (holder);
407451 if (android_get_device_api_level () >= 33 ) {
@@ -417,11 +461,11 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_create(
417461
418462extern " C" JNIEXPORT jint JNICALL
419463Java_org_akanework_gramophone_logic_utils_NativeTrack_set (
420- JNIEnv * env , jobject, jlong ptr, jint streamType, jint sampleRate, jint format,
464+ JNIEnv *, jobject, jlong ptr, jint streamType, jint sampleRate, jint format,
421465 jint channelMask, jint frameCount, jint trackFlags, jint sessionId, jfloat maxRequiredSpeed,
422466 jint selectedDeviceId, jint bitRate, jlong durationUs, jboolean hasVideo,
423467 jboolean isStreaming, jint bitWidth, jint offloadBufferSize, jint usage, jint contentType,
424- jint source, jint attrFlags, jstring inTags , jint notificationFrames,
468+ jint attrFlags, jint notificationFrames,
425469 jboolean doNotReconnect, jint transferMode) {
426470 if (android_get_device_api_level () < 23 && maxRequiredSpeed != 1 .0f ) {
427471 ALOGE (" Android 5.x does not support speed adjustment, maxRequiredSpeed != 1f is wrong" );
@@ -434,7 +478,6 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_set(
434478 auto holder = (track_holder*) ptr;
435479 jint ret = 0 ;
436480 fake_sp sharedMemory = {.thePtr = nullptr }; // TODO impl shared memory
437- const char * tags = env->GetStringUTFChars (inTags, nullptr );
438481 union {
439482 audio_offload_info_t_v30 newInfo = {};
440483 audio_offload_info_t_v26 oldInfo;
@@ -443,6 +486,7 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_set(
443486 audio_attributes_v28 newAttrs = {};
444487 audio_attributes_legacy oldAttrs;
445488 } audioAttributes;
489+ // We must always provide offloadInfo if we can to work around CAF being CAF.
446490 if (android_get_device_api_level () >= 28 ) {
447491 offloadInfo.newInfo = {
448492 .version = AUDIO_MAKE_OFFLOAD_INFO_VERSION (0 , 2 ),
@@ -465,11 +509,10 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_set(
465509 audioAttributes.newAttrs = {
466510 .content_type = contentType,
467511 .usage = usage,
468- .source = source ,
512+ .source = 0 ,
469513 .flags = (uint32_t )attrFlags,
470514 };
471- audioAttributes.newAttrs .tags [255 ] = ' \0 ' ;
472- strncpy (audioAttributes.newAttrs .tags , tags, 255 );
515+ audioAttributes.newAttrs .tags [0 ] = ' \0 ' ;
473516 } else {
474517 offloadInfo.oldInfo = {
475518 .version = AUDIO_MAKE_OFFLOAD_INFO_VERSION (0 , 1 ),
@@ -489,13 +532,11 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_set(
489532 audioAttributes.oldAttrs = {
490533 .content_type = contentType,
491534 .usage = usage,
492- .source = source ,
535+ .source = 0 ,
493536 .flags = (uint32_t )attrFlags,
494537 };
495- audioAttributes.oldAttrs .tags [255 ] = ' \0 ' ;
496- strncpy (audioAttributes.oldAttrs .tags , tags, 255 );
538+ audioAttributes.oldAttrs .tags [0 ] = ' \0 ' ;
497539 }
498- env->ReleaseStringUTFChars (inTags, tags);
499540 if (android_get_device_api_level () >= 31 ) { // Android 12.0 (SDK 31) or later
500541 auto refs = holder->callback ->createWeak (holder);
501542 fake_wp callback = {.thePtr = holder->callback , .refs = refs};
@@ -575,6 +616,7 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_set(
575616 /* maxRequiredSpeed = */ maxRequiredSpeed
576617 );
577618 } else if (android_get_device_api_level () >= 24 ) { // Android 7.x
619+ *(int32_t *)((uintptr_t )holder->track + 0x300 ) = selectedDeviceId; // TODO verify on CAF, MTK
578620 ret = ZN7android10AudioTrack3setE19audio_stream_type_tj14audio_format_tjm20audio_output_flags_tPFviPvS4_ES4_iRKNS_2spINS_7IMemoryEEEb15audio_session_tNS0_13transfer_typeEPK20audio_offload_info_tiiPK18audio_attributes_tbf (
579621 holder->track ,
580622 /* streamType = */ streamType,
@@ -598,6 +640,7 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_set(
598640 /* maxRequiredSpeed = */ maxRequiredSpeed
599641 );
600642 } else if (android_get_device_api_level () >= 23 ) { // Android 6.0 (SDK 23)
643+ *(int32_t *)((uintptr_t )holder->track + 0x2e0 ) = selectedDeviceId; // TODO verify on CAF, MTK
601644 if (maxRequiredSpeed > 1 .0f ) {
602645 if (trackFlags & 4 /* AUDIO_OUTPUT_FLAG_FAST */ ) {
603646 // if we're unlucky, the calculated frame count may be smaller than HAL frame count
@@ -703,39 +746,63 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_set(
703746 holder->deathEmulation = !ZNK7android10AudioTrack19isOffloadedOrDirectEv (holder->track );
704747 }
705748 }
706- if (android_get_device_api_level () >= 23 && android_get_device_api_level () <= 25 ) {
707- if (ret == 0 && selectedDeviceId != 0 ) {
708- // if selectedDeviceId != 0 before O, we can set it with setOutputDevice() which will
709- // invalidate the track (in O, we can call setOutputDevice() before set() which means
710- // there is no track yet). that won't work if it has doNotReconnect set, hence emulate
711- // that to allow setOutputDevice() to work for now.
712- // TODO use memory hacks instead and get rid of ignoredDeaths (that'll fix direct too)
713- if (ZNK7android10AudioTrack19isOffloadedOrDirectEv (holder->track )) {
714- ALOGE (" Ignoring selectedDeviceId emulation because track is offloaded/direct" );
715- } else {
716- holder->deathEmulation = doNotReconnect;
717- holder->ignoredDeaths = 1 ;
718- // This will instantly invalidate the track.
719- ZN7android10AudioTrack15setOutputDeviceEi (holder->track , selectedDeviceId);
720- // TODO the track is now invalid but not yet restored, which is inconsistent
721- }
722- }
723- }
724749 return ret;
725750}
726751
727752extern " C" JNIEXPORT jlong JNICALL
728753Java_org_akanework_gramophone_logic_utils_NativeTrack_getRealPtr (
729754 JNIEnv *, jobject, jlong ptr) {
755+ return (intptr_t )((track_holder*) ptr)->track ;
756+ }
757+
758+ extern " C" JNIEXPORT jint JNICALL
759+ Java_org_akanework_gramophone_logic_utils_NativeTrack_flagsFromOffset (
760+ JNIEnv *, jobject, jlong ptr) {
730761 auto holder = (track_holder*) ptr;
731- if (holder->died )
732- return 0 ;
733- return (intptr_t )holder->track ;
762+ // TODO verify on CAF, MTK
763+ switch (android_get_device_api_level ()) {
764+ case 27 :
765+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x338 );
766+ case 26 :
767+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x330 );
768+ case 25 :
769+ case 24 :
770+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x2a0 );
771+ case 23 :
772+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x280 );
773+ case 22 :
774+ case 21 :
775+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x228 );
776+ default :
777+ return INT32_MAX;
778+ };
779+ }
780+
781+ extern " C" JNIEXPORT jint JNICALL
782+ Java_org_akanework_gramophone_logic_utils_NativeTrack_notificationFramesActFromOffset (
783+ JNIEnv *, jobject, jlong ptr) {
784+ auto holder = (track_holder*) ptr;
785+ // TODO verify on CAF, MTK
786+ switch (android_get_device_api_level ()) {
787+ case 27 :
788+ case 26 :
789+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x228 );
790+ case 25 :
791+ case 24 :
792+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x220 );
793+ case 23 :
794+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x214 );
795+ case 22 :
796+ case 21 :
797+ return (int32_t )*(uint32_t *)((uintptr_t )holder->track + 0x1ec );
798+ default :
799+ return INT32_MAX;
800+ };
734801}
735802
736803extern " C" JNIEXPORT void JNICALL
737804Java_org_akanework_gramophone_logic_utils_NativeTrack_dtor (
738- JNIEnv *, jobject, jlong ptr) {
805+ JNIEnv * env , jobject, jlong ptr) {
739806 auto holder = (track_holder*) ptr;
740807 // RefBase will call the dtor
741808 if (android_get_device_api_level () >= 33 ) {
@@ -747,6 +814,7 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_dtor(
747814 holder->callback ->decStrong (holder);
748815 holder->track = nullptr ;
749816 holder->callback = nullptr ;
817+ env->DeleteGlobalRef (holder->thiz );
750818 delete holder;
751819}
752820
@@ -801,12 +869,6 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_00024Companion_isOffloadSu
801869 return ZN7android11AudioSystem18isOffloadSupportedERK20audio_offload_info_t (offloadInfo.oldInfo );
802870}
803871
804- // TODO hardcode offsets for 21/22/23/24/25/26/27, dump otherwise
805- // audio_output_flags_t getFlags() const { AutoMutex _l(mLock); return mFlags; }
806-
807- // TODO hardcode offsets for 21/22/23/24/25/26/27, dump otherwise
808- // uint32_t getNotificationPeriodInFrames() const { return mNotificationFramesAct; }
809-
810872// TODO need datatype but should be easy
811873// status_t addAudioDeviceCallback(const sp<AudioSystem::AudioDeviceCallback>& callback);
812874// status_t removeAudioDeviceCallback(
@@ -818,4 +880,9 @@ Java_org_akanework_gramophone_logic_utils_NativeTrack_00024Companion_isOffloadSu
818880// TODO types
819881// media::VolumeShaper::Status applyVolumeShaper(
820882// const sp<media::VolumeShaper::Configuration>& configuration,
821- // const sp<media::VolumeShaper::Operation>& operation);
883+ // const sp<media::VolumeShaper::Operation>& operation);
884+
885+ // TODO when can we use getTimestamp on CAF L/M?
886+
887+ // TODO on caf L/M, should we pretend to be MediaPlayer or a normal direct track?
888+ // is PCM_16_BIT_OFFLOAD the media player format, or is DIRECT_PCM flag?
0 commit comments