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
相关产品推荐
相关产品推荐

