一、腾讯云:2.语音合成-1:基础语音合成(TTS-文字转语音)

一、说明

 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();
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值