一、说明
1.为什么叫基础语音合成
之所以叫做基础语音合成,是因为除了基础语音合成,还包括实时语音合成、流式语音合成、长文本语音合成,这里先说基础语音合成。
2.注意事项
腾讯云的基础语音合成,最大支持150个汉字的语音合成,超过字数限制会合成失败并报错,如果的你的文字超过150字,可以考虑长文本语音合成(时间长,文档说30分钟内给结果),或者流式文本语音合成,或者换个平台使用(作者换的火山引擎大模型,最多支持300个汉字,满足作者的需求了)。
二、 创建秘钥
地址:访问密钥 - 控制台https://console.cloud.tencent.com/cam/capi
创建完后,要把SecretKey和SecretId的信息保存下来,因为上图提示:关闭了SecretKey的查看功能,只能查看到AppId和SecretId,如下:
三、代码如下
API文档地址如下:
语音合成 基础语音合成_腾讯云https://cloud.tencent.com/document/product/1073/37995
/// <summary>
/// 语音合成类-腾讯基础语音合成
/// </summary>
public class TencentCloudTTSManager : TTSManager
{
#region 腾讯云TTS参数
string _secretId ;//填你自己的SecretId
string _secretKey;//填你自己的SecretKey
const string HOST = "tts.tencentcloudapi.com";
const string URL = "https://"+ HOST;
const string ACTION = "TextToVoice";//调取接口名
const string VERSION = "2019-08-23";//操作的 API 的版本
#endregion
#region 声音参数
float _volume = 5;//音量大小,范围[-10,10]
float _speed= 0f;//语速,范围[-2,6]
int _voiceType = 101001;//音色ID,音色列表:"https://cloud.tencent.com/document/product/1073/92668"
#endregion
string _sessionId;
Task _task;
UnityWebRequest _request;
UploadHandlerRaw _uploadHandler;
DownloadHandlerBuffer _downHandler;
public override void Init()
public override void RequestTTS(string text, Action<string> callback=null)
{
_task = new Task(RequestTTS(text));
}
IEnumerator RequestTTS(string text)
{
string _sessionId =((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds).ToString();
string body= GetBody(text, _sessionId);
string auth= GetAuth(_secretId, _secretKey, HOST, "application/json; charset=utf-8", _sessionId, body);
using (_request = UnityWebRequest.Post(URL, body))
{
_request.SetRequestHeader("X-TC-Timestamp", _sessionId);
_request.SetRequestHeader("X-TC-Version", VERSION);
_request.SetRequestHeader("X-TC-Action", ACTION);
_request.SetRequestHeader("Authorization", auth);
_request.SetRequestHeader("Content-Type", "application/json; charset=utf-8");
byte[] bodyRaw = Encoding.UTF8.GetBytes(body);
using (_uploadHandler = new UploadHandlerRaw(bodyRaw))
{
_request.uploadHandler.Dispose();
_request.uploadHandler = _uploadHandler;
using (_downHandler = new DownloadHandlerBuffer())
{
_request.downloadHandler.Dispose();
_request.downloadHandler = _downHandler;
yield return _request.SendWebRequest();
if (_request.isDone && _request.error == null)
{
JObject jobj = JObject.Parse(_request.downloadHandler.text);
string audioBase64 = (string)jobj["Response"]["Audio"];
if (audioBase64 != null)//合成成功
{
AudioClip audioClip = SwitchToAudioClip(audioBase64);
AudioSource.PlayClipAtPoint(audioClip, Vector3.zero);
if (audioClip != null)
{
}
else
{
}
}
else//合成失败
{
JToken error = jobj["Response"]["Error"];
if (error != null)
{
Debug.LogError("调用API错误:" + error.ToString());
}
}
}
else
{
Debug.LogError("请求失败:" + _request.error);
}
_uploadHandler.Dispose();
_downHandler.Dispose();
_request.disposeDownloadHandlerOnDispose = true;
_request.disposeUploadHandlerOnDispose = true;
_request.Dispose();
}
}
}
}
#region 将请求结果转换为AuidoClip
AudioClip SwitchToAudioClip(string text)
{
byte[] audioBytes = Convert.FromBase64String(text);
int channels = BitConverter.ToInt16(audioBytes, 22);
int sampleRate = BitConverter.ToInt32(audioBytes, 24);
int dataOffset = 44;
float[] samples = new float[(audioBytes.Length - dataOffset) / 2];
for (int i = 0; i < samples.Length; i++)
{
short int16Sample = (short)(audioBytes[dataOffset + i * 2] | (audioBytes[dataOffset + i * 2 + 1] << 8));
samples[i] = int16Sample / 32768.0f;
}
AudioClip audioClip = AudioClip.Create("DecodedAudio", samples.Length, channels, sampleRate, false);
audioClip.SetData(samples, 0);
return audioClip;
}
#endregion
string GetBody(string text,string _sessionId)
{
return $"{{\"Text\":\"{text}\",\"SessionId\":\"{_sessionId}\",\"Volume\":{_volume},\"Speed\":{_speed},\"VoiceType\":{_voiceType},\"SampleRate\":{16000}}}";
}
#region 生成签名
/// <summary>
/// 生成签名
/// </summary>
/// <param name="secretId"></param>
/// <param name="secretKey"></param>
/// <param name="host"></param>
/// <param name="contentType"></param>
/// <param name="timestamp"></param>
/// <param name="body"></param>
/// <returns></returns>
string GetAuth(string secretId, string secretKey, string host, string contentType,string timestamp, string body)
{
var canonicalURI = "/";
var canonicalHeaders = "content-type:" + contentType + "\nhost:" + host + "\n";
var signedHeaders = "content-type;host";
var hashedRequestPayload = Sha256Hex(body);
var canonicalRequest = "POST" + "\n"
+ canonicalURI + "\n"
+ "\n"
+ canonicalHeaders + "\n"
+ signedHeaders + "\n"
+ hashedRequestPayload;
var algorithm = "TC3-HMAC-SHA256";
var date = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(int.Parse(timestamp))
.ToString("yyyy-MM-dd");
var service = host.Split('.')[0];
var credentialScope = date + "/" + service + "/" + "tc3_request";
var hashedCanonicalRequest = Sha256Hex(canonicalRequest);
var stringToSign = algorithm + "\n"
+ timestamp + "\n"
+ credentialScope + "\n"
+ hashedCanonicalRequest;
var tc3SecretKey = Encoding.UTF8.GetBytes("TC3" + secretKey);
var secretDate = HmacSha256(tc3SecretKey, Encoding.UTF8.GetBytes(date));
var secretService = HmacSha256(secretDate, Encoding.UTF8.GetBytes(service));
var secretSigning = HmacSha256(secretService, Encoding.UTF8.GetBytes("tc3_request"));
var signatureBytes = HmacSha256(secretSigning, Encoding.UTF8.GetBytes(stringToSign));
var signature = BitConverter.ToString(signatureBytes).Replace("-", "").ToLower();
return algorithm + " "
+ "Credential=" + secretId + "/" + credentialScope + ", "
+ "SignedHeaders=" + signedHeaders + ", "
+ "Signature=" + signature;
}
public static string Sha256Hex(string s)
{
using (SHA256 algo = SHA256.Create())
{
byte[] hashbytes = algo.ComputeHash(Encoding.UTF8.GetBytes(s));
StringBuilder builder = new StringBuilder();
for (int i = 0; i < hashbytes.Length; ++i)
{
builder.Append(hashbytes[i].ToString("x2"));
}
return builder.ToString();
}
}
private static byte[] HmacSha256(byte[] key, byte[] msg)
{
using (HMACSHA256 mac = new HMACSHA256(key))
{
return mac.ComputeHash(msg);
}
}
#endregion
public override void Reset()
{
if (_task != null)
{
_task.Stop();
}
if (_uploadHandler != null)
{
_uploadHandler.Dispose();
}
if (_downHandler != null)
{
_downHandler.Dispose();
}
if (_request != null)
{
_request.disposeDownloadHandlerOnDispose = true;
_request.disposeUploadHandlerOnDispose = true;
_request.Dispose();
}
}
}