腾讯云语音合成 C# 示例
按着官方文档的顺序开始写吧。
一、请求 url 拼接
首先的坑点就是url必须是按升序
排列进行拼接,不然接下来的鉴权
肯定失败
基础链接:
https://aai.qcloud.com/tts/v1/<appid>?
参数:
{
"projectid": "0",
"sub_service_type": "0",
"speech_format": "mp3",
"volume": "3",
"person": "0",
"speed": "0",
"secretid": "AKIDlfdHxN0ntSVt4KPH0xXWnGl21UUFNoO5",
"timestamp": "1484109983",
"expired": "1484113583",
"nonce": "1675199141"
}
这里以<appid> = 20170111, <SecretKey>=oaYWFO70LGDmcpfwo8uF1IInayysGtgZ
为例拼接签名原文,则拼接的签名原文为:
https://aai.qcloud.com/tts/v1/20170111?expired=1484113583&nonce=1675199141&person=0&projectid=0&secretid=AKIDlfdHxN0ntSVt4KPH0xXWnGl21UUFNoO5&speech_format=mp3&speed=0&sub_service_type=0×tamp=1484109983&volume=3
记得 url 一定要排序,不然鉴权肯定会失败的
二、加密
接上一步的结果,我们要去掉协议名 https://
加上POST(必须是大写)
POSTaai.qcloud.com/tts/v1/20170111?expired=1484113583&nonce=1675199141&person=0&projectid=0&secretid=AKIDlfdHxN0ntSVt4KPH0xXWnGl21UUFNoO5&speech_format=mp3&speed=0&sub_service_type=0×tamp=1484109983&volume=3
对原文进行加密处理:
Base64Encode(HmacSha1(签名原文, SecretKey))
C#方法示例:
public static string HmacSha1AndBase64(string mk, string secretkey)
{
HMACSHA1 hmacsha1 = new HMACSHA1();
hmacsha1.Key = Encoding.UTF8.GetBytes(secretkey);
byte[] dataBuffer = Encoding.UTF8.GetBytes(mk);
byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer);
return Convert.ToBase64String(hashBytes);
}
最终得到签名串为:
HRCKlbwPhWtVvfGn914qE5O1rwc=
三:Body拼接(需要转换成语音的文本设置)
如果返回 111 错误,那么百分之百是这里拼接出错
multipart/form-data;
的格式就不介绍了,网上一大堆
-------------------123456abcdefg
Content-Disposition: form-data; name="text"; filename="file1.txt"
这里是你需要转换的内容
-------------------123456abcdefg--
这里的 -----------------123456abcdefg 是边界符,以 --自定义字符串
开头,以 --自定义字符串--
结尾。
name="text
" 和 filename="file1
.txt" 都是任意取名
Demo代码:
#region 语音合成
/// <summary>
/// 语音合成
/// </summary>
/// <param name="appid"></param>
/// <param name="secretid"></param>
/// <param name="secretkey"></param>
/// <param name="text"></param>
/// <param name="mp3AbsolutePath"></param>
/// <param name="mp3Filename"></param>
/// <returns></returns>
public string TextToSpeech(string appid, string secretid, string secretkey, string text, string mp3AbsolutePath = "", string mp3Filename = "")
{
//边界符
var boundary = "-------------------" + DateTime.Now.Ticks.ToString("x");
var url = GetFullUrl(appid, secretid);
//创建请求
var webRequest = (HttpWebRequest)WebRequest.Create(url);
//设置请求头
SetHeaders(webRequest, url, secretkey, boundary);
//将数据写入Body并获取写入后的数据
var strBody = GetRequestBody(boundary, text);
//将Body写入到请求流中
WriteRequestStreamFormBody(webRequest, strBody);
//获取结果
string responseResult = GetResponseResult(webRequest);
var resJson = JObject.Parse(responseResult);
if (resJson.Value<string>("code").Equals("0"))
{
//转换为mp3保存
Base64ToAudio(resJson.Value<string>("speech"), mp3AbsolutePath, mp3Filename);
}
else
{
//转换失败,具体查看错误码
}
return responseResult;
}
#endregion
#region 使用密钥(secretkey)对url进行加密,得到签名
/// <summary>
/// 使用密钥(secretkey)对url进行加密,得到签名
/// </summary>
/// <param name="urlSorted">排序后的url地址</param>
/// <param name="secretkey">密钥</param>
/// <returns></returns>
private string GetAuthorization(string urlSorted, string secretkey)
{
return HmacSha1AndBase64("POST" + urlSorted.Substring(8),secretkey);
}
#endregion
#region HMACSHA1 & Base64 加密
/// <summary>
/// HMACSHA1 & Base64 加密
/// </summary>
/// <param name="mk">原文</param>
/// <param name="secretkey">密钥</param>
/// <returns></returns>
public static string HmacSha1AndBase64(string mk, string secretkey)
{
HMACSHA1 hmacsha1 = new HMACSHA1();
hmacsha1.Key = Encoding.UTF8.GetBytes(secretkey);
byte[] dataBuffer = Encoding.UTF8.GetBytes(mk);
byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer);
return Convert.ToBase64String(hashBytes);
}
#endregion
#region 获取排序后的完整路径地址
/// <summary>
/// 获取排序后的完整路径地址
/// </summary>
/// <param name="appid"></param>
/// <param name="secretid"></param>
/// <returns></returns>
private string GetFullUrl(string appid, string secretid)
{
/*
* 返回值示例:
*
* https://aai.qcloud.com/tts/v1/20170111
* ?expired=1484113583
* &nonce=1675199141
* &person=0
* &projectid=0
* &secretid=AKIDlfdHxN0ntSVt4KPH0xXWnGl21UUFNoO5
* &speech_format=mp3
* &speed=0
* &sub_service_type=0
* ×tamp=1484109983
* &volume=3
*
*/
StringBuilder url = new StringBuilder("https://aai.qcloud.com/tts/v1/" + appid + "?");
SortedDictionary<string, object> dic = new SortedDictionary<string, object>();
dic.Add("projectid", 0); //腾讯云项目 ID,不填为默认项目,即0,总长度不超过1024字节
dic.Add("sub_service_type", 0); //子服务类型。0:短文本实时合成。目前只支持短文本实时合成
dic.Add("speech_format", "mp3"); //合成语音格式,目前支持MP3格式
dic.Add("volume", 3); //音量,默认为5,取值范围为0-10
dic.Add("person", 0); //发音人,目前仅支持0,女声
dic.Add("speed", 0); //语速,默认值为0,取值范围为-40到40,1表示加速到原来的1.1倍,-1为相对于正常语速放慢1.1倍
dic.Add("secretid", secretid); //官网云API密钥中获得的SecretId
dic.Add("timestamp", DateTime.Now.ToUnixDate()); //当前时间戳,是一个符合 UNIX Epoch 时间戳规范的数值,单位为秒
dic.Add("expired", DateTime.Now.AddMinutes(5).ToUnixDate()); //签名的有效期,是一个符合 UNIX Epoch 时间戳规范的数值,单位为秒;expired 必须大于 timestamp 且 expired - timestamp 小于90天
dic.Add("nonce", new Random().Next(10000000, 99999999)); //随机正整数。用户需自行生成,最长10位
foreach (var item in dic)
{
url.Append(item.Key);
url.Append("=");
url.Append(item.Value);
url.Append("&");
}
return url.Remove(url.Length - 1, 1).ToString(); //移除末尾的 &
}
#endregion
#region 设置Header
/// <summary>
/// 设置Header
/// </summary>
/// <param name="webRequest"></param>
/// <param name="url"></param>
/// <param name="secretkey"></param>
/// <param name="boundary"></param>
private void SetHeaders(HttpWebRequest webRequest, string url, string secretkey, string boundary)
{
//设置属性
webRequest.Host = "aai.qcloud.com";
webRequest.Method = "POST";
webRequest.Timeout = 30000;
webRequest.ContentType = "multipart/form-data; boundary=" + boundary;
webRequest.Headers.Add("Authorization", GetAuthorization(url, secretkey));
}
#endregion
#region 获取设置文字内容后的Body
/// <summary>
/// 获取设置文字内容后的Body
/// </summary>
/// <param name="boundary"></param>
/// <param name="text"></param>
/// <returns></returns>
private string GetRequestBody(string boundary, string text)
{
//设置需要转语音的文字内容
string strBody = GetTextBody(boundary, text);
//在这里可以添加其他的Body内容
//strBody += GetOtherBody();
//设置结尾
strBody += "--" + boundary + "--\r\n";
return strBody;
}
#endregion
#region 获取设置文字内容后的Body段
/// <summary>
/// 设置文字内容
/// </summary>
/// <param name="boundary"></param>
/// <param name="text"></param>
/// <returns></returns>
private string GetTextBody(string boundary, string text)
{
return string.Format("--" + boundary +
"\r\nContent-Disposition: form-data; name=\"text\"; filename=\"file1.txt\"" +
//"\r\nContent-Type: text/plain" +
"\r\n\r\n{0}\r\n", text);
}
#endregion
#region 将body流写入到请求流中
/// <summary>
/// 将body流写入到请求流中
/// </summary>
/// <param name="webRequest"></param>
/// <param name="memStream"></param>
private static void WriteRequestStreamFormBody(HttpWebRequest webRequest, string body)
{
var strBodyBytes = Encoding.UTF8.GetBytes(body);
using (var requestStream = webRequest.GetRequestStream())
{
requestStream.Write(strBodyBytes, 0, strBodyBytes.Length);
}
}
#endregion
#region 获取响应结果
/// <summary>
/// 获取响应内容
/// </summary>
/// <param name="webRequest"></param>
/// <returns></returns>
private static string GetResponseResult(HttpWebRequest webRequest)
{
string responseResult;
using (var httpWebResponse = (HttpWebResponse)webRequest.GetResponse())
{
using (var httpStreamReader = new StreamReader(httpWebResponse.GetResponseStream(), Encoding.GetEncoding("utf-8")))
{
responseResult = httpStreamReader.ReadToEnd();
}
}
webRequest.Abort();
return responseResult;
}
#endregion
#region base64转换mp3
/// <summary>
/// base64转换mp3
/// </summary>
/// <param name="strBase64"></param>
/// <param name="absolutePath"></param>
/// <param name="filename"></param>
public void Base64ToAudio(string strBase64, string absolutePath = "", string filename = "")
{
try
{
absolutePath = (absolutePath != null && !"".Equals(absolutePath.Trim())) ? absolutePath : Server.MapPath("~/audio/");
filename = (filename != null && !"".Equals(filename.Trim())) ? filename : DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".mp3";
byte[] audioByteArray = Convert.FromBase64String(strBase64);
using (var mp3File = System.IO.File.Create(absolutePath + filename, audioByteArray.Length))
{
mp3File.Write(audioByteArray, 0, audioByteArray.Length);
}
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
调用代码:
TextToSpeech(appid, secretid, secretkey, "你好,世界!");
代码有些地方封装的欠妥,懒得弄了,先这样写着了,以后看情况重构吧。
腾讯的文档我已经不想吐槽了,可能是刚好赶上了文档的修改时期吧,我刚去看,发现很多地方已经修改了。
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。