diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallAudioRouter.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallAudioRouter.kt index 77a22e36..f17ebd75 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallAudioRouter.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallAudioRouter.kt @@ -330,7 +330,11 @@ class CallAudioRouter { /* ── мониторинг гарнитуры ── */ private fun onDevicesChanged() { if (!started) return + val previous = devices?.map { "${it.id}/${it.type}/${it.isSink}/${it.isSource}" }?.sorted() devices = am.getDevices(AudioManager.GET_DEVICES_ALL) + val current = devices?.map { "${it.id}/${it.type}/${it.isSink}/${it.isSource}" }?.sorted() + if (previous != current) LogManager.addLog("INFO", "CallAudioRouter", + "devices changed id/type/output/input=$current telecomHeadset=${telecom?.headsetAvailable} route=$route") /* если в режиме HEADSET появилось SCO-устройство — пере-применяем маршрут, * иначе SCO не замаршрутизируется автоматически */ if (telecom == null && route == AudioRoute.HEADSET) applyRoute(route) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/UtunConnection.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/UtunConnection.kt index d377eade..07722438 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/UtunConnection.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/UtunConnection.kt @@ -108,10 +108,12 @@ class UtunConnection( } override fun onAvailableCallEndpointsChanged(availableEndpoints: MutableList) { + val changed = this.availableEndpoints.map { it.identifier to it.endpointType }.toSet() != + availableEndpoints.map { it.identifier to it.endpointType }.toSet() this.availableEndpoints.clear() this.availableEndpoints.addAll(availableEndpoints) - LogManager.addLog("DEBUG", "UtunConn", - "availableEndpoints -> %s".format(availableEndpoints.joinToString { "${it.endpointName}/${it.endpointType}" })) + if (changed) LogManager.addLog("INFO", "UtunConn", + "endpoints id=$callId types=${availableEndpoints.map { it.endpointType }} headset=$headsetAvailable confirmed=$confirmedRoute") onEndpointsChanged?.invoke() wantedRoute?.let { routeTo(it) } } diff --git a/tools/chatgui-android/audio-probe/src/main/cpp/probe.cpp b/tools/chatgui-android/audio-probe/src/main/cpp/probe.cpp index 2e4b07bb..083fb6c2 100644 --- a/tools/chatgui-android/audio-probe/src/main/cpp/probe.cpp +++ b/tools/chatgui-android/audio-probe/src/main/cpp/probe.cpp @@ -13,6 +13,7 @@ struct Probe { int channels = 0; std::atomic error{0}, callbacks{0}, callbackFrames{0}; int previousError = 0; + int previousBuffer = -1; }; static Probe probe; @@ -60,6 +61,7 @@ extern "C" JNIEXPORT jboolean JNICALL Java_com_utun_audioprobe_ProbeActivity_ope return JNI_FALSE; } probe.channels = channels; probe.error = 0; probe.callbacks = 0; probe.callbackFrames = 0; probe.previousError = 0; + probe.previousBuffer = -1; AAudioStreamBuilder* builder = nullptr; if (!check_result("builder", AAudio_createStreamBuilder(&builder))) return JNI_FALSE; AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT); @@ -100,9 +102,12 @@ extern "C" JNIEXPORT jboolean JNICALL Java_com_utun_audioprobe_ProbeActivity_ope } extern "C" JNIEXPORT jint JNICALL Java_com_utun_audioprobe_ProbeActivity_writeNative(JNIEnv* env, jclass, jshortArray input) { + int64_t queued = AAudioStream_getFramesWritten(probe.stream) - AAudioStream_getFramesRead(probe.stream); + int available = int(std::max(0, AAudioStream_getBufferSizeInFrames(probe.stream) - queued)); + if (!available) return AAudioStream_getState(probe.stream) == AAUDIO_STREAM_STATE_DISCONNECTED ? AAUDIO_ERROR_DISCONNECTED : 0; int16_t samples[960]{}; env->GetShortArrayRegion(input, 0, 960, samples); - return AAudioStream_write(probe.stream, samples, 960 / probe.channels, 0); + return AAudioStream_write(probe.stream, samples, std::min(available, 960 / probe.channels), 0); } /* Timestamp опрашивается вне realtime callback. Java одинаково агрегирует AAudio и AudioTrack. */ @@ -111,6 +116,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_utun_audioprobe_ProbeActivity_sampleN DEBUG_ERROR(DEBUG_CATEGORY_CALL, "probe invalid sample"); return; } auto* s = probe.stream; + int buffer = AAudioStream_getBufferSizeInFrames(s); + if (buffer != probe.previousBuffer) { + DEBUG_INFO(DEBUG_CATEGORY_CALL, "probe buffer=%d capacity=%d written=%lld read=%lld", buffer, + AAudioStream_getBufferCapacityInFrames(s), (long long)AAudioStream_getFramesWritten(s), (long long)AAudioStream_getFramesRead(s)); + probe.previousBuffer = buffer; + } int64_t frame = -1, stamp = -1; int64_t written = AAudioStream_getFramesWritten(s); int result = AAudioStream_getTimestamp(s, CLOCK_MONOTONIC, &frame, &stamp);