Preserve Android raw capture through AEC and apply mute before Opus

This commit is contained in:
evgeny committed 2026-10-26 17:14:18 +01:00
1 parent bdaff406be
commit 000b28027c
3 files changed
+13 -12

No files matched your search

+1 -2
View File
@@ -63,8 +63,7 @@ int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count, int64_t
* Маска применяется после core AGC, результат/PTS — как у feed_pcm. */
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask, int64_t capture_pts_us);
/* Raw capture: AEC/AGC получают настоящий PCM; muted!=0 зануляет только вход Opus после обработки.
* Известный дефект Android26.10.2026: JNI пока вызывает feed_pcm с заранее занулённым PCM,
* AEC может снова создать ненулевой выход. Доказательство и план — doc/call_audit_findings.txt,раздел13. */
* Android передаёт исходный кадр и snapshot mute через JNI; результат/PTS — как у feed_pcm. */
int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted, int64_t capture_pts_us);
/* аудио-поток: выдать max_samples финального PCM (jitter + stretch + тоны + тишина).
@@ -352,7 +352,6 @@ class CallAudioEngine {
private fun captureLoop() {
val buf = ShortArray(FRAME_SAMPLES)
val silence = ShortArray(FRAME_SAMPLES)
var rec = audioRecord
var frame = 0L
var sinceSwap = 0
@@ -361,7 +360,7 @@ class CallAudioEngine {
var loggedMeasured: Boolean? = null
var reportNs = System.nanoTime()
var ageLow = Long.MAX_VALUE; var ageHigh = Long.MIN_VALUE; var ageSum = 0L
var ageCount = 0; var measuredCount = 0
var ageCount = 0; var measuredCount = 0; var mutedFrames = 0
while (active) {
/* проводная гарнитура появилась/исчезла — меняем источник захвата */
if (wantedWired != wiredActive) {
@@ -414,7 +413,7 @@ class CallAudioEngine {
}
sinceSwap++
frame++
val out = if (muted) silence else buf
val frameMuted = muted // Один snapshot на кадр; AEC/AGC всегда получают настоящий микрофон.
val stampOk = r.getTimestamp(stamp, AudioTimestamp.TIMEBASE_MONOTONIC) == AudioRecord.SUCCESS
val ptsUs = clock.firstSampleUs(capFramesRead, FRAME_SAMPLES, System.nanoTime() / 1000,
if (stampOk) stamp.framePosition else null, if (stampOk) stamp.nanoTime / 1000 else null)
@@ -428,14 +427,15 @@ class CallAudioEngine {
val ageUs = feedAt / 1000 - ptsUs
ageLow = minOf(ageLow, ageUs); ageHigh = maxOf(ageHigh, ageUs); ageSum += ageUs; ageCount++
if (clock.measured) measuredCount++
if (!NativeLib.callAudioFeed(callId, out, ptsUs)) {
LogManager.addLog("WARN", "CallAudio", "callAudioFeed returned false")
if (frameMuted) mutedFrames++
if (!NativeLib.callAudioFeed(callId, buf, frameMuted, ptsUs)) {
LogManager.addLog("WARN", "CallAudio", "callAudioFeed failed id=$callId muted=$frameMuted ptsUs=$ptsUs")
}
if (feedAt - reportNs >= 1_000_000_000) {
LogManager.addLog("DEBUG", "call", "audio capture latency id=$callId frames=$ageCount measured=$measuredCount " +
"firstSampleAgeUs=$ageLow/${ageSum / ageCount}/$ageHigh")
LogManager.addLog("DEBUG", "call", "audio capture latency id=$callId frames=$ageCount mutedFrames=$mutedFrames " +
"measured=$measuredCount firstSampleAgeUs=$ageLow/${ageSum / ageCount}/$ageHigh")
reportNs = feedAt; ageLow = Long.MAX_VALUE; ageHigh = Long.MIN_VALUE; ageSum = 0
ageCount = 0; measuredCount = 0
ageCount = 0; measuredCount = 0; mutedFrames = 0
}
}
}
@@ -294,7 +294,9 @@ object NativeLib {
/* ── P2P аудио-звонок (медиа) ── */
fun callAudioStart(callId: Long): Boolean = nativeCallAudioStart(callId)
fun callAudioFeed(callId: Long, samples: ShortArray, capturePtsUs: Long): Boolean = nativeCallAudioFeed(callId, samples, capturePtsUs)
/** Настоящий raw PCM и mute кадра: зануление выполняется в core после AEC/AGC. */
fun callAudioFeed(callId: Long, samples: ShortArray, muted: Boolean, capturePtsUs: Long): Boolean =
nativeCallAudioFeed(callId, samples, muted, capturePtsUs)
fun callAudioPull(callId: Long, out: ShortArray): Int = nativeCallAudioPull(callId, out)
fun callAudioProgress(callId: Long, epoch: Long, position: Long, atUs: Long, nowUs: Long, measured: Boolean) =
nativeCallAudioProgress(callId, epoch, position, atUs, nowUs, measured)
@@ -438,7 +440,7 @@ object NativeLib {
private external fun nativeSetPhoneState(state: Int, policy: Int)
private external fun nativeCallIsOnline(channelId: String, peerNodeId: Long): Boolean
private external fun nativeCallAudioStart(callId: Long): Boolean
private external fun nativeCallAudioFeed(callId: Long, samples: ShortArray, capturePtsUs: Long): Boolean
private external fun nativeCallAudioFeed(callId: Long, samples: ShortArray, muted: Boolean, capturePtsUs: Long): Boolean
private external fun nativeCallAudioPull(callId: Long, out: ShortArray): Int
private external fun nativeCallAudioProgress(callId: Long, epoch: Long, position: Long, atUs: Long, nowUs: Long, measured: Boolean)
private external fun nativeCallAudioClock(callId: Long, nowUs: Long, out: LongArray): Boolean