如何搭建一个AI实时对话功能,可以模拟大模型对话结合TTS和ASR,集成腾讯ASR和TTS实现语音对话交互功能方案

一、当前实现分析

通过查看代码,我发现当前系统已经实现了以下功能:

  • 使用WebRTC的getUserMedia获取音频流
  • 通过AudioContext和脚本节点处理音频数据
  • 使用WebSocket与后端进行实时通信
  • 实现了音频录制、发送和播放功能
  • 具备消息列表展示对话内容的UI

二、集成腾讯ASR和TTS的方案

1. 系统架构调整

需要在现有架构基础上增加与腾讯云服务的交互层:

2. 消息处理流程修改

修改后端消息处理逻辑:

  1. 接收前端发送的音频数据
  2. 调用腾讯云ASR服务进行语音识别
  3. 将识别结果发送给大模型(如有)
  4. 接收大模型返回的文本响应
  5. 调用腾讯云TTS服务将文本转换为音频
  6. 将音频数据发送回前端播放

3. 错误处理和优化

  • 添加网络异常处理
  • 实现音频数据缓存和重传机制
  • 优化WebSocket连接稳定性
  • 增加日志记录以便调试

三、实现方案

1、语音录制与发送逻辑

1. 初始化音频环境

this.play = true
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 })
this.scriptNode = this.audioCtx.createScriptProcessor(4096, 1, 1)


- 创建 AudioContext 对象:这是Web Audio API的核心,用于处理音频操作
- 设置采样率为16000Hz:这是语音识别常用的采样率
- 创建 ScriptProcessorNode :用于处理音频数据,参数分别表示缓冲区大小、输入声道数、输出声道数

2. 获取用户媒体设备
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  navigator.mediaDevices
    .getUserMedia({ audio: true, video: false })
    .then((stream) => {
      this.mediaStack = stream
      this.source = this.audioCtx.createMediaStreamSource(stream)

      this.source.connect(this.scriptNode)
      this.scriptNode.connect(this.audioCtx.destination)
    })
    .catch(function (err) {
      console.log('err', err)
    })
} else if (navigator.webkitGetUserMedia) {
  // 兼容旧版Chrome浏览器的代码
  // ...
} else {
  console.error("浏览器不支持 getUserMedia");
}

- 使用 getUserMedia API 请求访问用户麦克风
- 获取成功后,创建 MediaStreamSource 节点
- 连接音频源到脚本处理器,再连接到音频目的地(扬声器)
3. 音频数据处理逻辑

this.scriptNode.onaudioprocess = (audioProcessingEvent) => {
  const inputBuffer = audioProcessingEvent.inputBuffer
  const inputData = inputBuffer.getChannelData(0)
  const volume = inputData.reduce((sum, sample) => sum + Math.abs(sample), 0) / inputData.length
  const silenceThreshold = 0.018

  // 静音检测逻辑 ----检查音量的阈值
  // 进行一段话的收音结束操作
  if (volume < silenceThreshold) {
  
  } else {
   
    this.float32ArrayToBase64Wav(inputBuffer).then((base64Data) => {
      this.audioChunks.push(inputData)
      this.sendAudioBufferAppend(base64Data)
    })
  }
}

3.1 音量检测


-silenceThreshold :静音阈值,低于此值被认为是静音

 3.2 声音处理


- 格式为wav格式转化成base64发给后端进行转译

    async float32ArrayToBase64Wav(inputBuffer) {
      // const interleaved = new Float32Array(inputData);
      const channelData = [];
      for (let i = 0; i < inputBuffer.numberOfChannels; i++) {
        channelData.push(inputBuffer.getChannelData(i));
      }

      const interleaved = interleave(channelData);
      const pcmData = floatTo16BitPCM(interleaved);
      const wavBuffer = encodeWAV(pcmData, inputBuffer.sampleRate, inputBuffer.numberOfChannels);
      const wavBlob = new Blob([wavBuffer], { type: 'audio/wav' });

      // 处理生成的 WAV Blob,例如下载
      // const url = URL.createObjectURL(wavBlob);
      // const a = document.createElement('a');
      //     a.href = url;
      //     a.download = 'output.wav';
      //     a.click();
      //     URL.revokeObjectURL(url);

      const reader = new FileReader();
      reader.readAsDataURL(wavBlob);
      return new Promise((resolve) => {
        reader.onloadend = () => {
          resolve(reader.result.split(',')[1]);
        };
      });
    },

- 当检测到声音时,取消静音定时器
- 如果当前处于语音段结束状态,则重置缓冲区并返回
- 调用 float32ArrayToBase64Wav 将音频数据转换为Base64编码的WAV格式
- 将转换后的音频数据通过 WebSocket通信 发送到服务器

2、语音获取与播放逻辑

1.通过WebSocket通信 获取服务器传递的数据
 this.webSocketService = new WebSocketService(webSocketApiUrl); 
// 初始化WebSocket连接
// 进行获取后端发送过来的base64音频数据,其中有文字和音频
      this.webSocketService.onmessage((data) => {

})

 2. 音频数据解码
// 将Base64编码的音频数据转换为ArrayBuffer
const arrayBuffer = this.base64ToArrayBuffer(base64Data);
// 解码ArrayBuffer为AudioBuffer
const audioBuffer = await this.audioCtx.
decodeAudioData(arrayBuffer);
    base64ToArrayBuffer(base64) {
      const binaryString = atob(base64);
      const len = binaryString.length;
      const bytes = new Uint8Array(len);
      for (let i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
      }
      return bytes.buffer;
    },

3. 音频源创建与播放
// 创建音频缓冲区源节点
const source = this.audioCtx.createBufferSource();
// 将音频源和缓冲区添加到播放队列
this.audioBufferList.push({ source, audioBuffer }); 
this.playNextAudio()


createBufferSource() 创建一个用于播放音频缓冲区的节点
将创建的音频源和缓冲区对象存储在 audioBufferList 数组中,调用 playNextAudio 方法开始播放


 

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐