stt(mic): усиление микрофона очков ×10 — VAD/Whisper видят речь (пустая фраза)

This commit is contained in:
2026-08-24 20:57:06 +03:00
parent 40edd88c1d
commit 0d79b5aa33
3 changed files with 187 additions and 3 deletions
@@ -7,6 +7,7 @@ import java.io.File
import java.io.FileInputStream
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -29,7 +30,7 @@ class SttMicStream(
) {
private var job: Job? = null
/** Debug-дампер сырого микрофона (открывается по маркеру stt_dump.marker). */
/** Debug-дампер микрофона: пишет УСИЛЕННЫЕ чанки — те же, что уходят на телефон (по маркеру stt_dump.marker). */
private var dump: WavWriter? = null
fun start(filesDir: File, shouldRun: () -> Boolean) {
@@ -62,7 +63,8 @@ class SttMicStream(
while (isActive && shouldRun()) {
val frames = recorder.read(buf, 0, CHUNK_BYTES / 2) // inFrameCount — КАДРЫ
if (frames > 0) {
val chunk = micChunkBytes(buf, frames)
val raw = micChunkBytes(buf, frames)
val chunk = gainChunk(raw) // усиление: в дамп и на телефон — один и тот же чанк
if (dumpWriter != null) {
runCatching { dumpWriter.write(chunk) }
.onFailure { log("stt", "dump микрофона: ${it.message}") }
@@ -91,7 +93,7 @@ class SttMicStream(
/**
* Debug-дампы (задача audio-dump): если в filesDir лежит stt_dump.marker,
* открыть stt_dump_HHmmss.wav — ВСЕ чанки живого микрофона допишутся туда
* открыть stt_dump_HHmmss.wav — ВСЕ усиленные чанки живого микрофона допишутся туда
* до отправки в onChunk (эмуляторную ветку не трогаем).
*/
private fun openLiveMicDump(filesDir: File): WavWriter? {
@@ -152,6 +154,33 @@ internal fun micChunkBytes(buf: ByteArray, frames: Int): ByteArray {
return if (bytes == buf.size) buf else buf.copyOf(bytes)
}
/** Коэффициент усиления микрофона (микрофон RayNeo X2 пишет пик 1–9% шкалы). */
internal const val MIC_GAIN = 10f
/**
* Цифровое усиление чанка s16le mono с насыщением.
* Каждый сэмпл: v = (s * gain).roundToInt().coerceIn(-32768, 32767).
* Размер чанка не меняется (3200 байт = 1600 сэмплов).
* Для gain = 1 не аллоцируем заново — возвращаем исходный массив.
*/
internal fun gainChunk(bytes: ByteArray, gain: Float = MIC_GAIN): ByteArray {
if (gain == 1f) return bytes
val out = ByteArray(bytes.size)
var i = 0
var j = 0
while (i + 1 < bytes.size) {
val lo = bytes[i].toInt() and 0xff
val hi = bytes[i + 1].toInt() and 0xff
i += 2
var s = lo or (hi shl 8)
if (s >= 0x8000) s -= 0x10000
val v = (s * gain).roundToInt().coerceIn(-32_768, 32_767)
out[j++] = (v and 0xff).toByte()
out[j++] = ((v shr 8) and 0xff).toByte()
}
return out
}
/** FloatArray [-1, 1] → s16le ByteArray (16 кГц, mono). */
internal fun floatSamplesToPcm16(samples: FloatArray): ByteArray {
val out = ByteArray(samples.size * 2)