扫描设备
过滤指定的Uuid
/common/ScannerFragment.java
final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
final ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).setReportDelay(750).setUseHardwareBatchingIfSupported(false).setUseHardwareFilteringIfSupported(false).build();
final List<ScanFilter> filters = new ArrayList<>();
filters.add(new ScanFilter.Builder().setServiceUuid(mUuid).build());
scanner.startScan(filters, settings, scanCallback);
mUuid 的值:
public static final UUID THINGY_BASE_UUID = new UUID(0xEF6801009B354933L, 0x9B1052FFA9740042L);
配对连接
通过对扫描到的设备列表进行选择,指定要连接的设备
app/src/main/java/no/nordicsemi/android/nrfthingy/configuration/InitialConfigurationActivity.java
@Override
public void onDeviceSelected(BluetoothDevice device, String name) {
if (mThingySdkManager != null) {
mThingySdkManager.connectToThingy(this, device, ThingyService.class);
}
mDevice = device;
animateStepOne();
showConnectionProgressDialog();
}
public void connectToThingy(final Context context, final BluetoothDevice device, final Class<? extends BaseThingyService> service) {
final Intent intent = new Intent(context, service);
intent.putExtra(ThingyUtils.EXTRA_DEVICE, device);
context.startService(intent);
}
service 实现:thingy/ThingyService.java 继承自 BaseThingyService
连接的逻辑在 BaseThingyService 中实现.
thingylib/src/main/java/no/nordicsemi/android/thingylib/BaseThingyService.java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
final BluetoothDevice bluetoothDevice = intent.getParcelableExtra(ThingyUtils.EXTRA_DEVICE);
if (bluetoothDevice != null) {
mThingyConnections.put(bluetoothDevice, new ThingyConnection(this, bluetoothDevice));
if (!mDevices.contains(bluetoothDevice)) {
mDevices.add(bluetoothDevice);
}
}
}
return START_NOT_STICKY;
}
创建ThingyConnection对象,在ThingyConnection对象里面做连接操作,此后对蓝牙设备的操作基本都在ThingyConnection对象中执行.
thingylib/src/main/java/no/nordicsemi/android/thingylib/ThingyConnection.java
public ThingyConnection(final Context context, final BluetoothDevice bluetoothDevice) {
connect(bluetoothDevice);
this.mListener = (ThingyConnectionGattCallbacks) mContext;
}
connect 方法的实现
private void connect(final BluetoothDevice device) {
//建立连接并注册回调
mBluetoothGatt = device.connectGatt(mContext, false, this);
}
BluetoothGatt的回调处理
连接状态回调
@Override
public final void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
isConnected = true;
//发送本地广播通知连接状态
Intent intent = new Intent(ThingyUtils.ACTION_DEVICE_CONNECTED);
intent.putExtra(ThingyUtils.EXTRA_DATA, newState);
intent.putExtra(ThingyUtils.EXTRA_DEVICE, mBluetoothDevice);
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
mListener.onDeviceConnected(mBluetoothDevice, newState);
//开启服务检索
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//结果在onServicesDiscovered回调中
gatt.discoverServices();
}
}, 200);
}
}
服务检索回调务的Characteristic
@Override
public final void onServicesDiscovered(BluetoothGatt gatt, int status) {
final BluetoothGattService mSoundService = gatt.getService(ThingyUtils.THINGY_SOUND_SERVICE);
if (mSoundService != null) {
//初始化音频服务相关的Characteristic
mSoundConfigurationCharacteristic = mSoundService.getCharacteristic(ThingyUtils.THINGY_SOUND_CONFIG_CHARACTERISTIC);
mSpeakerDataCharacteristic = mSoundService.getCharacteristic(ThingyUtils.THINGY_SPEAKER_DATA_CHARACTERISTIC);
mSpeakerStatusCharacteristic = mSoundService.getCharacteristic(ThingyUtils.THINGY_SPEAKER_STATUS_CHARACTERISTIC);
mMicrophoneCharacteristic = mSoundService.getCharacteristic(ThingyUtils.THINGY_MICROPHONE_CHARACTERISTIC);
}
//读取Characteristics 数据
readThingyCharacteristics();
}
private final void readThingyCharacteristics() {
//结果在 onCharacteristicRead 回调中
add(RequestType.READ_CHARACTERISTIC, mSoundConfigurationCharacteristic);
add(RequestType.READ_DESCRIPTOR, mSpeakerStatusCharacteristic.getDescriptor(ThingyUtils.CLIENT_CHARACTERISTIC_CONFIGURATOIN_DESCRIPTOR));
if (mMicrophoneCharacteristic != null) {
add(RequestType.READ_DESCRIPTOR, mMicrophoneCharacteristic.getDescriptor(ThingyUtils.CLIENT_CHARACTERISTIC_CONFIGURATOIN_DESCRIPTOR));
}
}
CharacteristicRead回调
@Override
public final void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (mSoundConfigurationCharacteristic != null && characteristic.equals(mSoundConfigurationCharacteristic)) {
readSoundConfigurationCharacteristic();
}
mHandler.post(mProcessNextTask);
}
//读取SpeakerMode和MicrophoneMode
final void readSoundConfigurationCharacteristic() {
if (mSoundConfigurationCharacteristic != null) {
final BluetoothGattCharacteristic characteristic = mSoundConfigurationCharacteristic;
mSpeakerMode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
mMicrophoneMode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
Log.v(TAG, "Sound service configuration read completed");
}
}
onCharacteristicChanged回调
主要用于下面的录音流程和播放流程的数据获取
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
if (characteristic.equals(mSpeakerStatusCharacteristic)) {
final int speakerStatus = mSpeakerStatusCharacteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
switch (speakerStatus) {
case ThingyUtils.SPEAKER_STATUS_FINISHED:
if (mPlayPcmRequested) {
mWait = false;
if (mQueue.size() == 0) {
broadcastAudioStreamComplete();
}
} else if (mPlayVoiceInput) {
// speak回调
mHandler.post(mProcessNextTask);
}
break;
}
} else if (characteristic.equals(mMicrophoneCharacteristic)) {
if (mAdpcmDecoder != null) {
if (mMtu == ThingyUtils.MAX_MTU_SIZE_THINGY) { //Pre lollipop devices may not have the max mtu size hence the check
final byte[] data = new byte[131];
final byte[] tempData = characteristic.getValue();
System.arraycopy(tempData, 0, data, 0, 131);
//数据转码后给 audiotrack 播放
mAdpcmDecoder.add(data);
} else {
final byte[] data = characteristic.getValue();
mAdpcmDecoder.add(data);
}
}
}
}
播放音频流程
启动播放
thingylib/src/main/java/no/nordicsemi/android/thingylib/ThingySdkManager.java
public void enableThingyMicrophone(final BluetoothDevice device, boolean enable) {
if (device != null) {
if (mBinder != null) {
final ThingyConnection thingyConnection = mBinder.getThingyConnection(device);
if (thingyConnection != null) {
thingyConnection.enableThingyMicrophoneNotifications(enable);
}
}
}
}
thingylib/src/main/java/no/nordicsemi/android/thingylib/ThingyConnection.java
final void enableThingyMicrophoneNotifications(final boolean enable) {
if (mMicrophoneCharacteristic != null) {
final BluetoothGattDescriptor microphoneDescriptor = mMicrophoneCharacteristic.getDescriptor(ThingyUtils.CLIENT_CHARACTERISTIC_CONFIGURATOIN_DESCRIPTOR);
if (!isNotificationsAlreadyEnabled(microphoneDescriptor)) {
byte[] data = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
add(RequestType.WRITE_DESCRIPTOR, microphoneDescriptor, data);
}
enableAdpcmMode(enable);
}
}
private final void enableAdpcmMode(final boolean enable) {
if (mMicrophoneCharacteristic != null) {
mEnableThingyMicrophone = enable;
if (mMicrophoneMode != ThingyUtils.ADPCM_MODE) {
mMicrophoneMode = ThingyUtils.ADPCM_MODE;
add(RequestType.WRITE_CHARACTERISTIC, mSoundConfigurationCharacteristic, new byte[]{ThingyUtils.ADPCM_MODE, (byte) mSpeakerMode}, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
}
//创建AudioTrack
int bufferSize = AudioTrack.getMinBufferSize(16000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 16000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
mAudioTrack.play();
//CharacteristicChanged回调mAdpcmDecoder进行解码
mAdpcmDecoder = new ADPCMDecoder(mContext, false);
mAdpcmDecoder.setListener(new ADPCMDecoder.DecoderListener() {
@Override
public void onFrameDecoded(byte[] pcm, int frameNumber) {
if (mEnableThingyMicrophone && mAudioTrack != null) {
final int status = mAudioTrack.write(pcm, 0, pcm.length/*, AudioTrack.WRITE_NON_BLOCKING*/);
}
});
}
}
录入声音流程
app/src/main/java/no/nordicsemi/android/nrfthingy/sound/ThingyMicrophoneService.java
public void startRecordingAudio(final BluetoothDevice device) {
//创建AudioRecord对象
final AudioRecord audioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, AUDIO_BUFFER);
if(audioRecorder != null && audioRecorder.getState() != AudioRecord.STATE_UNINITIALIZED) {
byte audioData[] = new byte[AUDIO_BUFFER];
audioRecorder.startRecording();
while (mStartRecordingAudio) {
//读取audioRecorder的数据
int status = audioRecorder.read(audioData, 0, AUDIO_BUFFER);
try {
//蓝牙传输数据
thingyConnection.playVoiceInput(downSample(audioData));
//发送广播通知ui
sendAudioRecordBroadcast(device, audioData, status);
} catch (Exception e) {
break;
}
}
}
}
thingylib/src/main/java/no/nordicsemi/android/thingylib/ThingyConnection.java
public final void playVoiceInput(final byte[] sample) {
if (mSpeakerDataCharacteristic != null) {
mPlayVoiceInput = true;
mPcmSample = sample;
streamAudio(sample);
}
}
private void streamAudio(final byte[] sample) {
int index = 0;
int offset = 0;
int length;
int mChunkSize;
if (mMtu > ThingyUtils.MAX_MTU_SIZE_PRE_LOLLIPOP) {
mChunkSize = ThingyUtils.MAX_AUDIO_PACKET_SIZE;
} else {
mChunkSize = ThingyUtils.MAX_MTU_SIZE_PRE_LOLLIPOP;
}
mNumOfAudioChunks = (int) Math.ceil((double) sample.length / mChunkSize);
while (index < mNumOfAudioChunks) {
length = Math.min(mPcmSample.length - offset, mChunkSize);
final byte[] audio = new byte[length];
System.arraycopy(mPcmSample, offset, audio, 0, length);
add(RequestType.WRITE_CHARACTERISTIC, mSpeakerDataCharacteristic, audio, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
index++;
offset += length;
}
}
private void add(RequestType type, BluetoothGattCharacteristic characteristic, byte[] data, int writeType) {
Request request = new Request(type, characteristic, data, writeType);
add(request);
}
synchronized private void add(Request request) {
mQueue.add(request);
if (mQueue.size() == 1) {
mQueue.peek().start(mBluetoothGatt);
}
}
void start(BluetoothGatt bluetoothGatt) {
switch (requestType) {
case WRITE_CHARACTERISTIC:
characteristic.setValue(data);
characteristic.setWriteType(writeType);
if (!bluetoothGatt.writeCharacteristic(characteristic)) {
} else {
}
break;
}
}