美文网首页
蓝牙调研

蓝牙调研

作者: 道墟 | 来源:发表于2018-12-12 16:25 被阅读0次

    扫描设备

    过滤指定的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;
                }
            }
    

    相关文章

      网友评论

          本文标题:蓝牙调研

          本文链接:https://www.haomeiwen.com/subject/dxrphqtx.html