蓝牙ble数据转语音实现Android AudioRecord方法推荐

教程 

欢迎走进zozo的学习之旅。

概述

蓝牙BLE又称bluetooth smart,主打的是低功耗和快速链接,所以在支持的profile并没有audio的部分,而蓝牙语音协议A2DP只在传统蓝牙中有,本文就是提供一种利用ble数据来传输压缩语音,并最终在实现用android语音框架中的AudioRecord方法来获取语音流。

主要思路

首先问题的需求是从一种非标准的协议挂载成为一个标准协议。那通过修改kernel的bluetooth协议或者是修改android的语音框架都是可以实现的,但是不论哪种方式都要耗费大量的工作,而且这两种的哪一种的修改都会给平台的更换或者是系统版本的更换带来很大的障碍。

那这里提供的一种较为简单的思路来实现:在kernel内建议一个upcm的声卡,运行一个守护进程将ble的对应数据解压后放入声卡这样AudioRecord就可以获取PCM的语音流了。另外,android语音的挂载需要添加so库,并修改Audio的配置文件audio_policy.conf来添加。

UPCM分析

kernel声卡驱动

upcm的源码可关注我的代码仓库

蓝牙正常 连接 log

  1. [ 633.209000] input: Broadcom Bluetooth HID as /devices/virtual/misc/uhid/input4
  2. [ 633.217000] generic-bluetooth 0005:0000:0000.0002: input,hidraw0: BLUETOOTH HID v1.01 Mouse [Broadcom Bluetooth HID] on
  3. [ 641.437000] UPCM : snd_u_capture_open
  4. [ 641.440000] UPCM : snd_u_hw_params format 2, rate 16000, channels 1, period_bytes 2048, buffer_bytes 8192
  5. [ 641.451000] UPCM: format 0x2, rate 16000, channels 1
  6. [ 641.456000] UPCM : snd_u_pcm_prepare
  7. [ 641.460000] UPCM : snd_u_substream_capture_trigger, cmd 1
  8. [ 641.465000] UPCM: SNDRV_PCM_TRIGGER_START
  9. [ 649.407000] UPCM: upcm_char_release
  10. [ 651.592000] UPCM : snd_u_substream_capture_trigger, cmd 0
  11. [ 651.597000] UPCM: SNDRV_PCM_TRIGGER_STOP
  12. [ 651.602000] UPCM : snd_u_hw_free
  13. [ 651.605000] UPCM : snd_u_capture_close

在内核路径下进行交叉编译,把编译完的upcm.ko放到文件系统/system/etc/下,在板级的init.rc里加入insmod /system/etc/upcm.ko

这样上电就可以加载upcm.ko的驱动。驱动加载成功后,会建立/sys/class/sound/pcmC1D0c的虚拟通道,设备节点在 /dev/snd/pcmC1D0c

audio daemon

Audio daemon程序,从一个socket通道获取蓝牙BLE语音数据,解压ADPCM数据,喂给一个虚拟的声卡。Android语音中间层通过一个标准的audio库,从虚拟声卡中读取音频,提供给APP使用。APP只要调用标准的Android音频API,就能获取音频数据。


  • 使用Netlink的NETLINK_KOBJECT_UEVENT类型套接字与Kernel进行通信,查找hidraw设备
  1. int main_loop()
  2. {
  3. /* 套接字地址 */
  4. struct sockaddr_nl nls;
  5. /* 套接字文件描述符 */
  6. struct pollfd pfd;
  7. /* 接收内核发来的消息缓冲区大小 */
  8. char buf[512];
  9. /* 查找设备路径 */
  10. char dev_path[512];
  11. // Open hotplug event netlink socket
  12. memset(&nls,0,sizeof(struct sockaddr_nl));
  13. /* 1.添写套接字地址 */
  14. nls.nl_family = AF_NETLINK;
  15. /* 如果希望内核处理消息或多播消息,就把该字段设置为 0,
  16. 否则设置为处理消息的进程ID。 */
  17. nls.nl_pid = getpid();
  18. nls.nl_groups = -1;
  19. /*设置要求查询的事件掩码 */
  20. pfd.events = POLLIN;
  21. /* 2.创建套接字 */
  22. /* NETLINK_KOBJECT_UEVENT - 内核消息到用户空间*/
  23. pfd.fd = socket(PF_NETLINK, /* 使用 netlink */
  24. SOCK_DGRAM, /* 使用不连续不可信赖的数据包连接 */
  25. NETLINK_KOBJECT_UEVENT);
  26. /* 创建套接字失败 */
  27. if (pfd.fd < 0 )
  28. {
  29. printf("Failed to open netlink socket\n");
  30. return -1;
  31. }
  32. /* 3 Listen to netlink socket */
  33. if (bind(pfd.fd, (void *)&nls, sizeof(struct sockaddr_nl)))
  34. {
  35. printf("Failed to bind socket\n");
  36. return -1;
  37. }
  38. /* 创建子进程,为已经存在hidraw设备的uevent事件,添加 add 关键字*/
  39. deal_with_exist_hidraw_dev();
  40. while (1)
  41. {
  42. /* 等待事件 */
  43. int res = poll(&pfd, 1, -1) ;
  44. if (res == -1)
  45. {
  46. if (errno == EAGAIN || errno == EINTR)
  47. continue;
  48. break;
  49. }
  50. /* 接收内核消息 */
  51. int i = 0 ;
  52. int len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT);
  53. if (len == -1 )
  54. {
  55. if (errno == EAGAIN || errno == EINTR)
  56. continue;
  57. printf("Error when recv netlink package\n");
  58. return -1;
  59. }
  60. i = 0 ;
  61. char * token = buf;
  62. char * action_token = NULL;
  63. char * devname_token = NULL;
  64. /* 检查消息关键字 hidraw 设备 */
  65. while ( i<len )
  66. {
  67. token = buf + i;
  68. i += strlen(token) + 1;
  69. if (!strncmp(token, "ACTION=add", 10) )
  70. {
  71. action_token = token;
  72. }
  73. if (!strncmp(token, "DEVNAME=/dev/hidraw", 19) || !strncmp(token, "DEVNAME=hidraw", 14) )
  74. {
  75. devname_token = token;
  76. }
  77. /* 找到了hidraw设备 */
  78. if (action_token != NULL && devname_token != NULL)
  79. {
  80. if (!strncmp(devname_token, "/dev/", 5 ) )
  81. strcpy(dev_path, devname_token + 8 );
  82. else
  83. sprintf(dev_path, "/dev/%s", devname_token + 8);
  84. //char * dev_path = devname_token + 8;
  85. printf("Found new hidraw device\n", dev_path);
  86. handle_new_hidraw_dev(dev_path);
  87. break;
  88. }
  89. }
  90. }
  91. close(pfd.fd);
  92. }
  • 选择具有约定ble语音特征的hid设备
  1. inline bool select_device(char * dev_path)
  2. {
  3. int fd,res,desc_size,i;
  4. struct hidraw_devinfo info;
  5. struct hidraw_report_descriptor rpt_desc;
  6. char buf[100];
  7. /* 打开hidraw设备文件 */
  8. fd = open(dev_path, O_RDWR);
  9. if (fd < 0 )
  10. {
  11. sleep(1);
  12. fd = open(dev_path, O_RDWR);
  13. if (fd < 0 )
  14. return false;
  15. }
  16. ioctl(fd, HIDIOCGRDESCSIZE, &desc_size);
  17. ioctl(fd, HIDIOCGRAWINFO, &info);
  18. close(fd);
  19. int vid = info.vendor & 0xFFFF;
  20. int pid = info.product & 0xFFFF;
  21. for (i=0;;i++)
  22. {
  23. struct device * dev = dev_list[i];
  24. if (dev == NULL)
  25. break;
  26. /* 查找对应ble语音设备可以根据设备的特征绑定 */
  27. if ( (dev->vid <= 0 || dev->vid == vid)
  28. &&( dev->pid <=0 || dev->pid == pid)
  29. && (dev->desc_size <=0 || dev->desc_size == desc_size ) )
  30. {
  31. current_device = dev;
  32. strncpy(current_device->dev_path, dev_path, sizeof(current_device->dev_path));
  33. return true;
  34. }
  35. }
  36. return false;
  37. }
  1. /* 这里没有对vid pid做绑定,用了设备的报告描述符了做征绑定 */
  2. struct device dev_default =
  3. {
  4. .name = "default",
  5. .vid = 0,
  6. .pid = 0,
  7. .desc_size = 220,
  8. .audio_main = default_audio_main
  9. };
  • 自定义语音流控制,解压+输入到upcm
  1. int default_audio_main(int fd)
  2. {
  3. int len;
  4. short pcm_buf[1024];
  5. int offset = 0;
  6. unsigned char buf[1024];
  7. int total_cnt = 0;
  8. int i;
  9. int audio_fd = -1;
  10. while ( (len = read (fd, buf, 1024 )) >=0) {
  11. /* 这里没有对vid pid做绑定,用了设备的报告描述符了做征绑定 */
  12. if (buf[0] == 0x1F) {
  13. if (buf[1] == 0xFF && buf[2] == 0x01) {
  14. printf("Audio start\n");
  15. if (audio_fd < 0)
  16. audio_fd = open("audio.pcm", O_WRONLY | O_CREAT);
  17. open_upcm_dev();
  18. } else if (buf[1] == 0xFF && buf[2] == 0x00) {
  19. printf("Audio stopped\n");
  20. if (audio_fd >0) {
  21. close(audio_fd);
  22. audio_fd = -1;
  23. }
  24. close_upcm_dev();
  25. } else if (buf[1] == 0xFE ) {
  26. printf("Recv prevIndex and prevSample\n");
  27. state.prevIndex = buf[2];
  28. state.prevSample = covertTo16Int(buf[3], buf[4]);
  29. }
  30. } else if (buf[0] == 0x1E) {
  31. int sample_cnt = adpcm_decode(buf+1, len-1, pcm_buf);
  32. write(audio_fd, pcm_buf, sample_cnt * 2);
  33. write_upcm_dev((unsigned char *)pcm_buf, sample_cnt * 2);
  34. }
  35. }
  36. if (audio_fd > 0 )
  37. close(audio_fd);
  38. }
  • 编译完成后将工具audio_d放到system/bin,加入到系统里自动启动
  1. service hidraw /system/bin/audio_d
  2. class main
  3. oneshot

 注意,需要在加载upcm.ko之后运行。

注册系统声卡

  1. 编译audio的so库,Android.mk audio_hw.cpp
  2. audio.LSDAudio.default.so,放到system/lib/hw/下。
  3. 修改system/etc/audio_policy.conf 文件,把primary里的input删掉,只留output,并加上下面的内容。可参考文件包的样本,注意检查权限为644。

    audio.LSDAudio.default.so的加载,是靠audio_policy.conf里面建立PCM通道来加载的。这样就创建了一个input_mic的PCM通道。
  1. LSDAudio {
  2. inputs {
  3. LSDAudio {
  4. sampling_rates 8000|16000
  5. channel_masks AUDIO_CHANNEL_IN_MONO
  6. formats AUDIO_FORMAT_PCM_16_BIT
  7. devices AUDIO_DEVICE_IN_BUILTIN_MIC
  8. }
  9. }
  10. }

源代码下载

ble_audio_android

参考

蓝牙ble数据转语音实现Android AudioRecord方法推荐的更多相关文章

  1. 蓝牙BLE数据包格式汇总

    以蓝牙4.0为例说明: BLE包格式有:广播包.扫描包.初始化连接包.链路层控制包(LL层数据包).逻辑链路控制和自适应协议数据包(即L2CAP数据包)等: 其中广播包又分为:定向广播包和非定向广播包 ...

  2. android4.3 蓝牙BLE编程

    一.蓝牙4.0简介 蓝牙4.0标准包含两个蓝牙标准,准确的说,是一个双模的标准,它包含传统蓝牙部分(也有称之为经典蓝牙Classic Bluetooth)和低功耗蓝牙部分(Bluetooth Low ...

  3. Android4.3 蓝牙BLE初步

    一.关键概念: Generic Attribute Profile (GATT) 通过BLE连接,读写属性类小数据的Profile通用规范.现在所有的BLE应用Profile都是基于GATT的.   ...

  4. 【转】Android4.3 蓝牙BLE初步

    原文网址:http://www.cnblogs.com/savagemorgan/p/3722657.html 一.关键概念: Generic Attribute Profile (GATT) 通过B ...

  5. Android-低功耗蓝牙(BLE)-客户端(主机/中心设备)和服务端(从机/外围设备)

    一.Android 低功耗蓝牙(BLE)的API简介 从Android 4.3(API 18)才支持低功耗蓝牙(Bluetooth Low Energy, BLE)的核心功能, BLE蓝牙协议是GAT ...

  6. Android蓝牙BLE开发,扫描、连接、发送和读取信息;

    1.BLE开发权限 Android蓝牙BLE开发须打开蓝牙权限和6.0位置权限: <uses-permission android:name="android.permission.B ...

  7. Android蓝牙BLE低功耗相关简单总结

    在看Android4.42的源代码时看到有加入对BLE设备的处理.看的一头雾水,多方百度,最终有种柳暗花明的感觉. 本文总结来源于百度多篇文章,欢迎转载.分享交流 BLE蓝牙概念 BLE:Blueto ...

  8. 使用BleLib的轻松搞定Android低功耗蓝牙Ble 4.0开发具体解释

    转载请注明来源: http://blog.csdn.net/kjunchen/article/details/50909410 使用BleLib的轻松搞定Android低功耗蓝牙Ble 4.0开发具体 ...

  9. 10分钟完成一个最最简单的BLE蓝牙接收数据的DEMO

    这两天在研究蓝牙,网上有关蓝牙的内容非常有限,Github上的蓝牙框架也很少很复杂,为此我特地写了一个最最简单的DEMO,实现BLE蓝牙接收数据的问题, 不需要什么特定的UUID, 不需要什么断开重连 ...

随机推荐

  1. CF考古活动

    Codeforces Beta Round #1 http://codeforces.com/contest/1 A.测试用水题,呵呵.给三个数nma,求ceil(n/a)*ceil(m/a). 长整 ...

  2. 840. Magic Squares In Grid (5月27日)

    开头 这是每周比赛中的第一道题,博主试了好几次坑后才勉强做对了,第二道题写的差不多结果去试时结果比赛已经已经结束了(尴尬),所以今天只记录第一道题吧 题目原文 Magic Squares In Gri ...

  3. Springmvc+Spring+Mybatis整合开发(架构搭建)

    Springmvc+Spring+Mybatis整合开发(架构搭建) 0.项目结构 Springmvc:web层 Spring:对象的容器 Mybatis:数据库持久化操作 1.导入所有需要的jar包 ...

  4. 安装mysql zip 安装包 Navicat连接

    笔者在安装mysql时一直出现各种问题,今天难得成功一次,决定记录一下,留作纪念与参考 安装第一步,下载mysql https://dev.mysql.com/downloads/mysql/ 以在w ...

  5. Linux磁盘管理和lvm

    磁盘管理 硬盘接口和硬盘种类 从整体的角度上,硬盘接口分为IDE.SATA.SCSI和SAS四种,IDE接口硬盘多用于家用产品中,也部分应用于服务器,SCSI接口的硬盘则主要应用于服务器市场,而SAS ...

  6. js扩展String.prototype.format字符串拼接的功能

    1.题外话,有关概念理解:String.prototype 属性表示 String原型对象.所有 String 的实例都继承自 String.prototype. 任何String.prototype ...

  7. ES6 开发规范-最佳实践

    ES6 开发规范(最佳实践) 本文为开发规范,收集方便日后查看. [开发规范]https://blog.csdn.net/zzzkk2009/article/details/53171058?utm_ ...

  8. Python-调用系统指令小记

    import subprocess def exec_command(cmd, log_path, **kwargs): with open(log_path, 'w') as f: p = subp ...

  9. 【转载】 旧版本Microsoft Office正在配置解决方法

    原文:https://blog.csdn.net/sinat_37215184/article/details/81053931 在运行Microsoft Office 2010等旧版本的Office ...

  10. HDFS的JavaAPI

    配置windows平台的Hadoop环境 在 windows 上做 HDFS 客户端应用开发,需要设置 Hadoop 环境,而且要求是windows 平台编译的 Hadoop,不然会报以下的错误: F ...