【在线语音】基于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())

 

posted @ 2026-06-29 23:54  FBshark  阅读(1)  评论(0)    收藏  举报