Skip to content

Commit

Permalink
RecorderThread: Send input timestamps to MediaCodec encoders
Browse files Browse the repository at this point in the history
This was previously disabled due to incorrect troubleshooting of an
issue where the `c2.android.flac.encoder` component would crash during
recording. It turns out the crash was caused by incorrect math:

    inputTimestamp += frames * 1000000 / audioRecord.sampleRate

`frames` and `1000000` are both 32-bit integers, so the multiplication
result overflowed and caused `inputTimestamp` (a 64-bit integer) to
accumulate negative values, which would crash the encoder. This bug was
inadvertently fixed during refactorying in commit
3d3eb82.

This commit reenables the submission of timestamps to MediaCodec. To
avoid accumulated error due to integer division when the microseconds
per sample count is not perfectly divisible, the recording loop will
keep track of the total frame count and only calculate the timestamp
during buffer submission.

This should fix issues where the encoded output files had overlapping
audio or other weird audible artifacts. These were caused by
MediaCodec's multithreaded encoding, where the lack of timestamps would
cause encoded samples to be produced out of order.

Fixes: #39 #54
Signed-off-by: Andrew Gunnerson <[email protected]>
  • Loading branch information
chenxiaolong committed Jun 1, 2022
1 parent b398835 commit fb920f0
Showing 1 changed file with 19 additions and 22 deletions.
41 changes: 19 additions & 22 deletions app/src/main/java/com/chiller3/bcr/RecorderThread.kt
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ class RecorderThread(
mediaFormat: MediaFormat,
container: Container,
) {
var inputTimestamp = 0L
var numFrames = 0L
val bufferInfo = MediaCodec.BufferInfo()
val frameSize = audioRecord.format.frameSizeInBytesCompat

Expand All @@ -364,25 +364,24 @@ class RecorderThread(
logE( "Unexpected EOF from AudioRecord")
isCancelled = true
} else {
val frames = n / frameSize
inputTimestamp += frames * 1_000_000L / audioRecord.sampleRate

bufferInfo.offset = 0
bufferInfo.size = buffer.limit()
bufferInfo.presentationTimeUs = 0
bufferInfo.presentationTimeUs = numFrames * 1_000_000L / audioRecord.sampleRate
bufferInfo.flags = if (isCancelled) {
MediaCodec.BUFFER_FLAG_END_OF_STREAM
} else {
0
}

numFrames += n / frameSize

container.writeSamples(trackIndex, buffer, bufferInfo)
buffer.clear()
}

if (isCancelled) {
val duration = "%.1f".format(inputTimestamp / 1_000_000.0)
logD("Input complete after ${duration}s")
val durationSecs = numFrames.toDouble() / audioRecord.sampleRate
logD("Input complete after ${"%.1f".format(durationSecs)}s")
break
}
}
Expand All @@ -407,7 +406,7 @@ class RecorderThread(
* @throws MediaCodec.CodecException if the codec encounters an error
*/
private fun encodeLoop(audioRecord: AudioRecord, mediaCodec: MediaCodec, container: Container) {
var inputTimestamp = 0L
var numFrames = 0L
var inputComplete = false
val bufferInfo = MediaCodec.BufferInfo()
val frameSize = audioRecord.format.frameSizeInBytesCompat
Expand Down Expand Up @@ -436,25 +435,23 @@ class RecorderThread(
logE( "MediaCodec's ByteBuffer was not a direct buffer")
isCancelled = true
} else {
val frames = n / frameSize
inputTimestamp += frames * 1_000_000L / audioRecord.sampleRate
val flags = if (isCancelled) {
MediaCodec.BUFFER_FLAG_END_OF_STREAM
} else {
0
}

val timestampUs = numFrames * 1_000_000L / audioRecord.sampleRate
mediaCodec.queueInputBuffer(inputBufferId, 0, n, timestampUs, flags)

numFrames += n / frameSize
}

if (isCancelled) {
val duration = "%.1f".format(inputTimestamp / 1_000_000.0)
logD("Input complete after ${duration}s")
val durationSecs = numFrames.toDouble() / audioRecord.sampleRate
logD("Input complete after ${"%.1f".format(durationSecs)}s")
inputComplete = true
}

val flags = if (inputComplete) {
MediaCodec.BUFFER_FLAG_END_OF_STREAM
} else {
0
}

// Setting the presentation timestamp will cause `c2.android.flac.encoder`
// software encoder to crash with SIGABRT
mediaCodec.queueInputBuffer(inputBufferId, 0, n, 0, flags)
} else if (inputBufferId != MediaCodec.INFO_TRY_AGAIN_LATER) {
logW("Unexpected input buffer dequeue error: $inputBufferId")
}
Expand Down

0 comments on commit fb920f0

Please sign in to comment.