android 蓝牙sco开发
2017-10-13 22:57
267 查看
近段时间在做bluetooth双向通信,坑的不轻,各种问题不断,感觉这坑都填不完的一样。把这段时间的东西写下来,给需要的小伙伴参考下,能少坑是一点
有些要注意的地方
流程是要先获取bluetoothHeadset的对象,再通过bluetoothHeadset来发送连接成功的广播,接到广播打开sco,接到sco状态变更后再开始录音。
一定要记得在接到sco连接成功后调用
public class Main2Activity extends AppCompatActivity { AudioManager mAm; InavrSR inavrSR; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); mAm = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mAm.setMode(AudioManager.MODE_IN_COMMUNICATION); initSCO(); initBlueToothHeadset(); } private BluetoothHeadset bluetoothHeadset; private BluetoothDevice bluetoothDevice; BluetoothProfile.ServiceListener blueHeadsetListener = new BluetoothProfile.ServiceListener() { @Override public void onServiceDisconnected(int profile) { Log.i("blueHeadsetListener>>", "onServiceDisconnected:" + profile); if (profile == BluetoothProfile.HEADSET) { if (bluetoothHeadset != null) bluetoothHeadset.stopVoiceRecognition(bluetoothDevice); mAm.stopBluetoothSco(); mAm.setBluetoothScoOn(false); bluetoothHeadset = null; } // initBlueToothHeadset(); } @Override public void onServiceConnected(int profile, BluetoothProfile proxy) { Log.i("blueHeadsetListener", "onServiceConnected:" + profile); if (profile == BluetoothProfile.HEADSET) { bluetoothHeadset = (BluetoothHeadset) proxy; scoEnable(); Log.i("sco", mAm.isBluetoothA2dpOn() + "," + mAm.isBluetoothScoOn()); if (bluetoothHeadset.getConnectedDevices().size() > 0) { bluetoothDevice = bluetoothHeadset.getConnectedDevices().get(0); Log.i("Main2Activity:", bluetoothHeadset.startVoiceRecognition(bluetoothDevice) + ""); } } } }; private void initBlueToothHeadset() { BluetoothAdapter adapter; if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {//android4.3之前直接用BluetoothAdapter.getDefaultAdapter()就能得到BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); } else { BluetoothManager bm = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); adapter = bm.getAdapter(); } adapter.getProfileProxy(this, blueHeadsetListener, BluetoothProfile.HEADSET); } public void record() { final Player player = new Player(); Record re = new Record(new IReward() { @Override public void onReward(byte[] bytes, int i, int i1) { player.play(bytes); } @Override public void onError(RewardException e) { inavrSR.getWebsocketManager().newSocketConnection(); } }); re.startRecord(); } /* Broadcast receiver for the SCO State broadcast intent.*/ private final BroadcastReceiver mSCOHeadsetAudioState = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (intent.getAction().equals("android.bluetooth.device.action.ACL_CONNECTED")) { initBlueToothHeadset(); } else { //if(DEBUG) int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1); Log.e("SCO", " mSCOHeadsetAudioState--->onReceive:" + state); if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) { Log.i("SCO", "SCO_AUDIO_STATE_CONNECTED"); record(); unregisterReceiver(mSCOHeadsetAudioState); } else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) { Log.i("SCO ", "SCO_AUDIO_STATE_DISCONNECTED"); } } } }; private void initSCO() { IntentFilter newintent = new IntentFilter(); newintent.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); newintent.addAction("android.bluetooth.device.action.ACL_CONNECTED"); registerReceiver(mSCOHeadsetAudioState, newintent); } private void scoEnable() { if (mAm.isBluetoothScoOn()) { mAm.stopBluetoothSco(); mAm.setBluetoothScoOn(false); } mAm.setBluetoothScoOn(true); mAm.startBluetoothSco(); } }
/** * Created by kakahsh on 2017/2/24. */ public class Record extends ACEMCanceler implements IRecordAction { private static final int SAMPLE_RATE_IN_HZ = 16000; private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO; //// 2017/8/30 关于640*2的解释,理论上只要加640就可以了,但会提示RecordThread: buffer overflow private static final int BUFFER_SIZE = (AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ, CHANNEL, AudioFormat.ENCODING_PCM_16BIT) / 640) * 640 + 640 * 2; private AudioRecord mAudioRecord; private boolean isVoiceRunning; private Thread recordThread; private IReward reward; public Record(IReward iReward) { this.reward = iReward; mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, SAMPLE_RATE_IN_HZ, CHANNEL, AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE); if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { if (reward != null) reward.onError(new RewardException("AudioRecord初始化失败,请确认已经取得录音权限")); } int audioSessionId = mAudioRecord.getAudioSessionId(); // initAEC(audioSessionId); } private byte[] buffer = new byte[BUFFER_SIZE]; private void getNoiseLevel() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); while (isVoiceRunning) { //r是实际读取的数据长度,一般而言r会小于buffersize int r = mAudioRecord.read(buffer, 0, BUFFER_SIZE); // int calculateVolume = calculateVolume(buffer, 16); reward.onReward(buffer, 0, r); } } @Override public void startRecord() { mAudioRecord.startRecording(); recordThread = new Thread(new Runnable() { @Override public void run() { getNoiseLevel(); } }); recordThread.start(); isVoiceRunning = true; } @Override public void stopRecord() { isVoiceRunning = false; if (recordThread != null) { recordThread.interrupt(); } if (mAudioRecord != null) { mAudioRecord.stop(); } } @Override public void realese() { if (mAudioRecord != null) { stopRecord(); mAudioRecord.release(); } reward = null; } @Override public boolean isInRecord() { return isVoiceRunning; } }
/** * Created by kakahsh on 2017/2/28. */ public interface IReward { void onReward(byte[] bytes, int start, int lenght); void onError(RewardException e); }
/** * Created by kqw on 2016/8/26. * 播放音乐的线程 */ public class Player extends Thread implements IAudioPlayer { // 采样率 private final int mSampleRateInHz = 16000; // 单声道 private final int mChannelConfig = AudioFormat.CHANNEL_OUT_MONO; // 双声道(立体声) // private int mChannelConfig = AudioFormat.CHANNEL_OUT_STEREO; private static final String TAG = "PlayThread"; private AudioTrack mAudioTrack; private byte[] data = new byte[640]; private int bufferSize; private LinkedBlockingDeque<Object> dataQueue = new LinkedBlockingDeque<>(); private AudioDataInOut audioDataInOut; private MessageWrapper messageWrapper; // 互斥信号量 private Semaphore semaphore = new Semaphore(1); public Player() { bufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, AudioFormat.ENCODING_PCM_16BIT) / 640 * 640 + 640; initTrack(); try { // 默认需要抢占一个信号量。防止播放进程执行 semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } start(); } public Player(AudioDataInOut audioDataInOut) { this(); this.audioDataInOut = audioDataInOut; } public Player(AudioDataInOut audioDataInOut, MessageWrapper messageWrapper) { this(); this.audioDataInOut = audioDataInOut; this.messageWrapper = messageWrapper; } private void initTrack() { this.mAudioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, mSampleRateInHz, mChannelConfig, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);//, ACEMCanceler.getInstance().getAudioSessionId() } @Override public void run() { super.run(); while (true) { if (dataQueue.size() > 0) { Object o = dataQueue.pollFirst(); if (o instanceof MessageEntry) { if (messageWrapper != null) { messageWrapper.execute((MessageEntry) o); } } else { data = (byte[]) o; if (audioDataInOut != null) audioDataInOut.onInput(data, data.length); if (this.mAudioTrack != null) this.mAudioTrack.write(data, 0, data.length); } } else { try { semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override public void play() { if (mAudioTrack == null) initTrack(); if (mAudioTrack != null && mAudioTrack.getState() != AudioTrack.PLAYSTATE_PLAYING) mAudioTrack.play(); } @Override public void play(byte[] bytes, int length) { if (!isPlaying()) play(); pushToQueue(bytes); } public void play(Object o) { if (!isPlaying()) play(); pushToQueue(o); } private synchronized void pushToQueue(Object o) { try { dataQueue.putLast(o); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void releasePlayer() { if (null != mAudioTrack) { mAudioTrack.stop(); mAudioTrack.release(); mAudioTrack = null; } dataQueue.clear(); semaphore.release(); } @Override public void stopPlayer() { if (mAudioTrack != null) mAudioTrack.stop(); } @Override public boolean isPlaying() { return mAudioTrack != null && mAudioTrack.getState() == AudioTrack.PLAYSTATE_PLAYING; } public void clearQueue() { stopPlayer(); dataQueue.clear(); } public interface AudioDataInOut { void onInput(byte[] input, int length); } }
有些要注意的地方
mAm.setMode(AudioManager.MODE_IN_COMMUNICATION);,我在参考教程的时候,设置都是
MODE_IN_CALL,但我在用的时候是不行的,不知道是不我的使用的问题。
流程是要先获取bluetoothHeadset的对象,再通过bluetoothHeadset来发送连接成功的广播,接到广播打开sco,接到sco状态变更后再开始录音。
一定要记得在接到sco连接成功后调用
unregisterReceiver(mSCOHeadsetAudioState);关闭广播接收器,因为可能会有多条广播过来,可能出现在异常。
相关文章推荐
- 【Android应用开发】Android 蓝牙低功耗 (BLE) ( 第一篇 . 概述 . 蓝牙低功耗文档 翻译)
- Android蓝牙通信功能开发
- android蓝牙开发 蓝牙设备的查找和连接
- android蓝牙开发入门到精通4------通信标准
- android经典蓝牙开发
- Android 蓝牙开发基本流程
- android 蓝牙锁应用实例开发(一) 简介
- Android BLE低功耗蓝牙开发(下) BLE客户端(中央设备)与GATT服务的通讯
- 【Android应用开发】Android 蓝牙低功耗 (BLE) ( 第一篇 . 概述 . 蓝牙低功耗文档 翻译)
- 初涉Android蓝牙开发
- Android Bluetooth蓝牙开发\蓝牙协议\蓝牙通信例子_Android支持蓝牙4.0版本_BLE开发
- Android蓝牙开发(二):相关的API简介
- Android 蓝牙开发中的问题
- Android无线蓝牙开发总结
- android ble 蓝牙4.0开发日志(一)
- android蓝牙开发 蓝牙设备的查找和连接
- Android蓝牙开发浅析
- Android蓝牙开发浅析
- Android蓝牙开发
- Android 蓝牙开发详述