1. 申请账号及获得AppID等
控制台填写资料
控制台-讯飞开放平台
语音听写的文档及接口说明
语音听写(流式版)WebAPI 文档 | 讯飞开放平台文档中心
关键信息:
设置参数
业务数据流参数
data
返回数据解析
返回参数
2. 研究demo,发现问题
下载python demo和node js demo
原理都是调用websocket连接方式
nodejs 的demo可以直接运行,需要安装两个库
npm install crypto-js
npm install ws
/**** 运行前:请先填写Appid、APIKey、APISecret** 语音听写流式 WebAPI 接口调用示例 接口文档(必看):/rest_api/语音听写(流式版).html* webapi 听写服务参考帖子(必看):/forum.php?mod=viewthread&tid=38947&extra=* 语音听写流式WebAPI 服务,热词使用方式:登陆开放平台/后,找到控制台--我的应用---语音听写---服务管理--上传热词* 注意:热词只能在识别的时候会增加热词的识别权重,需要注意的是增加相应词条的识别率,但并不是绝对的,具体效果以您测试为准。* 错误码链接:/document/error-code (code返回错误码时必看)* @author iflytek*/const CryptoJS = require('crypto-js')const WebSocket = require('ws')var fs = require('fs')// 系统配置 const config = {// 请求地址hostUrl: "wss://iat-/v2/iat",host: "iat-",//在控制台-我的应用-语音听写(流式版)获取appid: "d9f99f36",//在控制台-我的应用-语音听写(流式版)获取apiSecret: "YTBkNzkyYWYwMGQ2ZDNiZTYzMDc4NDY1",//在控制台-我的应用-语音听写(流式版)获取apiKey: "d2519c5bab022ec11c27dc5752263604",file: "./16k_10.pcm", //请填写您的音频文件路径uri: "/v2/iat",highWaterMark: 1280}// 帧定义const FRAME = {STATUS_FIRST_FRAME: 0,STATUS_CONTINUE_FRAME: 1,STATUS_LAST_FRAME: 2}// 获取当前时间 RFC1123格式let date = (new Date().toUTCString())// 设置当前临时状态为初始化let status = FRAME.STATUS_FIRST_FRAME// 记录本次识别用sidlet currentSid = ""// 识别结果let iatResult = []let wssUrl = config.hostUrl + "?authorization=" + getAuthStr(date) + "&date=" + date + "&host=" + config.hostlet ws = new WebSocket(wssUrl)// 连接建立完毕,读取数据进行识别ws.on('open', (event) => {console.log("websocket connect!")var readerStream = fs.createReadStream(config.file, {highWaterMark: config.highWaterMark});readerStream.on('data', function(chunk) {send(chunk)});// 最终帧发送结束readerStream.on('end', function() {status = FRAME.STATUS_LAST_FRAMEsend("")});})// 得到识别结果后进行处理,仅供参考,具体业务具体对待ws.on('message', (data, err) => {if (err) {console.log(`err:${err}`)return}res = JSON.parse(data)if (res.code != 0) {console.log(`error code ${res.code}, reason ${res.message}`)return}let str = ""if (res.data.status == 2) {// res.data.status ==2 说明数据全部返回完毕,可以关闭连接,释放资源str += "最终识别结果"currentSid = res.sidws.close()} else {str += "中间识别结果"}iatResult[res.data.result.sn] = res.data.resultif (res.data.result.pgs == 'rpl') {res.data.result.rg.forEach(i => {iatResult[i] = null})str += "【动态修正】"}str += ":"iatResult.forEach(i => {if (i != null) {i.ws.forEach(j => {j.cw.forEach(k => {str += k.w})})}})console.log(str)// ... do something})// 资源释放ws.on('close', () => {console.log(`本次识别sid:${currentSid}`)console.log('connect close!')})// 建连错误ws.on('error', (err) => {console.log("websocket connect err: " + err)})// 鉴权签名function getAuthStr(date) {let signatureOrigin = `host: ${config.host}\ndate: ${date}\nGET ${config.uri} HTTP/1.1`let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, config.apiSecret)let signature = CryptoJS.enc.Base64.stringify(signatureSha)let authorizationOrigin = `api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`let authStr = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(authorizationOrigin))return authStr}// 传输数据function send(data) {let frame = "";let frameDataSection = {"status": status,"format": "audio/L16;rate=16000","audio": data.toString('base64'),"encoding": "raw"}switch (status) {case FRAME.STATUS_FIRST_FRAME:frame = {// 填充commoncommon: {app_id: config.appid},//填充businessbusiness: {language: "zh_cn",domain: "iat",accent: "mandarin",dwa: "wpgs" // 可选参数,动态修正},//填充datadata: frameDataSection}status = FRAME.STATUS_CONTINUE_FRAME;break;case FRAME.STATUS_CONTINUE_FRAME:case FRAME.STATUS_LAST_FRAME://填充frameframe = {data: frameDataSection}break;}ws.send(JSON.stringify(frame))}
播放的目录下的样例pcm文件,可以正常返回
C:\Users\pocea\Desktop\KXWELL\dev\语音识别\iat_ws_nodejs_demo>node iat-ws-node.js
websocket connect!
中间识别结果:四月
中间识别结果【动态修正】:四月十四日
中间识别结果【动态修正】:四月十三日
中间识别结果【动态修正】:四月十三日中国
中间识别结果【动态修正】:四月十三日中国台北
中间识别结果【动态修正】:四月十三日中国台北选手
中间识别结果【动态修正】:四月十三日中国台北选手代之
中间识别结果【动态修正】:四月十三日中国台北选手戴资颖在比赛
中间识别结果【动态修正】:4月13日,中国台北选手戴资颖在比赛中发球
中间识别结果:4月13日,中国台北选手戴资颖在比赛中发球,当日
中间识别结果:4月13日,中国台北选手戴资颖在比赛中发球,当日,在新加坡室内体育场举行的新加坡羽毛球公开赛女子单打半决赛中
中间识别结果:4月13日,中国台北选手戴资颖在比赛中发球,当日,在新加坡室内体育场举行的新加坡羽毛球公开赛女子单打半决赛中 ,中国台北选手戴资颖以2:1战胜日本选手山口茜
中间识别结果:4月13日,中国台北选手戴资颖在比赛中发球,当日,在新加坡室内体育场举行的新加坡羽毛球公开赛女子单打半决赛中 ,中国台北选手戴资颖以2:1战胜日本选手山口茜,晋级决赛
最终识别结果:4月13日,中国台北选手戴资颖在比赛中发球,当日,在新加坡室内体育场举行的新加坡羽毛球公开赛女子单打半决赛中 ,中国台北选手戴资颖以2:1战胜日本选手山口茜,晋级决赛。
本次识别sid:iat000dff5a@dx17a8f6bd0b67a1c802
connect close!
这里的动态修正怎么用还没搞明白。
# -*- coding:utf-8 -*-## author: iflytek## 本demo测试时运行的环境为:Windows + Python3.7# 本demo测试成功运行时所安装的第三方库及其版本如下,您可自行逐一或者复制到一个新的txt文件利用pip一次性安装:# cffi==1.12.3# gevent==1.4.0# greenlet==0.4.15# pycparser==2.19# six==1.12.0# websocket==0.2.1# websocket-client==0.56.0## 语音听写流式 WebAPI 接口调用示例 接口文档(必看):/rest_api/语音听写(流式版).html# webapi 听写服务参考帖子(必看):/forum.php?mod=viewthread&tid=38947&extra=# 语音听写流式WebAPI 服务,热词使用方式:登陆开放平台/后,找到控制台--我的应用---语音听写(流式)---服务管理--个性化热词,# 设置热词# 注意:热词只能在识别的时候会增加热词的识别权重,需要注意的是增加相应词条的识别率,但并不是绝对的,具体效果以您测试为准。# 语音听写流式WebAPI 服务,方言试用方法:登陆开放平台/后,找到控制台--我的应用---语音听写(流式)---服务管理--识别语种列表# 可添加语种或方言,添加后会显示该方言的参数值# 错误码链接:/document/error-code (code返回错误码时必看)# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #import websocketimport datetimeimport hashlibimport base64import hmacimport jsonfrom urllib.parse import urlencodeimport timeimport sslfrom wsgiref.handlers import format_date_timefrom datetime import datetimefrom time import mktimeimport _thread as threadSTATUS_FIRST_FRAME = 0 # 第一帧的标识STATUS_CONTINUE_FRAME = 1 # 中间帧标识STATUS_LAST_FRAME = 2 # 最后一帧的标识'''正常运行返回结果:sid:iat000d6ced@dx17a8fa05ca4a493802 call success!,data is:[{"bg": 64, "cw": [{"sc": 0, "w": "这"}]}, {"bg": 84, "cw": [{"sc": 0, "w": "是"}]}, {"bg": 104, "cw": [{"sc": 0, "w": "一"}]}, {"bg": 124, "cw": [{"sc": 0, "w": "段"}]}, {"bg": 144, "cw": [{"sc": 0, "w": "测试"}]}, {"bg": 184, "cw": [{"sc": 0, "w": "音频"}]}]sid:iat000d6ced@dx17a8fa05ca4a493802 call success!,data is:[{"bg": 0, "cw": [{"w": "。", "sc": 0}]}]0:00:02.382220'''class Ws_Param(object):# 初始化def __init__(self, APPID, APIKey, APISecret, AudioFile):self.APPID = APPIDself.APIKey = APIKeyself.APISecret = APISecretself.AudioFile = AudioFile# 公共参数(common)monArgs = {"app_id": self.APPID}# 业务参数(business),更多个性化参数可在官网查看self.BusinessArgs = {"domain": "iat", "language": "zh_cn", "accent": "mandarin", "vinfo":1,"vad_eos":2000}# 生成urldef create_url(self):url = 'wss://ws-/v2/iat'# 生成RFC1123格式的时间戳now = datetime.now()date = format_date_time(mktime(now.timetuple()))# 拼接字符串signature_origin = "host: " + "ws-" + "\n"signature_origin += "date: " + date + "\n"signature_origin += "GET " + "/v2/iat " + "HTTP/1.1"# 进行hmac-sha256进行加密signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),digestmod=hashlib.sha256).digest()signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (self.APIKey, "hmac-sha256", "host date request-line", signature_sha)authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')# 将请求的鉴权参数组合为字典v = {"authorization": authorization,"date": date,"host": "ws-"}# 拼接鉴权参数,生成urlurl = url + '?' + urlencode(v)# print("date: ",date)# print("v: ",v)# 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致# print('websocket url :', url)return url# 收到websocket消息的处理def on_message(ws, message):try:code = json.loads(message)["code"]sid = json.loads(message)["sid"]if code != 0:errMsg = json.loads(message)["message"]print("sid:%s call error:%s code is:%s" % (sid, errMsg, code))else:data = json.loads(message)["data"]["result"]["ws"]# print(json.loads(message))result = ""for i in data:for w in i["cw"]:result += w["w"]print("sid:%s call success!,data is:%s" % (sid, json.dumps(data, ensure_ascii=False)))except Exception as e:print("receive msg,but parse exception:", e)# 收到websocket错误的处理def on_error(ws, error):print("### error:", error)# 收到websocket关闭的处理def on_close(ws):print("### closed ###")# 收到websocket连接建立的处理def on_open(ws):def run(*args):frameSize = 8000 # 每一帧的音频大小intervel = 0.4 # 发送音频间隔(单位:s)status = STATUS_FIRST_FRAME # 音频的状态信息,标识音频是第一帧,还是中间帧、最后一帧with open(wsParam.AudioFile, "rb") as fp:while True:buf = fp.read(frameSize)# 文件结束if not buf:status = STATUS_LAST_FRAME# 第一帧处理# 发送第一帧音频,带business 参数# appid 必须带上,只需第一帧发送if status == STATUS_FIRST_FRAME:d = {"common": monArgs,"business": wsParam.BusinessArgs,"data": {"status": 0, "format": "audio/L16;rate=16000","audio": str(base64.b64encode(buf), 'utf-8'),"encoding": "lame"}} #mp3格式定义lame, pcm格式为rawd = json.dumps(d)ws.send(d)status = STATUS_CONTINUE_FRAME# 中间帧处理elif status == STATUS_CONTINUE_FRAME:d = {"data": {"status": 1, "format": "audio/L16;rate=16000","audio": str(base64.b64encode(buf), 'utf-8'),"encoding": "lame"}}ws.send(json.dumps(d))# 最后一帧处理elif status == STATUS_LAST_FRAME:d = {"data": {"status": 2, "format": "audio/L16;rate=16000","audio": str(base64.b64encode(buf), 'utf-8'),"encoding": "lame"}}ws.send(json.dumps(d))time.sleep(1)break# 模拟音频采样间隔time.sleep(intervel)ws.close()thread.start_new_thread(run, ())if __name__ == "__main__":# 测试时候在此处正确填写相关信息即可运行time1 = datetime.now()wsParam = Ws_Param(APPID='d9f99f36', APISecret='YTBkNzkyYWYwMGQ2ZDNiZTYzMDc4NDY1',APIKey='d2519c5bab022ec11c27dc5752263604',AudioFile=r'./iat_mp3_16k.mp3')websocket.enableTrace(False)wsUrl = wsParam.create_url()ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close)ws.on_open = on_openws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})time2 = datetime.now()print(time2-time1)
3. 对齐文件格式
python的例子一直有个问题,开始用wav文件来测试,实际是不行的,看来科大讯飞的API还不支持,改为用PCM文件来测试,但问题又来了,怎么来生成测试文件?又怎么播放测试文件,确认结果呢?所有有了上一篇文章,怎么写一个PCM PLAYER. PCM recorder还要晚一点,要不就用QT把两者打包成一个工具也不错,呵呵。
这里的关键就是文件格式和设置参数要对应
就是连接上传参数这里
d = {"common": monArgs,
"business": wsParam.BusinessArgs,
"data": {"status": 0, "format": "audio/L16;rate=16000",
"audio": str(base64.b64encode(buf), 'utf-8'),
"encoding": "lame"}} #mp3格式定义lame, pcm格式为raw
实测MP3和PCM都可以正常识别
4. 待研究
需要解决唤醒,断句等问题,因为需要实时流处理,所以还要考虑处理速度能不能满足识别后控制设备的要求。
如果觉得《科大讯飞语音接口调用实现语音识别》对你有帮助,请点赞、收藏,并留下你的观点哦!