Skip to content

Commit b8d6770

Browse files
committed
wip
1 parent bdc53d2 commit b8d6770

File tree

7 files changed

+218
-94
lines changed

7 files changed

+218
-94
lines changed

app/src/main/cpp/NativeTrack.cpp

Lines changed: 113 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <pthread.h>
2929
#include "helpers.h"
3030

31+
using DeviceIdVector = std::vector<int>;
3132
extern void *libaudioclient_handle;
3233
extern void *libpermission_handle;
3334
extern void *libandroid_runtime_handle;
@@ -94,16 +95,17 @@ class MyCallback;
9495
struct 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+
283322
static 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

418462
extern "C" JNIEXPORT jint JNICALL
419463
Java_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

727752
extern "C" JNIEXPORT jlong JNICALL
728753
Java_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

736803
extern "C" JNIEXPORT void JNICALL
737804
Java_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?

app/src/main/kotlin/org/akanework/gramophone/logic/GramophonePlaybackService.kt

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import android.content.IntentFilter
2828
import android.content.SharedPreferences
2929
import android.graphics.Bitmap
3030
import android.media.AudioDeviceInfo
31-
import android.media.AudioFormat
3231
import android.media.AudioManager
3332
import android.media.audiofx.AudioEffect
3433
import android.net.Uri
@@ -40,7 +39,6 @@ import android.os.HandlerThread
4039
import android.os.Looper
4140
import android.os.Process
4241
import android.util.Log
43-
import android.widget.Toast
4442
import androidx.concurrent.futures.CallbackToFutureAdapter
4543
import androidx.core.app.NotificationChannelCompat
4644
import androidx.core.app.NotificationCompat
@@ -580,15 +578,8 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
580578
} else {
581579
Log.e(TAG, "session id is 0? why????? THIS MIGHT BREAK EQUALIZER")
582580
}
583-
val s = NativeTrack.getDirectPlaybackSupport(
584-
this,
585-
192000,
586-
AudioFormat.ENCODING_PCM_16BIT,
587-
AudioFormat.CHANNEL_OUT_STEREO
588-
)
589-
Toast.makeText(this, "direct: ${s.directOrOffload}", Toast.LENGTH_LONG).show()
590-
//val track = NativeTrack(this)
591-
//track.release() TODO
581+
val track = NativeTrack.forTest(this)
582+
//track.release()
592583
}
593584

594585
private fun broadcastAudioSessionClose() {

app/src/main/kotlin/org/akanework/gramophone/logic/utils/AfFormatTracker.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ class AfFormatTracker(
425425

426426
private val flagRegex = Regex(".*, flags\\(0x(.*)\\).*")
427427
private val flagRegexOld = Regex(".*, flags\\((.*)\\).*")
428-
private fun getFlagFromDump(dump: String?): Int? {
428+
fun getFlagsFromDump(dump: String?): Int? {
429429
if (dump == null)
430430
return null
431431
// Flags are in dump output since below commit which first appeared in Pie.
@@ -589,7 +589,7 @@ class AfFormatTracker(
589589
null
590590
}
591591
val dump = dump(audioTrack)
592-
val grantedFlags = getFlagFromDump(dump) // TODO implement extraction for L-O based on fixed offsets (or mayhaps something smarter?)
592+
val grantedFlags = getFlagsFromDump(dump) // TODO implement extraction for L-O based on fixed offsets (or mayhaps something smarter?)
593593
AfFormatInfo(
594594
pn, id, t,
595595
mp?.id, mp?.name, mp?.flags,

app/src/main/kotlin/org/akanework/gramophone/logic/utils/AudioFormatDetector.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ object AudioFormatDetector {
127127
@OptIn(UnstableApi::class)
128128
enum class Encoding(val enc: Int?, val enc2: String?, val native: UInt?, val sdkRange: IntRange?, val res: Int) {
129129
ENCODING_INVALID(C.ENCODING_INVALID, null, 0xFFFFFFFFU, 0, R.string.spk_encoding_invalid),
130-
ENCODING_PCM_8BIT(C.ENCODING_PCM_8BIT, "AUDIO_FORMAT_PCM_8_BIT", 0x1U, 21, R.string.spk_encoding_pcm_8bit),
131-
ENCODING_PCM_16BIT(C.ENCODING_PCM_16BIT, "AUDIO_FORMAT_PCM_16_BIT", 0x2U, 21, R.string.spk_encoding_pcm_16bit),
130+
ENCODING_PCM_8BIT(C.ENCODING_PCM_8BIT, "AUDIO_FORMAT_PCM_8_BIT", 0x2U, 21, R.string.spk_encoding_pcm_8bit),
131+
ENCODING_PCM_16BIT(C.ENCODING_PCM_16BIT, "AUDIO_FORMAT_PCM_16_BIT", 0x1U, 21, R.string.spk_encoding_pcm_16bit),
132132
ENCODING_PCM_16BIT_BIG_ENDIAN(C.ENCODING_PCM_16BIT_BIG_ENDIAN, null, null, null, R.string.spk_encoding_pcm_16bit_big_endian),
133133
ENCODING_PCM_24BIT(C.ENCODING_PCM_24BIT, "AUDIO_FORMAT_PCM_24_BIT_PACKED", 0x6U, 21, R.string.spk_encoding_pcm_24bit),
134134
ENCODING_PCM_8_24BIT(null, "AUDIO_FORMAT_PCM_8_24_BIT", 0x4U, 21, R.string.spk_encoding_pcm_8_24bit),
@@ -501,6 +501,7 @@ object AudioFormatDetector {
501501
MimeTypes.AUDIO_FLAC,
502502
MimeTypes.AUDIO_ALAC,
503503
MimeTypes.AUDIO_WAV,
504+
MimeTypes.AUDIO_RAW,
504505
MimeTypes.AUDIO_TRUEHD -> true
505506

506507
// TODO distinguish lossless DTS-HD MA vs other lossy DTS-HD encoding schemes

0 commit comments

Comments
 (0)