AudioTrack 音频处理实战
使用 Kotlin/Native 通过 cinterop 直接调用 AAudio API,实现低延迟音频播放。本文展示如何用纯 Kotlin 代码操作音频硬件,而非传统的 C++ 和 Oboe 库。
Kotlin/Native vs 传统方式
传统 Android 低延迟音频需要 C++ 和 Oboe,而 Kotlin/Native 可以直接调用 AAudio:
kotlin
// ❌ 传统方式:C++ + Oboe
// oboe::AudioStreamBuilder builder;
// builder.setDirection(oboe::Direction::Output)
// ->setSharingMode(oboe::SharingMode::Exclusive);
// ✅ Kotlin/Native:纯 Kotlin 调用 AAudio
val builder = AAudio_createStreamBuilder()
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT)
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE)项目架构
AudioNativeDemo/
├── src/
│ ├── androidNativeArm64Main/kotlin/
│ │ ├── AAudioStream.kt # AAudio 流管理
│ │ ├── AudioGenerator.kt # 音频生成器
│ │ ├── RingBuffer.kt # 环形缓冲区
│ │ └── JniBridge.kt # JNI 桥接
│ └── nativeInterop/cinterop/
│ └── aaudio.def # AAudio 定义
└── build.gradle.ktscinterop 配置
aaudio.def 定义文件
# src/nativeInterop/cinterop/aaudio.def
headers = aaudio/AAudio.h
headerFilter = aaudio/*
libraryPaths = /path/to/ndk/platforms/android-26/arch-arm64/usr/lib
linkerOpts = -laaudio -llogGradle 配置
kotlin
// build.gradle.kts
kotlin {
androidNativeArm64 {
compilations.getByName("main") {
cinterops {
val aaudio by creating {
defFile(project.file("src/nativeInterop/cinterop/aaudio.def"))
packageName("platform.aaudio")
}
}
}
binaries {
sharedLib {
baseName = "audioengine"
}
}
}
}AAudio 流管理
Kotlin/Native 实现
kotlin
// src/androidNativeArm64Main/kotlin/AAudioStream.kt
@file:OptIn(ExperimentalForeignApi::class)
package com.example.audio
import kotlinx.cinterop.*
import platform.aaudio.*
import kotlin.math.PI
import kotlin.math.sin
class AAudioStream {
private var stream: AAudioStreamPtr? = null
private var sampleRate: Int = 48000
private var isPlaying = false
// 音频参数
private var frequency = 440.0 // A4 音符
private var amplitude = 0.5
private var phase = 0.0
@Volatile
private var volume = 1.0f
fun start(): Boolean {
memScoped {
// 创建流构建器
val builderPtr = alloc<AAudioStreamBuilderPtrVar>()
var result = AAudio_createStreamBuilder(builderPtr.ptr)
if (result != AAUDIO_OK) {
logError("Failed to create stream builder: ${AAudio_convertResultToText(result)?.toKString()}")
return false
}
val builder = builderPtr.value!!
// 配置低延迟输出流
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT)
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY)
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE)
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT)
AAudioStreamBuilder_setChannelCount(builder, 2) // 立体声
AAudioStreamBuilder_setSampleRate(builder, sampleRate)
// 设置数据回调
val callback = staticCFunction(::audioCallback)
AAudioStreamBuilder_setDataCallback(builder, callback, StableRef.create(this).asCPointer())
// 设置错误回调
val errorCallback = staticCFunction(::errorCallback)
AAudioStreamBuilder_setErrorCallback(builder, errorCallback, StableRef.create(this).asCPointer())
// 打开流
val streamPtr = alloc<AAudioStreamPtrVar>()
result = AAudioStreamBuilder_openStream(builder, streamPtr.ptr)
AAudioStreamBuilder_delete(builder)
if (result != AAUDIO_OK) {
logError("Failed to open stream: ${AAudio_convertResultToText(result)?.toKString()}")
return false
}
stream = streamPtr.value
// 获取实际采样率
sampleRate = AAudioStream_getSampleRate(stream)
logInfo("AAudio stream created:")
logInfo(" Sample rate: $sampleRate Hz")
logInfo(" Buffer capacity: ${AAudioStream_getBufferCapacityInFrames(stream)} frames")
logInfo(" Frames per burst: ${AAudioStream_getFramesPerBurst(stream)}")
logInfo(" Performance mode: ${AAudioStream_getPerformanceMode(stream)}")
// 启动流
result = AAudioStream_requestStart(stream)
if (result != AAUDIO_OK) {
logError("Failed to start stream: ${AAudio_convertResultToText(result)?.toKString()}")
AAudioStream_close(stream)
stream = null
return false
}
isPlaying = true
logInfo("AAudio stream started")
return true
}
}
fun stop() {
stream?.let { s ->
isPlaying = false
AAudioStream_requestStop(s)
AAudioStream_close(s)
stream = null
logInfo("AAudio stream stopped")
}
}
fun pause() {
stream?.let { s ->
isPlaying = false
AAudioStream_requestPause(s)
}
}
fun resume() {
stream?.let { s ->
isPlaying = true
AAudioStream_requestStart(s)
}
}
fun setVolume(vol: Float) {
volume = vol.coerceIn(0f, 1f)
}
fun setFrequency(freq: Double) {
frequency = freq.coerceIn(20.0, 20000.0)
}
fun getSampleRate(): Int = sampleRate
// 音频数据生成
internal fun generateAudio(buffer: CPointer<FloatVar>, numFrames: Int, channelCount: Int) {
if (!isPlaying) {
// 静音
for (i in 0 until numFrames * channelCount) {
buffer[i] = 0f
}
return
}
val phaseIncrement = 2.0 * PI * frequency / sampleRate
val vol = volume * amplitude
for (i in 0 until numFrames) {
val sample = (sin(phase) * vol).toFloat()
// 立体声输出
buffer[i * channelCount] = sample // 左声道
buffer[i * channelCount + 1] = sample // 右声道
phase += phaseIncrement
if (phase > 2.0 * PI) {
phase -= 2.0 * PI
}
}
}
// 错误处理
internal fun handleError(error: aaudio_result_t) {
logError("AAudio stream error: ${AAudio_convertResultToText(error)?.toKString()}")
// 尝试重启
stop()
start()
}
}
// 音频数据回调函数
private fun audioCallback(
stream: AAudioStreamPtr?,
userData: COpaquePointer?,
audioData: COpaquePointer?,
numFrames: Int
): aaudio_data_callback_result_t {
val audioStream = userData?.asStableRef<AAudioStream>()?.get() ?: return AAUDIO_CALLBACK_RESULT_STOP
val channelCount = AAudioStream_getChannelCount(stream)
val buffer = audioData?.reinterpret<FloatVar>() ?: return AAUDIO_CALLBACK_RESULT_STOP
audioStream.generateAudio(buffer, numFrames, channelCount)
return AAUDIO_CALLBACK_RESULT_CONTINUE
}
// 错误回调函数
private fun errorCallback(
stream: AAudioStreamPtr?,
userData: COpaquePointer?,
error: aaudio_result_t
) {
val audioStream = userData?.asStableRef<AAudioStream>()?.get() ?: return
audioStream.handleError(error)
}环形缓冲区
无锁环形缓冲区实现
kotlin
// src/androidNativeArm64Main/kotlin/RingBuffer.kt
@file:OptIn(ExperimentalForeignApi::class)
package com.example.audio
import kotlin.concurrent.AtomicInt
class RingBuffer<T>(private val capacity: Int, private val factory: () -> T) {
private val buffer = Array(capacity) { factory() }
private val writeIndex = AtomicInt(0)
private val readIndex = AtomicInt(0)
fun write(items: List<T>): Int {
var written = 0
val available = writeAvailable()
val toWrite = minOf(items.size, available)
var wIdx = writeIndex.value
for (i in 0 until toWrite) {
buffer[wIdx] = items[i]
wIdx = (wIdx + 1) % capacity
written++
}
writeIndex.value = wIdx
return written
}
fun read(count: Int): List<T> {
val available = readAvailable()
val toRead = minOf(count, available)
val result = mutableListOf<T>()
var rIdx = readIndex.value
for (i in 0 until toRead) {
result.add(buffer[rIdx])
rIdx = (rIdx + 1) % capacity
}
readIndex.value = rIdx
return result
}
fun readAvailable(): Int {
val w = writeIndex.value
val r = readIndex.value
return if (w >= r) w - r else capacity - r + w
}
fun writeAvailable(): Int {
return capacity - readAvailable() - 1
}
fun clear() {
readIndex.value = writeIndex.value
}
}
// Float 专用版本,更高效
class FloatRingBuffer(private val capacity: Int) {
private val buffer = FloatArray(capacity)
private val writeIndex = AtomicInt(0)
private val readIndex = AtomicInt(0)
fun write(data: FloatArray, offset: Int = 0, count: Int = data.size): Int {
val available = writeAvailable()
val toWrite = minOf(count, available)
var wIdx = writeIndex.value
for (i in 0 until toWrite) {
buffer[wIdx] = data[offset + i]
wIdx = (wIdx + 1) % capacity
}
writeIndex.value = wIdx
return toWrite
}
fun read(data: FloatArray, offset: Int = 0, count: Int = data.size): Int {
val available = readAvailable()
val toRead = minOf(count, available)
var rIdx = readIndex.value
for (i in 0 until toRead) {
data[offset + i] = buffer[rIdx]
rIdx = (rIdx + 1) % capacity
}
readIndex.value = rIdx
return toRead
}
fun readAvailable(): Int {
val w = writeIndex.value
val r = readIndex.value
return if (w >= r) w - r else capacity - r + w
}
fun writeAvailable(): Int = capacity - readAvailable() - 1
}音频合成器
多音色合成器
kotlin
// src/androidNativeArm64Main/kotlin/AudioGenerator.kt
@file:OptIn(ExperimentalForeignApi::class)
package com.example.audio
import kotlin.math.*
enum class Waveform {
SINE, SQUARE, SAWTOOTH, TRIANGLE
}
class Oscillator(
var frequency: Double = 440.0,
var amplitude: Double = 0.5,
var waveform: Waveform = Waveform.SINE
) {
private var phase = 0.0
fun generate(sampleRate: Int, numSamples: Int): FloatArray {
val output = FloatArray(numSamples)
val phaseIncrement = 2.0 * PI * frequency / sampleRate
for (i in 0 until numSamples) {
output[i] = (generateSample() * amplitude).toFloat()
phase += phaseIncrement
if (phase > 2.0 * PI) phase -= 2.0 * PI
}
return output
}
private fun generateSample(): Double {
return when (waveform) {
Waveform.SINE -> sin(phase)
Waveform.SQUARE -> if (phase < PI) 1.0 else -1.0
Waveform.SAWTOOTH -> (phase / PI) - 1.0
Waveform.TRIANGLE -> {
val normalized = phase / PI
if (normalized < 1.0) 2.0 * normalized - 1.0
else 3.0 - 2.0 * normalized
}
}
}
}
// ADSR 包络
class ADSREnvelope(
var attackTime: Float = 0.01f,
var decayTime: Float = 0.1f,
var sustainLevel: Float = 0.7f,
var releaseTime: Float = 0.3f
) {
private var state = State.IDLE
private var level = 0f
private var sampleRate = 48000
enum class State { IDLE, ATTACK, DECAY, SUSTAIN, RELEASE }
fun noteOn() {
state = State.ATTACK
level = 0f
}
fun noteOff() {
state = State.RELEASE
}
fun process(numSamples: Int): FloatArray {
val output = FloatArray(numSamples)
for (i in 0 until numSamples) {
output[i] = level
when (state) {
State.ATTACK -> {
level += 1f / (attackTime * sampleRate)
if (level >= 1f) {
level = 1f
state = State.DECAY
}
}
State.DECAY -> {
level -= (1f - sustainLevel) / (decayTime * sampleRate)
if (level <= sustainLevel) {
level = sustainLevel
state = State.SUSTAIN
}
}
State.SUSTAIN -> {
// 保持 sustain level
}
State.RELEASE -> {
level -= sustainLevel / (releaseTime * sampleRate)
if (level <= 0f) {
level = 0f
state = State.IDLE
}
}
State.IDLE -> {
level = 0f
}
}
}
return output
}
}JNI 桥接
kotlin
// src/androidNativeArm64Main/kotlin/JniBridge.kt
@file:OptIn(ExperimentalForeignApi::class)
package com.example.audio
import kotlinx.cinterop.*
import platform.android.*
private var audioStream: AAudioStream? = null
@CName("Java_com_example_audio_AudioEngine_nativeStart")
fun nativeStart(env: CPointer<JNIEnvVar>, thiz: jobject): jboolean {
if (audioStream == null) {
audioStream = AAudioStream()
}
return if (audioStream?.start() == true) JNI_TRUE.toByte() else JNI_FALSE.toByte()
}
@CName("Java_com_example_audio_AudioEngine_nativeStop")
fun nativeStop(env: CPointer<JNIEnvVar>, thiz: jobject) {
audioStream?.stop()
audioStream = null
}
@CName("Java_com_example_audio_AudioEngine_nativePause")
fun nativePause(env: CPointer<JNIEnvVar>, thiz: jobject) {
audioStream?.pause()
}
@CName("Java_com_example_audio_AudioEngine_nativeResume")
fun nativeResume(env: CPointer<JNIEnvVar>, thiz: jobject) {
audioStream?.resume()
}
@CName("Java_com_example_audio_AudioEngine_nativeSetVolume")
fun nativeSetVolume(env: CPointer<JNIEnvVar>, thiz: jobject, volume: jfloat) {
audioStream?.setVolume(volume)
}
@CName("Java_com_example_audio_AudioEngine_nativeSetFrequency")
fun nativeSetFrequency(env: CPointer<JNIEnvVar>, thiz: jobject, frequency: jdouble) {
audioStream?.setFrequency(frequency)
}
@CName("Java_com_example_audio_AudioEngine_nativeGetSampleRate")
fun nativeGetSampleRate(env: CPointer<JNIEnvVar>, thiz: jobject): jint {
return audioStream?.getSampleRate() ?: 0
}
// 日志
private fun logInfo(message: String) {
__android_log_print(ANDROID_LOG_INFO.toInt(), "AudioEngine", "%s", message)
}
private fun logError(message: String) {
__android_log_print(ANDROID_LOG_ERROR.toInt(), "AudioEngine", "%s", message)
}Android Kotlin 层
kotlin
// AudioEngine.kt (Android 模块)
package com.example.audio
class AudioEngine {
fun start(): Boolean = nativeStart()
fun stop() = nativeStop()
fun pause() = nativePause()
fun resume() = nativeResume()
var volume: Float = 1.0f
set(value) {
field = value.coerceIn(0f, 1f)
nativeSetVolume(field)
}
var frequency: Double = 440.0
set(value) {
field = value.coerceIn(20.0, 20000.0)
nativeSetFrequency(field)
}
val sampleRate: Int get() = nativeGetSampleRate()
private external fun nativeStart(): Boolean
private external fun nativeStop()
private external fun nativePause()
private external fun nativeResume()
private external fun nativeSetVolume(volume: Float)
private external fun nativeSetFrequency(frequency: Double)
private external fun nativeGetSampleRate(): Int
companion object {
init {
System.loadLibrary("audioengine")
}
}
}
// 使用示例
class MainActivity : AppCompatActivity() {
private val audioEngine = AudioEngine()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findViewById<Button>(R.id.btnStart).setOnClickListener {
audioEngine.start()
}
findViewById<SeekBar>(R.id.frequencySlider).setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
// 20Hz - 2000Hz
audioEngine.frequency = 20.0 + progress * 19.8
}
}
)
}
override fun onDestroy() {
audioEngine.stop()
super.onDestroy()
}
}性能对比
| 实现方式 | 往返延迟 | CPU 占用 | 代码复杂度 |
|---|---|---|---|
| AudioTrack (Java) | 80-120ms | 5% | 低 |
| C++ + Oboe | 10-20ms | 6% | 高 |
| Kotlin/Native + AAudio | 10-20ms | 6% | 中 |