C#AI系列(5): 从零开始 C# 轻松语音识别

人工智能历经多年演进,昔日高门槛的图像与语音识别任务,如今已有成熟的开源框架可供免费使用,只要花点时间,就可以零成本部署。本文以语音识别为例,看如何高效的将语音识别功能集成至C#系统中,后续大家可以继续完善扩展,去处理如语音指令、语音交互、字幕生成、会议纪要分析、语音翻译等相关任务。

C#AI系列(5): 从零开始 C# 轻松语音识别

本文项目在笔记本电脑上用cpu就可以自己动手轻松实现,所有代码均已开源,仅需关注 萤火初芒 公众号回复AISharp即可查看仓库地址,供学习交流使用,无套路。

一、环境配置基础

语音识别的方案有很多,windows系统本身也自带有语音识别的方案(System.Speech.Recognition),但是效果查强人意。既要简单好用,又要功能强大效果好,我们选择基于Whisper(MIT,https://github.com/openai/whisper)的Whisper.Net(MIT,https://github.com/sandrohanea/whisper.net)来实现。

C#AI系列(5): 从零开始 C# 轻松语音识别

Whisper 是2022年OpenAI发布的一个通用语音识别模型。它基于大量多样化的音频数据进行训练,同时也是一个多任务模型,能够执行多语言语音识别、语音翻译和语言识别任务。项目创建完成后直接在nuget拉取Whisper.net(1.9.0)和Whisper.net.Runtime(1.9.0)即可。

另外为了实现语音交互,还要在nuget拉取NAudio(2.2.1)(MIT,https://github.com/naudio/NAudio),以实现通过麦克风设备对声音的捕获。

二、核心代码实现

2.1 四行核心代码实现语音转文本

上面的控制台demo的main函数代码如下:

  private static readonly string OutFile = "d:\record.wav";  //临时输出文件    // 读取json配置文件,nuget拉取LumConfig,往期文章有介绍  private static LumConfigManager con = new LumConfigManager("model.conf");  private static void Main() {     // 1. 加载模型     // 输入模型地址,model文件夹下面提供了一个tiny版模型可以直接用     var wm = new WhisperManager((string)con.Get("modelPath"));       while (true)     {         bool started = false;         Console.WriteLine("按 Space 开始/停止录音");          while (true)         {             var key = Console.ReadKey(true).Key;             if (key == ConsoleKey.Spacebar)             {                 if (started)                 {                     // 2. 开始录音                     StopRec();                     break;                 }                 else                 {                     // 3. 结束录音                     StartRec();                 }                 started=!started;             }         }           // 4. 转文本          var res = wm.RunAsync(OutFile).GetAwaiter().GetResult();          Console.WriteLine(res);                  } }  

代码通过循环控制来实现反复读取语音再转换为文本的动作。其实核心代码只有四行,分别是加载模型、开始录音、停止录音、语音转文本。后面我们将对其一一拆解。

2.2 麦克风录制声音

麦克风的录制很简单,我们只需要完成2个动作,一个是开始录制声音,另一个是停止录制声音。WaveFileWriter方法两个重载,可以选择把声音录制到文件,也可以录制到流(stream)中。以录制到文件为例具体代码如下:

private static void StartRec() {                 waveIn = new WaveInEvent     {         DeviceNumber = 0,                      // 默认麦克风         WaveFormat = new WaveFormat(16000, 1), // 44.1kHz 单声道         BufferMilliseconds = 100     };      writer = new WaveFileWriter(OutFile, waveIn.WaveFormat);      // 数据到达事件     waveIn.DataAvailable += (s, e) =>     {         writer.Write(e.Buffer, 0, e.BytesRecorded);     };      // 录音停止事件(负责释放 writer)     waveIn.RecordingStopped += (s, e) =>     {         writer?.Dispose();         writer = null;         waveIn?.Dispose();         waveIn = null;         stop.Set();     };      waveIn.StartRecording();     Console.WriteLine(">>> 正在录音……"); }  private static void StopRec() {     Console.WriteLine("<<< 停止录音");     waveIn?.StopRecording();   // 触发 RecordingStopped 事件      // 停止录制时,流数据的处理不一定已经完成     // 我们额外使用一个信号量来实现简单的同步     stop.WaitOne();   } 

2.3 语音转文字的实现

尽管Whisper.Net已经封装好了所有相关功能,但是我们最好额外写一个WhisperManager类来高效的反复使用他。具体代码如下:

internal class WhisperManager:IDisposable {     WhisperFactory whisperFactory;     WhisperProcessor processor;       public WhisperManager(string path)     {         whisperFactory = WhisperFactory.FromPath(path);         processor = whisperFactory.CreateBuilder()             .WithLanguage("zh")             .WithPrompt("以下是,简体中文普通话的句子。")  // 否则大概率会输出繁体中文             .WithThreads(16)             .Build();                 }          public async Task<string> RunAsync(string path)     {         try         {             var sb = new StringBuilder();             using var fileStream = File.OpenRead(path);             //  异步获取识别后的分段结果             await foreach (var seg in processor.ProcessAsync(fileStream))             {                 var str = $"{seg.Start}->{seg.End}: {seg.Text}rn";                 sb.AppendLine(str);                 var per = (fileStream.Position / fileStream.Length*100).ToString("N2");             }             return sb.ToString();         }         catch (Exception ex)         {             return ex.Message;         }     }      public void Dispose()     {         processor.Dispose();         whisperFactory.Dispose();     } }  

Whisper重要基础参数配置:
WithLanguage,指定输入语音的语言,如zh、en、ja、auto(自动);
WithPrompt,输出提示词,这里与大语言模型不同,只能说引导模型生成,比如“以下是一个访谈。”。此处需要与指定语言一致,否则可能会强行切换,50~200 个字符足够,最好出现"标点符号"(不然可能会无标点)、"口语词、专有名词"(适配语境)。
另外还有很多与线程、输出相关的控制,大伙可根据需要自行研究了解。

2.4 模型、输入与输出

c#使用的Whisper模型是.bin的二进制格式的,现在可以在https://huggingface.co/ggerganov/whisper.cpp/tree/main下载。本项目里用到的是好久以前下载的ggml-model-whisper-tiny.bin,74mb,速度很快但效果中等。

本项目对Whisper的输入用的是wav格式的文件,且采样率必须为16kHz,否则会报错。如果导入音频时采样频率不对,可以用NAudio通过下代码进行转换:

public void Execute() {     //using (Mp3FileReader reader = new Mp3FileReader("D:\Documents\Temp\WhisperDesktop\上午对接结构录音.mp3"))     using (AudioFileReader reader = new AudioFileReader("D:\Documents\Temp\WhisperDesktop\录音.m4a"))     {       // 16kHz, 16bit,单声道       var newFormat = new WaveFormat(16000, 16, 1);        using (var conversionStream = new WaveFormatConversionStream(newFormat, reader))       {           WaveFileWriter.CreateWaveFile("D:\Documents\Temp\WhisperDesktop\录音.wav", conversionStream);       }                  } } 

Whisper输出是分多个段(IAsyncEnumerable)输出的,包含如起始时间,文本,语言等多个信息,我们可以选择性的进行导出查看。

public class SegmentData {     public string Text { get; } 文本      public TimeSpan Start { get; }      public TimeSpan End { get; }      public float MinProbability { get; }      public float MaxProbability { get; }      public float Probability { get; }      public string Language { get; }      public WhisperToken[] Tokens { get; }      public float NoSpeechProbability { get; } } 

打印的结果示意:

00:00:00->00:00:15: 一二三四五,上山打老虎,老虎没打到,打到了小松鼠。 

三、最后

  • 在使用tiny模型下,整体速度和质量还是基本可以的,如果追求更好的效果则考虑使用更大模型,但与之对内存的需求及运算时间会相应大幅增加。
  • 在转换时,语音越长,转换速度越慢,因此建议在转换前,主动对长语音进行分段。尽管这样可能导致上下文丢失造成一定程度的不精确,但却能显著提高转换速度。
  • 语音识别是支持多语言混合的。但翻译的功能,试了下,失灵时不灵,可能和模型本身及大小有关。

感谢您的阅读,本案例及更加完整丰富的机器学习模型案例的代码已全部开源,新朋友们可以关注公众号回复AISharp查看仓库地址,本期相关代码在仓库下面的Voic文件夹里,在model文件夹内可以找到案例使用的74mb大小的ggml-model-whisper-tiny.bin小模型,大家可以自行尝试。

C#AI系列(5): 从零开始 C# 轻松语音识别

发表评论

您必须 [ 登录 ] 才能发表留言!

相关文章