[语音技术]C#在win平台基于录音类试写打断
goJhou 发布于2017-10 浏览:17086 回复: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
#21goJhou回复于2017-11
#20 kohakuarc回复
有东西啊,大哥。我现在可以录音了,微软也是欺负人,wpf就配置的好好的,winform连个app.config都要自己配,真是烦死了。 另外想问,这响应速度你们接受么?如果要实现实时的把你的语音识别出来,有什么优化方案么?
展开

我是自己写着玩的。。。。只负责实现= =   性能啥的我都没在意

1
#20kohakuarc回复于2017-11
#18 goJhou回复
[代码] 并没有任何东西。。。。

有东西啊,大哥。我现在可以录音了,微软也是欺负人,wpf就配置的好好的,winform连个app.config都要自己配,真是烦死了。

另外想问,这响应速度你们接受么?如果要实现实时的把你的语音识别出来,有什么优化方案么?

1
#19goJhou回复于2017-11
#17 kohakuarc回复
请问你的app.config文件是怎么设置的?查了一下资料,似乎跟app.config设置有关。
展开

你先不开线程,试试能正常录正常结束吗。打断点看看有没有异常可以分析

1
#18goJhou回复于2017-11
#17 kohakuarc回复
请问你的app.config文件是怎么设置的?查了一下资料,似乎跟app.config设置有关。
展开


     
        
    
  
    
      
        
        
      
    
  

并没有任何东西。。。。

1
#17kohakuarc回复于2017-11
#16 goJhou回复
可能是吧,我当初也是到处搜,最后弄到了我那环境可用的然后改的

请问你的app.config文件是怎么设置的?查了一下资料,似乎跟app.config设置有关。

1
#16goJhou回复于2017-11
#15 kohakuarc回复
https://zhidao.baidu.com/question/74661123.html 另外想问一下大佬,上面是不是那个SoundRecord类的出处?我在百度知道搜到的。
展开

可能是吧,我当初也是到处搜,最后弄到了我那环境可用的然后改的

1
#15kohakuarc回复于2017-11

https://zhidao.baidu.com/question/74661123.html

另外想问一下大佬,上面是不是那个SoundRecord类的出处?我在百度知道搜到的。

0
#14kohakuarc回复于2017-11

Application.Current.Dispatcher.Invoke

我查了一下,winform是没有current方法的,wpf好像才有。

1
#13kohakuarc回复于2017-11
#12 goJhou回复
= =呃。Form有没有dispatcher.Invoke

没有,你是用wpf的吧?我用的C# winform编程,不知道winform的dispatch.invoke怎么写。

1
#12goJhou回复于2017-11
#11 kohakuarc回复
程序打断点都没用,真是。。。 另外CPU占用率很高

= =呃。Form有没有dispatcher.Invoke

1
#11kohakuarc回复于2017-11

程序打断点都没用,真是。。。

另外CPU占用率很高

1
#10kohakuarc回复于2017-11
#8 goJhou回复
按钮事件结束了。。。我估摸着。因为你那线程是局部变量声明在了按钮单击事件里。可能单击完了就垃圾回收了
展开

还是很奇怪,因为我这里已经把线程声明为窗体级变量了,设置的断点还是没能进入线程函数里面。真是头疼,不知道哪里出了问题。

 public partial class Form1 : Form
    {
        private SoundRecord sr;
        private string time;
        private Thread recordTd;
        private bool stopFlag;
        public Form1()
        {
            InitializeComponent();

            //count = 0;
            stopFlag = false;
        }
0
#9kohakuarc回复于2017-11
#8 goJhou回复
按钮事件结束了。。。我估摸着。因为你那线程是局部变量声明在了按钮单击事件里。可能单击完了就垃圾回收了
展开

是喔,没有把线程定义为全局变量,真是个低级的错误啊。感谢兄台的解答!

1
#8goJhou回复于2017-11
#7 kohakuarc回复
昨天调试的时候,把vs2017的开发环境都搞挂了。然后我跟踪了一下,发现线程里面装的函数根本就没有执行下去。请问线程提前死的原因是啥?
展开

按钮事件结束了。。。我估摸着。因为你那线程是局部变量声明在了按钮单击事件里。可能单击完了就垃圾回收了

1
#7kohakuarc回复于2017-11
#6 goJhou回复
看着好像没问题,你检查下这线程是不是提前死了

昨天调试的时候,把vs2017的开发环境都搞挂了。然后我跟踪了一下,发现线程里面装的函数根本就没有执行下去。请问线程提前死的原因是啥?

1
#6goJhou回复于2017-11
#5 kohakuarc回复
兄弟,你看下我的代码,为何不能录制出WAV文件? [代码]

看着好像没问题,你检查下这线程是不是提前死了

1
#5kohakuarc回复于2017-11
#4 goJhou回复
客气客气,互相学习

兄弟,你看下我的代码,为何不能录制出WAV文件?

        private void button1_Click(object sender, EventArgs e)
        {
            recordTd = new Thread(recordFun);
            recordTd.IsBackground = true;
            recordTd.Start();
        }

        private void recordFun()
        {
            time = DateTime.Now.ToString("yyyyMMddHHmmss");
            sr = new SoundRecord();

            sr.SetFileName(time + ".wav");//保存的文件名
            sr.RecStart();//录制开始
            while (IsChecking)
            {
                VolumnCount++;
                Thread.Sleep(10);
                if (VolumnCount >= 100)
                {
                    //VolumnCount = 0;
                    Console.Write(".");
                    sr.RecStop();
                    //Thread.Sleep(50);
                    IsChecking = false;
                }
            }
        }
1
#4goJhou回复于2017-10
#3 kohakuarc回复
过来膜拜大佬 =0=

客气客气,互相学习

1
#3kohakuarc回复于2017-10
#2 goJhou回复
哎呀   流程图排版有问题  我用画图画了一张,各位凑合理解一下 [图片]
展开

过来膜拜大佬 =0=

1
#2goJhou回复于2017-10

哎呀   流程图排版有问题  我用画图画了一张,各位凑合理解一下

1
TOP
切换版块