
有道语音识别API接口提供有道的语音识别服务,包含了中文和英文的识别功能。您只需要通过调用有道语音识别API,传入待识别的音频文件,并指定要识别的源语言种类,以POST方式请求就可以得到相应的识别结果。
上传的文件时长不能超过15s,文件大小不能超过2M。
有道语音识别API HTTP地址:
http://openapi.youdao.com/asrapi
有道语音识别API HTTPS地址:
https://openapi.youdao.com/asrapi
调用API需要向接口发送以下字段来访问服务。
字段名 | 类型 | 含义 | 必填 | 备注 |
---|---|---|---|---|
q | text | 要识别的音频文件的base64编码字符串 | True | 必须是Base64编码 |
langType | text | 源语言 | True | 语言列表 |
appKey | text | 应用 ID | True | 可在 应用管理 查看 |
salt | text | 随机数 | True | |
sign | text | 签名,通过md5(appKey+q+salt+密钥)生成 | True | appKey+q+salt+密钥的MD5值 |
format | text | 语音文件的格式,wav | true | wav |
rate | text | 采样率, 8000 或者 16000, 推荐 16000 采用率 | true | 8000 |
channel | text | 声道数, 仅支持单声道,请填写固定值1 | true | 1 |
type | text | 上传类型, 仅支持base64上传,请填写固定值1 | true | 1 |
签名生成方法如下:
appKey
,识别文本 q
(注意为UTF-8编码),随机数 salt
和密钥
(可在 应用管理 查看), 按照 appKey+q+salt+密钥
的顺序拼接得到字符串 str
。str
做md5,得到32位大写的 sign
(参考Java生成MD5示例)注意:
appKey+q+salt+密钥
字符串时,q
不需要做 URL encode,在生成签名之后,发送 HTTP 请求之前才需要对要发送的待识别文本字段 q
做 URL encode。响应结果是以json形式输出,包含字段如下表所示:
字段 | 含义 |
---|---|
errorCode | 识别结果错误码,一定存在 |
result | 识别结果,识别成功一定存在 |
{
"result": [
"今天天气不错"
], //识别结果
"errorCode": "0", //错误码。一定存在
}
支持中文和英文音频的识别。
语言 | 代码 |
---|---|
中文 | zh-CHS |
英文 | en |
格式支持:wav(不压缩、pcm编码)
采样率:8k或者16k。推荐16k。
编码:16bit位深的单声道
格式 | 代码 |
---|---|
wav | wav |
错误码 | 含义 |
---|---|
101 | 缺少必填的参数 |
102 | 不支持的语言类型 |
103 | 请求文本过长 |
104 | 不支持的API类型 |
105 | 不支持的签名类型 |
106 | 不支持的响应类型 |
107 | 不支持的传输加密类型 |
108 | appKey无效 |
109 | batchLog格式不正确 |
110 | 无相关服务的有效实例 |
111 | 用户无效 |
112 | 请求服务无效 |
113 | 请求文本不能为空 |
201 | 解密失败 |
202 | 签名检验失败 |
203 | 访问IP地址不在可访问IP列表 |
301 | 词典查询失败 |
302 | 小语种识别失败 |
303 | 服务的异常 |
401 | 账户已经欠费停止 |
402 | offlinesdk不可用 |
411 | 访问频率受限,请稍后访问 |
412 | 超过最大请求字符数 |
4001 | 不支持的语音识别格式 |
4002 | 不支持的语音识别采样率 |
4003 | 不支持的语音识别声道 |
4004 | 不支持的语音上传类型 |
4005 | 不支持的语言类型 |
4006 | 识别音频文件过大 |
4007 | 识别音频时长过长 |
4201 | 解密失败 |
4301 | 语音识别失败 |
4303 | 服务的异常 |
4411 | 访问频率受限,请稍后访问 |
4412 | 超过最大请求时长 |
应用没有绑定服务实例,可以新建服务实例,绑定服务实例。
appKey无效,注册账号, 登录后台创建应用和实例并完成绑定, 可获得应用ID和密钥等信息,其中应用ID就是appKey( 注意不是应用密钥)
首先确保必填参数齐全,然后,确认参数书写是否正确。
如果确认 appKey
和 appSecret
的正确性,仍返回202,一般是编码问题。请确保 q
为UTF-8编码.
本部分描述如何把其他格式的音频转成符合语音识别输入要求的格式文件。
语音识别底层使用的是pcm格式,因此推荐使用pcm格式音频。音频格式转换推荐使用ffmpeg
ffmpeg是一个自由软件,可以运行音频、视频多种格式的录影、转换、流功能,包含libavcodec--这是一个用于多个项目中的音频、视频的解码器库,以及libavformat--一个音频和视频格式转换库。
ffmpeg官网:https://www.ffmpeg.org/
ffmpeg的github地址:https://github.com/FFmpeg/FFmpeg
ffmpeg默认支持pcm与wav(pcm编码)格式,额外的编译参数如下:
—enable-libopencore-amrnb 支持amr-nb(8000 采样率) 读写
—enable-libopencore-amrwb 支持amr-wb(16000 采样率) 读取
—enable-libvo-amrwbenc 支持amr-wb(16000 采样率) 写入
—enable-libmp3lame 支持mp3 写入
ffmpeg -codecs 可以查看所有的格式:
D..... = Decoding supported # 读取
.E.... = Encoding supported # 写入
..A... = Audio codec # 音频编码
....L. = Lossy compression # 有损
.....S = Lossless compression # 无损
DEA..S pcm_s16le PCM signed 16-bit little-endian
DEA.LS wavpack WavPack
DEA.L. mp3 MP3 (MPEG audio layer 3)
DEA.L. amr_nb AMR-NB (Adaptive Multi-Rate NarrowBand)
DEA.L. amr_wb AMR-WB (Adaptive Multi-Rate WideBand)
-i 设定输入流
-f 设定格式
-ss 开始时间
wav、mp3、amr格式都自带头部,包含采样率、编码、多声道等信息。而pcm为原始音频信息,没有头部信息。wav(pcm编码)就是pcm文件加了wav的头部信息。
输入wav、mp3、amr:
-i audio.wav/audio.mp3/audio.amr
输入pcm格式:pcm需要额外告知编码格式,采样率,单声道信息
-f s16le -ac 1 -ar 16000 -i 16k.pcm //单声道、16000采样率、16bits编码的pcm文件
-ar 设定采样率
-ac 设定声音的channel数
-acodec 设定声音编解码器,未设定时则使用与输入流相同的编解码器
-an 不处理音频
-ab 设置比特率(单位:bit/s,也许老版是kb/s),前面ac设置为立体声时要以一半的比特率来设置,比如192kbps的就设置为96,转换默认比特率都较小,转换默认比特率都较小,要听到高品质声音的话建议设到160kbps(80)以上。
在原始采样率 大于或者接近16000的时候,推荐使用16000,8000采样率会降低识别效果。
输出wav和amr时,如果没有指定声音编解码器,则会选择默认的编码器。
输出pcm:
-f s16le -ac 1 -ar 16000 16k.pcm // 单声道 16000 采样率 16bits编码 pcm文件
输出wav:
-ar 1 -ar 16000 16k.wav //单声道 16000 采样率 16bits编码 pcm编码的wav文件
amr-nb:全称是:Adaptive Multi-Rate,自适应多速率,是一种音频编码文件格式,专用于有效地压缩语音频率。amr-nb格式只能选 8000采样率。bit rates越高音质越好,但是文件越大
bit rates 4.75k, 5.15k, 5.9k, 6.7k, 7.4k, 7.95k, 10.2k or 12.2k
输出 amr-wb 格式,采样率 16000。 bit rates越高音质越好,但是文件越大。
6600 8850 12650 14250 15850 18250 19850 23050 23850
-y 覆盖同名文件
-v 日志输出 基本如-v ERROR -v quiet等
ffmpeg {常用参数} {输入音频参数} {输出音频参数}
wav 文件转 16k 16bits 位深的单声道pcm文件
ffmpeg -y -i 16k.wav -acodec pcm_s16le -f s16le -ac 1 -ar 16000 16k.pcm
pcm文件转wav:
ffmpeg -y -f s16le -ar 16000 -ac 1 -acodec pcm_s16le 16k.pcm 16k.wav
m4a文件转16k 16bits 位深的单声道pcm文件
D:\ffmpeg\bin>ffmpeg -y -i test.m4a -acodec pcm_s16le -f s16le -ac 1 -ar 1600
0 16k.pcm
pcm保存的是未压缩的音频信息,没有头文件
16bits编码是指每次采样信息用2个字节保存。
16000采样率,是指1秒采样16000次,常见的音频是44100HZ,即一秒采样44100次。
单声道: 只有一个声道。
根据这些信息,可以得出:
1ms的16采样率音频文件大小是 2*16 = 32字节 。
1ms的8采样率音频文件大小是 2*8 = 16字节,由此即可得到音频的长度。
package outfox.ead.openapi;
import org.apache.http.HttpEntity;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import outfox.ead.openapi.util.EnDeCriptUtil;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by liyang_ly on 2018/2/6.
*/
public class AsrDemoForHttp {
static Logger logger = LoggerFactory.getLogger(OCRDemo.class);
public static String GetImageStr(String imgFile)
{//将图片文件转化为字节数组字符串,并对其进行Base64编码处理
InputStream in = null;
byte[] data = null;
//读取图片字节数组
try
{
in = new FileInputStream(imgFile);
data = new byte[in.available()];
in.read(data);
in.close();
}
catch (IOException e)
{
e.printStackTrace();
}
//对字节数组Base64编码
return EnDeCriptUtil.base64Encode(data);//返回Base64编码过的字节数组字符串
}
public static void main(String[] args) throws Exception {
Map<String, String> map = new HashMap<String, String>();
String apiUrl = "http://openapi.youdao.com/asrapi";
map.put("appKey", "您的应用ID");
String q = GetImageStr("D:/16k.wav");
map.put("q",q);
map.put("format", "wav");
map.put("rate", "16000");
map.put("channel", "1");
map.put("type", "1");
map.put("pkn", "adb");
map.put("salt", "123");
map.put("langType", "zh-CHS");
String sign = md5(map.get("appKey")+map.get("q")+map.get("salt")+"您的应用密钥");
map.put("sign", sign);
map.put("docType", "json");
String result = requestOCRForHttp(apiUrl, map);
System.out.println(result);
}
public static String requestOCR(String url,String s,int et) throws Exception{
String result = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
/**HttpPost*/
HttpPost httpPost = new HttpPost(url);
httpPost.setProtocolVersion(HttpVersion.HTTP_1_0);
httpPost.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("s", s));
params.add(new BasicNameValuePair("et", String.valueOf(et)));
httpPost.setEntity(new UrlEncodedFormEntity(params,"UTF-8"));
/**HttpResponse*/
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
try{
HttpEntity httpEntity = httpResponse.getEntity();
result = EntityUtils.toString(httpEntity, "utf-8");
EntityUtils.consume(httpEntity);
}finally{
try{
if(httpResponse!=null){
httpResponse.close();
}
}catch(IOException e){
logger.info("## release resouce error ##" + e);
}
}
return result;
}
public static String requestOCRForHttp(String url,Map<String,String> requestParams) throws Exception{
String result = null;
CloseableHttpClient httpClient = HttpClients.createDefault();
/**HttpPost*/
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> params = new ArrayList<NameValuePair>();
System.out.println(requestParams.keySet().size());
for(String key:requestParams.keySet()){
if(!"q".equals(key))
System.out.println(key+":"+requestParams.get(key));
params.add(new BasicNameValuePair(key, requestParams.get(key)));
}
httpPost.setEntity(new UrlEncodedFormEntity(params,"UTF-8"));
/**HttpResponse*/
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
try{
HttpEntity httpEntity = httpResponse.getEntity();
result = EntityUtils.toString(httpEntity, "utf-8");
EntityUtils.consume(httpEntity);
}finally{
try{
if(httpResponse!=null){
httpResponse.close();
}
}catch(IOException e){
logger.info("## release resouce error ##" + e);
}
}
return result;
}
/**
* 生成32位MD5摘要
* @param string
* @return
*/
public static String md5(String string) {
if(string == null){
return null;
}
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'};
byte[] btInput = string.getBytes();
try{
/** 获得MD5摘要算法的 MessageDigest 对象 */
MessageDigest mdInst = MessageDigest.getInstance("MD5");
/** 使用指定的字节更新摘要 */
mdInst.update(btInput);
/** 获得密文 */
byte[] md = mdInst.digest();
/** 把密文转换成十六进制的字符串形式 */
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
}catch(NoSuchAlgorithmException e){
return null;
}
}
/**
* 根据api地址和参数生成请求URL
* @param url
* @param params
* @return
*/
public static String getUrlWithQueryString(String url, Map<String, String> params) {
if (params == null) {
return url;
}
StringBuilder builder = new StringBuilder(url);
if (url.contains("?")) {
builder.append("&");
} else {
builder.append("?");
}
int i = 0;
for (String key : params.keySet()) {
String value = params.get(key);
if (value == null) { // 过滤空的key
continue;
}
if (i != 0) {
builder.append('&');
}
builder.append(key);
builder.append('=');
builder.append(encode(value));
i++;
}
return builder.toString();
}
/**
* 进行URL编码
* @param input
* @return
*/
public static String encode(String input) {
if (input == null) {
return "";
}
try {
return URLEncoder.encode(input, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return input;
}
}
# -*- coding: utf-8 -*-
import requests
import md5
import random
import wave
import base64
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
asrUrl = "http://openapi.youdao.com/asrapi"
appKey = "您的应用ID"
appSecret = "您的应用密钥"
def asr(app_key, q ,app_secret ,lang,channel,rate,format):
data = {}
salt = random.randint(1, 65536)
sign = app_key + q + str(salt) + app_secret
m1 = md5.new()
m1.update(sign)
sign = m1.hexdigest()
data['appKey'] = app_key
data['q'] = q
data['salt'] = salt
data['sign'] = sign
data['langType'] = lang
data['channel'] = channel
data['rate'] = rate
data['format'] = format
data['type'] = 1
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(asrUrl,data=data,headers = headers)
return response
filename = 'd:/en.wav'
extension = filename[filename.rindex('.')+1:]
if extension == "wav" :
# load wav
file_to_play = wave.open(filename, 'rb')
file_wav = open(r'd:\en.wav', 'rb')
q = base64.b64encode(file_wav.read())
file_wav.close()
sample_width = file_to_play.getsampwidth()
sample_rate = file_to_play.getframerate()
nchannels = file_to_play.getnchannels()
bit_rate = 16
# request
response = asr(appKey,q,appSecret,"en",nchannels,sample_rate,'wav')
print response.content
else:
print '不支持的音频类型'
using System;
using System.Web;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace zhiyun_csharp_demo
{
using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Security.Cryptography;
using System.Drawing;
class Asr
{
public static void Main()
{
String url = "http://openapi.youdao.com/asrapi";
Dictionary<String, String> dic = new Dictionary<String, String>();
string q = ImgToBase64String("d:/16k.wav");
string appKey = "您的应用ID";
string appSecret = "您的应用密钥";
/** 目标语言 */
string langType = "en";
/** 音频格式:目前支持pcm和wav(pcm编码) */
string format = "wav";
/** 音频采样率:目前支持16000和8000 */
string rate = "16000";
/** 音频频道 */
string channel = "1";
/** 上传类型 */
string type = "1";
/** 随机数,自己随机生成,建议时间戳 */
string salt = DateTime.Now.Millisecond.ToString();
MD5 md5 = new MD5CryptoServiceProvider();
string md5Str = appKey + q + salt + appSecret;
byte[] output = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(md5Str));
string sign = BitConverter.ToString(output).Replace("-", "");
dic.Add("q", System.Web.HttpUtility.UrlEncode(q));
dic.Add("appKey", appKey);
dic.Add("langType", langType);
dic.Add("format", format);
dic.Add("rate", rate);
dic.Add("channel", channel);
dic.Add("type", type);
dic.Add("salt", salt);
dic.Add("sign", sign);
string result = Post(url, dic);
Console.WriteLine(result);
}
protected static string ImgToBase64String(string Imagefilename)
{
try
{
FileStream filestream = new FileStream(Imagefilename, FileMode.Open);
byte[] arr = new byte[filestream.Length];
filestream.Position = 0;
filestream.Read(arr, 0, (int)filestream.Length);
filestream.Close();
return Convert.ToBase64String(arr);
}
catch (Exception ex)
{
return null;
}
}
public static string Post(string url, Dictionary<String, String> dic)
{
string result = "";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
#region 添加Post 参数
StringBuilder builder = new StringBuilder();
int i = 0;
foreach (var item in dic)
{
if (i > 0)
builder.Append("&");
builder.AppendFormat("{0}={1}", item.Key, item.Value);
i++;
}
Console.WriteLine(builder.ToString());
byte[] data = Encoding.UTF8.GetBytes(builder.ToString());
req.ContentLength = data.Length;
using (Stream reqStream = req.GetRequestStream())
{
reqStream.Write(data, 0, data.Length);
reqStream.Close();
}
#endregion
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
Stream stream = resp.GetResponseStream();
//获取响应内容
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
{
result = reader.ReadToEnd();
}
return result;
}
}
}
<?php
define("CURL_TIMEOUT", 2000);
define("URL", "http://nb036x.corp.youdao.com:8999/asrapi");
define("APP_KEY", "您的应用ID"); //替换为您的应用ID
define("SEC_KEY", "您的应用密钥");//替换为您的密钥
function asr($q, $type, $langType, $channel, $rate, $format)
{
$args = array(
'q' => $q,
'appKey' => APP_KEY,
'salt' => rand(10000,99999),
'type' => $type,
'langType' => $langType,
'channel' => $channel,
'rate' => $rate,
'format' => $format,
);
$args['sign'] = buildSign(APP_KEY, $q, $args['salt'], SEC_KEY);
$ret = call(URL, $args);
echo $ret;
$ret = json_decode($ret, true);
return $ret;
}
//加密
function buildSign($appKey, $query, $salt, $secKey)
{/*{{{*/
$str = $appKey . $query . $salt . $secKey;
$ret = md5($str);
return $ret;
}/*}}}*/
//发起网络请求
function call($url, $args=null, $method="post", $testflag = 0, $timeout = CURL_TIMEOUT, $headers=array())
{/*{{{*/
$ret = false;
$i = 0;
while($ret === false)
{
if($i > 1)
break;
if($i > 0)
{
sleep(1);
}
$ret = callOnce($url, $args, $method, false, $timeout, $headers);
$i++;
}
return $ret;
}/*}}}*/
function callOnce($url, $args=null, $method="post", $withCookie = false, $timeout = CURL_TIMEOUT, $headers=array())
{/*{{{*/
$ch = curl_init();
if($method == "post")
{
$data = convert($args);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_POST, 1);
}
else
{
$data = convert($args);
if($data)
{
if(stripos($url, "?") > 0)
{
$url .= "&$data";
}
else
{
$url .= "?$data";
}
}
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if(!empty($headers))
{
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
if($withCookie)
{
curl_setopt($ch, CURLOPT_COOKIEJAR, $_COOKIE);
}
$r = curl_exec($ch);
curl_close($ch);
return $r;
}/*}}}*/
function convert(&$args)
{/*{{{*/
$data = '';
if (is_array($args))
{
foreach ($args as $key=>$val)
{
if (is_array($val))
{
foreach ($val as $k=>$v)
{
$data .= $key.'['.$k.']='.rawurlencode($v).'&';
}
}
else
{
$data .="$key=".rawurlencode($val)."&";
}
}
return trim($data, "&");
}
return $args;
}/*}}}*/
$file="d:/en.wav";
$fp=fopen($file,"r") or die("Can't open file");
$q=chunk_split(base64_encode(fread($fp,filesize($file))));//base64编码
fclose($fp);
//调用asr
asr($q,"1","en","1",16000,'wav');
?>