You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android接收蓝牙听诊器OnCharacteristicChanged连续字节流的音频处理求助

问题:Android应用集成Litmann蓝牙听诊器:解析音频流并播放

我正在开发Android应用,与带蓝牙功能的Litmann听诊器做集成,已经完成设备配对并开启了Characteristic通知。开启通知后,每秒会收到数百次Characteristic changed通知,推测这是听诊器传感器传输的音频流。现在需要了解接收的音频格式,以及如何在应用中播放该音频。

每次Characteristic changed通知接收的字节样本如下(示例):

00 42 11 89 99 BB CA AA AA 80 0A 99 9E BB CD CA A9
01 99 90 08 A9 8A CB A9 10 11 02 20 11 03 53 35 42
02 32 42 21 43 53 23 34 33 34 33 53 35 43 34 34 33
03 34 32 34 42 23 34 32 22 32 21 08 91 90 90 90 99
04 9B BB BD DA BB EB BB BC BB AB CB BC BD BA BB BB
05 CB BC BA CB AB CB AA 9B B9 BB 9A BB BB AB 99 11
06 BA 01 BB 91 B0 91 90 BB BA 99 99 A9 99 BA BB A9

我的代码示例

public static final UUID stethoscopeServiceUuid = UUID.fromString("##respectiveUUID##");
public static final UUID stethoscopeCharacteristicUuid = UUID.fromString("##respectiveUUID##");
public static final UUID generalDescriptorUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
ByteArrayOutputStream stethescopeStream = new ByteArrayOutputStream();

private void handleConnectionStateChange(BluetoothGatt gatt, int newState) {
    if (newState == BluetoothProfile.STATE_CONNECTED) {
        gatt.discoverServices();
    }
}

private void handleServicesDiscovered(BluetoothGatt gatt, int status) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        BluetoothGattCharacteristic stethoscopeCharacteristic = gatt.getService(stethoscopeServiceUuid).getCharacteristic(stethoscopeCharacteristicUuid);
        gatt.setCharacteristicNotification(stethoscopeCharacteristic, true);
        BluetoothGattDescriptor descriptor = stethoscopeCharacteristic.getDescriptor(generalDescriptorUuid);
        descriptor.setValue(BluetoothGattDescriptor
            .ENABLE_NOTIFICATION_VALUE);
        gatt.writeDescriptor(descriptor);
    }
}

private void handleCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value){
    handleStethoscopeValueReceived(gatt.getDevice().getName(),characteristic, value);
}

private void handleStethoscopeValueReceived(String deviceName, BluetoothGattCharacteristic characteristic, byte[] value) {
    //value
    //00 42 11 89 99 BB CA AA AA 80 0A 99 9E BB CD CA A9
    //01 99 90 08 A9 8A CB A9 10 11 02 20 11 03 53 35 42
    //02 32 42 21 43 53 23 34 33 34 33 53 35 43 34 34 33
    //03 34 32 34 42 23 34 32 22 32 21 08 91 90 90 90 99
    //04 9B BB BD DA BB EB BB BC BB AB CB BC BD BA BB BB
    //05 CB BC BA CB AB CB AA 9B B9 BB 9A BB BB AB 99 11
    //06 BA 01 BB 91 B0 91 90 BB BA 99 99 A9 99 BA BB A9
    //....

    stethescopeStream.write(Arrays.copyOfRange(value, 0, value.length));
}

解答

1. 解析音频格式

从你提供的字节样本来看,每个数据包的第一个字节是帧序号(00、01、02...),剩余部分为音频采样数据。Litmann蓝牙听诊器的音频参数通常符合医疗设备标准:

  • 采样率:8kHz或16kHz(听诊器常用8kHz)
  • 位深:16位有符号整数(需验证字节序,大概率为小端模式)
  • 通道数:单声道(传感器为单通道采集)

验证方法:

  • 截取数据包序号后的字节,每2字节拼接为一个16位整数,将这些整数生成波形图,若呈现连续声波形态,则说明是16位采样格式。
  • 统计每秒接收的总采样数:比如每秒收到100个数据包,每个包含8个采样(16字节数据),总采样数为8000,对应8kHz采样率。

2. 音频播放实现步骤

步骤1:修正数据收集逻辑

当前代码直接写入整个数据包,需跳过首字节的帧序号,只保留音频数据:

private void handleStethoscopeValueReceived(String deviceName, BluetoothGattCharacteristic characteristic, byte[] value) {
    // 跳过首字节的帧序号,取后续音频数据
    byte[] audioData = Arrays.copyOfRange(value, 1, value.length);
    stethescopeStream.write(audioData, 0, audioData.length);
}

步骤2:初始化AudioTrack播放组件

使用Android原生AudioTrack播放原始PCM音频,配置参数需匹配听诊器的音频格式:

private AudioTrack audioTrack;

private void initAudioTrack() {
    // 假设采用8kHz采样率、16位单声道、小端编码
    int sampleRate = 8000;
    int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);

    // 初始化AudioTrack,采用流模式播放
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                                sampleRate,
                                channelConfig,
                                audioFormat,
                                bufferSize * 2, // 放大缓冲避免卡顿
                                AudioTrack.MODE_STREAM);
    audioTrack.play();
}

步骤3:实时写入音频数据

修改数据处理逻辑,收到音频数据后直接写入AudioTrack实现实时播放:

// 在应用启动或连接听诊器后调用initAudioTrack()

private void handleStethoscopeValueReceived(String deviceName, BluetoothGattCharacteristic characteristic, byte[] value) {
    // 检查AudioTrack状态,未初始化则创建
    if (audioTrack == null || audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
        initAudioTrack();
    }
    
    byte[] audioData = Arrays.copyOfRange(value, 1, value.length);
    // 写入AudioTrack实时播放
    audioTrack.write(audioData, 0, audioData.length);
    // 同时保存到流(如果需要录制)
    stethescopeStream.write(audioData, 0, audioData.length);
}

步骤4:处理数据包乱序问题

BLE通知可能出现数据包乱序,可通过帧序号维护一个缓存队列,将数据包按序号排序后再取出音频数据播放,避免音频卡顿或失真。

3. 额外优化建议

  • 协商BLE MTU:默认MTU为20字节,可调用requestMtu()协商更大的MTU(如512字节),减少数据包数量,降低传输延迟。
  • 电源管理:高频BLE通知会消耗大量电量,建议在不需要播放时关闭Characteristic通知。
  • 官方文档:优先获取Litmann官方开发者文档或SDK,里面会明确标注音频格式和传输协议(部分医疗设备需授权获取)。

内容的提问来源于stack exchange,提问作者Srikanth Sridharan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 22:27:33