百度TTS语音
鸣谢:感谢每一位无私奉献的传道者,在此不一一具名!
1-学习目标
- 1)了解百度TTS语音
- 2)使用百度TTS语音
2-参考网址
- 个人尝试代码仓库:https://gitee.com/enzoism/chrome_tampermonkey
1-应用列表查看地址:https://console.bce.baidu.com/ai/#/ai/speech/app/list
2-AccessToken接口文档:https://ai.baidu.com/ai-doc/REFERENCE/Ck3dwjhhu
3-音频实时接口文档:https://cloud.baidu.com/doc/SPEECH/s/Klbxern8v
4-短语音识别API文档:https://cloud.baidu.com/doc/SPEECH/s/Jlbxdezuf
3-执行过程记录
1-TTS市场现状
2024-12-01 目前应该是ChatTTS的呼声最高,但是为了不引入新的学习成本,暂时不考虑使用ChatTTS,直接使用在线API可以满足我的需求!(百度的TTS有太多的AI味,在这个时间节点上,百度可能还是没有把重心放在这些细节上)
2-Tampermonkey
已经完成整体的功能,可以直接复制当前脚本到Tampermonkey,选中文字即可进行语音播报
// ==UserScript==
// @name Text to Speech using Baidu API
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Convert selected text to speech using Baidu Text-to-Speech API
// @author Your name
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
// 配置信息 - 需要替换成你的百度API密钥信息
const API_KEY = '44kNzmCeZNnLHoUcNiGYnyI7';
const SECRET_KEY = '7sNV1osooTqJliQB1aHB5lJKEKlLrg5f';
let access_token = '';
// 获取access_token的函数
async function getAccessToken() {
const url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${API_KEY}&client_secret=${SECRET_KEY}`;
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: url,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
onload: function(response) {
if (response.status === 200) {
try {
const data = JSON.parse(response.responseText);
if (data.access_token) {
access_token = data.access_token;
GM_setValue('access_token', access_token);
resolve(access_token);
} else {
console.error('获取access_token失败: 返回数据格式错误', data);
reject(new Error('获取access_token失败: 返回数据格式错误'));
}
} catch (error) {
console.error('解析access_token响应失败:', error);
reject(error);
}
} else {
console.error('获取access_token失败:', response.status, response.responseText);
reject(new Error(`获取access_token失败: ${response.status}`));
}
},
onerror: function(error) {
console.error('请求access_token失败:', error);
reject(error);
}
});
});
}
// 文字转语音的函数
async function textToSpeech(text, params = {}) {
if (!access_token) {
access_token = GM_getValue('access_token') || await getAccessToken();
}
if (!access_token) {
alert('无法获取access_token,请检查API配置');
return;
}
const url = `https://tsn.baidu.com/text2audio`;
const requestParams = new URLSearchParams({
tex: encodeURIComponent(text),
tok: access_token,
cuid: 'tampermonkey_tts',
ctp: 1,
lan: 'zh',
spd: params.spd || 5,
pit: params.pit || 5,
vol: params.vol || 5,
per: params.per || 0,
aue: 3
});
try {
GM_xmlhttpRequest({
method: 'GET',
url: `${url}?${requestParams.toString()}`,
responseType: 'blob',
onload: function(response) {
if (response.status === 200) {
const blob = response.response;
const audio = new Audio(URL.createObjectURL(blob));
audio.play().catch(error => {
console.error('播放音频失败:', error);
alert('播放音频失败,请重试');
});
} else {
console.error('语音合成请求失败:', response.status);
alert('语音合成失败,请重试');
}
},
onerror: function(error) {
console.error('请求失败:', error);
alert('请求失败,请重试');
}
});
} catch (error) {
console.error('语音合成出错:', error);
alert('语音合成出错,请重试');
}
}
// 创建控制面板
function createControlPanel() {
const panel = document.createElement('div');
panel.style.position = 'fixed';
panel.style.bottom = '80px';
panel.style.right = '20px';
panel.style.zIndex = '9999';
panel.style.backgroundColor = 'white';
panel.style.padding = '10px';
panel.style.border = '1px solid #ccc';
panel.style.borderRadius = '5px';
panel.style.display = 'none';
// 语速控制
const speedControl = document.createElement('div');
speedControl.innerHTML = `
<label>语速(0-15):
<input type="range" min="0" max="15" value="5" id="tts-speed">
<span id="speed-value">5</span>
</label>
`;
// 音量控制
const volumeControl = document.createElement('div');
volumeControl.innerHTML = `
<label>音量(0-15):
<input type="range" min="0" max="15" value="5" id="tts-volume">
<span id="volume-value">5</span>
</label>
`;
// 音调控制
const pitchControl = document.createElement('div');
pitchControl.innerHTML = `
<label>音调(0-15):
<input type="range" min="0" max="15" value="5" id="tts-pitch">
<span id="pitch-value">5</span>
</label>
`;
// 发音人选择
const personControl = document.createElement('div');
personControl.innerHTML = `
<label>发音人:
<select id="tts-person">
<option value="0">女声</option>
<option value="1">男声</option>
<option value="3">度逍遥</option>
<option value="4">度丫丫</option>
</select>
</label>
`;
panel.appendChild(speedControl);
panel.appendChild(volumeControl);
panel.appendChild(pitchControl);
panel.appendChild(personControl);
// 添加事件监听
['speed', 'volume', 'pitch'].forEach(param => {
const input = panel.querySelector(`#tts-${param}`);
const value = panel.querySelector(`#${param}-value`);
input.addEventListener('input', () => {
value.textContent = input.value;
GM_setValue(`tts-${param}`, input.value);
});
});
panel.querySelector('#tts-person').addEventListener('change', (e) => {
GM_setValue('tts-person', e.target.value);
});
document.body.appendChild(panel);
return panel;
}
// 创建悬浮按钮
function createFloatingButton() {
const button = document.createElement('button');
button.innerHTML = '朗读选中文本';
button.style.position = 'fixed';
button.style.bottom = '20px';
button.style.right = '20px';
button.style.zIndex = '9999';
button.style.padding = '10px';
button.style.backgroundColor = '#4CAF50';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
const settingsButton = document.createElement('button');
settingsButton.innerHTML = '⚙️';
settingsButton.style.position = 'fixed';
settingsButton.style.bottom = '20px';
settingsButton.style.right = '140px';
settingsButton.style.zIndex = '9999';
settingsButton.style.padding = '10px';
settingsButton.style.backgroundColor = '#2196F3';
settingsButton.style.color = 'white';
settingsButton.style.border = 'none';
settingsButton.style.borderRadius = '5px';
settingsButton.style.cursor = 'pointer';
const panel = createControlPanel();
settingsButton.addEventListener('click', function() {
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
});
button.addEventListener('click', function() {
const selectedText = window.getSelection().toString().trim();
if (selectedText) {
const params = {
spd: parseInt(GM_getValue('tts-speed', 9)),
vol: parseInt(GM_getValue('tts-volume', 5)),
pit: parseInt(GM_getValue('tts-pitch', 5)),
per: parseInt(GM_getValue('tts-person', 0))
};
textToSpeech(selectedText, params);
} else {
alert('请先选择要朗读的文本');
}
});
document.body.appendChild(button);
document.body.appendChild(settingsButton);
}
// 初始化
createFloatingButton();
})();
3-Python版本
Python3.8.5版本可运行
import requests
import json
import base64
from urllib.parse import quote
import os
from playsound import playsound
class BaiduTTS:
def __init__(self):
# API配置信息
self.API_KEY = '44kNzmCeZNnLHoUcNiGYnyI7'
self.SECRET_KEY = '7sNV1osooTqJliQB1aHB5lJKEKlLrg5f'
self.access_token = None
def get_access_token(self):
"""获取access_token"""
url = f"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={self.API_KEY}&client_secret={self.SECRET_KEY}"
response = requests.post(url)
if response.status_code == 200:
result = response.json()
self.access_token = result['access_token']
return self.access_token
else:
print(f"获取access_token失败: {response.status_code}")
return None
def text_to_speech(self, text, params=None):
"""文字转语音"""
if not self.access_token:
self.access_token = self.get_access_token()
if not self.access_token:
print("无法获取access_token")
return False
# 默认参数
default_params = {
'spd': 9, # 语速,取值0-15
'pit': 5, # 音调,取值0-15
'vol': 5, # 音量,取值0-15
'per': 0, # 发音人,0为女声,1为男声,3为度逍遥,4为度丫丫
}
# 更新参数
if params:
default_params.update(params)
url = "https://tsn.baidu.com/text2audio"
params = {
'tex': quote(text),
'tok': self.access_token,
'cuid': 'python_tts',
'ctp': 1,
'lan': 'zh',
'aue': 3, # mp3格式
**default_params
}
try:
response = requests.get(url, params=params)
# 检查是否返回音频数据
if response.headers['Content-Type'].startswith('audio/'):
# 保存音频文件
audio_file = "output.mp3"
with open(audio_file, 'wb') as f:
f.write(response.content)
print(f"已保存音频文件: {audio_file}")
# 播放音频
playsound(audio_file)
return True
else:
error_msg = response.json()
print(f"转换失败: {error_msg}")
return False
except Exception as e:
print(f"请求失败: {str(e)}")
return False
def main():
# 使用示例
tts = BaiduTTS()
# 要转换的文本
text = input("请输入要转换的文字: ")
# 可选:自定义参数
params = {
'spd': 9, # 语速
'pit': 5, # 音调
'vol': 5, # 音量
'per': 0 # 发音人
}
# 执行转换
tts.text_to_speech(text, params)
if __name__ == "__main__":
main()
4-其他知识补充
1-TTS发展历史
TTS(Text-to-Speech,文本转语音)技术的发展历史可以追溯到20世纪50年代,经历了多个阶段的技术进步和创新。以下是TTS技术发展的主要里程碑:
1. 早期研究(20世纪50年代至70年代)
- 早期尝试:最早的TTS系统开始于20世纪50年代。研究人员尝试通过机械设备和简单的声音合成器来生成语音。
- 基础研究:在20世纪60年代和70年代,主要集中在语音合成的基本原理和方法上,如共振峰合成(Formant Synthesis),通过模拟人类声道的共振特性来生成语音。
2. 数字信号处理时代(20世纪80年代至90年代)
- 数字信号处理技术:随着数字信号处理(DSP)技术的发展,TTS系统开始使用计算机来处理和生成语音。这种方法提高了语音合成的质量和效率。
- 规则合成器:出现了基于规则的合成器,这些系统通过一系列规则将文本转换为语音。例如,线性预测编码(LPC)技术被广泛应用于语音合成。
- 有限状态机:有限状态机(FSM)被用于文本分析和语音合成,这种方法简化了复杂文本的处理。
3. 统计方法和数据驱动技术(21世纪初)
- 统计语音合成:2000年后,统计方法开始主导TTS技术的研究。这些方法依赖于大量的语音数据和文本数据,通过机器学习算法来生成语音。梅尔频率倒谱系数(MFCC)和隐马尔可夫模型(HMM)等技术被广泛应用。
- 语音库和TTS系统:出现了基于语音库的TTS系统,这些系统使用预先录制的语音片段来生成自然语音。例如,单元选择合成(Unit Selection Synthesis)通过选择和拼接预先录制的语音单元来生成语音。
4. 深度学习和神经网络时代(2010年至今)
- 神经网络TTS:随着深度学习技术的发展,神经网络在TTS中的应用迅速增长。递归神经网络(RNN)、长短期记忆网络(LSTM)和变分自编码器(VAE)等技术被用于生成更加自然和流畅的语音。
- 端到端模型:出现了端到端TTS模型,这些模型直接从文本生成语音,简化了处理流程。例如,WaveNet、Tacotron和Transformer等模型显著提高了语音合成的质量。
- 多语言和个性化TTS:现代TTS系统支持多语言和个性化语音合成。用户可以选择不同的语音风格和角色,甚至可以定制自己的语音模型。
5. 当前趋势和未来展望
- 低资源语言支持:为了让更多人受益于TTS技术,研究人员正在开发低资源语言的TTS系统,这些系统可以在数据有限的情况下仍然生成高质量的语音。
- 实时TTS:随着硬件和算法的进步,实时TTS系统的性能不断提升,使得语音合成的延迟大大降低。
- 多模态TTS:结合其他感知模态(如视觉和触觉)的多模态TTS系统正在研究中,以提供更加丰富的用户体验。
总的来说,TTS技术从早期的简单合成发展到了现在的深度学习驱动的自然语音生成,不断推动着人机交互和无障碍通信的发展。
2-TTS目前的发展
1-深度学习和神经网络时代(2010年至今)
- 神经网络TTS
- 技术应用:RNN, LSTM, VAE
- 目标:自然流畅的语音
- 端到端模型
- 模型:WaveNet, Tacotron, Transformer
- 优势:简化流程,提高质量
- 多语言和个性化TTS
- 支持:多语言,个性化语音
- 功能:选择语音风格和角色,定制语音模型
2-当前趋势和未来展望
- 低资源语言支持
- 目标:数据有限下生成高质量语音
- 实时TTS
- 进展:硬件和算法进步
- 结果:降低延迟
- 多模态TTS
- 方向:结合视觉和触觉
- 目标:丰富用户体验