[语音技术]C#在win平台基于录音类试写打断
goJhou 发布于2017-10 浏览:17007 回复:40
2
收藏

那在现实生活中,微信的语音功能让人铭记于心。手指按下录音,抬起发送。

试想一下,如果某些场景,我们在没有手的情况下如何让设备进行识别。

今天,我将我自己封装的语音打断的代码共享给大家,各位可以按自己的需求和场景需要修改代码细节。

原理介绍:

启动一个新线程 ←←←←←←←←←←←←←←←←←←←←

↓                                  ↑

开始录制(聆听)                         ↑

↓                                   ↑

当前音量低于从录音开始的平均音量多于设定阈值       ↑

↓                                   ↑

结束录制,生成文件并上传至百度进行语音识别(聆听)→→→

语音识别返回结果

将结果丢到下一个逻辑

线程销毁

 

 

依照以上的逻辑,我们可以在线程不间断的对接下识别所有的句子。各位可以调整设定阈值来控制打断时机。

这里安利一下上几章节给各位讲到的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类会一直存活,直到全部处理完成
                }
            });

//其他逻辑.......
}

 

主要的多线程循环识别的实现方式就是这样啦。可能会有点难喔 各位可以简化我的代码(因为我实在懒的去抠主逻辑了 因为我自己的代码都乱的看不下去)

收藏
点赞
2
个赞
共40条回复 最后由用户已被禁言回复于2022-04
#45荒墨丶迷失回复于2018-01
该评论已删除

这个是什么图片

1
#40笔墨哥回复于2017-11
#39 goJhou回复
= =其实现在社区人太少了。人多点就好了

嗯,每天看到的都是眼熟的ID~

1
#39goJhou回复于2017-11
#38 笔墨哥回复
额~其实好多回复不了的帖子,我都是默默看过去的...

= =其实现在社区人太少了。人多点就好了

1
#38笔墨哥回复于2017-11
#33 goJhou回复
。。。。。,,,,不同寻常的用户 我发现你能接所有人的话 然后又能让所有人接不了你的话  你也蛮厉害的
展开

额~其实好多回复不了的帖子,我都是默默看过去的...

1
#37goJhou回复于2017-11
#36 kohakuarc回复
大佬,出现了新问题,我把你的这个C#录音类移植到ASP.NET网站上的时候,在执行sr.recstart();的时候,会出现“试图访问已卸载的Appdomain”之类的错误。 此外,我想问一下,部署这种录音服务的网站服务器本身是不是也要安装录音设备呢?
展开

处理音频应该只是客户端的事情。你可以百度一下 ASP.NET与ActiveX之间是否存在什么调用问题。 因为网页我也没深入了解。他是不是没有宿主。。。

1
#36kohakuarc回复于2017-11
#35 goJhou回复
H5有getUserMedia API吧。好像可以调用麦克风的。你研究研究
展开

大佬,出现了新问题,我把你的这个C#录音类移植到ASP.NET网站上的时候,在执行sr.recstart();的时候,会出现“试图访问已卸载的Appdomain”之类的错误。

此外,我想问一下,部署这种录音服务的网站服务器本身是不是也要安装录音设备呢?

1
#35goJhou回复于2017-11
#34 荒墨丶迷失回复
java怎么搞 大哥 我录音是H5的

H5有getUserMedia API吧。好像可以调用麦克风的。你研究研究

1
#34荒墨丶迷失回复于2017-11

java怎么搞 大哥 我录音是H5的

1
#33goJhou回复于2017-11
#32 笔墨哥回复
嗯哼~此话怎讲? 来接这句,试试看吧~

。。。。。,,,,不同寻常的用户 我发现你能接所有人的话 然后又能让所有人接不了你的话  你也蛮厉害的

1
#32笔墨哥回复于2017-11
#28 goJhou回复
我发现你的回复我都接不下去!!!!

嗯哼~此话怎讲?

来接这句,试试看吧~

0
#31笔墨哥回复于2017-11
#30 goJhou回复
。。。。。。你这话我接不下去

嘿嘿,聊尴尬了

0
#30goJhou回复于2017-11
#29 kohakuarc回复
码工~~

。。。。。。你这话我接不下去

1
#29kohakuarc回复于2017-11
#28 goJhou回复
我发现你的回复我都接不下去!!!!

码工~~

1
#28goJhou回复于2017-11
#27 笔墨哥回复
哼~搬砖工比你勤劳,一天要搬很多砖呢~~~

我发现你的回复我都接不下去!!!!

1
#27笔墨哥回复于2017-11
#26 goJhou回复
大神还算不上呢 搬砖工

哼~搬砖工比你勤劳,一天要搬很多砖呢~~~

1
#26goJhou回复于2017-11
#24 笔墨哥回复
这就厉害啦呢~棒棒哒~

大神还算不上呢 搬砖工

1
#25goJhou回复于2017-11
#24 笔墨哥回复
这就厉害啦呢~棒棒哒~

哈哈哈哈哈哈哈哈哈哈。。。

1
#24笔墨哥回复于2017-11
#22 goJhou回复
实时我也有想过,后来放弃了。 因为现在的流识别只支持安卓和ios。 windows的语音识别,有一些上下文联想,必须整句丢过去请求。 不然一个字一识别估计全是错别字
展开

这就厉害啦呢~棒棒哒~

1
#23笔墨哥回复于2017-11
#21 goJhou回复
我是自己写着玩的。。。。只负责实现= =   性能啥的我都没在意

嗯,看大神切磋,就是过瘾~

1
#22goJhou回复于2017-11
#20 kohakuarc回复
有东西啊,大哥。我现在可以录音了,微软也是欺负人,wpf就配置的好好的,winform连个app.config都要自己配,真是烦死了。 另外想问,这响应速度你们接受么?如果要实现实时的把你的语音识别出来,有什么优化方案么?
展开

实时我也有想过,后来放弃了。

因为现在的流识别只支持安卓和ios。

windows的语音识别,有一些上下文联想,必须整句丢过去请求。

不然一个字一识别估计全是错别字

1
TOP
切换版块