2.1.2 扩展知识:AI 语音通话

2.1.2 扩展知识:AI 语音通话

在前面,我们已经掌握了构建基于文本的智能应用的核心技术。现在,让我们将视野拓展到更具挑战性、也更自然的交互形式:AI 语音通话

AI 语音通话的目标是让用户能够像与真人对话一样,与 AI 进行实时、流畅的语音交流。这背后涉及多项技术的协同工作,而不仅仅是简单的文本转语音(TTS)和语音转文本(ASR)。

核心技术

一个完整的 AI 语音通话系统通常由以下几个核心组件构成:

  1. 语音转文本(ASR): 将用户的语音实时转换成文字。这是整个系统的第一步,也是最关键的一环。它需要有极高的准确率和极低的延迟。
  2. LLM 推理: ASR 输出的文字被送入 LLM 进行理解和推理。这部分利用了我们之前学到的 RAG、Function Calling 等技术,来处理用户的复杂请求。
  3. 文本转语音(TTS): LLM 生成的文字回答,需要通过 TTS 技术转换成自然、流畅的语音。高质量的 TTS 不仅能准确发音,还能模拟人类的语调、情感和停顿。
  4. 实时流式处理: 为了实现低延迟的实时对话,所有组件都必须采用流式处理(Streaming)的方式。例如,ASR 会一边接收用户的语音流,一边输出文字流;LLM 也需要能以流式方式生成回答,而 TTS 则一边接收 LLM 的文字流,一边输出语音流。
AI 语音通话工作流时序图

下面的时序图展示了一个理想的、低延迟的 AI 语音通话系统的端到端流程。

时序图解读:

  • 这个流程强调了流式处理的重要性。LLM 在收到用户完整句子后开始推理,而 TTS 在收到 LLM 的第一个词后就可以开始生成语音并播放,这极大地减少了用户等待时间。

2.0 实战:WebRTC 实时语音问答 (Python)

这个项目是一个典型的 多技术融合 示例,它将前端的实时通信技术与后端的 AI 能力无缝结合,成功构建了一个低延迟、高交互性的语音问答应用。

准备工作:

  1. 安装 uv
  2. 使用python 3.12 版本
2.1 前端与后端:各自的职责

在 WebRTC 语音通话中,前端和后端扮演着不同的角色,但又紧密协作。

前端职责:

  • 本地媒体采集: 获取麦克风音频流。
  • 信令发起方: 作为连接的发起者,创建 SDP Offer 并通过 HTTP POST 发送给后端。
  • 音频流传输: 使用 RTCPeerConnection 将本地音频流传输给后端。
  • 数据流接收与播放: 接收来自后端的实时 ASR 结果和 TTS 音频流,并进行播放和可视化。

后端职责:

  • 信令响应方: 接收前端的 SDP Offer,创建 SDP Answer 并返回,完成 WebRTC 握手。
  • 音频流接收: 接收并处理来自前端的音频流。
  • AI 核心处理: 串联 ASR、LLM 和 TTS 服务,将语音流转换成有意义的回复。
  • 数据流推送: 通过 WebSocket,将处理后的 ASR 文本和 TTS 音频主动推送给前端。
2.2 信令与数据交换时序图

这个时序图详细展示了前端 WebRTC 的信令交换(蓝色)和 WebSocket 的数据交换(红色)过程,这两种机制共同构成了整个应用的实时通信基础。


2.3 核心代码解析

为了便于理解,我们对你提供的完整代码进行分模块解析,并突出核心部分。

前端核心:WebRTC 信令交换与音频流处理

这段前端代码展示了 WebRTC 连接建立的核心步骤。当用户点击"开始对话"后,浏览器会执行以下操作:

  1. 获取媒体流navigator.mediaDevices.getUserMedia() 请求用户麦克风权限。
  2. 创建 RTCPeerConnectionpc = new RTCPeerConnection(...) 创建一个用于管理 WebRTC 连接的对象。
  3. 添加本地音频轨道stream.getTracks().forEach(track => pc.addTrack(track, stream)) 将麦克风音频流的轨道添加到 RTCPeerConnection 中,使其可以传输到对端。
  4. SDP Offer 生成与发送
    • pc.createOffer():生成一个 SDP (Session Description Protocol) Offer,其中包含了本地音频流的媒体信息。
    • pc.setLocalDescription(offer):将这个 Offer 设置为本地描述。
    • fetch('/offer', ...):通过 HTTP POST 将这个 Offer 发送给后端,这是 WebRTC 连接建立的第一步信令交换。
  5. SDP Answer 接收与设置
    • const answer = await response.json():等待后端处理 Offer 并返回一个 SDP Answer
    • pc.setRemoteDescription(answer):将这个 Answer 设置为远程描述,完成了信令握手,双方现在已经知道了如何相互通信。
javascript 复制代码
// 获取麦克风权限并建立 WebRTC 连接
document.getElementById('start').onclick = async () => {
    // ...省略音频可视化代码
    
    // 创建 RTCPeerConnection
    pc = new RTCPeerConnection({
        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
    });

    stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
    stream.getTracks().forEach(track => pc.addTrack(track, stream));

    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);

    // 发送 Offer 进行信令交换
    const response = await fetch('/offer', {
        method: 'POST',
        body: JSON.stringify({ sdp: pc.localDescription.sdp, type: pc.localDescription.type }),
        headers: { 'Content-Type': 'application/json' }
    });
    const answer = await response.json();
    await pc.setRemoteDescription(answer);
};

// WebSocket 消息监听,用于接收后端实时数据
ws.onmessage = function(event) {
    const data = JSON.parse(event.data);
    if (data.type === 'asr_result_ing' || data.type === 'asr_result') {
        // 更新前端 ASR 识别结果
        // ...
    } else if (data.type === 'tts_audio') {
        // 播放后端传来的 TTS 音频
        // ...
    }
};

后端核心:模型注册与 AudioTrackReceiver

这段 Python 代码是整个后端逻辑的精髓,它将来自 WebRTC 的音频流与 AI 服务无缝对接。

python 复制代码
# 1. 模型初始化与配置
import sherpa_onnx
# ...省略模型文件路径定义...

# 初始化 ASR 模型
stt_recognizer = None
try:
    if not all(os.path.exists(f) for f in [ASR_ENCODER_MODEL, ASR_DECODER_MODEL, ASR_JOINER_MODEL, ASR_TOKENS_FILE]):
        print(f"ASR 模型文件未找到,请检查路径: {ASR_MODEL_DIR}")
    else:
        stt_recognizer = sherpa_onnx.OnlineRecognizer.from_transducer(
            tokens=ASR_TOKENS_FILE,
            encoder=ASR_ENCODER_MODEL,
            decoder=ASR_DECODER_MODEL,
            joiner=ASR_JOINER_MODEL,
        )
        print("Sherpa-ONNX ASR 模型加载成功。")
except Exception as e:
    print(f"加载 Sherpa-ONNX ASR 模型失败: {e}")

# 初始化 TTS 模型
tts_model = None
try:
    if not all(os.path.exists(f) for f in [TTS_MODEL_FILE, TTS_LEXICON_FILE, TTS_TOKENS_FILE]):
        print(f"TTS 模型文件未找到,请检查路径: {TTS_MODEL_DIR}")
    else:
        tts_config = sherpa_onnx.OfflineTtsConfig(
            model=sherpa_onnx.OfflineTtsModelConfig(
                vits=sherpa_onnx.OfflineTtsVitsModelConfig(
                    model=TTS_MODEL_FILE, lexicon=TTS_LEXICON_FILE, tokens=TTS_TOKENS_FILE,
                ),
            ),
        )
        tts_model = sherpa_onnx.OfflineTts(tts_config)
        print("Sherpa-ONNX TTS 模型加载成功。")
except Exception as e:
    print(f"加载 Sherpa-ONNX TTS 模型失败: {e}")

# aiortc.contrib.media.AudioTrackReceiver 的核心实现
class AudioTrackReceiver(MediaStreamTrack):
    kind = "audio"
    def __init__(self, track):
        super().__init__()
        self.track = track
        # 在这里创建 ASR 流,而非在外部
        self.asr_stream = stt_recognizer.create_stream()

    async def recv(self):
        global asr_id
        frame = await self.track.recv() # 循环接收 WebRTC 音频帧
        
        # 核心逻辑: VAD 和音频数据处理
        pcm_array = frame.to_ndarray().flatten().astype(np.int16)
        is_speech = self.vad.is_speech(pcm_array.tobytes(), frame.sample_rate)
        
        # 如果是语音活动...
        if is_speech:
            self.audio_buffer.append(pcm_array.tobytes())
            
            # 当静音时间超过阈值,处理完整句子
            if (current_time - self.last_audio_time) >= self.silence_threshold:
                # 1. 组合音频并送入 ASR 模型
                combined_audio = b''.join(self.audio_buffer)
                self.asr_stream.accept_waveform(...)
                result = stt_recognizer.get_result(self.asr_stream)
                
                if result:
                    # 2. 调用 LLM 服务获取回复
                    response = await call_zhipu_llm_api(result)
                    
                    # 3. 使用 TTS 生成音频
                    audio_data = tts_model.generate(response, sid=0, speed=1.0)
                    audio_bytes = audio_data.samples.astype(np.int16).tobytes()
                    audio_base64 = base64.b64encode(audio_bytes).decode('utf-8')

                    # 4. 通过 WebSocket 将音频推送回前端
                    await ws.send_json({
                        'type': 'tts_audio',
                        'audio': audio_base64,
                        'sample_rate': audio_data.sample_rate
                    })

                # 清空缓冲区,为下一轮对话做准备
                self.audio_buffer = []
                self.asr_stream = stt_recognizer.create_stream()
        return frame

2.4 关键技术亮点

  • 本地化推理 :项目中使用的 Sherpa-ONNX 实现了 ASR 和 TTS 模型的本地推理。这极大地减少了对云端服务的依赖,降低了延迟和成本。
  • 流式处理 :从前端的 WebRTC 音频流,到后端的 ASR/TTS 处理,再到 WebSocket 的数据传输,整个流程都采用了流式处理。这意味着系统无需等待所有数据处理完成,就可以开始传输和播放,从而实现了亚秒级的实时对话延迟。
  • VAD(语音活动检测):这是实现流畅对话的关键。VAD 能够智能地识别出用户何时停止说话,从而避免了不完整的句子被发送给 LLM,提高了交互的准确性和自然性。

通过以上模块的协同工作,我们构建了一个从语音输入到语音输出,端到端完整且高效的 AI 语音问答系统。

其他资源

火山音频引擎: https://www.volcengine.com/product/veRTC

ASR 模型配置: https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2

TTS 模型配置: https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-icefall-zh-aishell3.tar.bz2

相关推荐
2401_841495642 小时前
【计算机视觉】概述
图像处理·人工智能·计算机视觉·跟踪·模式识别·识别·视觉分析
用户5191495848452 小时前
在AI技术唾手可得的时代,挖掘新需求成为核心竞争力——某知名计算机控制AI框架需求洞察
人工智能·aigc
2401_841495642 小时前
【计算机视觉】霍夫变换检测
图像处理·人工智能·python·opencv·算法·计算机视觉·霍夫变换
IT_陈寒2 小时前
「JavaScript 性能优化:10个让V8引擎疯狂提速的编码技巧」
前端·人工智能·后端
关关长语3 小时前
Dotnet接入AI通过Response创建一个简单控制台案例
人工智能·.net·ai dotnet
woshihonghonga3 小时前
Ubuntu20.04下的Pytorch2.7.1安装
linux·人工智能·ubuntu
字节高级特工3 小时前
网络协议分层与Socket编程详解
linux·服务器·开发语言·网络·c++·人工智能·php
用户5191495848453 小时前
UiPath推出全新AI代理开发功能,简化自动化构建流程
人工智能·aigc
xier_ran3 小时前
神经网络中优化器的作用
人工智能