總體思路
FFmpeg對音視頻進行處理獲取wav音頻文件,對音頻文件按時間切片(30秒),每個片段調用百度或科大訊飛API進行識別(這裏主要是用免費的那種),得到結果後再拼接,保存到.TXT中。
FFmpeg音視頻處理
使用FFmpeg對音視頻進行格式轉換、切片
public class FFmpeg
{
private Process FFmpegProcess;
/// <summary>
/// ffmpeg
/// </summary>
/// <param name="arg"></param>
public void StartFfmpeg(string arg)
{
string path = Environment.CurrentDirectory + "\\ffmpeg.exe";
try
{
FFmpegProcess = new Process();//建立外部調用線程
FFmpegProcess.StartInfo.FileName = path;//要調用外部程序的絕對路徑
FFmpegProcess.StartInfo.Arguments = arg;
FFmpegProcess.StartInfo.UseShellExecute = false;//不使用操作系統外殼程序啓動線程(一定爲FALSE,詳細的請看MSDN)
FFmpegProcess.StartInfo.RedirectStandardError = true;//把外部程序錯誤輸出寫到StandardError流中(這個一定要注意,FFMPEG的所有輸出信息,都爲錯誤輸出流,用StandardOutput是捕獲不到任何消息的...這是我耗費了2個多月得出來的經驗...mencoder就是用standardOutput來捕獲的)
FFmpegProcess.StartInfo.CreateNoWindow = true;//不創建進程窗口
FFmpegProcess.ErrorDataReceived += new DataReceivedEventHandler(Output);//外部程序(這裏是FFMPEG)輸出流時候產生的事件,這裏是把流的處理過程轉移到下面的方法中,詳細請查閱MSDN
FFmpegProcess.Start();//啓動線程
FFmpegProcess.BeginErrorReadLine();//開始異步讀取
FFmpegProcess.WaitForExit();//阻塞等待進程結束
FFmpegProcess.Close();//關閉進程
FFmpegProcess.Dispose();//釋放資源
}
catch (Exception)
{
FFmpegProcess.Close();
FFmpegProcess.Dispose();
//Log(ex.ToString());
}
}
/// <summary>
/// ffmpeg輸出
/// </summary>
/// <param name="sendProcess"></param>
/// <param name="output"></param>
private void Output(object sendProcess, DataReceivedEventArgs output)
{
if (!string.IsNullOrEmpty(output.Data))
{
string msg = output.Data.ToString();
Console.WriteLine(msg);
}
}
public void Stop()
{
if (FFmpegProcess != null)
{
FFmpegProcess.Close();
}
}
/// <summary>
/// 轉成wav格式並切片,存放在臨時文件夾
/// </summary>
/// <param name="filePath"></param>
public void ToWav(string filePath, string fileName)
{
//轉成wav
string arg = $"-i \"{filePath}\" -ar 16000 -ac 1 -acodec pcm_s16le \"{fileName}.wav\"";
StartFfmpeg(arg);
var wav = fileName + ".wav";
//30秒切片
arg = $"-i \"{wav}\" -c copy -f segment -segment_time 30 \"{fileName}-%03d.wav\"";
StartFfmpeg(arg);
if (File.Exists(wav))
{
File.Delete(wav);
}
}
}
將FFmpeg處理後得到的wav音頻片段逐個傳入下列識別接口中,得到識別結果後再拼接即可。
百度語音識別
Nuget添加BaiduAI:
在百度雲AI開放平臺 申請語音識別應用,得到APP_ID API_KEY SECRET_KEY
這裏選擇wav格式
public static class BaiduAI
{
// 設置APPID/AK/SK
static readonly string APP_ID;
static readonly string API_KEY;
static readonly string SECRET_KEY;
static readonly Asr client;
static BaiduAI()
{
APP_ID = System.Configuration.ConfigurationManager.AppSettings["Baidu_APP_ID"];
API_KEY = System.Configuration.ConfigurationManager.AppSettings["Baidu_API_KEY"];
SECRET_KEY = System.Configuration.ConfigurationManager.AppSettings["Baidu_SECRET_KEY"];
client = new Asr(APP_ID, API_KEY, SECRET_KEY)
{
Timeout = 120000 // 修改超時時間
};
}
public static string AsrData(string path)
{
Console.WriteLine(path);
var data = File.ReadAllBytes(path);
// 可選參數
var options = new Dictionary<string, object>
{
{"dev_pid", 1537}
};
var result = client.Recognize(data, "wav", 16000, options);
Console.WriteLine(result);
//File.Delete(pcm);
if (result["err_no"].ToString() == "0")
{
return result["result"][0].ToString();
}
else
{
return "";//result["err_no"].ToString()+"------"+ result["err_msg"].ToString();
}
}
}
科大訊飛
訊飛雲平臺申請應用
有兩種方式:1、通過SDK調用。2、通過WebAPI調用。
第一種方式,需要在雲平臺相應的應用中下載SDK。
第二種方式,需要在雲平臺相應的應用中加入白名單。
這裏採用第二種,下列代碼後面註釋掉的部分是第一種方式
/// <summary>
/// 科大訊飛語音識別
/// </summary>
public static class Xfyun
{
readonly static string x_appid;
readonly static string api_key;
static Xfyun()
{
x_appid = System.Configuration.ConfigurationManager.AppSettings["Xfyun_x_appid"];
api_key = System.Configuration.ConfigurationManager.AppSettings["Xfyun_x_api_key"];
}
/// <summary>
/// WebAPI調用
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string Request(string path)
{
Console.WriteLine(path);
string aue = "raw", engine_type = "sms16k";
string param = "{\"aue\":\"" + aue + "\"" + ",\"engine_type\":\"" + engine_type + "\"}";
System.Text.Encoding encode = System.Text.Encoding.ASCII;
byte[] bytedata = encode.GetBytes(param);
string x_param = Convert.ToBase64String(bytedata);
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
string curTime = Convert.ToInt64(ts.TotalSeconds).ToString();
// MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
string result = string.Format("{0}{1}{2}", api_key, curTime, x_param);
//Console.WriteLine(Program.Md5(x_param));
string X_checksum =Helper.Md5(result);
//Console.WriteLine(X_checksum);
byte[] arr = File.ReadAllBytes(path);
string cc = Convert.ToBase64String(arr);
string data = "audio=" + cc;
string Url = "http://api.xfyun.cn/v1/service/v1/iat";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Headers["X-Appid"] = x_appid;
request.Headers["X-CurTime"] = curTime;
request.Headers["X-Param"] = x_param;
request.Headers["X-CheckSum"] = X_checksum;
request.ContentLength = Encoding.UTF8.GetByteCount(data);
Stream requestStream = request.GetRequestStream();
StreamWriter streamWriter = new StreamWriter(requestStream, Encoding.GetEncoding("gb2312"));
streamWriter.Write(data);
streamWriter.Close();
string htmlStr = string.Empty;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
using (StreamReader reader = new StreamReader(responseStream, Encoding.GetEncoding("UTF-8")))
{
htmlStr = reader.ReadToEnd();
}
responseStream.Close();
Console.WriteLine(htmlStr);
var json = Newtonsoft.Json.JsonConvert.DeserializeObject<XfyunResult>(htmlStr);
if (json.code == "0")
{
return json.data;
}
else
{
Console.WriteLine(json.desc);
return string.Empty;
}
}
class XfyunResult
{
public string code { get; set; }
public string data { get; set; }
public string desc { get; set; }
public string sid { get; set; }
}
#region 客戶端SDK調用
//const string my_appid = "appid = 5cc5121a";
//const string session_begin_params = "sub = iat, domain = iat, language = zh_cn, accent = mandarin, sample_rate = 16000, result_type = plain, result_encoding =gb2312";
///// <summary>
///// 初始化(登錄科大訊飛)
///// </summary>
///// <returns></returns>
//public static bool Init_audio()
//{
// int res = mscDLL.MSPLogin(null, null, my_appid);//用戶名,密碼,登陸信息,前兩個均爲空
// if (res != (int)Errors.MSP_SUCCESS)
// {//說明登陸失敗
// Console.WriteLine("登陸失敗!");
// MessageBox.Show("網絡不穩定,請檢查網絡!", "提示");
// //this.Close();
// return false;
// }
// Console.WriteLine("登陸成功!");
// return true;
//}
///// <summary>
///// 語音識別
///// </summary>
///// <param name="audio_path"></param>
///// <param name="session_begin_params"></param>
///// <returns></returns>
//public static string Audio_iat(string audio_path)
//{
// if (audio_path == null || audio_path == "") return "";
// IntPtr session_id;
// StringBuilder result = new StringBuilder();//存儲最終識別的結果
// var aud_stat = AudioStatus.MSP_AUDIO_SAMPLE_CONTINUE;//音頻狀態
// var ep_stat = EpStatus.MSP_EP_LOOKING_FOR_SPEECH;//端點狀態
// var rec_stat = RecogStatus.MSP_REC_STATUS_SUCCESS;//識別狀態
// int errcode = (int)Errors.MSP_SUCCESS;
// byte[] audio_content; //用來存儲音頻文件的二進制數據
// int totalLength = 0;//用來記錄總的識別後的結果的長度,判斷是否超過緩存最大值
// try
// {
// audio_content = File.ReadAllBytes(audio_path);
// //SoundPlayer player = new SoundPlayer(audio_path);
// //player.Play();
// }
// catch (Exception e)
// {
// Console.WriteLine(e);
// audio_content = null;
// }
// if (audio_content == null)
// {
// Console.WriteLine("沒有讀取到任何內容");
// return "";
// }
// Console.WriteLine("開始進行語音聽寫.......");
// /*
// * QISRSessionBegin();
// * 功能:開始一次語音識別
// * 參數一:定義關鍵詞識別||語法識別||連續語音識別(null)
// * 參數2:設置識別的參數:語言、領域、語言區域。。。。
// * 參數3:帶回語音識別的結果,成功||錯誤代碼
// * 返回值intPtr類型,後面會用到這個返回值
// */
// session_id = mscDLL.QISRSessionBegin(null, session_begin_params, ref errcode);
// if (errcode != (int)Errors.MSP_SUCCESS)
// {
// if (errcode == 10114)
// {
// MessageBox.Show("網絡故障!請檢查網絡", "提示");
// }
// Console.WriteLine("開始一次語音識別失敗!");
// return "";
// }
// /*
// QISRAudioWrite();
// 功能:寫入本次識別的音頻
// 參數1:之前已經得到的sessionID
// 參數2:音頻數據緩衝區起始地址
// 參數3:音頻數據長度,單位字節。
// 參數4:用來告知MSC音頻發送是否完成 MSP_AUDIO_SAMPLE_FIRST = 1 第一塊音頻
// MSP_AUDIO_SAMPLE_CONTINUE = 2 還有後繼音頻
// MSP_AUDIO_SAMPLE_LAST = 4 最後一塊音頻
// 參數5:端點檢測(End-point detected)器所處的狀態
// MSP_EP_LOOKING_FOR_SPEECH = 0 還沒有檢測到音頻的前端點。
// MSP_EP_IN_SPEECH = 1 已經檢測到了音頻前端點,正在進行正常的音頻處理。
// MSP_EP_AFTER_SPEECH = 3 檢測到音頻的後端點,後繼的音頻會被MSC忽略。
// MSP_EP_TIMEOUT = 4 超時。
// MSP_EP_ERROR = 5 出現錯誤。
// MSP_EP_MAX_SPEECH = 6 音頻過大。
// 參數6:識別器返回的狀態,提醒用戶及時開始\停止獲取識別結果
// MSP_REC_STATUS_SUCCESS = 0 識別成功,此時用戶可以調用QISRGetResult來獲取(部分)結果。
// MSP_REC_STATUS_NO_MATCH = 1 識別結束,沒有識別結果。
// MSP_REC_STATUS_INCOMPLETE = 2 正在識別中。
// MSP_REC_STATUS_COMPLETE = 5 識別結束。
// 返回值:函數調用成功則其值爲MSP_SUCCESS,否則返回錯誤代碼。
// 本接口需不斷調用,直到音頻全部寫入爲止。上傳音頻時,需更新audioStatus的值。具體來說:
// 當寫入首塊音頻時,將audioStatus置爲MSP_AUDIO_SAMPLE_FIRST
// 當寫入最後一塊音頻時,將audioStatus置爲MSP_AUDIO_SAMPLE_LAST
// 其餘情況下,將audioStatus置爲MSP_AUDIO_SAMPLE_CONTINUE
// 同時,需定時檢查兩個變量:epStatus和rsltStatus。具體來說:
// 當epStatus顯示已檢測到後端點時,MSC已不再接收音頻,應及時停止音頻寫入
// 當rsltStatus顯示有識別結果返回時,即可從MSC緩存中獲取結果*/
// int res = mscDLL.QISRAudioWrite(session_id, audio_content, (uint)audio_content.Length, aud_stat, ref ep_stat, ref rec_stat);
// if (res != (int)Errors.MSP_SUCCESS)
// {
// Console.WriteLine("寫入識別的音頻失敗!" + res);
// return "";
// }
// res = mscDLL.QISRAudioWrite(session_id, null, 0, AudioStatus.MSP_AUDIO_SAMPLE_LAST, ref ep_stat, ref rec_stat);
// if (res != (int)Errors.MSP_SUCCESS)
// {
// Console.WriteLine("寫入音頻失敗!" + res);
// return "";
// }
// while (RecogStatus.MSP_REC_STATUS_COMPLETE != rec_stat)
// {//如果沒有完成就一直繼續獲取結果
// /*
// QISRGetResult();
// 功能:獲取識別結果
// 參數1:session,之前已獲得
// 參數2:識別結果的狀態
// 參數3:waitTime[in] 此參數做保留用
// 參數4:錯誤編碼||成功
// 返回值:函數執行成功且有識別結果時,返回結果字符串指針;其他情況(失敗或無結果)返回NULL。
// */
// IntPtr now_result = mscDLL.QISRGetResult(session_id, ref rec_stat, 0, ref errcode);
// if (errcode != (int)Errors.MSP_SUCCESS)
// {
// Console.WriteLine("獲取結果失敗:" + errcode);
// return "";
// }
// if (now_result != null)
// {
// int length = now_result.ToString().Length;
// totalLength += length;
// if (totalLength > 4096)
// {
// Console.WriteLine("緩存空間不夠" + totalLength);
// return "";
// }
// result.Append(Marshal.PtrToStringAnsi(now_result));
// }
// Thread.Sleep(150);//防止頻繁佔用cpu
// }
// mscDLL.QISRSessionEnd(session_id, "normal end");
// Console.WriteLine("語音聽寫結束");
// Console.WriteLine("結果:\n");
// Console.WriteLine(result);
// return result.ToString();
//}
#endregion
}
總結
1、若帶有背景音樂之類的干擾,識別不了或者效果很差。
2、容易出現同音錯別字
3、語氣詞準確率不高
4、由於是按30秒切片,在切片的時間點易造成識別出現問題,不準確
5、總體還是不錯的,畢竟免費~~