那在现实生活中,微信的语音功能让人铭记于心。手指按下录音,抬起发送。
试想一下,如果某些场景,我们在没有手的情况下如何让设备进行识别。
今天,我将我自己封装的语音打断的代码共享给大家,各位可以按自己的需求和场景需要修改代码细节。
原理介绍:
启动一个新线程 ←←←←←←←←←←←←←←←←←←←←
↓ ↑
开始录制(聆听) ↑
↓ ↑
当前音量低于从录音开始的平均音量多于设定阈值 ↑
↓ ↑
结束录制,生成文件并上传至百度进行语音识别(聆听)→→→
↓
语音识别返回结果
↓
将结果丢到下一个逻辑
↓
线程销毁
依照以上的逻辑,我们可以在线程不间断的对接下识别所有的句子。各位可以调整设定阈值来控制打断时机。
这里安利一下上几章节给各位讲到的windows录音类。
《[语音技术]C#在windows平台的录音类封装》 url:http://ai.baidu.com/forum/topic/show/492634
《[语音技术]C#在win平台基于录音类试写唤醒》 url:http://ai.baidu.com/forum/topic/show/492635
有要用UNIT的同学可以看一下c#调用UNIT通用API的文章
《[UNIT] C#利用API调用UNIT》 url:http://ai.baidu.com/forum/topic/show/492630
关于UNIT配置 请移步至UNIT板块,有我详细的3篇配置UNIT的经验贴 这里就不说了
这里我在上一章节的基础上再次封装一个类,目的是为了更好的控制识别流程,是根据我项目场景来的。我命名为了SoundListener
class SoundListener
{
private string time; //文件名
private bool IsChecking;//是否在识别中
private SoundRecord sr; //录音类
private int VolumnCount; //音量计数器
private readonly Asr _asrClient; //ASR SDK
public static bool IsJarvis; //判断是否触发唤醒
public SoundListener(Asr asr,bool isjarvis) //构造函数
{
time = DateTime.Now.ToString("yyyyMMddHHmmss"); //获取时间
IsChecking = true; //开始录制信号
sr = new SoundRecord(); //new一个录音类
sr.SetFileName(time + ".wav"); //设置文件名
_asrClient = asr; //创建语音识别
IsJarvis = isjarvis; //继承唤醒信号
}
public Task Start() //核心方法 往后看 会再介绍
{
return Task.Run(() =>
{
sr.RecStart();
while (IsChecking)
{
if (sr.CurrentVolume < sr.AverageVolumn)
{
VolumnCount++;
Thread.Sleep(10);
}
else
{
VolumnCount = 0;
}
if (VolumnCount >= 100)
{
VolumnCount = 0;
Console.Write(".");
sr.RecStop();
//Thread.Sleep(50);
IsChecking = false;
}
}
});
}
public Task Update() //上传
{
return Task.Run(() => //Task线程
{
var data = File.ReadAllBytes(time + ".wav"); //读文件
Dictionary d = new Dictionary(); //asr接口参数
//d.Add("lan", "zh"); //指定中文识别
var result = _asrClient.Recognize(data, "pcm", 16000, d); //开始识别
if (result.GetValue("err_msg").ToString() == "success.") //如果识别成功
{
File.Delete(time + ".wav"); //文件删除
Application.Current.Dispatcher.Invoke(() => //再开一个线程
{
string res = result.GetValue("result").First.ToString(); //拿到识别结果的第一个(接口可能会返回多个结果,但机器不会判断哪个好用,好吧应该能判断,我写不来,所以我默认第一个了)
Console.WriteLine(result.GetValue("result").ToString()); //将识别结果打印出来 以便可以查看识别准确率
Regex regex = new Regex("贾维斯"); //我自己的唤醒词
Match match = regex.Match(res); //与识别结果去匹配
if (match.Success || IsJarvis) //说出了贾维斯 或 处于唤醒状态 开始一系列操作
{
if(IsJarvis) //处于贾维斯
{
if (match.Success) 说了贾维斯 进入睡眠逻辑
{
IsJarvis = false;//退出贾维斯模式
Console.Write("\t进入睡眠,等待唤醒");
}
else //没说贾维斯,开始处理识别结果
{
if (!UNIT.UNIT.IsFinishedThisUnit) //UNIT未结束意图
{
Queue.Queue.WaitForIntentionsWord.Add(WordToNumber.WordToNumberClass.WordToNumber(res));//文字转数字后添加到意图澄清队列(我自己写的消息队列,比较蠢 这里是词槽澄清的逻辑 不用UNIT的可以忽略)
}
else //UNIT返回的意图是satisfy
Queue.Queue.WaitForDealFromVoice.Add(WordToNumber.WordToNumberClass.WordToNumber(res));//文字转数字后添加到待意图识别队列(我自己写的消息队列,比较蠢 这里是UNIT意图识别完成返回执行函数的逻辑 不用UNIT的可以忽略)
}
}else //不处于唤醒状态时说了贾维斯
{
IsJarvis = true; //进入唤醒模式
Console.Write("\t唤醒成功");
Queue.Queue.WaitForDealFromVoice.Add(WordToNumber.WordToNumberClass.WordToNumber(res));//res..Replace(",", "").Replace("贾维斯", "")) //将唤醒词去掉后 文字转数字 丢入待意图识别队列
}
}
});
return result.GetValue("result").First.ToString().Replace(",",""); //方法执行成功返回语音识别的第一句
}
return null; //分析失败返回空
});
}
}
那这里介绍那个Start方法 如下:
private int VolumnCount;
public Task Start() //声明了一个Task方法 (Task贼好玩 有兴趣可以自行了解一下)
{
return Task.Run(() => //使用Task类 开启一个新的线程
{
sr.RecStart(); //开始录制
while (IsChecking)
{
if (sr.CurrentVolume < sr.AverageVolumn) //当前音量小于平均音量
{
VolumnCount++;
Thread.Sleep(10); //睡眠10毫秒
}
else //如果当前音量大于平均音量了 清空检测数值重头计算
{
VolumnCount = 0;
}
if (VolumnCount >= 100) //当抵达100*10=1000毫秒时 进入打断逻辑
{
VolumnCount = 0;
Console.Write("."); //输出信号,为了调试的时候让自己知道打断了
sr.RecStop(); //停止录制
IsChecking = false; //跳出这次死循环
}
}
});
}
以下是整个打断的调用方式:
bool IsJarvis = false;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//其他逻辑......
//语音识别线程
Application.Current.Dispatcher.Invoke(async () => //开启一个新的线程,以防止处理过程阻塞到UI线程 因为线程内会使用到异步等待方法,所以需要加上async关键字
{
while (true) //陷入死循环 因为我的设定是不停的听。所以不考虑出循环
{
SoundListener sl = new SoundListener(_asrClient,IsJarvis); //继承自上一次的唤醒状态新建对象
await sl.Start(); //调用SoundListener.Start方法,一直会阻塞到录制完成
Task t = new Task(async () =>//同样有异步等待的需求,加async关键字
{
string res = await sl.Update(); //上传 并等待上一次的执行结果 阻塞到识别逻辑全部结束
IsJarvis = SoundListener.IsJarvis; //将唤醒标识从SoundListener类中取出 然后Task自己会被辣鸡处理 因为第一次唤醒会默认执行,所以不考虑第一次的识别 之后的识别也会如期丢入UNIT识别
});
t.Start(); //Task类会一直存活,直到全部处理完成
}
});
//其他逻辑.......
}
主要的多线程循环识别的实现方式就是这样啦。可能会有点难喔 各位可以简化我的代码(因为我实在懒的去抠主逻辑了 因为我自己的代码都乱的看不下去)
这个是什么图片
嗯,每天看到的都是眼熟的ID~
= =其实现在社区人太少了。人多点就好了
额~其实好多回复不了的帖子,我都是默默看过去的...
处理音频应该只是客户端的事情。你可以百度一下 ASP.NET与ActiveX之间是否存在什么调用问题。 因为网页我也没深入了解。他是不是没有宿主。。。
大佬,出现了新问题,我把你的这个C#录音类移植到ASP.NET网站上的时候,在执行sr.recstart();的时候,会出现“试图访问已卸载的Appdomain”之类的错误。
此外,我想问一下,部署这种录音服务的网站服务器本身是不是也要安装录音设备呢?
H5有getUserMedia API吧。好像可以调用麦克风的。你研究研究
java怎么搞 大哥 我录音是H5的
。。。。。,,,,不同寻常的用户 我发现你能接所有人的话 然后又能让所有人接不了你的话 你也蛮厉害的
嗯哼~此话怎讲?
来接这句,试试看吧~
嘿嘿,聊尴尬了
。。。。。。你这话我接不下去
码工~~
我发现你的回复我都接不下去!!!!
哼~搬砖工比你勤劳,一天要搬很多砖呢~~~
大神还算不上呢 搬砖工
哈哈哈哈哈哈哈哈哈哈。。。
这就厉害啦呢~棒棒哒~
嗯,看大神切磋,就是过瘾~
实时我也有想过,后来放弃了。
因为现在的流识别只支持安卓和ios。
windows的语音识别,有一些上下文联想,必须整句丢过去请求。
不然一个字一识别估计全是错别字