Skip to content

AudioTrack 音频处理实战

源:Android Audio Performance

使用 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.kts

cinterop 配置

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 -llog

Gradle 配置

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-120ms5%
C++ + Oboe10-20ms6%
Kotlin/Native + AAudio10-20ms6%