3.0 KiB
3.0 KiB
低延迟音频_kotlin_side
使用“低延迟模式”标志
val audioTrack = AudioTrack.Builder()
.setAudioAttributes(AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME) // 游戏用途延迟最低
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
// 请求低延迟硬件标志
.setFlags(AudioAttributes.FLAG_LOW_LATENCY)
.build())
.setAudioFormat(AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT) // TODO: 查看 scrcpy 官方仓库了解实际 PCM 参数 `B:\Git\scrcpy`
.setSampleRate(48000) // 匹配硬件原生采样率
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build())
// 性能模式设置为 LOW_LATENCY
.setBufferSizeInBytes(minBufferSize)
.setTransferMode(AudioTrack.MODE_STREAM)
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
.build()
// audioTrack.performanceMode 检查结果
匹配“原生采样率”与“缓冲区步长”
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
// 获取原生采样率 (通常是 48000)
val sampleRate = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)?.toInt() ?: 48000
// 获取硬件每秒处理的帧数步长 (Frames Per Burst)
val framesPerBurst = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)?.toInt() ?: 128
val minBuffer = AudioTrack.getMinBufferSize(
sampleRate,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT
)
// 16bit 2 bytes
// stereo 2 channels
// 双缓冲 2 个 burst
val targetBuffer = framesPerBurst * 2 * 2 * 2
val bufferSize = max(minBuffer, targetBuffer)
使用 WRITE_NON_BLOCKING // 已用
audioTrack 启动预填充静音音频 // 没必要做
查看 scrcpy-server 回传的 PCM 具体参数 // 先搁置
如果音频源支持,直接使用 AudioFormat.ENCODING_PCM_FLOAT
获取并应用系统“最优缓冲区大小”
// 获取硬件每一跳的帧数 (例如 128, 256 或 512)
val framesPerBurst = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER).toInt()
// 缓冲区设置为 2 倍的 Burst 帧数(双缓冲),这是理论上的延迟平衡点
val optimizedBufferSize = framesPerBurst * 2 * (if (isStereo) 2 else 1) * (if (isFloat) 4 else 2)
绕过音频效果器
.setAudioAttributes(AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setFlags(AudioAttributes.FLAG_LOW_LATENCY)
// 告诉系统不要过各种 DSP 效果器
.setAllowedCapturePolicy(AudioAttributes.ALLOW_CAPTURE_BY_NONE)
.build())
音频线程优先级
// 在负责读取 Socket 并写入 AudioTrack 的线程内执行
// 单独线程 + 无锁队列,跟网络线程分离
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO)
缓冲区复用减少 GC
val buffer = ByteArray(FIXED_SIZE)