【语音工程开发】

硕士毕业一年左右,在AI芯片公司智能座舱部门,工作内容以多模语音SDK开发为主,写这篇博客的目的最主要是对自己过往一年的复盘总结,其次,后面就业方向可能转变为自动驾驶相关,但是不可避免在未来的某些项目中也需要用到语音相关的知识。总结起来,技术栈核心还是C++和cmake工程开发,语音工程开发是对语音识别过程的数据流进行调度,需要对语音基本知识以及基本处理流程需要有充分的了解,其次也需要了解图像处理、linux系统、操作系统、计算机网络等基础知识

AI赋能的两大领域,一个是语音,另一个则是图像,均具有不同方向众多落地场景。相比于利润率高的互联网企业,从营业能力层面来看,国内主营为语音的公司:科大讯飞、思必驰、云知声、出门问问等基本很难有利润,第一梯队的讯飞也要靠一些教育类的产品盈利。反观图像公司,国内最知名的AI四小龙(商汤、旷世、云从、依图)研发投入也巨大,盈利更是遥遥无期。在自动驾驶领域,也是以图像AI为驱动的行业,技术为主,单从研发投入上也是巨大的。怎么说呢,科技兴国,虽然目前这些高科技公司亏损严重,但个人看来,这是不可改变的历史潮流,人类无论如何也是需要这些创新科技的公司来推动整个社会的技术的革命。未来一定是更加智能的,自动驾驶、智能机器人、流畅的人机交互在未来的某一天将会到临。

语音识别是将mic采集的音频数字信号转换成文本,并进行理解和分析语义的一个过程。整体流程大致可以分为以下几个部分:预处理、VAD端点激活音检测、唤醒识别、ASR语音识别、NLU自然语音理解、NLG自然语音生成、TTS(TextToSpeech)文本到语音。

1.1 概念介绍

  • 采样率,每1秒采样的音频个数或者频率,通常为16k(每秒采样16000次),也有48k,44.1k,采样率越高,获取的信息越多
  • 采样位数(位深),每个采样点的大小,通常为16bit(2字节),也有8bit,32bit
  • 通道数,每次采样的通道数量,通常和mic的数量相同,一般为双声道,即录制的音频是两个通道的,也有单通道,4通道,8通道的音频
  • 音频大小,采样率16k,位深16bit,通道数量为4,则录制1s的音频buffer大小大概是128kb(实际稍微小一点1000 / 1024)
buffer_size = 16000 * 16 / 8 * 4  (字节)
  • 大小端:每个采样点的存储模式一般为小端模式(低位地址为高位)
    [En]

    large and small end: the storage mode of each sampling point is generally small end mode (low-order address is in high-order)*

  • 音频交错,一次存储录制音频
    [En]

    Audio interleaving, storage of recording audio at one time*

1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 41 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4
  • tinyalsa录音,参考tinyalsa录音实现;除此之外,linux底层是提供了录音的原生驱动alsa(tinyalsa相当于是对内核ALSA的接口一层封装,使用上更加便捷)。
  • wav和pcm区别:相比于pcm保存的全部是原始音频的信息,wav多了一个头部,存储了该段音频的一些属性信息,通常多出44字节,根据录制方式,可能头部大小也有可能存储更多的信息,大于44字节。
    【语音工程开发】
  • 音频分析工具:audition
  • 音频二进度读取工具:Binary Viewer 下载

1.2 语音基础

  • 唤醒:预先设定的特定唤醒词,如嗨siri,小爱同学,唤醒之后即可对通过mic对声音进行采集,而后才进行后续的语音识别相关算法检测;所以对唤醒率的要求还是比较高,因为需要在任何场景下,对于指定的唤醒词能够一触即发,同时,随着唤醒率的提高,唤醒的误报(在无声或者没有唤醒词的情况下被唤醒)也可能变高,这是相互矛盾的,但是总归有方法来平衡唤醒率与误报次数之间的关系
  • 全时免唤醒:无需唤醒词,即可对mic采集的音频进行识别,相当于,语音系统后台一直在对音频进行识别
  • 语音激活音(VAD检测):底层算法应该是根据音频的强度信息来检测指定音频的开始时间和结束时间,相当于在这段时间检测出的音频是有声音,其他时间段是无声的;其作用也很明显,经过vad检测截取的音频是更加有效的(音频长度更短,但是更加准确)
  • 语音识别(ASR检测):根据vad检测截取的音频,将二进制的数字化的音频转换成文本的一个过程。asr一般由声学模型,语言模型和解码器三者组成,用更加通俗的话来说,声学模型是将一段音频转换成拼音,语言模型将拼音转换成对应的文字,而且解码器即是结合声学模型和语言模型的结果通过一定的算法来匹配出概率最大的一句完成的话。对于asr来说,其指标就是句准率,即系统检测的这句话和标签的重合度有多少。另外,asr识别分为云端(在线)和端侧(离线),云端的话依赖网络,检测效果肯定更好;端侧的话不依赖网络,直接在本地即完成检测
  • 自然语言处理(NLP):包括自然语言理解(NLU)和自然语音生成(NLG)
  • NLU:将asr检测出的文本转换成机器可以理解的结果,比如说”空调开到23度”,nlu则负责理解其domain(领域)、intent(意图)和一些slots(属性);车机才知道车控相关领域,需要打开空调,打开的大小为23度
  • NLG:对机器说”今天天气怎么样”,机器在理解你说的话之后,需要做相应的响应,来生成一句话回复你,如”今天天气多云,温度为23度”
  • TTS:语音合成,机器生成一段语音,并播报出来

1.3 工程经验

做工程开发是比较吃经验的一份工作,所以尤其需要扎实基础;对于应届生来说,在学校的项目中,很难有系统性地学习工程方面的知识,顶多谢谢应用层面的一些代码,不清楚所有模块具体是如果构建出来;所以,做一个项目快速积累经验的话,一定要理解底层原理,对于后期开发大有利处。举个简单的例子:经常使用vector和map,得知道其底层是如何实现的;代码经过编译只有才能运行,得知道机器是如何编译的;cmake是如果管理不同源文件的;使用过内存池、线程池,知道它的优点,那么他们是如何实现的;性能分析有哪些方法,又是如何能优化的;perf工具可以抓取程序运行时的一些堆栈信息,那么它是如何实现的呢;C++11掌握程度如何,那C++14、17、20+呢;操作系统掌握程度如何,是否了解系统底层的一些原理;是否充分了解linux内核,汇编,能够基于底层对C++工程代码进行优化。我觉得,在项目中成长自己,也需要我们额外地学习更多知识反馈于项目之中,这才是对项目的充分理解。同时,在面试过程中,知晓其中的原理,总比调调接口,写写应用层的代码更加重要。而且,面试官更加看重的也是你对项目的理解,对底层原理的理解,以及你的思维能力,沟通能力,适应新团队的能力

此外,普通毕业生从事互联网或车企相关工作,前期以提高技术、扎实基础为基础,投入足够的时间始终可以成为一名优秀的程序员或产品经理。在一个内部交易量大的时代,技术是我们的目的地吗?如果你热爱技术,也许你可以考虑如何成为一名系统架构师;如果你喜欢商业,你可以在项目中探索一些商业经验,如何领导一个产品的实施;善于沟通和技术,你也可以走上管理之路。普通人的成长之路,首先要成为一名大兵,然后再看机会一步步探索,找到适合自己的方向。

[En]

In addition, ordinary graduates engaged in Internet or car company related work, the early stage to improve technology, a solid foundation as the foundation, invest enough time can always be a good programmer or a product manager. In an era of internal volume, is technology our destination? Maybe if you love technology, you can think about how to become a system architect; if you like business, you can explore some business experience in the project and how to lead the implementation of a product; be good at communication and technology, and you can also embark on the road of management. The way for ordinary people to grow up should first become a big soldier, and then look at the opportunity to explore step by step to find a direction suitable for them.

2.1 C++11

代码如下(示例):


shared_ptr<>
weak_ptr<>
unique_ptr<>

2.2 cmake编译

CMakeLists.txt 常用语法笔记示例:

#指定版本号
cmake_minimum_required(VERSION 2.8)

#指定项目名  引入两个变量CSDK_BINARY_DIR和CSDK_SOURCE_DIR
PROJECT(CSDK)

设置变量的值
set()

find_package(OpenCV REQUIRED)寻找OpenCV库的头文件和库文件的指令,找到则可提供OpenCV_INCLUDE_DIRS和OpenCV_LIBRARIES目录

include() 引入cmake代码
include_directories() #用来提供找头文件路径的(全路径)

include_directories(${PROJECT_SOURCE_DIR}/) #库文件搜索以PROJECT_SOURCE_DIR路径为基础
Linux设置包含目录 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}")

link_directories(directory1 directory2 ...) #添加需要链接的库文件目录LINK_DIRECTORIES,全路径
Linux设置链接目录 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/libs")

add_exectualbe() 生成一个可执行文件
add_library() 生成一个库文件
add_library(common STATIC util.cpp) # 生成静态库
add_library(common SHARED util.cpp) # 生成动态库或共享库
add_subdirectory() 添加一个子目录并构建该子目录
cmake .. 在CMakeList.txt所在的路径下执行命令
message() 打印信息

PROJECT_SOURCE_DIR:工程的根目录PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
PROJECT_NAME:返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE:当前所在的行
CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

设置SOURCE_INC变量包含的文件
file(GLOB SOURCE_INC
        "include/hobotalsasdk/*.hpp"
        "include/hobotalsasdk/*.h"
        )

install(TARGETS test DESTINATION bin)  #将test安装到/usr/local/bin目录下
install(FILES filedemo DESTINATION include)  #将filedemo安装到/usr/local/include目录下

在cmake语法中,link_libraries和target_link_libraries是很重要的两个链接库的方式,虽然写法上很相似,但是功能上有很大区别:

1,link_libraries用在add_executable之前,指定查找的路径,该路径下可能包含lib,没有指定target;而target_link_libraries用在add_executable之后,用来链接库,类似GCC中的-L概念,指定了target并且调用链接库(比如将库文件链接到可执行程序中)。
2,link_libraries用来链接静态库,target_link_libraries用来链接导入库,即按照header file + .lib + .dll方式隐式调用动态库的.lib库

set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") 在指定域中设置一个命名属性;第一个参数决定该属性设置所在的域;必选项PROPERTY后面紧跟着要获取的属性的名字
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY LINK_DIRECTORIES) 获取一个属性值

在include_directories()后,获取已经添加的目录
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
foreach(dir ${dirs})
  message(STATUS "dir='${dir}'")
endforeach()

message :为用户显示一条消息
message( [STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
(无) = 重要消息;
 STATUS = 非重要消息;
 WARNING = CMake 警告, 会继续执行;
 AUTHOR_WARNING = CMake 警告 (dev), 会继续执行;
 SEND_ERROR = CMake 错误, 继续执行,但是会跳过生成的步骤;
 FATAL_ERROR = CMake 错误, 终止所有处理过程;

 set(CMAKE_BUILD_TYPE Debug) 生成debug库  file libcaboremm.so(not stripped)
 set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-s") 去掉debug库的符号表,变成release库(not stripped)

 file libhrsc.so 可以查看是否是debug库,not stripped为debug,stripped为release库

#编译debug库
set(CMAKE_BUILD_TYPE Debug)
cmake .. DCMAKE_BUILD_TYPE=Debug

#在CMakeLists.txt中宏定义OFFLINETEST_JSON变量,需要加-D
add_definitions(-DOFFLINETEST_JSON)
#OFFLINETEST_JSON可以用作C++代码的宏定义判断,控制代码的开启和关闭
#ifdef(OFFLINETEST_JSON) 或者 #if defined(OFFLINETEST_JSON)

查看编译及链接耗时
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CMAKE_COMMAND} -E time")

2.3 开发工具

2.4 脚本语言

三剑客:grep、awk、sed

2.5 代码规范

C++:官方代码规范如下,很有必要学习,虽然看着枯燥,其实也是对自己基础的一种提升,更是大部分公司规范编码格式的强制要求

SDK&#xFF08;Software Development Kit&#xFF09;&#xFF0C;&#x8F6F;&#x4EF6;&#x5F00;&#x53D1;&#x5DE5;&#x5177;&#x5305;&#xFF0C;&#x4E00;&#x822C;&#x90FD;&#x662F;&#x4E00;&#x4E9B;&#x8F6F;&#x4EF6;&#x5DE5;&#x7A0B;&#x5E08;&#x4E3A;&#x7279;&#x5B9A;&#x7684;&#x8F6F;&#x4EF6;&#x5305;&#x3001;&#x8F6F;&#x4EF6;&#x6846;&#x67B6;&#x3001;&#x786C;&#x4EF6;&#x5E73;&#x53F0;&#x3001;&#x64CD;&#x4F5C;&#x7CFB;&#x7EDF;&#x7B49;&#x5EFA;&#x7ACB;&#x5E94;&#x7528;&#x8F6F;&#x4EF6;&#x65F6;&#x7684;&#x5F00;&#x53D1;&#x5DE5;&#x5177;&#x7684;&#x96C6;&#x5408;&#x3002;&#x4E00;&#x4E9B;&#x901A;&#x7528;&#x7684;&#x5DE5;&#x5177;&#x6216;&#x8005;&#x7B97;&#x6CD5;&#x53EF;&#x4EE5;&#x901A;&#x8FC7;&#x5C01;&#x88C5;&#x6210;SDK&#x7684;&#x5F62;&#x5F0F;&#x63D0;&#x4F9B;&#x7ED9;&#x9700;&#x6C42;&#x65B9;&#xFF08;&#x5305;&#x62EC;&#x516C;&#x53F8;&#x5185;&#x90E8;&#x6216;&#x8005;&#x5916;&#x90E8;&#xFF09;&#xFF0C;&#x901A;&#x5E38;&#x4F1A;&#x63D0;&#x4F9B;&#x5DE5;&#x5177;&#x7684;&#x8BBE;&#x8BA1;&#x6587;&#x6863;&#xFF0C;&#x52A8;&#x6001;&#x5E93;&#x6216;&#x8005;&#x9759;&#x6001;&#x5E93;&#x4EE5;&#x53CA;&#x5BF9;&#x5E94;&#x7684;&#x5934;&#x6587;&#x4EF6;&#xFF0C;&#x793A;&#x4F8B;&#x4EE3;&#x7801;&#x7ED9;&#x9700;&#x6C42;&#x65B9;&#x8FDB;&#x884C;&#x96C6;&#x6210;

3.1. 设计流程

&#x9700;&#x6C42;&#x5206;&#x6790;&#x3001;&#x65B9;&#x6848;&#x8BC4;&#x4F30;&#x3001;&#x65B9;&#x6848;&#x8BBE;&#x8BA1;&#x3001;&#x6587;&#x6863;&#x7F16;&#x5199;&#x3001;&#x63A5;&#x53E3;&#x8BBE;&#x8BA1;&#x3001;&#x4EE3;&#x7801;&#x7F16;&#x5199;&#x3002;

3.2 设计原则

4.1 cpu占用

  • 管理一个任务队列queue,一个线程池vector,然后每次取一个任务分配给一个线程去做,循环往复;
    *
  &#x5E94;&#x7528;&#xFF1A;&#x521D;&#x59CB;&#x5316;&#x521B;&#x5EFA;PRE_SIZE 20&#x4E2A;&#x7EBF;&#x7A0B;&#xFF0C;&#x585E;&#x5165;free_list&#x95F2;&#x4F59;&#x7EBF;&#x7A0B;&#x94FE;&#x8868;&#xFF1B;&#x6BCF;&#x4E2A;&#x7EBF;&#x7A0B;&#x5BF9;&#x5E94;&#x540C;&#x4E00;&#x4E2A;&#x4EFB;&#x52A1;&#x7684;&#x961F;&#x5217;queue&#xFF08;&#x5927;&#x5C0F;&#x4E3A;60&#xFF09;&#xFF0C;&#x4EFB;&#x52A1;&#x6570;&#x91CF;&#x5927;&#x4E8E;60&#x65F6;&#xFF0C;&#x6E05;&#x7A7A;&#x8BE5;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#xFF1B;&#x6BCF;&#x589E;&#x52A0;&#x4E00;&#x4E2A;&#x4EFB;&#x52A1;&#xFF0C;&#x4F7F;&#x7528;&#x4E00;&#x4E2A;&#x95F2;&#x4F59;&#x7684;free_list&#x7EBF;&#x7A0B;&#xFF0C;&#x5E76;&#x589E;&#x52A0;&#x4E00;&#x4E2A;used_list&#x7EBF;&#x7A0B;&#xFF0C;&#x5E76;&#x5C06;&#x7EBF;&#x7A0B;&#x585E;&#x5165;&#x7EBF;&#x7A0B;&#x94FE;&#x8868;&#x7684;&#x9996;&#x90E8;&#xFF1B;&#x95F2;&#x4F59;&#x7EBF;&#x7A0B;free_list&#x4E3A;0&#xFF0C;&#x5373;&#x7EBF;&#x7A0B;&#x6570;&#x91CF;&#x5927;&#x4E8E;20&#x65F6;&#xFF08;&#x6700;&#x5927;&#x6570;&#x91CF;&#x4E0D;&#x80FD;&#x8D85;&#x8FC7;MAX_SIZE 50&#xFF09;&#xFF0C;&#x989D;&#x5916;&#x518D;&#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x7EBF;&#x7A0B;&#xFF0C;&#x7EBF;&#x7A0B;&#x7684;&#x751F;&#x5B58;&#x65F6;&#x95F4;&#x4E3A;600s&#xFF0C;&#x5E76;&#x5BF9;&#x7EBF;&#x7A0B;&#x94FE;&#x8868;&#x7684;&#x5C3E;&#x90E8;&#x8FDB;&#x884C;&#x5224;&#x65AD;&#xFF08;&#x5C3E;&#x90E8;&#x7EBF;&#x7A0B;&#x521B;&#x5EFA;&#x7684;&#x65F6;&#x95F4;&#x8D85;&#x8FC7;600s&#xFF0C;&#x8BE5;&#x7EBF;&#x7A0B;&#x52A0;&#x5165;free_list&#xFF0C;&#x540C;&#x65F6;used_list&#x4F7F;&#x7528;&#x7684;&#x7EBF;&#x7A0B;&#x51CF;1&#xFF09;
  • 任务队列是典型的生产者-消费者模型,通常实现为一个 mutex + 一个条件变量,或是一个 mutex + 一个信号量。mutex 实际上就是锁,保证任务的添加和移除(获取)的互斥性,一个条件变量是保证获取 task 的同步性(条件阻塞),一个 empty 的队列,线程应该等待(阻塞);
  • 设置线程名,线程优先级,绑定线程到指定CPU核数

4.2 内存占用

4.3 延迟统计

&#x4E00;&#x6BB5;1s&#x7684;&#x97F3;&#x9891;&#xFF0C;&#x5DE5;&#x7A0B;&#x53CA;&#x7B97;&#x6CD5;&#x5408;&#x8BA1;&#x5904;&#x7406;&#x65F6;&#x95F4;&#x4E3A;0.3s&#xFF0C;&#x5373;&#x53EF;&#x5F97;&#x5230;&#x7ED3;&#x679C;&#xFF0C;&#x5219;&#x8BED;&#x97F3;&#x8BC6;&#x522B;&#x7684;&#x603B;&#x4F53;&#x5B9E;&#x65F6;&#x7387;&#x4E3A;0.3

4.4 音频诊断

读取录制的音频实质上一组二进制的数字,幅度范围在00~FF之间,判断录制的一端音频是否有效,可以判断该断音频中幅度为00或者FF的个数占总音频总长度的百分比是否超过一定阈值,如果基本全为00,该音频基本是静音的,如果基本全为FF,这段音频也是无效的杂音

All in AI

Original: https://blog.csdn.net/weixin_43273308/article/details/125122778
Author: 自动驾驶小哥
Title: 【语音工程开发】

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/515041/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球