一、前言
最近应要求做一个蓝牙 Sco 播放音频的小工具,具体需求是可以选择不同采样率设置蓝牙 Sco 通道来播音频。蓝牙一般有两种语音相关的模式是 A2dp 和 Sco,前者是高质量音乐播放(俗称:只进不出),后者是语音通话(俗称:有进有出)。要实现语音从蓝牙进,那么它一定得处于 Sco 模式下,也是通话模式下。
二、Sco 通道设置
因 Sco 通道不能一直占用,所以在播放的时间建立,停止的时候切换回原模式。
建立 Sco 通道连接:
if (mAudioManager.isBluetoothScoAvailableOffCall) {
if (mAudioManager.isBluetoothScoOn) {
mAudioManager.stopBluetoothSco()
Log.e("xmamiga", "mAudioManager.stopBluetoothSco()")
}
mAudioManager.startBluetoothSco()
var timeout = 100
while (!mAudioManager.isBluetoothScoOn && timeout-- > 0) {
Thread.sleep(10)
if (timeout == 50) {
Log.e("xmamiga", "startBluetoothSco:2")
mAudioManager.startBluetoothSco()
}
Log.e("xmamiga", "change BluetoothScoOn" + mAudioManager.isBluetoothScoOn + ":" + timeout)
}
释放 Sco 通道:
if (mAudioManager.isBluetoothScoOn) {
mAudioManager.stopBluetoothSco()
Log.e("xmamiga", "1mAudioManager.stopBluetoothSco()")
}
如果不释放的话,其他应用将无法使用蓝牙。
三、设置播放器参数
AudioTrack 是管理和播放单一音频资源的类。它用于 PCM 音频流的回放,实现方式是通过 write 方法把数据 push 到 AudioTrack 对象。
选择播放采样率:
btn_8000.setOnClickListener {
SAMPLE_RATE_HZ = 8000;
pausePlyaer()
btn_8000.setTextColor(Color.RED)
btn_16000.setTextColor(Color.BLACK)
}
btn_16000.setOnClickListener {
SAMPLE_RATE_HZ = 16000;
pausePlyaer()
btn_8000.setTextColor(Color.BLACK)
btn_16000.setTextColor(Color.RED)
}
初始化播放器参数:
private fun initPlayer() {
mAudioManager = getSystemService(android.content.Context.AUDIO_SERVICE) as android.media.AudioManager
playerBufSize = AudioTrack.getMinBufferSize(
SAMPLE_RATE_HZ,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT)
player = AudioTrack(
AudioManager.STREAM_VOICE_CALL,
SAMPLE_RATE_HZ,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
playerBufSize,
AudioTrack.MODE_STREAM)
mPlayer = Player(this)
mPlayer!!.setRateHZ(SAMPLE_RATE_HZ)
isPlaying = true
player!!.play()
}
放播音频:
tv_start.setOnClickListener {
if (TextUtils.isEmpty(mFileName)) {
Toast.makeText(this@MainActivity, getString(R.string.select_file), Toast.LENGTH_SHORT).show();
return@setOnClickListener
}
Thread(Runnable {
pausePlyaer()
initPlayer();
mPlayer!!.startRecord(object : Player.RecoderListener {
override fun onData(data: ByteArray) {
}
})
val fis = FileInputStream(mFileName)
val buffer = ByteArray(playerBufSize)
while (fis.available() > 0 && isPlaying) {
val readCount = fis.read(buffer)
if (readCount == -1) {
break
}
player!!.write(buffer, 0, buffer.size)
}
}).start()
}
四、总结
其实对于传统蓝牙来说,可用的接口不多,调试过程中主要出在一些可参数配置和兼容性的问题上。如因声道设置的不对,播放出来的音频就出现了异常(如单声道:CHANNEL_OUT_MONO;立体声:CHANNEL_OUT_STEREO)。