Android蓝 牙 Sco 播放

一、前言

最近应要求做一个蓝牙 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)。