Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AMR-WB and AMR-NB #497

Merged
merged 1 commit into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions app/src/main/java/com/chiller3/bcr/IoHelpers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.chiller3.bcr

import android.system.Os
import java.io.FileDescriptor
import java.io.IOException
import java.nio.ByteBuffer

// These can't be extension methods (yet): https://github.com/Kotlin/KEEP/issues/348.

fun writeFully(fd: FileDescriptor, buffer: ByteBuffer) {
while (buffer.remaining() > 0) {
val n = Os.write(fd, buffer)
if (n == 0) {
throw IOException("Unexpected EOF when writing data")
}
}
}

fun writeFully(fd: FileDescriptor, bytes: ByteArray, byteOffset: Int, byteCount: Int) {
var offset = byteOffset
var remaining = byteCount

while (remaining > 0) {
val n = Os.write(fd, bytes, offset, remaining)
if (n == 0) {
throw IOException("Unexpected EOF when writing data")
}

offset += n
remaining -= n
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/com/chiller3/bcr/RecorderThread.kt
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ class RecorderThread(

val metadataFile = dirUtils.createFileInOutputDir(path, MIME_METADATA)
dirUtils.openFile(metadataFile, true).use {
Os.write(it.fileDescriptor, metadataBytes, 0, metadataBytes.size)
writeFully(it.fileDescriptor, metadataBytes, 0, metadataBytes.size)
}

return OutputFile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ class FormatParamDialogFragment : DialogFragment() {

val multiplier = when (paramInfo.type) {
RangedParamType.CompressionLevel -> 1U
RangedParamType.Bitrate -> 1000U
RangedParamType.Bitrate -> {
if (paramInfo.range.first % 1_000U == 0U && paramInfo.range.last % 1_000U == 0U) {
1000U
} else {
1U
}
}
}

binding = DialogTextInputBinding.inflate(layoutInflater)
Expand All @@ -60,8 +66,11 @@ class FormatParamDialogFragment : DialogFragment() {
val translated = when (paramInfo.type) {
RangedParamType.CompressionLevel ->
getString(R.string.format_param_compression_level, "\u0000")
RangedParamType.Bitrate ->
getString(R.string.format_param_bitrate, "\u0000")
RangedParamType.Bitrate -> if (multiplier == 1_000U) {
getString(R.string.format_param_bitrate_kbps, "\u0000")
} else {
getString(R.string.format_param_bitrate_bps, "\u0000")
}
}
val placeholder = translated.indexOf('\u0000')
val hasPrefix = placeholder > 0
Expand Down
75 changes: 75 additions & 0 deletions app/src/main/java/com/chiller3/bcr/format/AmrContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@file:OptIn(ExperimentalUnsignedTypes::class)

package com.chiller3.bcr.format

import android.media.MediaCodec
import android.media.MediaFormat
import android.system.Os
import com.chiller3.bcr.writeFully
import java.io.FileDescriptor
import java.nio.ByteBuffer

class AmrContainer(private val fd: FileDescriptor, private val isWideband: Boolean) : Container {
private var isStarted = false
private var track = -1

override fun start() {
if (isStarted) {
throw IllegalStateException("Container already started")
}

Os.ftruncate(fd, 0)

val header = if (isWideband) { HEADER_WB } else { HEADER_NB }
val headerBytes = header.toByteArray(Charsets.US_ASCII)

writeFully(fd, headerBytes, 0, headerBytes.size)

isStarted = true
}

override fun stop() {
if (!isStarted) {
throw IllegalStateException("Container not started")
}

isStarted = false
}

override fun release() {
if (isStarted) {
stop()
}
}

override fun addTrack(mediaFormat: MediaFormat): Int {
if (isStarted) {
throw IllegalStateException("Container already started")
} else if (track >= 0) {
throw IllegalStateException("Track already added")
}

track = 0

@Suppress("KotlinConstantConditions")
return track
}

override fun writeSamples(trackIndex: Int, byteBuffer: ByteBuffer,
bufferInfo: MediaCodec.BufferInfo) {
if (!isStarted) {
throw IllegalStateException("Container not started")
} else if (track < 0) {
throw IllegalStateException("No track has been added")
} else if (track != trackIndex) {
throw IllegalStateException("Invalid track: $trackIndex")
}

writeFully(fd, byteBuffer)
}

companion object {
private const val HEADER_WB = "#!AMR-WB\n"
private const val HEADER_NB = "#!AMR\n"
}
}
39 changes: 39 additions & 0 deletions app/src/main/java/com/chiller3/bcr/format/AmrNbFormat.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@file:OptIn(ExperimentalUnsignedTypes::class)

package com.chiller3.bcr.format

import android.media.MediaFormat
import java.io.FileDescriptor

data object AmrNbFormat : Format() {
override val name: String = "AMR-NB"
override val paramInfo: FormatParamInfo = RangedParamInfo(
RangedParamType.Bitrate,
4_750u..12_200u,
12_200u,
// AMR-NB only supports 8 possible bit rates. If the user picks a bit rate that's not one
// of the 8 possibilities, then Android will fall back to 7950 bits/second.
uintArrayOf(
4_750u,
7_950u,
12_200u,
),
)
override val sampleRateInfo: SampleRateInfo = DiscreteSampleRateInfo(
uintArrayOf(8_000u),
8_000u,
)
override val mimeTypeContainer: String = "audio/amr"
override val mimeTypeAudio: String = MediaFormat.MIMETYPE_AUDIO_AMR_NB
override val passthrough: Boolean = false
override val supported: Boolean = true

override fun updateMediaFormat(mediaFormat: MediaFormat, param: UInt) {
mediaFormat.apply {
val channelCount = getInteger(MediaFormat.KEY_CHANNEL_COUNT)
setInteger(MediaFormat.KEY_BIT_RATE, param.toInt() * channelCount)
}
}

override fun getContainer(fd: FileDescriptor): Container = AmrContainer(fd, false)
}
39 changes: 39 additions & 0 deletions app/src/main/java/com/chiller3/bcr/format/AmrWbFormat.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@file:OptIn(ExperimentalUnsignedTypes::class)

package com.chiller3.bcr.format

import android.media.MediaFormat
import java.io.FileDescriptor

data object AmrWbFormat : Format() {
override val name: String = "AMR-WB"
override val paramInfo: FormatParamInfo = RangedParamInfo(
RangedParamType.Bitrate,
6_600u..23_850u,
23_850u,
// AMR-WB only supports 9 possible bit rates. If the user picks a bit rate that's not one
// of the 9 possibilities, then Android will fall back to 23050 bits/second.
uintArrayOf(
6_600u,
15_850u,
23_850u,
),
)
override val sampleRateInfo: SampleRateInfo = DiscreteSampleRateInfo(
uintArrayOf(16_000u),
16_000u,
)
override val mimeTypeContainer: String = MediaFormat.MIMETYPE_AUDIO_AMR_WB
override val mimeTypeAudio: String = mimeTypeContainer
override val passthrough: Boolean = false
override val supported: Boolean = true

override fun updateMediaFormat(mediaFormat: MediaFormat, param: UInt) {
mediaFormat.apply {
val channelCount = getInteger(MediaFormat.KEY_CHANNEL_COUNT)
setInteger(MediaFormat.KEY_BIT_RATE, param.toInt() * channelCount)
}
}

override fun getContainer(fd: FileDescriptor): Container = AmrContainer(fd, true)
}
7 changes: 3 additions & 4 deletions app/src/main/java/com/chiller3/bcr/format/FlacContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.media.MediaFormat
import android.system.Os
import android.system.OsConstants
import android.util.Log
import com.chiller3.bcr.writeFully
import java.io.FileDescriptor
import java.io.IOException
import java.nio.ByteBuffer
Expand Down Expand Up @@ -76,7 +77,7 @@ class FlacContainer(private val fd: FileDescriptor) : Container {
throw IllegalStateException("Invalid track: $trackIndex")
}

Os.write(fd, byteBuffer)
writeFully(fd, byteBuffer)

if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
lastPresentationTimeUs = bufferInfo.presentationTimeUs
Expand Down Expand Up @@ -140,9 +141,7 @@ class FlacContainer(private val fd: FileDescriptor) : Container {
buf[25] = (frames and 0xffu).toUByte()

Os.lseek(fd, 21, OsConstants.SEEK_SET)
if (Os.write(fd, buf.asByteArray(), 21, 5) != 5) {
throw IOException("EOF reached when writing frame count")
}
writeFully(fd, buf.asByteArray(), 21, 5)
}

companion object {
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/java/com/chiller3/bcr/format/Format.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@ sealed class Format {
companion object {
const val KEY_X_FRAME_SIZE_IN_BYTES = "x-frame-size-in-bytes"

val all: Array<Format> = arrayOf(OpusFormat, AacFormat, FlacFormat, WaveFormat)
val all: Array<Format> = arrayOf(
OpusFormat,
AacFormat,
FlacFormat,
WaveFormat,
AmrWbFormat,
AmrNbFormat,
)
private val default: Format = all.first { it.supported }

/** Find output format by name. */
Expand Down
11 changes: 8 additions & 3 deletions app/src/main/java/com/chiller3/bcr/format/FormatParamInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,17 @@ class RangedParamInfo(
when (type) {
RangedParamType.CompressionLevel ->
context.getString(R.string.format_param_compression_level, param.toString())
RangedParamType.Bitrate ->
context.getString(R.string.format_param_bitrate, (param / 1_000U).toString())
RangedParamType.Bitrate -> {
if (param % 1_000U == 0U) {
context.getString(R.string.format_param_bitrate_kbps, (param / 1_000U).toString())
} else {
context.getString(R.string.format_param_bitrate_bps, param.toString())
}
}
}
}

object NoParamInfo : FormatParamInfo(0u, uintArrayOf()) {
data object NoParamInfo : FormatParamInfo(0u, uintArrayOf()) {
override fun validate(param: UInt) {
// Always valid
}
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/chiller3/bcr/format/WaveContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.media.MediaCodec
import android.media.MediaFormat
import android.system.Os
import android.system.OsConstants
import com.chiller3.bcr.writeFully
import java.io.FileDescriptor
import java.nio.ByteBuffer
import java.nio.ByteOrder
Expand Down Expand Up @@ -41,7 +42,7 @@ class WaveContainer(private val fd: FileDescriptor) : Container {
val fileSize = Os.lseek(fd, 0, OsConstants.SEEK_CUR)
val header = buildHeader(fileSize)
Os.lseek(fd, 0, OsConstants.SEEK_SET)
Os.write(fd, header)
writeFully(fd, header)
}
}

Expand Down Expand Up @@ -76,7 +77,7 @@ class WaveContainer(private val fd: FileDescriptor) : Container {
throw IllegalStateException("Invalid track: $trackIndex")
}

Os.write(fd, byteBuffer)
writeFully(fd, byteBuffer)
}

private fun buildHeader(fileSize: Long): ByteBuffer =
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<string name="format_param_dialog_message">Gebe einen Wert von [%1$s, %2$s] ein.</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">Komprimierungsstufe %s</string>

<!-- Notifications -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
<string name="format_param_dialog_message">Entrer une valeur dans l\'intervalle [%1$s, %2$s].</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">Niveau %s</string>

<!-- Notifications -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-hi/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
<string name="format_param_dialog_message">[%1$s, %2$s] श्रेणी में एक मान दर्ज करें।</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">लेवल %s</string>

<!-- Notifications -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
<string name="format_param_dialog_message">Inserisci un valore nel range [%1$s, %2$s].</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">Livello %s</string>

<!-- Notifications -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-iw/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Supported variables: <annotation type="supported_vars">PLACEHOLDER</annotation>.
<string name="format_param_dialog_message">הזן ערך בטווח [%1$s, %2$s].</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">רמה %s</string>

<!-- Notifications -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<string name="format_param_dialog_message">Wprowadź wartość z zakresu [%1$s, %2$s].</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">Poziom %s</string>

<!-- Notifications -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-pt-rPT/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
<string name="format_param_dialog_message">Introduza um valor no intervalo [%1$s, %2$s].</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">Nível %s</string>

<!-- Notifications -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
<string name="format_param_dialog_message">Введите значение в диапазоне [%1$s, %2$s].</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">Уровень %s</string>

<!-- Notifications -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-sk/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
<string name="format_param_dialog_message">Zadajte hodnotu z rozsahu [%1$s, %2$s].</string>

<!-- Format parameter -->
<string name="format_param_bitrate">%s kbps</string>
<string name="format_param_bitrate_kbps">%s kbps</string>
<string name="format_param_compression_level">Úroveň %s</string>

<!-- Notifications -->
Expand Down
Loading
Loading