You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin开发蓝牙输出双正弦拍频APP:AudioTrack替代及无限播放方案

Android拍频生成与音频播放优化方案

1. 替代过时的AudioTrack构造函数

官方已废弃旧的AudioTrack构造方法,推荐使用AudioTrack.Builder构建实例,它能更灵活地配置音频参数,符合现代Android API规范。

2. 实现无限播放的核心思路

原代码采用MODE_STATIC一次性加载所有采样数据,无法支持无限播放。改用MODE_STREAM模式,在后台线程实时生成并写入音频数据,通过一个可控制的布尔标记变量来启停播放流程。

3. 拍频生成逻辑

拍频由两个不同频率的正弦波叠加而成,计算公式为:sample = A*(sin(2πf1*t) + sin(2πf2*t)),计算后需做归一化处理(除以2),避免音频溢出失真。

4. 蓝牙音频输出配置

通过AudioAttributes指定音频用途为USAGE_MEDIA、内容类型为CONTENT_TYPE_MUSIC,系统会自动将音频路由到已连接的蓝牙设备;也可配合AudioManager强制切换到蓝牙音频路径。

完整实现代码

import android.media.AudioAttributes
import android.media.AudioManager
import android.media.AudioTrack
import kotlin.math.PI
import kotlin.math.sin
import kotlinx.coroutines.*

class BeatFrequencyPlayer {
    private val sampleRate = 44100
    private val channelConfig = AudioFormat.CHANNEL_OUT_MONO
    private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
    private var audioTrack: AudioTrack? = null
    private var isPlaying = false
    private var playJob: Job? = null

    // 初始化AudioTrack实例
    private fun initAudioTrack() {
        val bufferSize = AudioTrack.getMinBufferSize(
            sampleRate,
            channelConfig,
            audioFormat
        )

        val audioAttributes = AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build()

        audioTrack = AudioTrack.Builder()
            .setAudioAttributes(audioAttributes)
            .setAudioFormat(
                AudioFormat.Builder()
                    .setSampleRate(sampleRate)
                    .setEncoding(audioFormat)
                    .setChannelMask(channelConfig)
                    .build()
            )
            .setBufferSizeInBytes(bufferSize)
            .setTransferMode(AudioTrack.MODE_STREAM)
            .build()
    }

    // 启动拍频播放
    fun startBeatPlayback(freq1: Int, freq2: Int) {
        if (isPlaying) return
        isPlaying = true
        initAudioTrack()
        audioTrack?.play()

        playJob = CoroutineScope(Dispatchers.IO).launch {
            val buffer = ShortArray(sampleRate / 10) // 每次生成100ms的音频数据
            val angularFreq1 = 2 * PI * freq1.toDouble()
            val angularFreq2 = 2 * PI * freq2.toDouble()
            var sampleIndex = 0L

            while (isPlaying) {
                for (i in buffer.indices) {
                    val time = sampleIndex.toDouble() / sampleRate
                    val sample1 = sin(angularFreq1 * time)
                    val sample2 = sin(angularFreq2 * time)
                    // 叠加后归一化,防止音频溢出
                    val combinedSample = (sample1 + sample2) * Short.MAX_VALUE / 2
                    buffer[i] = combinedSample.toInt().toShort()
                    sampleIndex++
                }
                audioTrack?.write(buffer, 0, buffer.size)
            }
        }
    }

    // 停止播放并释放资源
    fun stopPlayback() {
        isPlaying = false
        playJob?.cancel()
        audioTrack?.run {
            stop()
            release()
        }
        audioTrack = null
    }
}

使用说明

  • 在Activity/Fragment中实例化BeatFrequencyPlayer,点击播放按钮调用startBeatPlayback(freq1, freq2),点击停止按钮调用stopPlayback()。
  • 如需强制蓝牙输出,可额外添加以下代码:
    val audioManager = getSystemService(AudioManager::class.java)
    audioManager.isBluetoothScoOn = true
    audioManager.startBluetoothSco()
    
    停止播放时记得调用audioManager.stopBluetoothSco()。

注意事项

  • 后台线程建议使用协程或HandlerThread,避免阻塞主线程。
  • 停止播放时必须释放AudioTrack资源,防止内存泄漏。
  • 建议处理音频焦点:注册AudioManager.OnAudioFocusChangeListener,在失去焦点时自动停止播放,提升用户体验。

内容的提问来源于stack exchange,提问作者ortech

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 11:58:25