【在线语音】基于CI130x的音频播放——MP3文件播放改流式播放过程
整体架构
最一开始的,应该是对整体的架构
A. 要有一个流式音频生成源:比如各种API的流式音频服务
B. 要有一个数据结构,这里我选择环形缓存:流式音频生成源往里面放数据,然后串口从里面拿数据传给CI130x
C. 要有一个时刻判断的机制:什么时候开始放入环形缓存,什么时候拿数据给CI130x;什么时候通知CI130x音频完毕
当前的逻辑是:
- 当环形缓存中数据大小>一定阈值,比如4096字节时,就发送开始播放命令给CI130x
- 当CI130x发送请求时,给CI130x数据
- 当环形缓存中的数据为空白一定次数后,向CI130x发送停止播放命令
流式音频生成源
首先,我认为,应该具备流式生成TTS音频的能力,这里我用到了 ElevenLabs,下面是它流程生成TTS的完整示例代码:
import os
from typing import IO
from io import BytesIO
from dotenv import load_dotenv
from elevenlabs import VoiceSettings
from elevenlabs.client import ElevenLabs
load_dotenv()
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
elevenlabs = ElevenLabs(
api_key=ELEVENLABS_API_KEY,
)
def text_to_speech_stream(text: str) -> IO[bytes]:
# Perform the text-to-speech conversion
response = elevenlabs.text_to_speech.stream(
voice_id="pNInz6obpgDQGcFmaJgB", # Adam pre-made voice
output_format="mp3_22050_32",
text=text,
model_id="eleven_multilingual_v2",
# Optional voice settings that allow you to customize the output
voice_settings=VoiceSettings(
stability=0.0,
similarity_boost=1.0,
style=0.0,
use_speaker_boost=True,
speed=1.0,
),
)
# Create a BytesIO object to hold the audio data in memory
audio_stream = BytesIO()
# Write each chunk of audio data to the stream
for chunk in response:
if chunk:
audio_stream.write(chunk)
# Reset stream position to the beginning
audio_stream.seek(0)
# Return the stream for further use
return audio_stream
按照面向对象的方式,可以将其改写为下面格式,通过调用对象动作来
class ElevenLabsTTSClient:
"""
ElevenLabs TTS 客户端类,封装了 ElevenLabs 语音合成的相关功能(异步版本)
"""
def __init__(
self,
api_key: str = "xxx",
voice_id: str = "JBFqnCBsd6RMkjVDRZzb",
model_id: str = "eleven_v3",
#output_format: str = "mp3_44100_32",
output_format: str = "mp3_24000_48"
):
"""
初始化 ElevenLabs 语音合成客户端
Args:
api_key: ElevenLabs API Key
voice_id: 音色ID
model_id: 模型ID
output_format: 输出音频格式
"""
self.api_key = api_key
self.voice_id = voice_id
self.model_id = model_id
self.output_format = output_format
# 加载环境变量
load_dotenv()
# 初始化客户端
self.client = ElevenLabs(api_key=self.api_key)
async def text_to_speech_stream(self, text: str, output_file: str = None,
buffer_callback=None) -> bool:
"""
流式生成语音,支持实时处理音频数据块
Args:
text: 要转换的文本
output_file: 输出音频文件路径(可选)
buffer_callback: 回调函数,用于处理每个音频数据块
Returns:
bool: 转换是否成功
"""
start_time = time.time()
try:
# 使用流式 API
response = await asyncio.to_thread(
self.client.text_to_speech.stream,
voice_id=self.voice_id,
output_format=self.output_format,
text=text,
model_id=self.model_id,
)
chunk_count = 0
total_size = 0
audio_bytes = b""
print(f"[ElevenLabs] 开始流式接收音频数据...")
# 处理每个音频数据块
for chunk in response:
if chunk:
# 如果有回调函数,处理数据块
if buffer_callback:
await asyncio.to_thread(buffer_callback, chunk)
# 收集数据用于保存
audio_bytes += chunk
chunk_count += 1
total_size += len(chunk)
if chunk_count % 10 == 0: # 每10个块打印一次进度
print(f"[ElevenLabs] 已接收 {chunk_count} 个数据块,共 {total_size} 字节")
end_time = time.time()
total_duration_ms = (end_time - start_time) * 1000
except Exception as e:
end_time = time.time()
total_duration_ms = (end_time - start_time) * 1000
print(f"[ElevenLabs] 流式转换失败 (耗时: {total_duration_ms:.2f} ms): {e}")
return False
if __name__ == "__main__":
async def main():
client = ElevenLabsTTSClient()
# 方式2: 使用流式方法并自定义回调处理数据
def handle_chunk(chunk):
# 自定义处理逻辑,例如写入到环形缓冲区
print(f"处理数据块大小: {len(chunk)} 字节")
# tts_buffer.write_data(chunk) # 假设有缓冲区
await client.text_to_speech_stream(
text="使用回调函数处理流式数据。",
buffer_callback=handle_chunk
)
asyncio.run(main())

浙公网安备 33010602011771号