用于 Freeswitch 智能语音开发的 ASR
ASR(Automatic Speech Recognition)自动语音识别技术是一种将人的语音转换为文本的技术。
一、freeswitch如何使用asr
freeswitch提供两个app功能detect_speech和play_and_detect_speech给用户调用,detect_speech是异步的,play_and_detect_speech是同步的。
1、detect_speech
1.1语法:detect_speech <mod_name> <gram_name> <name> <dest>
detect_speech stop
mod_name: 识别模块名称 如ali_asr[:params],其中params是param1=value1,param2=value2类型格式传入到switch_asr_interface_t接口中asr_open接口函数
gram_name:switch_asr_interface_t接口中asr_load_grammar接口函数传入参数
name: switch_asr_interface_t接口中asr_load_grammar接口函数传入参数
dest:switch_asr_interface_t接口中asr_open接口函数传入参数
stop:停止识别
1.2识别事件:设置通道变量fire_asr_events为true。freeswitch会触发SWITCH_EVENT_DETECTED_SPEECH事件返回给事件回调函数。
1.2.1事件头Speech-Type:
begin-speaking:检测到用户说话
detected-speech:识别结果
closed:识别关闭
1.2.2 事件body:识别结果
备注:事件可以用switch_event_bind("mod_asr", SWITCH_EVENT_DETECTED_SPEECH, NULL, my_event_handler, NULL)绑定;
2、play_and_detect_speech
2.1语法 play_and_detect_speech <file> detect:<engine>[:<mrcp_profile>] {param1=val1,param2=val2}<grammar>
file:语音文件或者tts
engine:识别模块
mrcp_profile:mrcp文件
{param1=val1,param2=val2}<grammar> 参数+语法文件
2.2用户可以通过app返回通道变量detect_speech_result获取识别结果
综上所述,异步函数detect_speech可以通过事件SWITCH_EVENT_DETECTED_SPEECH获取识别结果,同步函数 play_and_detect_speech既可以交互语音还能获取识别结果。
二、如何实现一个自定义的asr模块
众所周知:市面上的asr对接的中文识别引擎有阿里,科大讯飞,百度,想在fs中实现对接有3种方式:
1、mrcp对接,fs提供mod_unimrcp模块通过mrcp对接各类厂家asr服务,前提是厂家支持这种协议。具体细节可以参考官网的例子mod_unimrcp - FreeSWITCH - Confluence
2、通过实现media bug接口来实现
我们可以通过switch_core_media_bug_add(session, "my_detect_speech", key, speech_callback, sth, 0, SMBF_READ_STREAM | SMBF_NO_PAUSE | SMBF_ONE_ONLY, &sth->bug))来绑定识别回调函数,我们只要简单的实现回调函数来获取语音流媒体,把流媒体发送给识别引擎就获取识别结果。
static switch_bool_t speech_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
fastcall_speech_handle_t *sth = (fastcall_speech_handle_t *) user_data;
uint8_t data[SWITCH_RECOMMENDED_BUFFER_SIZE];
switch_frame_t frame = { 0 };
switch_asr_flag_t flags = SWITCH_ASR_FLAG_DATA;
frame.data = data;
frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE;
switch (type) {
case SWITCH_ABC_TYPE_INIT:
{
}
break;
case SWITCH_ABC_TYPE_CLOSE:
{
}
break;
case SWITCH_ABC_TYPE_READ:
if (sth->ah) {
if (switch_core_media_bug_read(bug, &frame, SWITCH_FALSE) != SWITCH_STATUS_FALSE) {
//frame.data, frame.datalen是我们需要的语音数据
}
}
}
break;
case SWITCH_ABC_TYPE_WRITE:
default:
break;
}
return SWITCH_TRUE;
}
3、通过fs现有ASR接口方式
asr_interface = (switch_asr_interface_t *)switch_loadable_module_create_interface(*module_interface, SWITCH_ASR_INTERFACE);
asr_interface->interface_name = "fastcall_asr";
asr_interface->asr_open = fastcall_asr_open;
asr_interface->asr_load_grammar = fastcall_asr_load_grammar;
asr_interface->asr_unload_grammar = fastcall_asr_unload_grammar;
asr_interface->asr_close = fastcall_asr_close;
asr_interface->asr_feed = fastcall_asr_feed;
//asr_interface->asr_feed_dtmf = fastcall_asr_feed_dtmf;
asr_interface->asr_resume = fastcall_asr_resume;
asr_interface->asr_pause = fastcall_asr_pause;
asr_interface->asr_start_input_timers = fastcall_asr_start_input_timers;
asr_interface->asr_check_results = fastcall_asr_check_results;
asr_interface->asr_get_results = fastcall_asr_get_results;
asr_interface->asr_text_param = fastcall_asr_text_param;
asr_interface->asr_numeric_param = fastcall_asr_numeric_param;
asr_interface->asr_float_param = fastcall_asr_float_param;
我们只需要实现对应的函数就可以获取到识别结果。
三、软件架构设计
1、编写mod_AI模块实现Freeswitch的ASR和TTS接口。
2、编写具有插件功能的ASR_Server系统,生成接口
struct fsl_asr_interface {
/*! the name of the interface */
const char *interface_name;
/*! function to open the asr interface */
int (*asr_open) (fsl_asr_handle_t *ah, char *param, int realtime);
/*! function to close the asr interface */
int (*asr_close) (fsl_asr_handle_t *ah);
int (*asr_start_session) (fsl_asr_handle_t* ah, int rate, char *param);
int (*asr_stop_session) (fsl_asr_handle_t* ah);
/*! function to feed audio to the ASR */
int (*asr_feed) (fsl_asr_handle_t *ah, void *data, unsigned int len, int audio_state, int* ep_stat, int* rec_stat);
int (*asr_realtime_feed) (fsl_asr_handle_t* ah, void* data, unsigned int len, char** rst);
/*! function to read results from the ASR */
int (*asr_get_result)(fsl_asr_handle_t *ah, char **rst);
fsl_thread_rwlock_t *rwlock;
int refs;
fsl_mutex_t *reflock;
fsl_loadable_module_interface_t *parent;
struct fsl_asr_interface *next;
};
给插件使用,方便扩展。
3、分别编写阿里,讯飞,百度模块提供识别引擎功能
4、ASR_Server通过mod_AI传送过来的参数interface_name来调用对应的模块进行识别。
原文地址:https://www.cnblogs.com/kn-zheng/p/17100736.html
推荐阅读
-
【摩尔线程+Colossal-AI强强联手】MusaBert登上CLUE榜单TOP10:技术细节揭秘 - 技术实力:摩尔线程凭借"软硬兼备"的技术底蕴,让MusaBert得以从底层优化到顶层。其内置多功能GPU配备AI加速和并行计算模块,提供了全面的AI与科学计算支持,为AI推理和低资源条件下的大模型训练等场景带来了高效、经济且环保的算力。 - 算法层面亮点:依托Colossal-AI AI大模型开发系统,MusaBert在训练过程中展现出了卓越的并行性能与易用性,特别在预处理阶段对DataLoader进行了优化,适应低资源环境高效处理海量数据。同时,通过精细的建模优化、领域内数据增强以及Adan优化器等手段,挖掘和展示了预训练语言模型出色的语义理解潜力。基于MusaBert,摩尔线程自主研发的MusaSim通过对比学习方法微调,结合百万对标注数据,MusaSim在多个任务如语义相似度、意图识别和情绪分析中均表现出色。 - 数据资源丰富:MusaBert除了自家高质量语义相似数据外,还融合了悟道开源200GB数据、CLUE社区80GB数据,以及浪潮公司提供的1TB高质量数据,保证模型即便在较小规模下仍具备良好性能。 当前,MusaBert已成功应用于摩尔线程的智能客服与数字人项目,并广泛服务于语义相似度、情绪识别、阅读理解与声韵识别等领域。为了降低大模型开发和应用难度,MusaBert及其相关高质量模型代码已在Colossal-AI仓库开源,可快速训练优质中文BERT模型。同时,通过摩尔线程与潞晨科技的深度合作,仅需一张多功能GPU单卡便能高效训练MusaBert或更大规模的GPT2模型,显著降低预训练成本,进一步推动双方在低资源大模型训练领域的共享目标。 MusaBert荣登CLUE榜单TOP10,象征着摩尔线程与潞晨科技联合研发团队在中文预训练研究领域的领先地位。展望未来,双方将携手探索更大规模的自然语言模型研究,充分运用上游数据资源,产出更为强大的模型并开源。持续强化在摩尔线程多功能GPU上的大模型训练能力,特别是在消费级显卡等低资源环境下,致力于降低使用大模型训练的门槛与成本,推动人工智能更加普惠。而潞晨科技作为重要合作伙伴,将继续发挥关键作用。
-
[物联网部分 ] 12 - 联发科 MTK8516 开发环境设置 - MT8516 还支持多达 8 个 TDM 通道和 2 个 PDM 输入,以支持远场(Far-field)麦克风语音控制和智能音频设备的多源音频输入。
-
用于 Freeswitch 智能语音开发的 ASR
-
Web3年薪百万的 "空投 "到底是什么?教你开发用于批量转账的空投 DApp(涵盖前端、智能合约)
-
WiseGoods 发布适用于 Tenguet 900 平台的无线 AR 智能眼镜参考设计,推动下一代无线 AR 开发
-
CLion 2023:专注于 C 和 C++ 编程的智能集成开发环境,适用于 mac/win。
-
创建一个让FreeSWITCH通过mod_unimrcp与科大讯飞相连的MRCP服务器,实现语音转文本(TTS)和语音识别(ASR)功能。
-
F#探险之旅(二):函数式编程(上)-函数式编程范式简介 F#主要支持三种编程范式:函数式编程(Functional Programming,FP)、命令式编程(Imperative Programming)和面向对象(Object-Oriented,OO)的编程。回顾它们的历史,FP是最早的一种范式,第一种FP语言是IPL,产生于1955年,大约在Fortran一年之前。第二种FP语言是Lisp,产生于1958,早于Cobol一年。Fortan和Cobol都是命令式编程语言,它们在科学和商业领域的迅速成功使得命令式编程在30多年的时间里独领风骚。而产生于1970年代的面向对象编程则不断成熟,至今已是最流行的编程范式。有道是“*代有语言出,各领风骚数十年”。 尽管强大的FP语言(SML,Ocaml,Haskell及Clean等)和类FP语言(APL和Lisp是现实世界中最成功的两个)在1950年代就不断发展,FP仍停留在学院派的“象牙塔”里;而命令式编程和面向对象编程则分别凭着在商业领域和企业级应用的需要占据领先。今天,FP的潜力终被认识——它是用来解决更复杂的问题的(当然更简单的问题也不在话下)。 纯粹的FP将程序看作是接受参数并返回值的函数的集合,它不允许有副作用(side effect,即改变了状态),使用递归而不是循环进行迭代。FP中的函数很像数学中的函数,它们都不改变程序的状态。举个简单的例子,一旦将一个值赋给一个标识符,它就不会改变了,函数不改变参数的值,返回值是全新的值。 FP的数学基础使得它很是优雅,FP的程序看起来往往简洁、漂亮。但它无状态和递归的天性使得它在处理很多通用的编程任务时没有其它的编程范式来得方便。但对F#来说这不是问题,它的优势之一就是融合了多种编程范式,允许开发人员按照需要采用最好的范式。 关于FP的更多内容建议阅读一下这篇文章:Why Functional Programming Matters(中文版)。F#中的函数式编程 从现在开始,我将对F#中FP相关的主要语言结构逐一进行介绍。标识符(Identifier) 在F#中,我们通过标识符给值(value)取名字,这样就可以在后面的程序中引用它。通过关键字let定义标识符,如: let x = 42 这看起来像命令式编程语言中的赋值语句,两者有着关键的不同。在纯粹的FP中,一旦值赋给了标识符就不能改变了,这也是把它称为标识符而非变量(variable)的原因。另外,在某些条件下,我们可以重定义标识符;在F#的命令式编程范式下,在某些条件下标识符的值是可以修改的。 标识符也可用于引用函数,在F#中函数本质上也是值。也就是说,F#中没有真正的函数名和参数名的概念,它们都是标识符。定义函数的方式与定义值是类似的,只是会有额外的标识符表示参数: let add x y = x + y 这里共有三个标识符,add表示函数名,x和y表示它的参数。关键字和保留字关键字是指语言中一些标记,它们被编译器保留作特殊之用。在F#中,不能用作标识符或类型的名称(后面会讨论“定义类型”)。它们是: abstract and as asr assert begin class default delegate do donedowncast downto elif else end exception extern false finally forfun function if in inherit inline interface internal land lazy letlor lsr lxor match member mod module mutable namespace new nullof open or override private public rec return sig static structthen to true try type upcast use val void when while with yield 保留字是指当前还不是关键字,但被F#保留做将来之用。可以用它们来定义标识符或类型名称,但编译器会报告一个警告。如果你在意程序与未来版本编译器的兼容性,最好不要使用。它们是: atomic break checked component const constraint constructor continue eager event external fixed functor global include method mixinobject parallel process protected pure sealed trait virtual volatile 文字值(Literals) 文字值表示常数值,在构建计算代码块时很有用,F#提供了丰富的文字值集。与C#类似,这些文字值包括了常见的字符串、字符、布尔值、整型数、浮点数等,在此不再赘述,详细信息请查看F#手册。 与C#一样,F#中的字符串常量表示也有两种方式。一是常规字符串(regular string),其中可包含转义字符;二是逐字字符串(verbatim string),其中的(")被看作是常规的字符,而两个双引号作为双引号的转义表示。下面这个简单的例子演示了常见的文字常量表示: let message = "Hello World"r"n!" // 常规字符串let dir = @"C:"FS"FP" // 逐字字符串let bytes = "bytes"B // byte 数组let xA = 0xFFy // sbyte, 16进制表示let xB = 0o777un // unsigned native-sized integer,8进制表示let print x = printfn "%A" xlet main = print message; print dir; print bytes; print xA; print xB; main Printf函数通过F#的反射机制和.NET的ToString方法来解析“%A”模式,适用于任何类型的值,也可以通过F#中的print_any和print_to_string函数来完成类似的功能。值和函数(Values and Functions) 在F#中函数也是值,F#处理它们的语法也是类似的。 let n = 10let add a b = a + blet addFour = add 4let result = addFour n printfn "result = %i" result 可以看到定义值n和函数add的语法很类似,只不过add还有两个参数。对于add来说a + b的值自动作为其返回值,也就是说在F#中我们不需要显式地为函数定义返回值。对于函数addFour来说,它定义在add的基础上,它只向add传递了一个参数,这样对于不同的参数addFour将返回不同的值。考虑数学中的函数概念,F(x, y) = x + y,G(y) = F(4, y),实际上G(y) = 4 + y,G也是一个函数,它接收一个参数,这个地方是不是很类似?这种只向函数传递部分参数的特性称为函数的柯里化(curried function)。 当然对某些函数来说,传递部分参数是无意义的,此时需要强制提供所有参数,可是将参数括起来,将它们转换为元组(tuple)。下面的例子将不能编译通过: let sub(a, b) = a - blet subFour = sub 4 必须为sub提供两个参数,如sub(4, 5),这样就很像C#中的方法调用了。 对于这两种方式来说,前者具有更高的灵活性,一般可优先考虑。 如果函数的计算过程中需要定义一些中间值,我们应当将这些行进行缩进: let halfWay a b = let dif = b - a let mid = dif / 2 mid + a 需要注意的是,缩进时要用空格而不是Tab,如果你不想每次都按几次空格键,可以在VS中设置,将Tab字符自动转换为空格;虽然缩进的字符数没有限制,但一般建议用4个空格。而且此时一定要用在文件开头添加#light指令。作用域(Scope)作用域是编程语言中的一个重要的概念,它表示在何处可以访问(使用)一个标识符或类型。所有标识符,不管是函数还是值,其作用域都从其声明处开始,结束自其所处的代码块。对于一个处于最顶层的标识符而言,一旦为其赋值,它的值就不能修改或重定义了。标识符在定义之后才能使用,这意味着在定义过程中不能使用自身的值。 let defineMessage = let message = "Help me" print_endline message // error 对于在函数内部定义的标识符,一般而言,它们的作用域会到函数的结束处。 但可使用let关键字重定义它们,有时这会很有用,对于某些函数来说,计算过程涉及多个中间值,因为值是不可修改的,所以我们就需要定义多个标识符,这就要求我们去维护这些标识符的名称,其实是没必要的,这时可以使用重定义标识符。但这并不同于可以修改标识符的值。你甚至可以修改标识符的类型,但F#仍能确保类型安全。所谓类型安全,其基本意义是F#会避免对值的错误操作,比如我们不能像对待字符串那样对待整数。这个跟C#也是类似的。 let changeType = let x = 1 let x = "change me" let x = x + 1 print_string x 在本例的函数中,第一行和第二行都没问题,第三行就有问题了,在重定义x的时候,赋给它的值是x + 1,而x是字符串,与1相加在F#中是非法的。 另外,如果在嵌套函数中重定义标识符就更有趣了。 let printMessages = let message = "fun value" printfn "%s" message; let innerFun = let message = "inner fun value" printfn "%s" message innerFun printfn "%s" message printMessages 打印结果: fun value inner fun valuefun value 最后一次不是inner fun value,因为在innerFun仅仅将值重新绑定而不是赋值,其有效范围仅仅在innerFun内部。递归(Recursion)递归是编程中的一个极为重要的概念,它表示函数通过自身进行定义,亦即在定义处调用自身。在FP中常用于表达命令式编程的循环。很多人认为使用递归表示的算法要比循环更易理解。 使用rec关键字进行递归函数的定义。看下面的计算阶乘的函数: let rec factorial x = match x with | x when x < 0 -> failwith "value must be greater than or equal to 0" | 0 -> 1 | x -> x * factorial(x - 1) 这里使用了模式匹配(F#的一个很棒的特性),其C#版本为: public static long Factorial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException("value must be greater than or equal to 0"); } if (n == 0) { return 1; } return n * Factorial (n - 1); } 递归在解决阶乘、Fibonacci数列这样的问题时尤为适合。但使用的时候要当心,可能会写出不能终止的递归。匿名函数(Anonymous Function) 定义函数的时候F#提供了第二种方式:使用关键字fun。有时我们没必要给函数起名,这种函数就是所谓的匿名函数,有时称为lambda函数,这也是C#3.0的一个新特性。比如有的函数仅仅作为一个参数传给另一个函数,通常就不需要起名。在后面的“列表”一节中你会看到这样的例子。除了fun,我们还可以使用function关键字定义匿名函数,它们的区别在于后者可以使用模式匹配(本文后面将做介绍)特性。看下面的例子: let x = (fun x y -> x + y) 1 2let x1 = (function x -> function y -> x + y) 1 2let x2 = (function (x, y) -> x + y) (1, 2) 我们可优先考虑fun,因为它更为紧凑,在F#类库中你能看到很多这样的例子。 注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译。 F#系列随笔索引页面
-
智能语音助手在移动设备上的 AI 离线开发