STM32MP157-Linux音频应用编程-语音转文字项目
文章目录
- 前言
- 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 key
和Secret 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音频应用编程-语音转文字项目相关推荐
- Linux应用编程-音频应用编程-语音转文字项目
文章目录 前言 Linux语音识别 alsa-lib简介: 安装alsa-lib库: API调用 录音 相关概念 样本长度(Sample) 声道数(channel) 帧(frame) 周期(perio ...
- 有什么开源的python汉语语音转文字项目?
随着语音技术的不断发展,语音识别技术已经逐渐成熟,成为了很多智能应用的重要组成部分,比如智能家居.语音助手等等.而在语音识别技术中,汉语语音识别则是一个更具有挑战性的领域.为了方便程序员们进行汉语语音 ...
- Linux音频OSS编程指南
转载地址:https://blog.csdn.net/TJU355/article/details/6943202 OSS--跨平台的音频接口简介 http://bbs.lemote.com/view ...
- STM32MP157-Linux音频应用编程-简易语音助手
文章目录 前言 STM32MP157简易语音助手 alsa-lib简介: 移植alsa-lib库: libcurl库简介: 移植libcurl库: API调用 修改asrmain.c文件 修改toke ...
- 【正点原子Linux连载】第二十三章 音频应用编程-摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.1
第二十三章 音频应用编程 ALPHA I.MX6U开发板支持音频,板上搭载了音频编解码芯片WM8960,支持播放以及录音功能! 本章我们来学习Linux下的音频应用编程,音频应用编程相比于前面几个章节 ...
- linux下录音识别成文字软件下载,语音转文字专家app-语音转文字专家手机版下载v3.2.0-Linux公社...
语音转文字专家为用户提供的是非常强大的翻译功能,不仅可以用来记录会议.办公速记而且可以用来记录上课笔记.采访等等,任何场合都可以使用,翻译十分精准,用户可以放心使用,不会耽误办公和学习的哦,此外在语音 ...
- java文字转语音支持ubuntu系统_9个(实时)语音转文字APP分享(推荐收藏)
" 做会议记录.看无字幕网课再也不用担心,解放双手,提高效率." 随着语音转文字技术的发展,我们记录会议.上课内容等有了更好的方式. 实时语音转文字实现边听边看,并且还可回看转译记 ...
- 度秘语音引擎app_「资源」9个(实时)语音转文字APP分享(推荐收藏)
" 做会议记录.看无字幕网课再也不用担心,解放双手,提高效率." 随着语音转文字技术的发展,我们记录会议.上课内容等有了更好的方式. 实时语音转文字实现边听边看,并且还可回看转译记 ...
- STM32MP157驱动开发——Linux 音频驱动
STM32MP157驱动开发--Linux 音频驱动 一.简介 1.CS42L51 简介 2.I2S总线 3.STM32MP1 SAI 总线接口 二.驱动开发 1.音频驱动 1)修改设备树 i2c 接 ...
最新文章
- 科学探索奖首批50名获奖者都有谁?
- 如何干掉恶心的 SQL 注入?
- python调用通达信公式_对照通达信一些指标的Python实现
- AGC056E-Cheese【dp】
- html css 基础(标签选择,分页,行和块元素)
- 基于Docker的Redis集群简单搭建
- 移动端中如何检测设备方向的变化?
- potplayer 多个进程_创建守护进程的步骤
- sql查询三级菜单分类_SQL面试50题——思路解答与分类整理(中)窗口函数与子查询...
- surface 哪个系列适合java开发,iPad Pro和Surface Pro两大顶级平板该如何选择?
- Talib技术因子详解(八)
- 南瑞科技服务器型号,南瑞--NSC通讯概述
- 常用服务器管理口IP及账号密码(持续更新)
- html下划线怎么做成超链接,html超链接下划线应该加吗?
- 混合整数分布式蚁群优化算法-MIDACO介绍和试用
- CTFhub备份文件下载
- ECharts Title文字前添加图片
- 【探讨】打码平台是什么意思呢
- 装机防忽悠征文]对付不良商家,攒机知识。
- 企业办公网安全问题及其解决方案
热门文章
- Hud检测之图像点数计算
- Redis持久化数据之RDB和AOF
- 怎样让自己变得更优秀?职场精英是如何炼成的?怎样成为行业精英
- pyinstaller 生成exe之后不报毒的终极方法(亲测可用)
- k8s创建service
- 振荡次数计算机控制系统,计算机控制第四章.ppt
- 第1195期机器学习日报(2017-12-26)
- python中perf_counter_Python time.perf_counter()用法及代码示例
- 配置node服务器并且链接微信公众号接口配置(超详细)
- SAP销售开票同一客户实现不同统驭科目配置