AI实时语音交互应用开发完全教程

教程简介

全面讲解AI实时语音交互应用开发的核心技术,涵盖语音AI技术架构、GPT-4o Realtime API/Gemini Live/Qwen2.5-Omni等主流语音模型对比、语音识别、语音合成、实时流式处理、语音Agent架构设计、端到端Speech-to-Speech实现、低延迟优化、语音RAG系统等核心内容。

AI实时语音交互应用开发完全教程

从语音识别到端到端Speech-to-Speech,全面掌握AI语音交互核心技术


教程简介

随着GPT-4o Realtime API、Gemini Live、Qwen2.5-Omni等多模态大模型的发布,AI实时语音交互正从实验室走向大规模商用。本教程将系统性地讲解AI语音交互的完整技术栈,从传统的级联式架构(VAD→ASR→LLM→TTS)到前沿的端到端Speech-to-Speech方案,帮助开发者构建低延迟、高自然度的语音AI系统。

你将学到:

  • 语音AI技术架构的演进与选型(级联式 vs 端到端)
  • 主流语音大模型的能力对比与实战接入
  • 语音识别(ASR)与语音合成(TTS)核心技术
  • 实时流式语音处理(WebSocket/WebRTC)
  • 语音Agent架构设计与工程实现
  • 低延迟优化、语音RAG、场景落地等高级话题

第一章:语音AI技术架构概述

1.1 级联式架构(Cascaded Pipeline)

传统的语音交互系统采用级联式架构,将语音处理分为多个独立模块串联执行:

用户语音 → VAD → ASR → LLM → TTS → 播放语音

核心模块:

  • VAD(Voice Activity Detection):检测用户是否在说话,决定何时开始/停止录音
  • ASR(Automatic Speech Recognition):将语音转为文本
  • LLM(Large Language Model):理解文本含义并生成回复
  • TTS(Text-to-Speech):将文本回复转为语音输出

优势: 各模块独立优化,技术成熟度高,可替换性强 劣势: 端到端延迟较高(通常1-3秒),信息在文本转换中丢失韵律、情感等副语言信息

1.2 端到端架构(End-to-End Speech-to-Speech)

新一代端到端架构直接在语音模态上进行理解和生成:

用户语音 → Speech-to-Speech Model → AI语音回复

代表方案: GPT-4o Realtime API、Gemini Live API、Qwen2.5-Omni

优势: 延迟极低(<500ms),保留语音中的情感和韵律信息,支持更自然的对话 劣势: 技术较新,可控性相对较弱,成本较高

1.3 混合架构实践

在实际工程中,常采用混合架构平衡延迟与可控性:

# 混合架构示意
class HybridVoiceAgent:
    def __init__(self):
        self.vad = SileroVAD()
        self.asr = WhisperASR()
        self.llm = GPT4oRealtime()  # 或传统LLM
        self.tts = CosyVoiceTTS()
        self.mode = "cascade"  # cascade / e2e / hybrid
    
    async def process(self, audio_stream):
        if self.mode == "e2e":
            # 端到端模式:直接处理语音流
            return await self.llm.stream_audio(audio_stream)
        elif self.mode == "cascade":
            # 级联模式:分步处理
            text = await self.asr.transcribe(audio_stream)
            response = await self.llm.generate(text)
            audio = await self.tts.synthesize(response)
            return audio
        else:
            # 混合模式:简单问题用端到端,复杂问题用级联
            if self.is_simple_query(audio_stream):
                return await self.llm.stream_audio(audio_stream)
            else:
                text = await self.asr.transcribe(audio_stream)
                response = await self.llm.generate(text)
                return await self.tts.synthesize(response)

第二章:主流语音大模型对比与实战

2.1 GPT-4o Realtime API

OpenAI的GPT-4o Realtime API是目前最成熟的端到端语音交互方案,支持双向音频流。

核心特性:

  • 原生语音理解与生成,无需ASR/TTS
  • 支持WebSocket实时双向通信
  • 支持function calling(工具调用)
  • 支持多种语音音色(alloy, echo, shimmer等)
  • 典型延迟300-500ms

接入示例:

import asyncio
import websockets
import json
import base64

class GPT4oRealtimeClient:
    def __init__(self, api_key):
        self.api_key = api_key
        self.ws_url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview"
        self.ws = None
    
    async def connect(self):
        self.ws = await websockets.connect(
            self.ws_url,
            extra_headers={
                "Authorization": f"Bearer {self.api_key}",
                "OpenAI-Beta": "realtime=v1"
            }
        )
        # 配置session
        await self.ws.send(json.dumps({
            "type": "session.update",
            "session": {
                "modalities": ["text", "audio"],
                "instructions": "你是一个友好的AI助手,用中文回答问题。",
                "voice": "alloy",
                "input_audio_format": "pcm16",
                "output_audio_format": "pcm16",
                "turn_detection": {
                    "type": "server_vad",
                    "threshold": 0.5,
                    "prefix_padding_ms": 300,
                    "silence_duration_ms": 500
                }
            }
        }))
    
    async def send_audio(self, audio_bytes: bytes):
        """发送音频数据(PCM16格式)"""
        audio_b64 = base64.b64encode(audio_bytes).decode()
        await self.ws.send(json.dumps({
            "type": "input_audio_buffer.append",
            "audio": audio_b64
        }))
    
    async def listen(self):
        """监听服务端响应"""
        async for message in self.ws:
            event = json.loads(message)
            
            if event["type"] == "response.audio.delta":
                # 收到音频片段,实时播放
                audio_chunk = base64.b64decode(event["delta"])
                yield audio_chunk
            
            elif event["type"] == "response.text.delta":
                # 收到文本片段(用于显示字幕)
                print(event["delta"], end="", flush=True)
            
            elif event["type"] == "response.done":
                print("\n[回复完成]")
            
            elif event["type"] == "error":
                print(f"[错误] {event['error']['message']}")

# 使用示例
async def main():
    client = GPT4oRealtimeClient("your-api-key")
    await client.connect()
    
    # 发送音频
    with open("user_input.pcm", "rb") as f:
        while chunk := f.read(4096):
            await client.send_audio(chunk)
    
    # 接收回复
    async for audio_chunk in client.listen():
        # 将audio_chunk写入播放缓冲区
        pass

asyncio.run(main())

2.2 Gemini Live API

Google的Gemini Live API提供了类似的实时语音交互能力,特点是支持视频输入和更长的上下文。

import asyncio
from google import genai

class GeminiLiveClient:
    def __init__(self, api_key):
        self.client = genai.Client(api_key=api_key)
    
    async def start_session(self):
        config = {
            "response_modalities": ["AUDIO"],
            "system_instruction": "你是一个知识渊博的AI助手。",
            "speech_config": {
                "voice_config": {
                    "prebuilt_voice_config": {
                        "voice_name": "Aoede"
                    }
                }
            }
        }
        
        async with self.client.aio.live.connect(
            model="gemini-2.0-flash-live-001",
            config=config
        ) as session:
            # 发送音频
            await session.send(input={
                "audio": audio_bytes  # 16kHz PCM16
            }, end_of_turn=True)
            
            # 接收回复
            async for response in session.receive():
                if response.audio:
                    yield response.audio.data

2.3 Qwen2.5-Omni

阿里巴巴的Qwen2.5-Omni是开源的端到端多模态模型,支持本地部署。

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import soundfile as sf

class QwenOmniClient:
    def __init__(self, model_path="Qwen/Qwen2.5-Omni-7B"):
        self.model = AutoModelForCausalLM.from_pretrained(
            model_path,
            torch_dtype=torch.bfloat16,
            device_map="auto",
            trust_remote_code=True
        )
        self.tokenizer = AutoTokenizer.from_pretrained(
            model_path, trust_remote_code=True
        )
    
    def chat_with_audio(self, audio_path: str, text_prompt: str = ""):
        """输入音频+文本,获取文本+语音回复"""
        messages = [
            {
                "role": "system",
                "content": "你是一个有帮助的AI助手。"
            },
            {
                "role": "user",
                "content": [
                    {"type": "audio", "audio_url": audio_path},
                    {"type": "text", "text": text_prompt or "请回答音频中的问题。"}
                ]
            }
        ]
        
        text, audio = self.model.chat(
            messages=messages,
            tokenizer=self.tokenizer
        )
        
        # 保存回复音频
        sf.write("response.wav", audio, samplerate=24000)
        return text, audio

2.4 模型选型对比

特性 GPT-4o Realtime Gemini Live Qwen2.5-Omni
部署方式 云端API 云端API 本地/云端
延迟 300-500ms 400-600ms 500-1000ms
多语言 优秀 优秀 良好
Function Calling 支持 支持 有限
视频输入 不支持 支持 支持
成本 较高 中等 可自控
中文能力 优秀 良好 优秀
开源

第三章:语音识别(ASR)核心技术

3.1 Whisper — OpenAI开源语音识别

Whisper是OpenAI开源的通用语音识别模型,支持99种语言,是目前最流行的开源ASR方案。

import whisper
import numpy as np

class WhisperASR:
    def __init__(self, model_size="medium"):
        """
        model_size: tiny, base, small, medium, large, turbo
        中文推荐medium或large-v3
        """
        self.model = whisper.load_model(model_size)
    
    def transcribe(self, audio_path: str, language: str = "zh") -> dict:
        """完整转录"""
        result = self.model.transcribe(
            audio_path,
            language=language,
            task="transcribe",
            fp16=False  # CPU环境设为False
        )
        return {
            "text": result["text"],
            "segments": result["segments"],
            "language": result["language"]
        }
    
    def transcribe_streaming(self, audio_chunks, language="zh"):
        """流式转录(简化实现)"""
        buffer = np.array([], dtype=np.float32)
        
        for chunk in audio_chunks:
            buffer = np.append(buffer, chunk)
            
            # 每积累2秒音频处理一次
            if len(buffer) >= 16000 * 2:
                result = self.model.transcribe(
                    buffer,
                    language=language,
                    fp16=False
                )
                yield result["text"]
                buffer = np.array([], dtype=np.float32)
        
        # 处理剩余音频
        if len(buffer) > 0:
            result = self.model.transcribe(buffer, language=language, fp16=False)
            yield result["text"]

# 使用示例
asr = WhisperASR(model_size="medium")
result = asr.transcribe("meeting_recording.wav")
print(f"识别结果: {result['text']}")

3.2 Paraformer — 阿里达摩院高效ASR

Paraformer是阿里达摩院提出的非自回归语音识别模型,推理速度比Whisper快10倍以上。

# 使用FunASR框架(集成了Paraformer)
from funasr import AutoModel

class ParaformerASR:
    def __init__(self):
        # 加载Paraformer-large模型,支持标点恢复和时间戳
        self.model = AutoModel(
            model="paraformer-zh",
            vad_model="fsmn-vad",       # 语音活动检测
            punc_model="ct-punc",       # 标点恢复
            spk_model="cam++"           # 说话人识别
        )
    
    def transcribe(self, audio_path: str) -> dict:
        result = self.model.generate(input=audio_path)
        return {
            "text": result[0]["text"],
            "timestamp": result[0].get("timestamp", []),
            "sentences": result[0].get("sentence_info", [])
        }
    
    def transcribe_with_speaker(self, audio_path: str) -> list:
        """带说话人分离的转录"""
        result = self.model.generate(
            input=audio_path,
            batch_size_s=300  # 批处理大小(秒)
        )
        
        sentences = result[0].get("sentence_info", [])
        return [
            {
                "speaker": f"Speaker_{s.get('spk', 0)}",
                "text": s["text"],
                "start": s["start"],
                "end": s["end"]
            }
            for s in sentences
        ]

# 使用示例
asr = ParaformerASR()
result = asr.transcribe("meeting.wav")
print(f"识别结果: {result['text']}")

# 说话人分离
segments = asr.transcribe_with_speaker("meeting.wav")
for seg in segments:
    print(f"[{seg['speaker']}] ({seg['start']}-{seg['end']}ms): {seg['text']}")

3.3 流式ASR实现

在实时语音交互中,流式ASR至关重要。以下是基于WebSocket的流式识别方案:

import asyncio
import websockets
import numpy as np
from collections import deque

class StreamingASR:
    """流式语音识别服务"""
    
    def __init__(self, model_size="base"):
        import whisper
        self.model = whisper.load_model(model_size)
        self.sample_rate = 16000
        self.chunk_duration = 1.0  # 每秒处理一次
        self.context_duration = 3.0  # 上下文窗口3秒
        self.buffer = deque(maxlen=int(self.context_duration * self.sample_rate))
    
    def process_chunk(self, audio_chunk: np.ndarray) -> str:
        """处理一个音频块"""
        self.buffer.extend(audio_chunk)
        
        # 使用VAD判断是否有语音
        if self._has_speech(list(self.buffer)):
            audio_array = np.array(list(self.buffer), dtype=np.float32)
            # Whisper需要30秒输入,不足部分补零
            if len(audio_array) < self.sample_rate * 30:
                padding = np.zeros(self.sample_rate * 30 - len(audio_array))
                audio_array = np.concatenate([audio_array, padding])
            
            result = self.model.transcribe(
                audio_array,
                language="zh",
                fp16=False,
                no_speech_threshold=0.6
            )
            
            text = result["text"].strip()
            if text and result.get("segments"):
                # 只返回最后一个segment(最新内容)
                return result["segments"][-1]["text"].strip()
        
        return ""
    
    def _has_speech(self, audio_list):
        """简单的能量检测VAD"""
        if len(audio_list) < 1600:
            return False
        audio = np.array(audio_list[-1600:])  # 最近100ms
        energy = np.mean(audio ** 2)
        return energy > 0.001

# WebSocket流式ASR服务器
async def asr_websocket_handler(websocket):
    asr = StreamingASR(model_size="base")
    
    async for message in websocket:
        if isinstance(message, bytes):
            # 接收PCM16音频数据
            audio = np.frombuffer(message, dtype=np.int16).astype(np.float32) / 32768.0
            text = asr.process_chunk(audio)
            if text:
                await websocket.send(json.dumps({
                    "type": "transcription",
                    "text": text,
                    "is_final": False
                }))

第四章:语音合成(TTS)核心技术

4.1 VITS — 端到端语音合成

VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)是端到端的TTS模型,质量高且速度快。

import torch
import soundfile as sf
from vits import SynthesizerTrn, get_hparams_from_file

class VITSTTS:
    def __init__(self, model_path, config_path):
        hps = get_hparams_from_file(config_path)
        self.model = SynthesizerTrn(
            len(hps.symbols),
            hps.data.filter_length // 2 + 1,
            hps.train.segment_size // hps.data.hop_length,
            **hps.model
        )
        self.model.load_state_dict(torch.load(model_path)["model"])
        self.model.eval()
        self.hps = hps
    
    def synthesize(self, text: str, speaker_id: int = 0) -> np.ndarray:
        """文本转语音"""
        from vits import text_to_sequence, symbols
        
        # 文本转音素序列
        sequence = text_to_sequence(text, [self.hps.data.text_cleaners])
        sequence = torch.LongTensor(sequence).unsqueeze(0)
        
        with torch.no_grad():
            audio = self.model.infer(
                sequence,
                torch.LongTensor([speaker_id]),
                noise_scale=0.667,
                noise_scale_w=0.8,
                length_scale=1.0
            )[0][0].numpy()
        
        return audio
    
    def save(self, audio: np.ndarray, path: str, sample_rate: int = 22050):
        sf.write(path, audio, sample_rate)

# 使用示例
tts = VITSTTS("model.pth", "config.json")
audio = tts.synthesize("你好,欢迎使用AI语音助手!")
tts.save(audio, "output.wav")

4.2 XTTS — 多语言声音克隆TTS

Coqui XTTS支持17种语言和零样本声音克隆,只需几秒参考音频即可克隆任意声音。

from TTS.api import TTS
import torch

class XTTSCloneTTS:
    def __init__(self):
        self.tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to("cuda")
    
    def clone_and_synthesize(
        self,
        text: str,
        speaker_wav: str,
        language: str = "zh",
        output_path: str = "output.wav"
    ):
        """
        使用参考音频克隆声音并合成
        
        Args:
            text: 要合成的文本
            speaker_wav: 参考说话人音频文件路径(10-30秒最佳)
            language: 语言代码 (zh, en, ja, ko, fr, de, etc.)
            output_path: 输出音频路径
        """
        self.tts.tts_to_file(
            text=text,
            speaker_wav=speaker_wav,
            language=language,
            file_path=output_path
        )
        print(f"合成完成: {output_path}")
    
    def stream_synthesize(self, text: str, speaker_wav: str, language: str = "zh"):
        """流式合成(返回音频chunk)"""
        for chunk in self.tts.tts_stream(
            text=text,
            speaker_wav=speaker_wav,
            language=language
        ):
            yield chunk

# 使用示例
tts = XTTSCloneTTS()
tts.clone_and_synthesize(
    text="这是一段使用克隆声音合成的语音。",
    speaker_wav="reference_speaker.wav",
    language="zh",
    output_path="cloned_output.wav"
)

4.3 CosyVoice — 阿里语音合成

CosyVoice是阿里开源的高保真语音合成系统,支持中文零样本声音克隆和情感控制。

from cosyvoice import CosyVoice
import soundfile as sf

class CosyVoiceTTS:
    def __init__(self, model_dir="pretrained_models/CosyVoice-300M"):
        self.model = CosyVoice(model_dir)
    
    def synthesize(self, text: str, mode: str = "sft", **kwargs) -> tuple:
        """
        语音合成
        
        Args:
            text: 输入文本
            mode: 
                - "sft": 预训练音色(speaker参数选择音色)
                - "zero_shot": 零样本声音克隆(需要prompt_wav和prompt_text)
                - "cross_lingual": 跨语言合成
                - "instruct": 指令式合成(描述想要的语音风格)
        """
        if mode == "sft":
            speaker = kwargs.get("speaker", "中文女")
            for result in self.model.inference_sft(text, speaker):
                return result["tts_speech"], 22050
        
        elif mode == "zero_shot":
            prompt_wav = kwargs["prompt_wav"]  # 参考音频路径
            prompt_text = kwargs["prompt_text"]  # 参考音频对应的文本
            for result in self.model.inference_zero_shot(
                text, prompt_text, prompt_wav
            ):
                return result["tts_speech"], 22050
        
        elif mode == "instruct":
            instruct = kwargs.get("instruct", "用温柔的声音朗读")
            speaker = kwargs.get("speaker", "中文女")
            for result in self.model.inference_instruct(text, instruct, speaker):
                return result["tts_speech"], 22050

# 使用示例
tts = CosyVoiceTTS()

# 预训练音色合成
audio, sr = tts.synthesize("你好,欢迎来到AI语音助手的世界。", mode="sft", speaker="中文女")
sf.write("sft_output.wav", audio.numpy(), sr)

# 零样本声音克隆
audio, sr = tts.synthesize(
    "用你的声音说出这段话。",
    mode="zero_shot",
    prompt_wav="speaker_ref.wav",
    prompt_text="这是参考音频的文本内容。"
)
sf.write("clone_output.wav", audio.numpy(), sr)

# 指令式情感合成
audio, sr = tts.synthesize(
    "今天天气真好啊!",
    mode="instruct",
    instruct="用开心活泼的语气说",
    speaker="中文女"
)
sf.write("emotion_output.wav", audio.numpy(), sr)

4.4 F5-TTS — 非自回归零样本TTS

F5-TTS采用非自回归架构,推理速度极快,适合实时场景。

import torch
import soundfile as sf
from f5_tts.api import F5TTS

class F5TTSEngine:
    def __init__(self):
        self.model = F5TTS(device="cuda")
    
    def synthesize(
        self,
        text: str,
        ref_audio: str,
        ref_text: str = "",
        output_path: str = "output.wav"
    ):
        """
        零样本语音合成
        
        Args:
            text: 要合成的文本
            ref_audio: 参考音频路径(3-10秒)
            ref_text: 参考音频对应文本(可选,留空自动推断)
            output_path: 输出路径
        """
        audio, sr = self.model.infer(
            ref_file=ref_audio,
            ref_text=ref_text,
            gen_text=text,
            speed=1.0
        )
        sf.write(output_path, audio, sr)
        return audio, sr

# 使用示例
engine = F5TTSEngine()
engine.synthesize(
    text="这是一段使用F5-TTS合成的高质量语音。",
    ref_audio="reference.wav",
    ref_text="参考音频的文字内容。",
    output_path="f5_output.wav"
)

第五章:实时流式语音处理

5.1 WebSocket实时通信

WebSocket是语音实时交互的基础协议,支持全双工通信。

import asyncio
import websockets
import json
import numpy as np
from typing import AsyncGenerator

class VoiceStreamingServer:
    """基于WebSocket的语音流式处理服务器"""
    
    def __init__(self, asr_model, llm_client, tts_model):
        self.asr = asr_model
        self.llm = llm_client
        self.tts = tts_model
        self.sample_rate = 16000
    
    async def handle_client(self, websocket):
        """处理单个客户端连接"""
        print(f"新客户端连接: {websocket.remote_address}")
        
        audio_buffer = bytearray()
        conversation_history = []
        
        try:
            async for message in websocket:
                if isinstance(message, bytes):
                    # 音频数据
                    audio_buffer.extend(message)
                    
                    # 检查是否积累了足够的音频(2秒)
                    if len(audio_buffer) >= self.sample_rate * 2 * 2:  # 16bit = 2 bytes/sample
                        # 转为numpy数组
                        audio = np.frombuffer(bytes(audio_buffer), dtype=np.int16)
                        audio_float = audio.astype(np.float32) / 32768.0
                        
                        # 语音识别
                        text = await self.asr.transcribe_async(audio_float)
                        audio_buffer.clear()
                        
                        if text.strip():
                            # 发送识别结果
                            await websocket.send(json.dumps({
                                "type": "asr_result",
                                "text": text
                            }))
                            
                            # 生成LLM回复
                            conversation_history.append({"role": "user", "content": text})
                            
                            response_text = ""
                            async for token in self.llm.stream_generate(conversation_history):
                                response_text += token
                                # 流式发送文本
                                await websocket.send(json.dumps({
                                    "type": "llm_token",
                                    "token": token
                                }))
                            
                            conversation_history.append({
                                "role": "assistant",
                                "content": response_text
                            })
                            
                            # 语音合成并发送
                            audio_chunks = await self.tts.synthesize_streaming(response_text)
                            for chunk in audio_chunks:
                                await websocket.send(chunk.tobytes())
                            
                            # 发送完成信号
                            await websocket.send(json.dumps({"type": "turn_complete"}))
                
                elif isinstance(message, str):
                    # 控制消息
                    data = json.loads(message)
                    if data.get("type") == "config":
                        # 更新配置
                        pass
                    elif data.get("type") == "interrupt":
                        # 用户打断,清空缓冲区
                        audio_buffer.clear()
                        
        except websockets.exceptions.ConnectionClosed:
            print(f"客户端断开: {websocket.remote_address}")

# 启动服务器
async def main():
    server = VoiceStreamingServer(asr_model, llm_client, tts_model)
    
    async with websockets.serve(
        server.handle_client,
        "0.0.0.0",
        8765,
        max_size=10 * 1024 * 1024,  # 10MB
        ping_interval=20,
        ping_timeout=10
    ):
        print("语音流式服务器启动在 ws://0.0.0.0:8765")
        await asyncio.Future()  # 永远运行

asyncio.run(main())

5.2 WebRTC低延迟方案

WebRTC提供更低延迟的实时通信,特别适合浏览器端语音交互。

# 使用aiortc库实现WebRTC语音处理
import asyncio
from aiortc import RTCPeerConnection, MediaStreamTrack
from aiortc.contrib.media import MediaRelay
import numpy as np

class AudioProcessorTrack(MediaStreamTrack):
    """处理WebRTC音频流的自定义轨道"""
    kind = "audio"
    
    def __init__(self, track, asr, llm, tts):
        super().__init__()
        self.track = track
        self.asr = asr
        self.llm = llm
        self.tts = tts
        self.audio_buffer = []
        self.sample_rate = 48000  # WebRTC默认48kHz
    
    async def recv(self):
        frame = await self.track.recv()
        
        # 转为numpy处理
        audio = frame.to_ndarray()
        self.audio_buffer.append(audio)
        
        # 积累足够音频后处理
        total_samples = sum(len(a) for a in self.audio_buffer)
        if total_samples >= self.sample_rate * 2:  # 2秒
            # 合并音频
            full_audio = np.concatenate(self.audio_buffer, axis=-1)
            self.audio_buffer.clear()
            
            # 降采样到16kHz用于ASR
            audio_16k = self._resample(full_audio, 48000, 16000)
            
            # ASR -> LLM -> TTS处理
            text = await self.asr.transcribe_async(audio_16k)
            if text.strip():
                response = await self.llm.generate_async(text)
                tts_audio = await self.tts.synthesize_async(response)
                
                # 上采样回48kHz
                audio_48k = self._resample(tts_audio, 22050, 48000)
                return self._numpy_to_frame(audio_48k)
        
        return frame  # 无语音时返回静音帧
    
    def _resample(self, audio, from_rate, to_rate):
        """简单的重采样"""
        if from_rate == to_rate:
            return audio
        ratio = to_rate / from_rate
        new_length = int(len(audio) * ratio)
        indices = np.linspace(0, len(audio) - 1, new_length)
        return np.interp(indices, np.arange(len(audio)), audio).astype(np.float32)

5.3 音频格式处理

实时语音交互中,音频格式处理是关键环节:

import numpy as np
import struct
import io
import wave

class AudioFormatUtils:
    """音频格式转换工具"""
    
    @staticmethod
    def pcm16_to_float(pcm_data: bytes) -> np.ndarray:
        """PCM16字节转float32数组"""
        return np.frombuffer(pcm_data, dtype=np.int16).astype(np.float32) / 32768.0
    
    @staticmethod
    def float_to_pcm16(audio: np.ndarray) -> bytes:
        """float32数组转PCM16字节"""
        audio = np.clip(audio, -1.0, 1.0)
        pcm = (audio * 32767).astype(np.int16)
        return pcm.tobytes()
    
    @staticmethod
    def resample(audio: np.ndarray, from_rate: int, to_rate: int) -> np.ndarray:
        """高质量重采样(使用线性插值)"""
        if from_rate == to_rate:
            return audio
        duration = len(audio) / from_rate
        new_length = int(duration * to_rate)
        old_indices = np.arange(len(audio))
        new_indices = np.linspace(0, len(audio) - 1, new_length)
        return np.interp(new_indices, old_indices, audio).astype(audio.dtype)
    
    @staticmethod
    def add_wav_header(audio: bytes, sample_rate: int, channels: int = 1, bits: int = 16) -> bytes:
        """添加WAV头"""
        buffer = io.BytesIO()
        with wave.open(buffer, 'wb') as wf:
            wf.setnchannels(channels)
            wf.setsampwidth(bits // 8)
            wf.setframerate(sample_rate)
            wf.writeframes(audio)
        return buffer.getvalue()
    
    @staticmethod
    def split_audio_chunks(audio: np.ndarray, chunk_duration_ms: int, sample_rate: int) -> list:
        """将音频分割为固定时长的块"""
        chunk_size = int(sample_rate * chunk_duration_ms / 1000)
        chunks = []
        for i in range(0, len(audio), chunk_size):
            chunks.append(audio[i:i + chunk_size])
        return chunks

第六章:语音Agent架构设计

6.1 VAD(语音活动检测)

VAD是语音Agent的第一个组件,决定何时开始/停止录音。

import torch
import numpy as np

class SileroVAD:
    """基于Silero的语音活动检测"""
    
    def __init__(self, threshold: float = 0.5, sample_rate: int = 16000):
        self.model, _ = torch.hub.load(
            repo_or_dir='snakers4/silero-vad',
            model='silero_vad',
            force_reload=False
        )
        self.threshold = threshold
        self.sample_rate = sample_rate
        self.model.reset_states()
    
    def is_speech(self, audio_chunk: np.ndarray) -> bool:
        """判断音频块是否包含语音"""
        if len(audio_chunk) < 512:
            return False
        
        audio_tensor = torch.from_numpy(audio_chunk).float()
        if len(audio_tensor.shape) == 1:
            audio_tensor = audio_tensor.unsqueeze(0)
        
        prob = self.model(audio_tensor, self.sample_rate).item()
        return prob > self.threshold
    
    def detect_speech_segments(self, audio: np.ndarray, min_speech_ms: int = 250) -> list:
        """检测语音片段"""
        chunk_size = 512  # Silero VAD要求512采样点
        segments = []
        current_start = None
        min_speech_samples = int(self.sample_rate * min_speech_ms / 1000)
        
        for i in range(0, len(audio), chunk_size):
            chunk = audio[i:i + chunk_size]
            if len(chunk) < chunk_size:
                chunk = np.pad(chunk, (0, chunk_size - len(chunk)))
            
            is_speech = self.is_speech(chunk)
            position = i
            
            if is_speech and current_start is None:
                current_start = position
            elif not is_speech and current_start is not None:
                if position - current_start >= min_speech_samples:
                    segments.append((current_start, position))
                current_start = None
        
        # 处理最后一段
        if current_start is not None and len(audio) - current_start >= min_speech_samples:
            segments.append((current_start, len(audio)))
        
        return segments

6.2 完整语音Agent实现

将所有组件整合为一个完整的语音Agent:

import asyncio
import numpy as np
from dataclasses import dataclass, field
from typing import Optional, Callable
from enum import Enum

class AgentState(Enum):
    IDLE = "idle"
    LISTENING = "listening"
    THINKING = "thinking"
    SPEAKING = "speaking"

@dataclass
class VoiceAgentConfig:
    asr_model: str = "medium"
    tts_model: str = "cosyvoice"
    llm_model: str = "gpt-4o"
    vad_threshold: float = 0.5
    silence_duration_ms: int = 800
    max_recording_duration_s: int = 30
    language: str = "zh"
    voice_id: str = "中文女"
    system_prompt: str = "你是一个友好的AI助手。"

class VoiceAgent:
    """完整的语音Agent"""
    
    def __init__(self, config: VoiceAgentConfig):
        self.config = config
        self.state = AgentState.IDLE
        self.conversation_history = []
        
        # 初始化各组件
        self.vad = SileroVAD(threshold=config.vad_threshold)
        self.asr = self._init_asr()
        self.tts = self._init_tts()
        self.llm = self._init_llm()
        
        # 状态回调
        self.on_state_change: Optional[Callable] = None
        self.on_transcription: Optional[Callable] = None
        self.on_response_text: Optional[Callable] = None
        self.on_audio_chunk: Optional[Callable] = None
    
    def _init_asr(self):
        if self.config.asr_model.startswith("whisper"):
            return WhisperASR(model_size="medium")
        else:
            return ParaformerASR()
    
    def _init_tts(self):
        if self.config.tts_model == "cosyvoice":
            return CosyVoiceTTS()
        elif self.config.tts_model == "xtts":
            return XTTSCloneTTS()
        else:
            return VITSTTS("model.pth", "config.json")
    
    def _init_llm(self):
        # 返回LLM客户端
        from openai import OpenAI
        return OpenAI()
    
    def _set_state(self, new_state: AgentState):
        self.state = new_state
        if self.on_state_change:
            self.on_state_change(new_state)
    
    async def process_audio_stream(self, audio_generator):
        """处理音频流的核心逻辑"""
        self._set_state(AgentState.LISTENING)
        
        audio_buffer = []
        silence_count = 0
        silence_threshold_chunks = int(
            self.config.silence_duration_ms / 1000 * 16000 / 512
        )
        
        async for audio_chunk in audio_generator:
            # VAD检测
            if self.vad.is_speech(audio_chunk):
                audio_buffer.append(audio_chunk)
                silence_count = 0
            else:
                if audio_buffer:  # 已经开始录音
                    silence_count += 1
                    audio_buffer.append(audio_chunk)  # 保留部分静音
                    
                    if silence_count >= silence_threshold_chunks:
                        # 用户说完,开始处理
                        full_audio = np.concatenate(audio_buffer)
                        audio_buffer.clear()
                        silence_count = 0
                        
                        await self._process_speech(full_audio)
    
    async def _process_speech(self, audio: np.ndarray):
        """处理一段完整的语音输入"""
        # ASR
        self._set_state(AgentState.THINKING)
        text = self.asr.transcribe(audio, language=self.config.language)
        
        if not text.strip():
            self._set_state(AgentState.IDLE)
            return
        
        if self.on_transcription:
            self.on_transcription(text)
        
        # LLM生成
        self.conversation_history.append({"role": "user", "content": text})
        
        response_text = ""
        for token in self._stream_llm():
            response_text += token
            if self.on_response_text:
                self.on_response_text(token)
        
        self.conversation_history.append({
            "role": "assistant",
            "content": response_text
        })
        
        # TTS合成并播放
        self._set_state(AgentState.SPEAKING)
        audio_result = self.tts.synthesize(response_text)
        
        if self.on_audio_chunk:
            if isinstance(audio_result, tuple):
                audio_data, sr = audio_result
                self.on_audio_chunk(audio_data, sr)
            else:
                self.on_audio_chunk(audio_result, 22050)
        
        self._set_state(AgentState.IDLE)
    
    def _stream_llm(self):
        """流式LLM生成"""
        response = self.llm.chat.completions.create(
            model=self.config.llm_model,
            messages=[
                {"role": "system", "content": self.config.system_prompt},
                *self.conversation_history[-10:]  # 保留最近10轮
            ],
            stream=True,
            max_tokens=500
        )
        
        for chunk in response:
            if chunk.choices[0].delta.content:
                yield chunk.choices[0].delta.content

# 使用示例
config = VoiceAgentConfig(
    asr_model="whisper",
    tts_model="cosyvoice",
    llm_model="gpt-4o",
    system_prompt="你是一个专业的AI健康顾问,用温和的语气回答问题。"
)

agent = VoiceAgent(config)
agent.on_state_change = lambda s: print(f"[状态] {s.value}")
agent.on_transcription = lambda t: print(f"[用户] {t}")
agent.on_response_text = lambda t: print(t, end="", flush=True)

6.3 打断(Barge-in)支持

语音交互中,用户经常需要打断AI的回复。实现打断检测:

class BargeInDetector:
    """打断检测器"""
    
    def __init__(self, vad: SileroVAD, energy_threshold: float = 0.02):
        self.vad = vad
        self.energy_threshold = energy_threshold
        self.is_ai_speaking = False
        self.user_speech_start_time = None
        self.min_interrupt_ms = 300  # 至少300ms语音才触发打断
    
    def should_interrupt(self, audio_chunk: np.ndarray) -> bool:
        """判断是否应该打断AI"""
        if not self.is_ai_speaking:
            return False
        
        # 检查能量
        energy = np.sqrt(np.mean(audio_chunk ** 2))
        if energy < self.energy_threshold:
            self.user_speech_start_time = None
            return False
        
        # 检查是否为语音
        if not self.vad.is_speech(audio_chunk):
            self.user_speech_start_time = None
            return False
        
        import time
        current_time = time.time() * 1000
        
        if self.user_speech_start_time is None:
            self.user_speech_start_time = current_time
            return False
        
        # 持续说话超过阈值,触发打断
        if current_time - self.user_speech_start_time > self.min_interrupt_ms:
            self.user_speech_start_time = None
            return True
        
        return False
    
    def on_ai_start_speaking(self):
        self.is_ai_speaking = True
    
    def on_ai_stop_speaking(self):
        self.is_ai_speaking = False
        self.user_speech_start_time = None

第七章:语音情感识别与表达

7.1 语音情感识别(SER)

识别用户语音中的情感,可以帮助AI更好地理解用户意图。

import torch
import numpy as np
from transformers import pipeline

class SpeechEmotionRecognizer:
    """语音情感识别"""
    
    # 常见情感标签
    EMOTIONS = ["neutral", "happy", "sad", "angry", "fear", "surprise", "disgust"]
    EMOTION_ZH = {
        "neutral": "平静", "happy": "开心", "sad": "悲伤",
        "angry": "生气", "fear": "恐惧", "surprise": "惊讶", "disgust": "厌恶"
    }
    
    def __init__(self):
        # 使用HuggingFace的情感识别模型
        self.classifier = pipeline(
            "audio-classification",
            model="ehcalabres/wav2vec2-lg-xlsr-en-speech-emotion-recognition",
            device=0 if torch.cuda.is_available() else -1
        )
    
    def recognize(self, audio: np.ndarray, sample_rate: int = 16000) -> dict:
        """识别音频中的情感"""
        results = self.classifier(audio, sampling_rate=sample_rate)
        
        # 返回所有情感的置信度
        emotions = {r["label"]: r["score"] for r in results}
        top_emotion = results[0]
        
        return {
            "dominant_emotion": top_emotion["label"],
            "dominant_emotion_zh": self.EMOTION_ZH.get(top_emotion["label"], top_emotion["label"]),
            "confidence": top_emotion["score"],
            "all_emotions": emotions
        }
    
    def get_emotion_prompt(self, emotion_result: dict) -> str:
        """根据识别的情感生成系统提示"""
        emotion = emotion_result["dominant_emotion"]
        confidence = emotion_result["confidence"]
        
        if confidence < 0.5:
            return ""
        
        emotion_instructions = {
            "happy": "用户语气开心,回复时也保持积极愉快的语调。",
            "sad": "用户语气低落,回复时请表达理解和关怀,语气温和。",
            "angry": "用户语气生气,请保持冷静和专业,不要反驳,先表示理解。",
            "fear": "用户语气紧张恐惧,请给予安慰和安全感。",
            "surprise": "用户语气惊讶,可以适当表示共鸣。",
        }
        
        return emotion_instructions.get(emotion, "")

7.2 情感化语音合成

根据对话情感调整TTS的输出风格:

class EmotionAwareTTS:
    """情感感知的语音合成"""
    
    def __init__(self, base_tts):
        self.base_tts = base_tts
    
    def synthesize_with_emotion(
        self,
        text: str,
        target_emotion: str = "neutral",
        reference_audio: str = None
    ) -> tuple:
        """
        根据目标情感合成语音
        
        Args:
            text: 输入文本
            target_emotion: 目标情感 (happy, sad, calm, excited, etc.)
            reference_audio: 参考音频(用于声音克隆)
        """
        # 情感控制提示
        emotion_prompts = {
            "happy": "用开心、活泼的语气说",
            "sad": "用低沉、缓慢的语气说",
            "calm": "用平静、温和的语气说",
            "excited": "用兴奋、充满活力的语气说",
            "serious": "用严肃、正式的语气说",
            "gentle": "用温柔、体贴的语气说"
        }
        
        instruct = emotion_prompts.get(target_emotion, "用自然的语气说")
        
        if hasattr(self.base_tts, 'synthesize') and 'instruct' in self.base_tts.synthesize.__code__.co_varnames:
            # CosyVoice支持指令式合成
            return self.base_tts.synthesize(
                text,
                mode="instruct",
                instruct=instruct
            )
        else:
            # 其他TTS通过SSML或语速控制实现
            return self.base_tts.synthesize(text)
    
    def auto_emotion_synthesize(self, text: str, context: list) -> tuple:
        """根据上下文自动决定合成情感"""
        # 简单规则:根据LLM回复内容判断情感
        emotion = self._detect_text_emotion(text)
        return self.synthesize_with_emotion(text, emotion)
    
    def _detect_text_emotion(self, text: str) -> str:
        """从文本中检测情感倾向"""
        happy_words = ["开心", "高兴", "太好了", "哈哈", "恭喜", "棒"]
        sad_words = ["抱歉", "遗憾", "可惜", "难过", "对不起"]
        excited_words = ["太棒了", "厉害", "amazing", "哇", "真的吗"]
        
        text_lower = text.lower()
        
        happy_count = sum(1 for w in happy_words if w in text_lower)
        sad_count = sum(1 for w in sad_words if w in text_lower)
        excited_count = sum(1 for w in excited_words if w in text_lower)
        
        if excited_count > 0:
            return "excited"
        elif happy_count > sad_count:
            return "happy"
        elif sad_count > 0:
            return "gentle"
        else:
            return "calm"

第八章:多语言语音交互

8.1 多语言ASR实现

import whisper

class MultilingualASR:
    """多语言语音识别"""
    
    LANGUAGE_MAP = {
        "zh": "Chinese",
        "en": "English",
        "ja": "Japanese",
        "ko": "Korean",
        "fr": "French",
        "de": "German",
        "es": "Spanish",
        "ru": "Russian",
        "ar": "Arabic",
        "pt": "Portuguese",
    }
    
    def __init__(self, model_size="large-v3"):
        self.model = whisper.load_model(model_size)
    
    def detect_language(self, audio_path: str) -> tuple:
        """自动检测语言"""
        audio = whisper.load_audio(audio_path)
        audio = whisper.pad_or_trim(audio)
        mel = whisper.log_mel_spectrogram(audio).to(self.model.device)
        
        _, probs = self.model.detect_language(mel)
        detected = max(probs, key=probs.get)
        
        return detected, probs[detected]
    
    def transcribe(self, audio_path: str, language: str = None) -> dict:
        """多语言转录,语言可选自动检测"""
        options = {"fp16": False}
        
        if language:
            options["language"] = language
        
        result = self.model.transcribe(audio_path, **options)
        
        return {
            "text": result["text"],
            "language": result["language"],
            "segments": [
                {
                    "text": seg["text"],
                    "start": seg["start"],
                    "end": seg["end"],
                    "language": seg.get("language", result["language"])
                }
                for seg in result["segments"]
            ]
        }

# 使用示例
asr = MultilingualASR()

# 自动检测语言
lang, confidence = asr.detect_language("audio.wav")
print(f"检测到语言: {lang} (置信度: {confidence:.2f})")

# 指定语言转录
result = asr.transcribe("chinese_audio.wav", language="zh")
print(f"中文识别: {result['text']}")

# 自动语言转录
result = asr.transcribe("multilingual_audio.wav")
print(f"语言: {result['language']}, 内容: {result['text']}")

8.2 实时翻译语音交互

class RealtimeTranslator:
    """实时语音翻译系统"""
    
    def __init__(self):
        self.asr = MultilingualASR()
        self.llm = None  # LLM客户端
        self.tts = CosyVoiceTTS()
    
    async def translate_speech(
        self,
        audio: np.ndarray,
        source_lang: str,
        target_lang: str
    ) -> tuple:
        """
        语音翻译:输入语言A的语音,输出语言B的语音
        
        Returns:
            (原文文本, 翻译文本, 翻译音频)
        """
        # 1. 语音识别
        text = self.asr.transcribe(audio, language=source_lang)["text"]
        
        # 2. LLM翻译
        translation = await self._llm_translate(text, source_lang, target_lang)
        
        # 3. 语音合成
        audio_result = self.tts.synthesize(translation)
        
        if isinstance(audio_result, tuple):
            synth_audio, sr = audio_result
        else:
            synth_audio, sr = audio_result, 22050
        
        return text, translation, synth_audio
    
    async def _llm_translate(self, text: str, from_lang: str, to_lang: str) -> str:
        """使用LLM进行翻译"""
        lang_names = {"zh": "中文", "en": "英文", "ja": "日文", "ko": "韩文"}
        
        response = self.llm.chat.completions.create(
            model="gpt-4o",
            messages=[
                {
                    "role": "system",
                    "content": f"你是一个专业的翻译。将以下{lang_names.get(from_lang, from_lang)}文本翻译为{lang_names.get(to_lang, to_lang)},保持自然口语化。只输出翻译结果。"
                },
                {"role": "user", "content": text}
            ],
            max_tokens=500
        )
        
        return response.choices[0].message.content

第九章:端到端Speech-to-Speech实现

9.1 基于GPT-4o的端到端实现

import asyncio
import websockets
import json
import base64
import pyaudio
import threading

class SpeechToSpeechApp:
    """基于GPT-4o Realtime API的端到端语音对话应用"""
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.ws_url = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview"
        self.is_running = False
        self.is_ai_speaking = False
        
        # 音频配置
        self.sample_rate = 24000
        self.chunk_size = 1024
        self.format = pyaudio.paInt16
        self.channels = 1
    
    async def start(self):
        """启动语音对话"""
        self.is_running = True
        
        # 连接WebSocket
        self.ws = await websockets.connect(
            self.ws_url,
            extra_headers={
                "Authorization": f"Bearer {self.api_key}",
                "OpenAI-Beta": "realtime=v1"
            }
        )
        
        # 配置session
        await self._configure_session()
        
        # 同时运行录音和播放
        await asyncio.gather(
            self._record_and_send(),
            self._receive_and_play(),
            self._handle_events()
        )
    
    async def _configure_session(self):
        await self.ws.send(json.dumps({
            "type": "session.update",
            "session": {
                "modalities": ["text", "audio"],
                "instructions": (
                    "你是一个友好的AI助手,名叫小智。"
                    "你善于用自然、生动的语气和用户交流。"
                    "回答简洁明了,适合语音对话场景。"
                ),
                "voice": "alloy",
                "input_audio_format": "pcm16",
                "output_audio_format": "pcm16",
                "input_audio_transcription": {
                    "model": "whisper-1"
                },
                "turn_detection": {
                    "type": "server_vad",
                    "threshold": 0.5,
                    "prefix_padding_ms": 300,
                    "silence_duration_ms": 500
                },
                "temperature": 0.8,
                "max_response_output_tokens": 4096
            }
        }))
    
    async def _record_and_send(self):
        """录音并发送到服务器"""
        p = pyaudio.PyAudio()
        stream = p.open(
            format=self.format,
            channels=self.channels,
            rate=self.sample_rate,
            input=True,
            frames_per_buffer=self.chunk_size
        )
        
        print("🎤 开始录音,按Ctrl+C停止...")
        
        try:
            while self.is_running:
                audio_data = stream.read(self.chunk_size, exception_on_overflow=False)
                audio_b64 = base64.b64encode(audio_data).decode()
                
                await self.ws.send(json.dumps({
                    "type": "input_audio_buffer.append",
                    "audio": audio_b64
                }))
                
                await asyncio.sleep(0.01)  # 控制发送频率
        finally:
            stream.stop_stream()
            stream.close()
            p.terminate()
    
    async def _receive_and_play(self):
        """接收服务器音频并播放"""
        p = pyaudio.PyAudio()
        stream = p.open(
            format=self.format,
            channels=self.channels,
            rate=self.sample_rate,
            output=True
        )
        
        try:
            async for message in self.ws:
                event = json.loads(message)
                
                if event["type"] == "response.audio.delta":
                    audio_chunk = base64.b64decode(event["delta"])
                    stream.write(audio_chunk)
                    self.is_ai_speaking = True
                
                elif event["type"] == "response.audio.done":
                    self.is_ai_speaking = False
                
                elif event["type"] == "response.audio_transcript.delta":
                    print(event["delta"], end="", flush=True)
                
                elif event["type"] == "response.audio_transcript.done":
                    print()
        finally:
            stream.stop_stream()
            stream.close()
            p.terminate()
    
    async def _handle_events(self):
        """处理其他事件"""
        async for message in self.ws:
            event = json.loads(message)
            
            if event["type"] == "error":
                print(f"错误: {event['error']['message']}")
            elif event["type"] == "session.created":
                print("✅ 会话已建立")
            elif event["type"] == "input_audio_buffer.speech_started":
                print("🎙️ 检测到语音输入...")
            elif event["type"] == "input_audio_buffer.speech_stopped":
                print("🔇 语音输入结束")

# 运行
async def main():
    app = SpeechToSpeechApp("your-api-key")
    try:
        await app.start()
    except KeyboardInterrupt:
        print("\n会话结束")

asyncio.run(main())

9.2 开源端到端方案

对于需要本地部署的场景,可以使用Qwen2.5-Omni等开源模型:

import torch
import sounddevice as sd
import numpy as np
import queue
import threading

class LocalSpeechToSpeech:
    """基于开源模型的本地端到端语音对话"""
    
    def __init__(self, model_path="Qwen/Qwen2.5-Omni-7B"):
        from transformers import AutoModelForCausalLM, AutoTokenizer
        
        self.model = AutoModelForCausalLM.from_pretrained(
            model_path,
            torch_dtype=torch.bfloat16,
            device_map="auto",
            trust_remote_code=True
        )
        self.tokenizer = AutoTokenizer.from_pretrained(
            model_path, trust_remote_code=True
        )
        
        self.audio_queue = queue.Queue()
        self.is_running = False
    
    def start_conversation(self):
        """开始语音对话"""
        self.is_running = True
        
        # 启动录音线程
        record_thread = threading.Thread(target=self._record_audio)
        record_thread.daemon = True
        record_thread.start()
        
        print("🎤 语音对话已开始,按Ctrl+C停止...")
        
        try:
            while self.is_running:
                # 收集音频
                audio_chunks = []
                while not self.audio_queue.empty():
                    audio_chunks.append(self.audio_queue.get())
                
                if audio_chunks:
                    audio = np.concatenate(audio_chunks)
                    
                    # 检查是否有足够的语音内容
                    if np.max(np.abs(audio)) > 0.01:
                        self._process_audio(audio)
                
                import time
                time.sleep(0.1)
        
        except KeyboardInterrupt:
            self.is_running = False
            print("\n对话结束")
    
    def _record_audio(self):
        """录音线程"""
        def callback(indata, frames, time_info, status):
            self.audio_queue.put(indata.copy())
        
        with sd.InputStream(
            samplerate=16000,
            channels=1,
            dtype='float32',
            blocksize=4000,
            callback=callback
        ):
            while self.is_running:
                sd.sleep(100)
    
    def _process_audio(self, audio: np.ndarray):
        """处理音频输入"""
        import soundfile as sf
        import tempfile
        
        # 保存临时音频文件
        with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
            sf.write(f.name, audio, 16000)
            audio_path = f.name
        
        # 使用模型处理
        messages = [
            {"role": "user", "content": [
                {"type": "audio", "audio_url": audio_path},
                {"type": "text", "text": "请回答音频中的问题。"}
            ]}
        ]
        
        text, response_audio = self.model.chat(
            messages=messages,
            tokenizer=self.tokenizer
        )
        
        print(f"AI: {text}")
        
        # 播放回复音频
        sd.play(response_audio.numpy(), samplerate=24000)
        sd.wait()

第十章:低延迟优化技巧

10.1 延迟分析与优化策略

语音交互的延迟是用户体验的核心指标。以下是各环节的延迟分析和优化策略:

import time
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Dict

@dataclass
class LatencyMetrics:
    """延迟指标"""
    vad_latency_ms: float = 0
    asr_latency_ms: float = 0
    llm_first_token_ms: float = 0
    llm_total_ms: float = 0
    tts_first_chunk_ms: float = 0
    tts_total_ms: float = 0
    total_e2e_ms: float = 0

class LatencyOptimizer:
    """延迟优化器"""
    
    def __init__(self):
        self.metrics_history = []
    
    @contextmanager
    def measure(self, name: str):
        """测量某环节延迟"""
        start = time.perf_counter()
        yield
        elapsed = (time.perf_counter() - start) * 1000
        print(f"[延迟] {name}: {elapsed:.1f}ms")
    
    def optimize_asr(self):
        """ASR优化策略"""
        return {
            "1_使用流式识别": "边录边识别,不等用户说完",
            "2_模型量化": "使用INT8量化模型,推理速度提升2-3倍",
            "3_GPU加速": "确保ASR模型在GPU上运行",
            "4_音频降采样": "16kHz足够,不需要更高采样率",
            "5_并行处理": "录音和识别在不同线程/进程中执行",
        }
    
    def optimize_llm(self):
        """LLM优化策略"""
        return {
            "1_流式输出": "使用streaming API,不等全部生成完",
            "2_减少上下文": "只保留最近几轮对话",
            "3_使用小模型": "对话场景7B-14B模型足够",
            "4_预测解码": "使用speculative decoding加速",
            "5_减少max_tokens": "语音对话回复控制在100-200字",
        }
    
    def optimize_tts(self):
        """TTS优化策略"""
        return {
            "1_流式合成": "边生成文本边合成语音",
            "2_句子级合成": "不等全部文本,按句子分批合成",
            "3_音频缓存": "常用回复预合成缓存",
            "4_轻量模型": "实时场景使用轻量TTS模型",
        }
    
    def optimize_network(self):
        """网络优化策略"""
        return {
            "1_音频压缩": "使用Opus编码,压缩比10:1",
            "2_UDP传输": "WebRTC使用UDP,比TCP更实时",
            "3_边缘部署": "将ASR/TTS部署到靠近用户的边缘节点",
            "4_预连接": "建立WebSocket/RTC连接池",
        }

10.2 流式TTS与LLM并行

关键优化:LLM生成文本的同时,TTS已开始合成前面的句子。

import asyncio
from typing import AsyncGenerator

class StreamingVoicePipeline:
    """流式语音管线:LLM和TTS并行执行"""
    
    def __init__(self, llm, tts):
        self.llm = llm
        self.tts = tts
    
    async def process(
        self,
        user_text: str,
        history: list
    ) -> AsyncGenerator[bytes, None]:
        """
        流式处理:LLM生成token的同时,TTS合成已完成的句子
        """
        sentence_buffer = ""
        tts_queue = asyncio.Queue()
        
        # 启动TTS消费者
        tts_task = asyncio.create_task(
            self._tts_consumer(tts_queue)
        )
        
        # LLM流式生成
        async for token in self._stream_llm(user_text, history):
            sentence_buffer += token
            
            # 检查是否构成完整句子
            sentence = self._extract_sentence(sentence_buffer)
            if sentence:
                sentence_buffer = sentence_buffer[len(sentence):]
                await tts_queue.put(sentence)
        
        # 处理剩余文本
        if sentence_buffer.strip():
            await tts_queue.put(sentence_buffer.strip())
        
        # 发送结束信号
        await tts_queue.put(None)
        
        # 等待TTS完成
        async for audio_chunk in self._collect_tts_results(tts_queue):
            yield audio_chunk
    
    async def _stream_llm(self, text: str, history: list):
        """流式LLM生成"""
        response = self.llm.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system", "content": "简洁回答,适合语音播放。"},
                *history,
                {"role": "user", "content": text}
            ],
            stream=True,
            max_tokens=300
        )
        
        for chunk in response:
            if chunk.choices[0].delta.content:
                yield chunk.choices[0].delta.content
    
    def _extract_sentence(self, text: str) -> str:
        """提取完整句子"""
        sentence_endings = ["。", "!", "?", "!", "?", ".", "\n"]
        
        for i, char in enumerate(text):
            if char in sentence_endings:
                return text[:i + 1]
        
        # 如果缓冲区太长,强制分割
        if len(text) > 50:
            # 在逗号或分号处分割
            for i in range(len(text) - 1, 0, -1):
                if text[i] in [",", ",", ";", ";"]:
                    return text[:i + 1]
            return text[:50]
        
        return ""
    
    async def _tts_consumer(self, queue: asyncio.Queue):
        """TTS消费者任务"""
        while True:
            sentence = await queue.get()
            if sentence is None:
                await queue.put("DONE")
                break
            
            audio = await self._synthesize_async(sentence)
            await queue.put(audio)
    
    async def _synthesize_async(self, text: str) -> bytes:
        """异步TTS合成"""
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(None, self.tts.synthesize, text)

10.3 音频缓冲与播放优化

import numpy as np
import threading
import queue
import time

class AudioPlaybackBuffer:
    """音频播放缓冲区,确保平滑播放"""
    
    def __init__(self, sample_rate: int = 24000, buffer_ms: int = 100):
        self.sample_rate = sample_rate
        self.buffer_size = int(sample_rate * buffer_ms / 1000)
        self.play_queue = queue.Queue()
        self.is_playing = False
        self.play_thread = None
    
    def start_playback(self):
        """开始播放"""
        self.is_playing = True
        self.play_thread = threading.Thread(target=self._play_loop)
        self.play_thread.daemon = True
        self.play_thread.start()
    
    def add_audio(self, audio: np.ndarray):
        """添加音频到播放队列"""
        self.play_queue.put(audio)
    
    def stop(self):
        """停止播放"""
        self.is_playing = False
        self.play_queue.put(None)
    
    def _play_loop(self):
        """播放循环"""
        import pyaudio
        
        p = pyaudio.PyAudio()
        stream = p.open(
            format=pyaudio.paFloat32,
            channels=1,
            rate=self.sample_rate,
            output=True,
            frames_per_buffer=self.buffer_size
        )
        
        # 预缓冲
        pre_buffer = []
        min_buffer_chunks = 3
        
        try:
            while self.is_playing:
                try:
                    audio = self.play_queue.get(timeout=0.5)
                    if audio is None:
                        break
                    
                    if len(pre_buffer) < min_buffer_chunks:
                        pre_buffer.append(audio)
                        if len(pre_buffer) >= min_buffer_chunks:
                            # 开始播放预缓冲
                            for chunk in pre_buffer:
                                stream.write(chunk.astype(np.float32).tobytes())
                            pre_buffer.clear()
                    else:
                        stream.write(audio.astype(np.float32).tobytes())
                
                except queue.Empty:
                    continue
        
        finally:
            stream.stop_stream()
            stream.close()
            p.terminate()

第十一章:语音RAG系统

11.1 语音RAG架构

语音RAG将检索增强生成技术应用于语音场景,让AI语音助手能基于知识库回答问题。

from dataclasses import dataclass
from typing import List, Optional
import numpy as np

@dataclass
class VoiceRAGConfig:
    asr_model: str = "paraformer"
    embedding_model: str = "text-embedding-3-small"
    llm_model: str = "gpt-4o"
    tts_model: str = "cosyvoice"
    top_k: int = 3
    similarity_threshold: float = 0.7

class VoiceRAGSystem:
    """语音RAG系统"""
    
    def __init__(self, config: VoiceRAGConfig):
        self.config = config
        self.asr = ParaformerASR()
        self.tts = CosyVoiceTTS()
        self.knowledge_base = []
        self.embeddings = []
    
    def add_documents(self, documents: List[dict]):
        """
        添加文档到知识库
        
        Args:
            documents: [{"title": "...", "content": "...", "source": "..."}]
        """
        from openai import OpenAI
        client = OpenAI()
        
        for doc in documents:
            # 文档分块
            chunks = self._split_document(doc["content"])
            
            for chunk in chunks:
                # 生成嵌入
                response = client.embeddings.create(
                    model=self.config.embedding_model,
                    input=chunk
                )
                embedding = response.data[0].embedding
                
                self.knowledge_base.append({
                    "title": doc["title"],
                    "content": chunk,
                    "source": doc.get("source", ""),
                    "embedding": embedding
                })
                self.embeddings.append(embedding)
        
        self.embeddings = np.array(self.embeddings)
    
    def _split_document(self, text: str, chunk_size: int = 500, overlap: int = 50) -> List[str]:
        """文档分块"""
        chunks = []
        sentences = text.replace("。", "。\n").replace("!", "!\n").replace("?", "?\n").split("\n")
        
        current_chunk = ""
        for sentence in sentences:
            if len(current_chunk) + len(sentence) > chunk_size:
                if current_chunk:
                    chunks.append(current_chunk.strip())
                current_chunk = sentence
            else:
                current_chunk += sentence
        
        if current_chunk.strip():
            chunks.append(current_chunk.strip())
        
        return chunks
    
    def retrieve(self, query: str) -> List[dict]:
        """检索相关文档"""
        from openai import OpenAI
        client = OpenAI()
        
        # 查询嵌入
        response = client.embeddings.create(
            model=self.config.embedding_model,
            input=query
        )
        query_embedding = np.array(response.data[0].embedding)
        
        # 计算相似度
        similarities = np.dot(self.embeddings, query_embedding) / (
            np.linalg.norm(self.embeddings, axis=1) * np.linalg.norm(query_embedding)
        )
        
        # 获取top_k
        top_indices = np.argsort(similarities)[::-1][:self.config.top_k]
        
        results = []
        for idx in top_indices:
            if similarities[idx] >= self.config.similarity_threshold:
                results.append({
                    **self.knowledge_base[idx],
                    "score": float(similarities[idx])
                })
        
        return results
    
    async def process_voice_query(self, audio: np.ndarray) -> tuple:
        """
        处理语音查询
        
        Returns:
            (查询文本, 回复文本, 回复音频)
        """
        # 1. 语音识别
        query_text = self.asr.transcribe(audio, language="zh")["text"]
        
        # 2. 检索相关文档
        relevant_docs = self.retrieve(query_text)
        
        # 3. 构建增强prompt
        context = "\n".join([
            f"[来源: {doc['title']}] {doc['content']}"
            for doc in relevant_docs
        ])
        
        prompt = f"""基于以下参考资料回答用户问题。如果资料中没有相关信息,请说明你不确定。
        
参考资料:
{context}

用户问题:{query_text}

请用简洁、自然的口语化方式回答:"""
        
        # 4. LLM生成回复
        from openai import OpenAI
        client = OpenAI()
        
        response = client.chat.completions.create(
            model=self.config.llm_model,
            messages=[
                {"role": "system", "content": "你是一个知识丰富的AI助手,用简洁的口语化方式回答。"},
                {"role": "user", "content": prompt}
            ],
            max_tokens=300
        )
        
        reply_text = response.choices[0].message.content
        
        # 5. 语音合成
        audio_result = self.tts.synthesize(reply_text)
        
        if isinstance(audio_result, tuple):
            reply_audio, sr = audio_result
        else:
            reply_audio, sr = audio_result, 22050
        
        return query_text, reply_text, reply_audio

11.2 语音文档索引

class VoiceDocumentIndexer:
    """语音文档索引器:将音频/视频内容索引为可检索的知识"""
    
    def __init__(self):
        self.asr = ParaformerASR()
        self.embedder = None  # 文本嵌入模型
    
    def index_audio_file(self, audio_path: str, metadata: dict = None) -> List[dict]:
        """索引音频文件"""
        # 1. 语音识别
        result = self.asr.transcribe(audio_path)
        
        # 2. 分段
        segments = result.get("sentences", [])
        
        # 3. 为每个片段生成嵌入并索引
        indexed = []
        for i, seg in enumerate(segments):
            entry = {
                "content": seg["text"],
                "start_time": seg.get("start", 0),
                "end_time": seg.get("end", 0),
                "source": audio_path,
                "type": "audio",
                "metadata": metadata or {},
                "segment_index": i
            }
            indexed.append(entry)
        
        return indexed
    
    def index_meeting(self, audio_path: str, meeting_info: dict = None) -> List[dict]:
        """索引会议录音"""
        # 带说话人分离的识别
        segments = self.asr.transcribe_with_speaker(audio_path)
        
        indexed = []
        for seg in segments:
            entry = {
                "content": f"[{seg['speaker']}] {seg['text']}",
                "speaker": seg["speaker"],
                "start_time": seg["start"],
                "end_time": seg["end"],
                "source": audio_path,
                "type": "meeting",
                "metadata": meeting_info or {}
            }
            indexed.append(entry)
        
        return indexed

第十二章:场景落地实战

12.1 智能客服语音系统

class VoiceCustomerService:
    """AI语音客服系统"""
    
    def __init__(self):
        self.agent = VoiceAgent(VoiceAgentConfig(
            system_prompt="""你是一个专业的客服助手。
规则:
1. 先确认用户问题类型(咨询/投诉/技术支持/其他)
2. 用简洁明了的语言回答
3. 如果无法解决,引导用户转人工
4. 注意识别用户情绪,投诉时先表示歉意
5. 每次回复控制在3句话以内"""
        ))
        
        self.faq_knowledge = VoiceRAGSystem(VoiceRAGConfig())
    
    async def handle_call(self, audio_stream):
        """处理来电"""
        # 初始化对话
        greeting = "您好,我是AI客服小助手,请问有什么可以帮您?"
        yield await self._speak(greeting)
        
        # 处理用户输入
        async for audio_chunk in self.agent.process_audio_stream(audio_stream):
            yield audio_chunk
    
    async def _speak(self, text: str) -> bytes:
        """合成语音"""
        audio = self.agent.tts.synthesize(text)
        if isinstance(audio, tuple):
            return audio[0]
        return audio

12.2 语音笔记系统

class VoiceNoteSystem:
    """AI语音笔记:将语音转为结构化笔记"""
    
    def __init__(self):
        self.asr = ParaformerASR()
        self.llm = None  # LLM客户端
    
    async def create_note_from_audio(self, audio_path: str) -> dict:
        """从音频创建结构化笔记"""
        # 1. 语音识别
        transcription = self.asr.transcribe(audio_path)
        raw_text = transcription["text"]
        
        # 2. LLM结构化处理
        prompt = f"""请将以下语音转录内容整理为结构化笔记:

原始内容:
{raw_text}

请输出JSON格式:
{{
    "title": "笔记标题",
    "summary": "内容摘要(50字以内)",
    "key_points": ["要点1", "要点2", ...],
    "action_items": ["待办事项1", ...],
    "categories": ["分类标签"],
    "full_text": "整理后的完整文本"
}}"""
        
        from openai import OpenAI
        client = OpenAI()
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
            response_format={"type": "json_object"}
        )
        
        note = json.loads(response.choices[0].message.content)
        note["raw_transcription"] = raw_text
        note["segments"] = transcription.get("segments", [])
        note["source_file"] = audio_path
        
        return note
    
    async def summarize_meeting(self, audio_path: str) -> dict:
        """会议纪要生成"""
        # 带说话人分离
        segments = self.asr.transcribe_with_speaker(audio_path)
        
        # 整理会议内容
        meeting_text = "\n".join([
            f"[{s['speaker']}] ({s['start']}ms-{s['end']}ms): {s['text']}"
            for s in segments
        ])
        
        prompt = f"""请根据以下会议记录生成会议纪要:

{meeting_text}

输出格式:
1. 会议主题
2. 参与者
3. 讨论要点(分点列出)
4. 决议事项
5. 后续行动"""
        
        from openai import OpenAI
        client = OpenAI()
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}]
        )
        
        return {
            "summary": response.choices[0].message.content,
            "participants": list(set(s["speaker"] for s in segments)),
            "duration_ms": segments[-1]["end"] if segments else 0,
            "segments": segments
        }

12.3 智能音箱应用架构

class SmartSpeakerApp:
    """智能音箱应用架构"""
    
    def __init__(self):
        # 核心组件
        self.wake_word_detector = WakeWordDetector(keyword="小智小智")
        self.vad = SileroVAD()
        self.asr = WhisperASR(model_size="base")  # 音箱用base足够
        self.tts = CosyVoiceTTS()
        self.llm = None
        
        # 技能系统
        self.skills = {
            "weather": WeatherSkill(),
            "music": MusicSkill(),
            "alarm": AlarmSkill(),
            "smart_home": SmartHomeSkill(),
            "chat": ChatSkill(),
        }
    
    async def run(self):
        """主运行循环"""
        print("智能音箱启动,等待唤醒词...")
        
        while True:
            # 1. 唤醒词检测
            if await self._detect_wake_word():
                print("唤醒词检测到!")
                
                # 2. 提示音
                await self._play_prompt_sound()
                
                # 3. 录音
                audio = await self._record_user_speech()
                
                # 4. ASR
                text = self.asr.transcribe(audio)["text"]
                print(f"用户说: {text}")
                
                # 5. 意图识别和技能路由
                skill_name, params = await self._route_intent(text)
                
                # 6. 执行技能
                response = await self.skills[skill_name].execute(params)
                
                # 7. TTS播放
                await self._speak(response)
    
    async def _detect_wake_word(self) -> bool:
        """唤醒词检测"""
        # 实际实现使用Porcupine或Snowboy
        return True
    
    async def _record_user_speech(self) -> np.ndarray:
        """录制用户语音(VAD控制)"""
        import pyaudio
        
        p = pyaudio.PyAudio()
        stream = p.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=16000,
            input=True,
            frames_per_buffer=512
        )
        
        audio_chunks = []
        silence_count = 0
        max_silence = 15  # 15个静音块后停止
        
        while True:
            data = stream.read(512, exception_on_overflow=False)
            audio = np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0
            
            if self.vad.is_speech(audio):
                audio_chunks.append(audio)
                silence_count = 0
            elif audio_chunks:
                silence_count += 1
                if silence_count >= max_silence:
                    break
        
        stream.stop_stream()
        stream.close()
        p.terminate()
        
        if audio_chunks:
            return np.concatenate(audio_chunks)
        return np.array([], dtype=np.float32)
    
    async def _route_intent(self, text: str) -> tuple:
        """意图识别和路由"""
        prompt = f"""判断以下用户意图,返回JSON格式:
用户说:{text}

可选意图:
- weather: 查询天气
- music: 播放音乐
- alarm: 设置闹钟
- smart_home: 控制智能家居
- chat: 闲聊

返回:{{"intent": "xxx", "params": {{}}}}"""
        
        from openai import OpenAI
        client = OpenAI()
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
            response_format={"type": "json_object"}
        )
        
        result = json.loads(response.choices[0].message.content)
        intent = result.get("intent", "chat")
        params = result.get("params", {})
        params["original_text"] = text
        
        return intent, params

最佳实践总结

性能优化清单

  1. ASR优化

    • 使用Paraformer或Whisper Turbo获得最佳速度/质量平衡
    • 启用流式识别,边录边识别
    • GPU加速是必须的
  2. LLM优化

    • 使用流式API,首token延迟 < 500ms
    • 控制max_tokens,语音场景100-300字足够
    • 保持上下文窗口简洁
  3. TTS优化

    • 使用流式合成,按句子分批
    • CosyVoice/F5-TTS适合中文实时场景
    • 预热模型,避免首次加载延迟
  4. 系统优化

    • 异步并行:录音、ASR、LLM、TTS在不同协程中执行
    • 音频缓冲:预缓冲2-3个chunk再开始播放
    • 网络优化:使用WebSocket长连接,避免频繁建连

架构选择指南

场景 推荐架构 延迟目标
智能客服 级联式 < 2s
实时翻译 级联式 < 3s
语音助手 端到端 < 1s
智能音箱 级联式 < 1.5s
语音游戏 端到端 < 500ms

安全与隐私

  1. 音频数据安全:传输使用TLS加密,存储加密
  2. 用户隐私:明确告知录音,提供关闭选项
  3. 内容安全:ASR和LLM输出需经过内容审核
  4. 合规要求:遵循当地隐私法规(如GDPR、个人信息保护法)

总结

本教程系统性地介绍了AI实时语音交互的完整技术栈:

  1. 架构层面:从级联式到端到端,理解不同架构的权衡
  2. 模型层面:GPT-4o Realtime、Gemini Live、Qwen2.5-Omni等主流方案对比
  3. 核心组件:ASR(Whisper/Paraformer)、TTS(VITS/XTTS/CosyVoice/F5-TTS)
  4. 工程实现:WebSocket/WebRTC实时通信、流式处理管线
  5. 高级话题:情感识别、声音克隆、多语言、语音RAG
  6. 场景落地:智能客服、语音笔记、智能音箱

AI语音交互正处于快速发展期,端到端模型的能力在不断增强。建议开发者:

  • 先用级联式架构快速验证产品
  • 在用户体验要求高的场景尝试端到端方案
  • 关注开源模型的进展,降低部署成本
  • 重视延迟优化,这是语音交互体验的核心

本教程内容为技术分享,所有代码示例均为教学目的编写。实际部署时请参考各项目的官方文档。

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录