最近由于需要做一个关于语音的c#项目,经老师推荐使用了讯飞的语音识别和合成的接口,但由于网上资源关于c#实时语音识别这块实在是太少了,经查阅网上资源和研究源代码,最终完成了一个还算满意的demo,供各位后来者参考和借鉴,希望后来者能少走点弯路。
以下是界面图:
MSC模块导入及添加枚举常量等
导入msc.dll,讯飞的语音识别和语音函数是封装起来的,需要我们import进去
除此之外,我们还需要创建需要用到的枚举常量和结构体等
语音识别模块
这部分模块我们需要用到的是麦克风音频(NAudio)和语音识别类。
首先,连续的语音识别是需要用到麦克风的,这时候就需要创建一个麦克风类,来实现声音的获取,及对声音的识别。
其中较为关键的函数是wis_back_DataAvailable和OnDataAvailable,前一个是处理得到的音量,后一个是处理得到的数据。
wis_back_DataAvailable和OnDataAvailable具体实现
。
private void wis_back_DataAvailable(object sender, WaveInEventArgs e)
{
long sh = System.BitConverter.ToInt64(e.Buffer, 0);
long width = (long)Math.Pow(2, 50);
float svolume = Math.Abs(sh / width);
if (svolume > 1500.0f) { svolume = 1500.0f; }//音量最大值
if (svolume < 50.0f) { svolume = 50.0f; }//音量最小值
this.volume = svolume / 15.0f;//最小3.3333333
DataArrived(this, new DataArrivedEventArgs(this.volume));//激发音量数据到达事件
}
void OnDataAvailable(object sender, WaveInEventArgs e)
{
totalBufferLength += e.Buffer.Length;
secondsRecorded = (float)(totalBufferLength / 32000);
VoiceData data = new VoiceData();
for (int i = 0; i < e.Buffer.Length; i++)
{
data.data[i] = e.Buffer[i];
}
VoiceBuffer.Add(data);//添加录音数据
if (volume < 4)//音量低于4则开始准备识别
Ends = Ends - 1;
else
Ends = 5;
if (Ends == 0)
{
if (VoiceBuffer.Count() > 5)
{
isr.RunIAT(VoiceBuffer);//调用语音识别
}
VoiceBuffer.Clear();
Ends = 5;
}
}
其中VoiceData 是我创建的处理音频数据的类
接下来是语音识别类,这是实现语音识别的关键类
其中有两个语音识别的函数,其区别是一个通过麦克风获取声音数据来识别,一个通过读取文件数据来识别。
// 语音识别(从麦克风中获取数据)
public void RunIAT(List<VoiceData> VoiceBuffer)
{
IntPtr session_id = IntPtr.Zero;//sessionID是本次识别的句柄
string rec_result = string.Empty;//识别结果
string hints = "正常结束";
AudioStatus aud_stat = AudioStatus.ISR_AUDIO_SAMPLE_CONTINUE;//用来告知MSC音频发送是否完成
EpStatus ep_stat = EpStatus.ISR_EP_LOOKING_FOR_SPEECH;//端点检测器所处的状态
RecogStatus rec_stat = RecogStatus.ISR_REC_STATUS_SUCCESS;//识别器返回的状态,提醒用户及时开始\停止获取识别结果
int errcode = (int)ErrorCode.MSP_SUCCESS;
session_id = MSCDLL.QISRSessionBegin(null, QISRsession_begin_params, ref errcode);
if ((int)ErrorCode.MSP_SUCCESS != errcode)
{
Console.WriteLine("QISRSessionBegin failed!(语音识别开始阶段) error code:" + errcode);
return;
}
for (int i = 0; i < VoiceBuffer.Count(); i++)
{
aud_stat = AudioStatus.ISR_AUDIO_SAMPLE_CONTINUE;
if (i == 0)
aud_stat = AudioStatus.ISR_AUDIO_SAMPLE_FIRST;
errcode = MSCDLL.QISRAudioWrite(MSCDLL.PtrToStr(session_id), VoiceBuffer[i].data, (uint)VoiceBuffer[i].data.Length, aud_stat, ref ep_stat, ref rec_stat);
if ((int)ErrorCode.MSP_SUCCESS != errcode)
{
MSCDLL.QISRSessionEnd(MSCDLL.PtrToStr(session_id), null);
Console.WriteLine("QISRSessionEnd failed!(音频写入过程) error code:" + errcode);
}
}
//写入最后一块音频(空的)
errcode = MSCDLL.QISRAudioWrite(MSCDLL.PtrToStr(session_id), null, 0, AudioStatus.ISR_AUDIO_SAMPLE_LAST, ref ep_stat, ref rec_stat);
if ((int)ErrorCode.MSP_SUCCESS != errcode)
{
Console.WriteLine("QISRAudioWrite failed!(写入最后一块音频) error code:" + errcode);
return;
}
while (RecogStatus.ISR_REC_STATUS_SPEECH_COMPLETE != rec_stat)
{
IntPtr rslt = MSCDLL.QISRGetResult(MSCDLL.PtrToStr(session_id), ref rec_stat, 0, ref errcode);
if ((int)ErrorCode.MSP_SUCCESS != errcode)
{
Console.WriteLine("QISRGetResult failed!(获得结果阶段) error code: " + errcode);
break;
}
if (IntPtr.Zero != rslt)
{
string tempRes = MSCDLL.PtrToStr(rslt);
rec_result = rec_result + tempRes;
if (rec_result.Length >= BUFFER_SIZE)
{
Console.WriteLine("no enough buffer for rec_result !\n");
break;
}
}
Thread.Sleep(100);//可省略
}
int errorcode = MSCDLL.QISRSessionEnd(MSCDLL.PtrToStr(session_id), hints);
//语音识别结果
if (rec_result.Length != 0)
{
DataArrived(this, new DataArrivedEventArgs(rec_result));//订阅文本事件
//返回错误代码10111时,可调用SpeechRecognition()函数执行MSPLogin
}
}
/// <summary>
/// 语音识别(通过文件的识别只能识别一分钟)
/// </summary>
/// <param name="audio_path"></param>
/// <returns></returns>
private StringBuilder RunIATFile(string audio_path)
{
byte[] audio_content;
if (audio_path == null || audio_path == "")
{
Console.WriteLine("还没有选择语音文件");
return null;
}
else
{
audio_content = File.ReadAllBytes(audio_path);
player = new SoundPlayer(audio_path);
player.Play();//播放语音
}
IntPtr session_id;
StringBuilder result = new StringBuilder();//存储最终识别的结果
string hints = "正常结束";
AudioStatus aud_stat = AudioStatus.ISR_AUDIO_SAMPLE_CONTINUE;//用来告知MSC音频发送是否完成
EpStatus ep_stat = EpStatus.ISR_EP_LOOKING_FOR_SPEECH;//端点检测器所处的状态
RecogStatus rec_stat = RecogStatus.ISR_REC_STATUS_SUCCESS;//识别器返回的状态,提醒用户及时开始\停止获取识别结果
RecogStatus rec_rslt = RecogStatus.ISR_REC_STATUS_SUCCESS;
int errcode = (int)ErrorCode.MSP_SUCCESS;
session_id = MSCDLL.QISRSessionBegin(null, QISRsession_begin_params, ref errcode);
if (errcode != (int)ErrorCode.MSP_SUCCESS)
{
Console.WriteLine("开始一次语音识别失败!");
MSCDLL.QISRSessionEnd(MSCDLL.PtrToStr(session_id), hints);
return null;
}
#region 边读取文件边识别,效率高
FileStream fp = new FileStream(audio_path, FileMode.Open)
{
Position = 44//wav文件要求
};
int len;
int buff_num = 1024 * 20;
byte[] buff = new byte[buff_num];
IntPtr bp = Marshal.AllocHGlobal(buff_num);
while (fp.Position != fp.Length)
{
if (stop_audio)
{
break;
}
len = fp.Read(buff, 0, buff_num);
Marshal.Copy(buff, 0, bp, buff.Length);
errcode = MSCDLL.QISRAudioWrite(MSCDLL.PtrToStr(session_id), bp, (uint)len, aud_stat, ref ep_stat, ref rec_stat);
if (errcode != (int)ErrorCode.MSP_SUCCESS)
{
fp.Close();
Console.WriteLine("写入识别的音频失败!" + errcode);
return null;
}
if (rec_stat == RecogStatus.ISR_REC_STATUS_SUCCESS)
{
IntPtr p = MSCDLL.QISRGetResult(MSCDLL.PtrToStr(session_id), ref rec_rslt, 0, ref errcode);
if (p != IntPtr.Zero)
{
string temp = MSCDLL.PtrToStr(p);
DataArrived(this, new DataArrivedEventArgs(temp));
result.Append(temp);
Console.WriteLine("部分结果:" + temp);
}
}
Thread.Sleep(200);
}
fp.Close();
errcode = MSCDLL.QISRAudioWrite(MSCDLL.PtrToStr(session_id), bp, 1, AudioStatus.ISR_AUDIO_SAMPLE_LAST, ref ep_stat, ref rec_stat);
if (errcode != (int)ErrorCode.MSP_SUCCESS)
{
Console.WriteLine("写入音频失败!" + errcode);
return null;
}
Marshal.FreeHGlobal(bp);
int loop_count = 0;
do
{
IntPtr p = MSCDLL.QISRGetResult(MSCDLL.PtrToStr(session_id), ref rec_rslt, 0, ref errcode);
if (p != IntPtr.Zero)
{
string temp = MSCDLL.PtrToStr(p);
DataArrived(this, new DataArrivedEventArgs(temp));
result.Append(temp);
Console.WriteLine("最后一块音频:" + temp);
}
if (errcode != (int)ErrorCode.MSP_SUCCESS)
{
Console.WriteLine("写入音频失败!" + errcode);
return null;
}
Thread.Sleep(500);
} while (rec_rslt != RecogStatus.ISR_REC_STATUS_SPEECH_COMPLETE && loop_count++ < 30);
#endregion
int errorcode = MSCDLL.QISRSessionEnd(MSCDLL.PtrToStr(session_id), hints);
Console.WriteLine("语音听写结束");
Console.WriteLine("语音识别结果:");
Console.WriteLine(result);
return result;
}
这里使用了两种识别方法,通过麦克风数据使用的识别是一次性读取数据后识别,而通过文件数据使用的是边读取数据边识别。由于说话的时间一般比较短,所以通过麦克风的设置为一次性读取(此处可以改进),而文件有时候过大,识别的时间过长,因此分段读取识别更好。注意,讯飞语音识别暂时只支持一分钟内的音频识别!
如果需要延长识别间隔,可以修改麦克风类的ENDS
超过一分钟后就无法继续识别
语音合成模块
语音合成类较为简单,主要涉及到了将得到的文本转化为语音,然后进行存储。这里加入了生成的语音的声音选择。这里生成的语音都为wav格式,主要是为了方便讯飞接口调用。选择文本文件均为txt,暂时没有进行异常处理,大伙如果需要可以自行实现。
最终结果
最终封装为一个类库,方便使用
工程使用说明
然后我们的前期准备就完成了
最终工程包含四个项目
其中iFlyDoNet为类库,TestMscDll为主要测试的项目,VoiceForm为windowsForm项目,VoiceWPF为WPF项目。
首先,你需要去讯飞官网下载SDK,因为他的APPID是和他SDK中msc.dll是配套的,如果不匹配则会报错。msc.dll文件在项目的bin目录下,其中讯飞的msc.dll由于是用dllimport引入的,所以需要放到debug或者release文件夹中。
然后下载NAudio.dll(提取码:i1m2),这是一个开源的音频库,里面封装了很多可以非常强大的接口,有兴趣的小伙伴可以研究一下它的源代码(github地址)。
使用说明
TestMscDll
首先iFlyDoNet类库生成iFlyDoNet.dll,TestMscDll中添加引用,包括NAudio.dll也要添加进去
然后窗口代码中using iFlyDoNet;
即可以使用其中的封装的类。
VoiceForm和VoiceWPF
不需要添加iFlyDoNet.dll,其余步骤一样。
代码下载
最后,工程文件下载地址 (提取码:v4jv)
如果遇到什么问题或者有什么建议,都可以说出了,作为一个新手,你的回复就是对我最大的肯定。
最后,感谢这些博主的贡献
https://www.cnblogs.com/lingLuoChengMi/p/8397755.html
http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=10182&extra=page%3D1