imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示

时间:2024-04-04 15:23:50
前面我们准备好了所有的软件硬件,现在是时候开始正在的业务逻辑功能的实现了。

语音数据的格式
语音遥控器走的是BLE, 对于Voice Over BLE有多种方案,不同的厂家和方案商都不同的实现。下面我们使用TI的来说明,其他的也是类似的。

这里面主要是说明了各种格式,以及传递协议。 我们需要接受和decoder语音,那么就需要搞明白这些东西。
具体而言,语音的Profile如下:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示

交互过程如下:

imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示
注意第一包语音数据有头部:

语音数据从Gatt Notification中获取过来,我们先说一下这个压缩后的语音格式ADPCM。
具体可以参考:

简单而言,使用的是差分压缩:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示
下面是ADPCM的压缩与解压图:

imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示

解压后我们得到的是PCM audio data, 格式为PCM S16_LE, 16Kps的采样率。

adpcm到pcm的解压函数可以参考:https://github.com/dbry/adpcm-xq/blob/master/adpcm-xq.c


BlueZ的D-Bus API的使用

前面是假设得到了ADPCM数据后的解压代码, 我们还需用对前面指定的Characteristic进行notification enable与获取数据。
这些需要借助BlueZ的功能, BlueZ将控制与交互使用D-Bus暴露出来。
具体的文档位于 bluez/doc下面。例如对于我们需要使用的gatt api就在文件gatt-api.txt中。对应的代码可以参考gatttool.c,例如我们需要的notification就在下面这个event中处理:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示
参考这个代码,我们可以完成notification data的获取。PCM用于语音识别获取了PCM数据后,我们可以将PCM convert 到wav 文件,这个比较容易,直接加头部即可。代码如下:
unsigned char RiffHeader[] = {
    'R' , 'I' , 'F' , 'F' , // Chunk ID (RIFF)
    0x70, 0x70, 0x70, 0x70, // Chunk payload size (calculate after rec!)
    'W' , 'A' , 'V' , 'E' , // RIFF resource format type
    'f' , 'm' , 't' , ' ' , // Chunk ID (fmt )
    0x10, 0x00, 0x00, 0x00, // Chunk payload size (0x14 = 20 bytes)
    0x01, 0x00,             // Format Tag ()
    0x01, 0x00,             // Channels (1)
    0x80, 0x3e, 0x00, 0x00, // Byte rate       32.0K
    //0x40, 0x1f, 0x00, 0x00, // Sample Rate,  = 8.0kHz
    0x00, 0x7d, 0x00, 0x00, // Sample Rate,  = 16.0kHz
    0x02, 0x00,             // BlockAlign == NumChannels * BitsPerSample/8
    0x10, 0x00     // BitsPerSample  PCM_16
};
unsigned char RIFFHeader504[] = {
    'd' , 'a' , 't' , 'a' , // Chunk ID (data)
    0x70, 0x70, 0x70, 0x70  // Chunk payload size (calculate after rec!)
};

void pcm2wav(const char *pcm_file, const char *wav_file) {
    FILE *fpi,*fpo;
    unsigned long iLen,temp;
    unsigned long i = 0;
    unsigned long j;
    int headflag = -1;

    fpi=fopen(pcm_file,"rb");
    if(fpi==NULL) {
        printf("\nread error!\n");
        printf("\n%ld\n",i);
        exit(0);
    }
    fseek(fpi,0,SEEK_END);
    temp = ftell(fpi);
    printf("temp:%lu\n", temp);

    fpo=fopen(wav_file,"w+");
    if(fpo==NULL) {
        printf("\nwrite error!\n");
        exit(0);
    }
    fseek(fpo,0,SEEK_SET);
    fwrite(RiffHeader,sizeof(RiffHeader),1,fpo);
    fwrite(RIFFHeader504,sizeof(RIFFHeader504),1,fpo);
    fseek(fpi,0,SEEK_SET);
    fread(savedata,1,temp,fpi);
    fwrite(savedata,temp,1,fpo);

    // ChunkSize
    RiffHeader[4] = (unsigned char)((36 + temp)&0x000000ff);
    RiffHeader[5] = (unsigned char)(((36 + temp)&0x0000ff00)>>8);
    RiffHeader[6] = (unsigned char)(((36 + temp)&0x00ff0000)>>16);
    RiffHeader[7] = (unsigned char)(((36 + temp)&0xff000000)>>24);
    fseek(fpo,4,SEEK_SET);
    fwrite(&RiffHeader[4],4,1,fpo);

    RIFFHeader504[4] = (unsigned char)(temp&0x000000ff);
    RIFFHeader504[5] = (unsigned char)((temp&0x0000ff00)>>8);
    RIFFHeader504[6] = (unsigned char)((temp&0x00ff0000)>>16);
    RIFFHeader504[7] = (unsigned char)((temp&0xff000000)>>24);
    fseek(fpo,40,SEEK_SET);
    fwrite(&RIFFHeader504[4],4,1,fpo);
    fclose(fpi);
    fclose(fpo);
   


从语音到文字
得到了wav文件后,我们需要做的就是使用百度语音识别来做语音识别了。前面的文章中,我们已经下载了sdk, 然后我们找到对应的Rest API 文档:

了解其调用流程:
设置key --> 填充json数据(包括格式,length, language, wav文件)  --> post出去 --> 得到识别结果。
下面是百度识别核心的代码:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示
我们需要做的是将wav文件和格式等各种参数填入到JSON中,然后post出去。
然后我们会得到results,这个result为response为string:
            Json::Value result = this->request_asr(_asr, data, response);

需要注意的是我们需要设置语言,以及格式,下面是英语识别的相关代码:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示

重新编译libbaidu_voice, 我们做一个测试,得到的结果如下, 和我们说话符合:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示

从文字到交互控制
得到result string后,我们就可以用来做控制了。简单的控制,我们可以对string中的字符做判断来处理。
类似于:
if (string.contains(substring) ) {
do xxxx
} else if(string.contains(substring2) ) {
do YYYYY
}



MYdev板子上面有一个LED:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示

这个LED的sys 控制interface位于:
/sys/devices/platform/leds/leds/cpu/brightness
例如echo 0过去就可以将其关闭,1可以将其turn on:
echo 0 > /sys/devices/platform/leds/leds/cpu/brightness

因此我们可以使用这个LED作为被控制的对象来集合进语音控制部分:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示
编译运行并测试:
imx6ul蓝牙语音识别项目之6:语音格式说明以及整合与实际效果演示

可以看到我们已经可以控制LED的ON OFF, 其他复杂的控制也是类似的。下面是实际测试效果的视频:

http://v.youku.com/v_show/id_XMzUxMTIyNzYzNg==.html?x&sharefrom=android&sharekey=a861aa19b6c632d4aab08ff4889359d37

<embed src='http://player.youku.com/player.php/sid/XMzUxMTIyNzYzNg==/v.swf' allowFullScreen='true' quality='high' width='480' height='400' align='middle' allowScriptAccess='always' type='application/x-shockwave-flash'></embed>

另外,实际上我们还可以添加语音合成,对结果进行播放提醒,还可以添加网络远程控制。