文章目录

  • 前言
  • STM32MP157语音识别
    • alsa-lib简介:
    • 移植alsa-lib库:
    • libcurl库简介:
    • 安装CURL
    • 移植CURL
    • API调用
    • 录音
    • 文件IO
      • 打开音频文件
      • 打开缓冲文件
    • 文件描述符重定向(已删除)
    • CURL命令
    • 字符串拼接与解析
    • exec函数族
    • 进程创建
    • 多线程
    • 主循环
    • 实现效果及注意事项
      • 实现效果
      • 注意事项
  • 源代码(转载请注明出处)

前言

本篇分享:

Linux应用编程之音频编程,使用户可以录制一段音频并进行识别(语音转文字)

环境介绍:

系统:Linux
硬件:正点原子STM32MP157开发板
声卡:开发板自带


STM32MP157语音识别

实现目标 :用户可以录制一段音频并进行识别(语音转文字)
知识点 : C语言文件IO文件描述符重定向alsa-lib 库CURL命令API调用字符串拼接与解析进程创建exec函数族多线程

alsa-lib简介:

alsa-lib是一套 Linux 应用层的 C 语言函数库,为音频应用程序开发提供了一套统一、标准的接口,应用程序只需调用这一套 API 即可完成对底层声卡设备的操控,譬如播放与录音。
用户空间的alsa-lib对应用程序提供了统一的API 接口,这样可以隐藏驱动层的实现细节,简化了应用 程序的实现难度、无需应用程序开发人员直接去读写音频设备节点。所以,主要就是学习alsa-lib库函数的使用、如何基于alsa-lib库函数开发音频应用程序。
alsa-lib官方说明文档:https://www.alsa-project.org/alsa-doc/alsa-lib/

移植alsa-lib库:

正点STM32MP157开发板出厂已移植(非广告!),需要请参考其他教程。

要在嵌入式Linux系统上运行使用alsa-lib库的程序,需要移植alsa-lib库,可以参考网上移植alsa-lib库的方法,或自行下载alsa-lib资源包,自行编译移植。

开源ALSA架构的官网地址:https://www.alsa-project.org/wiki/Main_Page

libcurl库简介:

libcurl是一个跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议。libcurl同样支持HTTPS证书授权,HTTP POST, HTTP PUT, FTP 上传, HTTP基本表单上传,代理,cookies,和用户认证。

官网地址:http://curl.haxx.se/

安装CURL

在使用curl指令之前,需要先安装curl软件包。在大多数Linux发行版中,在ubuntu中可以使用以下命令来安装curl

sudo apt-get install curl

移植CURL

正点STM32MP157开发板出厂已移植(非广告!),需要请参考其他教程。
可以使用curl指令测试是否安装该软件包。

curl官方网站:https://curl.se/

API调用

该程序使用的是百度语音识别API

注册后领取免费额度及创建中文普通话应用(创建前先领取免费额度(180 天免费额度,可调用约 5 万次左右) )

创建好应用后,可以得到API keySecret Key(填写到程序中的相应位置)

调用API相关说明,Demo代码中有多种语言的调用示例可以参考,使用c语言的话也可以直接在本程序上面再次更改:

录音

查看Linux应用编程-音频应用编程-语音转文字项目中相应的标题。

文件IO

我们需要将录制的音频文件保存到本地,就需要用到文件IO相关知识,打开音频文件以及向音频文件写数据。

打开音频文件

函数:

函数原型:
FILE *fopen(const char *filename, const char *mode)参数:
filename -- 字符串,表示要打开的文件名称。
mode -- 字符串,表示文件的访问模式。作用:
以指定的方式打开文件。

代码:

/*创建一个保存PCM数据的文件*/
if ((pcm_data_file = fopen(argv[1], "wb")) == NULL)
{printf("无法创建%s音频文件.\n", argv[1]);exit(1);
}
printf("用于录制的音频文件已打开.\n");参数:
argv[1]:程序执行时传递的参数,例./voice record.cpm,则该参数为"record.cpm"
"wb":只写打开或新建一个二进制文件,只允许写数据。

打开缓冲文件

除了要保存音频数据的文件,还要有保存CURL执行返回结果的文件作为程序缓冲文件,进而从缓冲文件中提取关键信息。

函数:

头文件:
#include <fcntl.h>函数原型:
int open(const char *pathname, int flags, mode_t mode);参数:
pathname -- 文件路径名或文件名。
flags -- 打开文件所采用的操作。
mode -- 设置文件访问权限的初始值。作用:
以指定的方式打开文件。

代码:

int fd = open(buffer_FileName,O_WRONLY|O_CREAT|O_TRUNC,0777);参数:
buffer_FileName -- 文件路径名或文件名。
O_WRONLY -- 以只读方式打开。
O_CREAT -- 若不存在则创建该文件。
O_TRUNC -- 若以只读方式打开,并存在该文件,则清空文件原内容。
0777 -- 打开全部权限。

文件描述符重定向(已删除)

重定向的作用是使"一个文件描述符"指向"另一文件描述符所指向的文件",这里需要使终端不出现无关输出,又需要将CURL指令执行返回结果保存(CURL指令执行后会返回结果)。
后续发现curl参数-o可以实现相同功能,代码已更改,这部分就当复习好了…

例如:

获取Token:

识别音频文件:

我们当然不希望这些返回结果直接打印在终端上,而后续程序又需要利用这些返回结果,这里就需要用到文件描述符的重定向。
学习过文件IO相关知识应该知道,终端标准输出对应的文件描述符是1(标准输入0,标准错误2),所以,我们只需要创建一个数据缓冲文件,得到数据缓冲文件的文件描述符fd,再将标准输出重定向到fd即可,这样终端输出的数据都会存储到fd指向的文件中。

函数:

函数原型:
int dup(int oldfd)参数:
oldfd -- 待拷贝的文件描述符。作用:
使用现有的文件描述符,拷贝生成一个新的文件描述符,且函数调用前后这个两个文件
描述符指向同一文件,即oldfd指向的文件。函数原型:
int dup2(int oldfd, int newfd)参数:
oldfd -- 待拷贝的文件描述符。
newfd -- 新文件描述符。作用:
使新文件描述符指向待拷贝文件描述符所指向的文件。

代码:

int OUT_fd = dup(STDOUT_FILENO);
int fd = open(buffer_FileName,O_WRONLY|O_CREAT|O_TRUNC,0777);
dup2(fd,STDOUT_FILENO);1.首先创建一个OUT_fd保存原标准输出,为了后续需要终端输出提示时能重定向回来。
2.打开数据缓冲文件,得到文件描述符fd。
3.重定向标准输出到fd。

CURL命令

获取Token:

格式:
curl -i -s 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=【百度云应用的AK】&client_secret=【百度云应用的SK】' -o 【数据缓冲文件】示例:
curl -i -k 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=xxxxxxxxx&client_secret=xxxxxxxxx' -o buffer.txt参数:
-i -- 显示传输文档,经常用于测试连接本身,含是否调用成功信息。
-s -- 静默模式,不输出任何东西。
-o -- 把输出写到该文件中。

音频文件识别:

格式:
curl -i -X POST -H【固定头部header】"【语音识别请求地址】?dev_pid=【语言参数】&cuid=【用户唯一标识】&token=【token】" --data-binary "@【音频文件路径名】"示例:
curl -i -X POST -H "Content-Type: audio/pcm;rate=16000" "http://vop.baidu.com/server_api?dev_pid=1537&cuid=xxxxx&token=1.a6b7dbd428f731035f771b8d********.86400.1292922000-2346678-124328" --data-binary "@/home/test/test.pcm"

字符串拼接与解析

当我们需要执行CURL指令时,需要对多个字符串进行拼接。

函数:

函数原型:
int snprintf(char *str, size_t size, const char *format, ...)参数:
str -- 拼接的结果保存在该字符串中。
size -- 如果平格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0');如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0')。
format -- 字符串格式。示例:
snprintf(buf, 200, "%s,%d", string,num);作用:
格式化字符串。

代码(构建获取Token的命令):

void Get_Token(char api_Key[],char secret_Key[])
{char API_TOKEN_URL[] = "https://aip.baidubce.com/oauth/2.0/token";char url_pattern[] = "%s?grant_type=client_credentials&client_id=%s&client_secret=%s";char url_common[200];pid_t pid;/*构建命令参数*/snprintf(url_common, 200, url_pattern, API_TOKEN_URL, api_Key, secret_Key);
}

exec函数族

在程序中如果需要调用如"ls、mv、cp"等相关命令,就可以用exec函数族中的execl 函数来实现该功能。这里使用execl函数实现curl命令的调用。

函数:

函数原型:
int execl(const char *path, const char *arg, ...);语法:
int execl("绝对路径", "标识符",  "需要的参数(需要多少传入多少)" ,NULL);示例:
execl("ls","ls","-l",NULL);参数:
绝对路径 -- 文件存储的绝对路径,使用程序名在 PATH 中搜索。在终端可使用的命令这里直接输入命令即可。
标识符 -- 命令。
参数 -- 执行命令所需参数。作用:
加载一个进程,通过路径+程序名来加载。

代码:

以执行CURL命令获取Token为例说明:

void Get_Token(char api_Key[],char secret_Key[])
{.../*构建命令参数*/snprintf(url_common, 200, url_pattern, API_TOKEN_URL, api_Key, secret_Key);/*调用curl命令*/execlp("curl","curl","-i","-s","-k",url_common,NULL);...
}

进程创建

由于exec函数族的函数一旦调用成功即执行新的程序,不返回。 只有失败才返回,错误值-1。这样就会导致调用execl函数后整个程序终止,后续代码无法执行。
所以我们需要创建一个子进程去调用该函数,函数结束,子进程也就结束。

函数:

函数原型:
pid_t fork(void)作用:
创建一个子进程。返回值:
失败返回-1;成功返回:父进程返回子进程的ID(非负);子进程返回 0。注意返回值,不是 fork 函数能返回两个值,而是 fork 后,fork 函数变为两个,父子【各自】返回一个。

代码:

void Get_Token(char api_Key[],char secret_Key[])
{.../*创建子进程执行curl指令 执行完毕子进程结束*/pid = fork();if(pid == 0) execlp("curl","curl","-i","-s",url_common,"-o",buffer_FileName,NULL);/*回收子进程 不回收会存在僵尸进程*/wait(NULL);...
}

多线程

函数:

函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)thread -- 传出参数,保存系统为我们分配好的线程 ID
attr -- 通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
start_routine -- 函数指针,指向线程主函数,该函数运行结束,则线程结束。
arg -- 线程主函数执行期间所使用的参数。作用:
创建一个新线程。函数原型:
int int truncate(const char *path,off_t length);参数:
path -- 文件路径名。
length --  截断长度,若文件大小>length大小,额外的数据丢失。若文件大小<length大小,那么,这个文件将会被扩展,扩展的部分将补以null,也就是‘\0’。作用:
截断或扩展文件。

代码:

/*创建子线程检测按键是否按下*/
pthread_t tid;
ret = pthread_create(&tid, NULL, button_tfn, NULL);
if (ret != 0) perror("pthread_create failed");void *button_tfn(void *arg)
{struct input_event in_ev = {0};int fd;int value = -1;/*打开按键事件对应的文件*/if (0 > (fd = open("/dev/input/event0", O_RDONLY))){perror("open error");exit(-1);}while(1){/*循环读取数据*/if (sizeof(struct input_event) != read(fd, &in_ev, sizeof(struct input_event))){perror("read error");exit(-1);}if (EV_KEY == in_ev.type && in_ev.code == 114)//114为KEY0 { /*按键事件*/switch (in_ev.value){/*KEY0松开*/case 0:/*** 1.更新按键状态为松开* 2.延时等待主循环判断,否则可能出现主循环先判断标志位为1而出现PCM设备停止还在继续读数据* 3.停止PCM设备*/key_flag_now = 0;sleep(1);snd_pcm_drop(capture_handle);break;/*KEY0按下*/case 1:/*** 1.清空文件,使文件从头开始写,等于重新录制音频* 2.同样注意顺序,先使设备恢复进入准备状态,避免出现主循环先检测到标志位为1而读取声卡设备* 3.更新按键状态为按下*/truncate(pcm_file_name,1);snd_pcm_prepare(capture_handle);key_flag_now = 1;break;}}else if(EV_KEY == in_ev.type && in_ev.code == 115)//115为KEY1{/*按键事件*/switch (in_ev.value){/*KEY1按下*/case 1:/*退出程序*/exit_program();break;}}}
}

主循环

主循环内判断声卡设备状态是否改变(按键状态决定),若当前声卡为运行状态则进行音频采集,若当前声卡为停止状态则调用API进行识别。

while (1)
{/*判断按键状态是否更新*/if(key_flag_now != key_flag_old){/*视当前状态为旧状态*/key_flag_old = key_flag_now;/*若按键按下*/if(key_flag_now == 1)printf("开始采集音频数据...\n");/*若按键松开*/else{printf("采集结束!\n");/*识别音频 成功输出结果 出错退出程序*/ret = Speech_Recognition(argv[1],result);if(ret == 0)printf("识别的结果为:%s\n",result);elseexit_program();printf("请长按KEY0按键开始采集音频数据!单击KEY1退出程序!\n");}}/*若按键按下*/if(key_flag_now == 1){/*从声卡设备读取一帧音频数据:2048字节*/ret = snd_pcm_readi(capture_handle, buffer, buffer_frames);if(0 > ret){printf("从音频接口读取失败(%s)\n", snd_strerror(ret));exit(1);}/*写数据到文件: 音频的每帧数据样本大小是16位=2个字节*/fwrite(buffer, (ret * AUDIO_CHANNEL_SET), frame_byte, pcm_data_file);}
}

实现效果及注意事项

实现效果

如图所示长按KEY0按键开始音频录制,松开即音频录制结束,再调用百度语言API进行识别,并向用户展示识别的结果。之后用户可自行选择继续识别或退出程序。

注意事项

该程序在声卡不进行录音时是将声卡设备给停止工作了的,在停止声卡设备前需要加入一小段的延时等待,若不添加延时等待,可能会出现子线程使声卡设备停止的同时主线程在读取声卡设备,从而导致下图中出现的错误:


源代码(转载请注明出处)

STM32MP157-Linux音频应用编程-语音转文字项目相关推荐

  1. Linux应用编程-音频应用编程-语音转文字项目

    文章目录 前言 Linux语音识别 alsa-lib简介: 安装alsa-lib库: API调用 录音 相关概念 样本长度(Sample) 声道数(channel) 帧(frame) 周期(perio ...

  2. 有什么开源的python汉语语音转文字项目?

    随着语音技术的不断发展,语音识别技术已经逐渐成熟,成为了很多智能应用的重要组成部分,比如智能家居.语音助手等等.而在语音识别技术中,汉语语音识别则是一个更具有挑战性的领域.为了方便程序员们进行汉语语音 ...

  3. Linux音频OSS编程指南

    转载地址:https://blog.csdn.net/TJU355/article/details/6943202 OSS--跨平台的音频接口简介 http://bbs.lemote.com/view ...

  4. STM32MP157-Linux音频应用编程-简易语音助手

    文章目录 前言 STM32MP157简易语音助手 alsa-lib简介: 移植alsa-lib库: libcurl库简介: 移植libcurl库: API调用 修改asrmain.c文件 修改toke ...

  5. 【正点原子Linux连载】第二十三章 音频应用编程-摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.1

    第二十三章 音频应用编程 ALPHA I.MX6U开发板支持音频,板上搭载了音频编解码芯片WM8960,支持播放以及录音功能! 本章我们来学习Linux下的音频应用编程,音频应用编程相比于前面几个章节 ...

  6. linux下录音识别成文字软件下载,语音转文字专家app-语音转文字专家手机版下载v3.2.0-Linux公社...

    语音转文字专家为用户提供的是非常强大的翻译功能,不仅可以用来记录会议.办公速记而且可以用来记录上课笔记.采访等等,任何场合都可以使用,翻译十分精准,用户可以放心使用,不会耽误办公和学习的哦,此外在语音 ...

  7. java文字转语音支持ubuntu系统_9个(实时)语音转文字APP分享(推荐收藏)

    " 做会议记录.看无字幕网课再也不用担心,解放双手,提高效率." 随着语音转文字技术的发展,我们记录会议.上课内容等有了更好的方式. 实时语音转文字实现边听边看,并且还可回看转译记 ...

  8. 度秘语音引擎app_「资源」9个(实时)语音转文字APP分享(推荐收藏)

    " 做会议记录.看无字幕网课再也不用担心,解放双手,提高效率." 随着语音转文字技术的发展,我们记录会议.上课内容等有了更好的方式. 实时语音转文字实现边听边看,并且还可回看转译记 ...

  9. STM32MP157驱动开发——Linux 音频驱动

    STM32MP157驱动开发--Linux 音频驱动 一.简介 1.CS42L51 简介 2.I2S总线 3.STM32MP1 SAI 总线接口 二.驱动开发 1.音频驱动 1)修改设备树 i2c 接 ...

最新文章

  1. 科学探索奖首批50名获奖者都有谁?
  2. 如何干掉恶心的 SQL 注入?
  3. python调用通达信公式_对照通达信一些指标的Python实现
  4. AGC056E-Cheese【dp】
  5. html css 基础(标签选择,分页,行和块元素)
  6. 基于Docker的Redis集群简单搭建
  7. 移动端中如何检测设备方向的变化?
  8. potplayer 多个进程_创建守护进程的步骤
  9. sql查询三级菜单分类_SQL面试50题——思路解答与分类整理(中)窗口函数与子查询...
  10. surface 哪个系列适合java开发,iPad Pro和Surface Pro两大顶级平板该如何选择?
  11. Talib技术因子详解(八)
  12. 南瑞科技服务器型号,南瑞--NSC通讯概述
  13. 常用服务器管理口IP及账号密码(持续更新)
  14. html下划线怎么做成超链接,html超链接下划线应该加吗?
  15. 混合整数分布式蚁群优化算法-MIDACO介绍和试用
  16. CTFhub备份文件下载
  17. ECharts Title文字前添加图片
  18. 【探讨】打码平台是什么意思呢
  19. 装机防忽悠征文]对付不良商家,攒机知识。
  20. 企业办公网安全问题及其解决方案

热门文章

  1. Hud检测之图像点数计算
  2. Redis持久化数据之RDB和AOF
  3. 怎样让自己变得更优秀?职场精英是如何炼成的?怎样成为行业精英
  4. pyinstaller 生成exe之后不报毒的终极方法(亲测可用)
  5. k8s创建service
  6. 振荡次数计算机控制系统,计算机控制第四章.ppt
  7. 第1195期机器学习日报(2017-12-26)
  8. python中perf_counter_Python time.perf_counter()用法及代码示例
  9. 配置node服务器并且链接微信公众号接口配置(超详细)
  10. SAP销售开票同一客户实现不同统驭科目配置