Capture encode and play stereo audio in Android live streams

This commit is contained in:
evgeny committed 2026-10-13 14:15:44 +02:00
1 parent 22b62699ac
commit e06e9b0e22
3 files changed
+42 -31

No files matched your search

@@ -345,11 +345,12 @@ class StreamCaptureEngine(
}
@SuppressLint("MissingPermission")
private fun startAudio() {
val min = AudioRecord.getMinBufferSize(48000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)
val min = AudioRecord.getMinBufferSize(48000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT)
check(min > 0)
val rec = AudioRecord(MediaRecorder.AudioSource.CAMCORDER, 48000, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, (min * 2).coerceAtLeast(960 * 8))
val rec = AudioRecord(MediaRecorder.AudioSource.CAMCORDER, 48000, AudioFormat.CHANNEL_IN_STEREO,
AudioFormat.ENCODING_PCM_16BIT, (min * 2).coerceAtLeast(1920 * 8))
record = rec; check(rec.state == AudioRecord.STATE_INITIALIZED)
check(rec.channelCount == 2) { "CAMCORDER stereo capture unavailable: channels=${rec.channelCount}" }
rec.startRecording(); check(rec.recordingState == AudioRecord.RECORDSTATE_RECORDING) { "CAMCORDER capture did not start" }
audioRunning = true
LogManager.addLog("INFO", "stream", "audio capture started id=$id source=CAMCORDER(${rec.audioSource}) " +
@@ -358,7 +359,7 @@ class StreamCaptureEngine(
var opus = 0L
try {
opus = NativeLib.streamOpusCreate(true); check(opus != 0L)
val pcm = ShortArray(960); val stamp = AudioTimestamp()
val pcm = ShortArray(1920); val stamp = AudioTimestamp()
var total = 0L; var waitingStamp = true; var last = -1L
while (audioRunning) {
var filled = 0
@@ -368,7 +369,7 @@ class StreamCaptureEngine(
if (n > 0) filled += n
}
if (!audioRunning || filled != pcm.size) break
val first = total; total += filled
val first = total; total += filled / 2 // AudioTimestamp считает кадры на канал, а read — interleaved сэмплы.
if (rec.getTimestamp(stamp, AudioTimestamp.TIMEBASE_MONOTONIC) != AudioRecord.SUCCESS) {
if (waitingStamp) LogManager.addLog("DEBUG", "stream", "audio timestamp pending id=$id samples=$total")
continue
@@ -117,7 +117,10 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
while (running && offset < pcm.size) {
val count = t.write(pcm, offset, pcm.size - offset, AudioTrack.WRITE_NON_BLOCKING)
check(count >= 0) { "AudioTrack write=$count" }
if (count > 0) { offset += count; written += count }
if (count > 0) {
check(count % 2 == 0) { "AudioTrack returned an incomplete stereo frame: samples=$count" }
offset += count; written += count / 2
}
else {
check(System.nanoTime() < deadline) { "AudioTrack stalled for 100ms" }
Thread.sleep(2)
@@ -207,13 +210,13 @@ class StreamPlayerEngine(private val group: Long, private val source: Long, priv
opus = NativeLib.streamOpusCreate(false); check(opus != 0L)
track = AudioTrack.Builder().setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build()).setAudioFormat(AudioFormat.Builder()
.setSampleRate(48000).setChannelMask(AudioFormat.CHANNEL_OUT_MONO).setEncoding(AudioFormat.ENCODING_PCM_16BIT).build())
.setBufferSizeInBytes(AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT)
.coerceAtLeast(9600)).setTransferMode(AudioTrack.MODE_STREAM).build()
.setSampleRate(48000).setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).setEncoding(AudioFormat.ENCODING_PCM_16BIT).build())
.setBufferSizeInBytes(AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT)
.coerceAtLeast(19200)).setTransferMode(AudioTrack.MODE_STREAM).build()
check(track!!.state == AudioTrack.STATE_INITIALIZED)
audioBase = audios.first.pts
track!!.play()
LogManager.addLog("INFO", "stream", "audio playback starts id=$id PTS=$audioBase")
LogManager.addLog("INFO", "stream", "audio playback starts id=$id PTS=$audioBase rate=48000 channels=${track!!.channelCount}")
}
val t = track
if (t != null && audios.isNotEmpty()) {