如今的 AI 交互已经不局限于文字问答。用户可以直接开口说话,让 AI 通过摄像头理解眼前的物体,也可以共享屏幕,让 AI 结合正在显示的内容进行解释。
这类应用看起来像"和 AI 打电话",背后却包含音频采集、视频传输、流式播放、视觉理解、语音打断和会话管理等多个环节。
本文围绕两个模型介绍实现思路:
qwen-audio-3.0-realtime-plus:实时语音交流。qwen3.8-omni-flash-realtime:结合音频、摄像头和屏幕画面的实时交流。
文章中的代码为通用实现片段,用于说明关键流程,不包含完整的生产级客户端。
文档核对时间:2026 年 10 月。模型权限、地域、音色、接口参数与价格可能调整,实际接入应以官方文档和控制台为准。
一、两个模型分别负责什么?
1. Qwen-Audio:让 AI 能听、能说
qwen-audio-3.0-realtime-plus 面向实时语音交互,支持音频和文本输入,可返回语音与文字,并支持工具调用。官方模型能力说明
传统语音助手常采用三个服务串接:
用户说话
↓
ASR:声音转文字
↓
LLM:理解问题、生成回答
↓
TTS:文字转声音
使用实时语音模型后,应用主要面对的是持续交换音频的接口:
输入音频流 → 实时语音模型 → 输出音频流
它适合语音问答、口语交流、语音助手等以声音为主要输入的场景。
不过,模型支持实时语音,不代表浏览器自动拥有完整的通话能力。麦克风采集、音频编码、声音播放和打断控制仍然需要应用实现。
2. Qwen-Omni:让 AI 边听边看
qwen3.8-omni-flash-realtime 面向实时音视频交互,支持文本、音频、图像和视频输入,输出文字和语音。官方实时模型说明
它适合以下交互:
- 用户打开摄像头,询问眼前物体的特征。
- 用户展示一份纸质资料,让 AI 结合画面解释。
- 用户演示一个操作,让 AI 根据可见过程交流。
- 用户共享屏幕,询问页面、图片或图表的内容。
这里的"视频聊天"是指:
用户提供摄像头画面,AI 理解画面并通过声音或文字回答。
它不等于 AI 自动生成一个真人视频,也不意味着应用已经具备数字人形象。
3. 如何选择?
| 交互需求 | 模型选择 |
|---|---|
| 只需要实时语音交流 | qwen-audio-3.0-realtime-plus |
| 需要 AI 理解摄像头画面 | qwen3.8-omni-flash-realtime |
| 需要 AI 理解共享屏幕 | qwen3.8-omni-flash-realtime |
| 需要精细读取密集文字或细小数字 | 在实时交流之外,增加高清截图分析流程 |
对于视觉场景,重点不仅是"支持图片",还包括画面质量、上传时机,以及画面是否与当前问题对应。
二、整体架构怎么设计?
以浏览器和 WebSocket 为例,可以采用下面的架构:
浏览器
├─ 麦克风采集
├─ 摄像头采集
├─ 屏幕共享采集
├─ 回复音频播放
└─ 字幕与通话状态
⇅
应用实时网关
├─ 用户认证
├─ 模型鉴权
├─ 音视频事件转发
├─ 工具调用执行
└─ 用量与错误记录
⇅
Qwen Realtime 服务
这是服务端转发方案,并非唯一接入方式。它的好处是长期 API Key 保留在服务端,权限、工具调用和用量统计也便于集中管理。
浏览器不应该持有可长期使用的供应商密钥。可以让浏览器先向业务服务申请短期连接凭证,再连接实时网关。
摄像头和屏幕共享可以复用视觉处理链路,但需要明确当前发送的是哪个画面源。
三、实时语音如何接入?
1. 建立持久连接
实时交流需要持续上传音频,同时接收 AI 回复,因此可以通过 WebSocket 长连接交换事件。
常见事件包括:
| 事件 | 用途 |
|---|---|
session.update |
配置会话参数 |
input_audio_buffer.append |
上传音频片段 |
response.audio.delta |
接收回复音频片段 |
response.done |
一次回复结束 |
基本流程是:
建立连接 → 配置会话 → 发送音频 → 接收并播放回复 → 关闭连接
这些属于实时接口协议,不能直接用普通 Chat Completions 请求代替。Qwen-Audio 接入指南
2. 采集麦克风
浏览器端可以这样获取麦克风:
const microphoneStream =
await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
回声消除尤其重要。否则扬声器播放的 AI 回复可能再次被麦克风采集,造成错误打断或自问自答。
采集后,还需要根据模型协议进行编码:
麦克风音频
↓
AudioWorklet 读取采样
↓
重采样、声道处理
↓
PCM 编码
↓
按协议发送
例如,Qwen-Audio 官方文档中的 PCM 接入规格为:输入 16 kHz、16 位、单声道,输出 24 kHz、16 位、单声道。官方音频格式说明
不同模型应分别核对音频要求,不能把某个模型的采样率配置直接套用到所有模型。
也不能把 MediaRecorder 生成的 WebM/Opus 文件片段直接当作 PCM 上传。
四、实时视频聊天如何实现?
1. 同时获取摄像头和麦克风
实时视频聊天可以通过 getUserMedia() 获取音视频流:
// 在用户点击"开始视频聊天"后调用。
const cameraStream =
await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
facingMode: "user"
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
const preview = document.querySelector("#camera-preview");
preview.srcObject = cameraStream;
preview.muted = true;
preview.playsInline = true;
await preview.play();
这里的 muted 是关闭本地预览的声音,避免用户听到自己的延迟回放,不代表停止向模型发送麦克风音频。
如果希望移动端优先使用后置摄像头,可以把 facingMode 设置为 "environment"。实际设备选择仍应允许用户调整。
2. AI 是如何理解视频的?
视频聊天中,用户的问题和画面需要共同参与理解:
用户说:"这个物体是什么?"
↓
麦克风音频 + 当前摄像头画面
↓
Omni 模型结合声音与视觉内容分析
↓
返回文字和语音回答
摄像头权限成功、页面出现预览,只能证明浏览器拿到了画面。
要让 AI 真正理解视频,还必须把画面送到模型。仅在页面播放本地预览,模型是看不到的。
3. WebSocket 与 WebRTC 的视频传输方式不同
两种接入方式需要区分:
| 接入方式 | 画面传输方式 |
|---|---|
| WebSocket | 从视频中提取图像帧,再发送图像事件 |
| WebRTC | 通过视频轨道传输画面 |
在 WebSocket 路径中,Omni 使用 input_image_buffer.append 接收图像帧;在 WebRTC 路径中,应通过视频轨道发送,不使用这个图像事件。官方音视频输入流程
对于 WebSocket,可以将摄像头视频定时绘制到 Canvas,编码后上传:
摄像头 MediaStream
↓
video 元素
↓
Canvas 提取当前帧
↓
JPEG 编码
↓
Base64
↓
input_image_buffer.append
官方推荐视频输入从每秒一帧开始接入。这个频率适合作为起点,但不是所有任务的最佳值。
如果通过低频抽帧理解快速动作,可能遗漏中间过程。因此不能把"看到了若干帧"理解成"完整观察了每一个动作"。
4. 如何提高摄像头识别效果?
摄像头场景需要关注:
- 光线是否充足。
- 目标是否对焦清晰。
- 物体是否足够大。
- 关键部分是否被遮挡。
- 用户是否快速移动摄像头。
- 画面上传时间是否与提问对应。
如果用户展示的是一页纸,应让文字占据足够大的画面面积。远距离拍摄整张纸,再把视频压缩成小图,细节很容易丢失。
关闭摄像头时,还应停止视频轨道和抽帧任务,不能只隐藏预览组件。
五、屏幕共享与视频聊天有什么区别?
摄像头与屏幕共享的视觉理解链路很接近,主要区别在于图像来源。
| 项目 | 摄像头视频 | 屏幕共享 |
|---|---|---|
| 采集接口 | getUserMedia() |
getDisplayMedia() |
| 内容来源 | 现实场景、物体、纸质资料 | 窗口、标签页、桌面 |
| 常见问题 | 光线、对焦、抖动、遮挡 | 小字体、缩放、滚动、旧画面 |
| 视觉输入 | 摄像头帧 | 屏幕帧 |
屏幕共享的浏览器调用示例:
// 由用户点击按钮触发。
const screenStream =
await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: false
});
浏览器会要求用户选择共享内容。应用不能绕过选择过程读取任意窗口;该接口还要求安全上下文及用户操作触发。MDN:getDisplayMedia
如果共享屏幕时仍要与 AI 说话,可以继续使用单独的麦克风流。共享画面的音轨与麦克风音轨不是同一个概念,也不能假定所有浏览器都能采集共享窗口的声音。
六、摄像头与共享屏幕可以共用抽帧代码
对 WebSocket 方案来说,只要 <video> 已经在播放有效画面,就可以复用同一套抽帧逻辑:
// video:正在播放摄像头或屏幕流的 HTMLVideoElement。
// sendFrame:应用自己的上传函数。
// 此片段只演示抽帧,不包含完整协议与错误处理。
function startFrameCapture(video, sendFrame) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
let stopped = false;
let sending = false;
const timer = setInterval(async () => {
if (
stopped ||
sending ||
!video.videoWidth ||
!video.videoHeight
) {
return;
}
sending = true;
try {
// 示例尺寸,应根据模型限制与识别需求调整。
const scale = Math.min(1, 1280 / video.videoWidth);
canvas.width = Math.round(video.videoWidth * scale);
canvas.height = Math.round(video.videoHeight * scale);
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const image = canvas
.toDataURL("image/jpeg", 0.85)
.split(",")[1];
if (!stopped) {
await sendFrame(image);
}
} finally {
sending = false;
}
}, 1000);
return function stopFrameCapture() {
stopped = true;
clearInterval(timer);
};
}
协议适配层发送:
upstream.send(JSON.stringify({
type: "input_image_buffer.append",
image: base64Image
}));
需要注意发送顺序:官方 WebSocket 流程要求先发送至少一次音频追加事件,再发送图像;画面还要进入正确的对话轮次。不是定时上传图片后,模型就一定会主动分析并持续播报。官方实时接口说明
生产环境还需要补充:
- 根据连接状态决定是否发送。
- 处理 WebSocket 缓冲区积压。
- 丢弃过时帧,避免延迟不断增加。
- 切换画面源时取消旧采集任务。
- 用户停止共享时立即停止发送。
- 对上传大小和格式进行服务端校验。
七、看得到画面,为什么还是可能理解错误?
模型接收到的是经过采集、缩放和编码后的视觉信息。
例如,一张桌面截图中同时包含浏览器工具栏、侧边栏和正文,真正需要分析的文字可能只占很小区域。经过缩放之后,数字、小数点和上下标可能已经无法分辨。
摄像头同样如此:如果纸面反光、文字失焦,即使模型能力很强,也缺少可靠的输入证据。
建议将处理分为两条路径:
实时交流路径
连续语音 + 低频画面
→ 保持交流,理解当前场景
精细分析路径
固定高清截图 + 明确分析区域
→ 读取细节,输出可核对的结论
精细分析时,可以保留整体上下文,同时提供局部高清区域。
还应让模型区分:
- 实际识别到的内容。
- 基于识别内容作出的判断。
- 缺失或看不清的信息。
对于不完整的画面,应要求补充条件,而不是猜测被遮挡或未显示的内容。
八、自然打断为什么容易出现"串音"?
AI 的回复通常分块生成、传输和播放。
当用户开始说话时,系统可能仍有:
- 正在生成的旧回复。
- 网络中尚未到达的旧音频。
- 已经解码但尚未播放的音频。
- 扬声器正在播放的片段。
因此,打断不能只做"停止生成",还需要处理客户端播放状态:
用户开始说话
↓
停止当前播放
↓
清空待播放队列
↓
取消仍在生成的旧回复
↓
丢弃旧回复迟到的音频
Qwen-Audio 提供 response.cancel 取消当前推理,但应用需要判断是否存在正在进行的响应,不能无条件重复发送取消事件。官方客户端事件说明
实现时可以结合响应 ID 和本地播放代次,避免已经被取消的音频重新进入播放队列。
还要区分两个状态:
- 模型已经生成结束。
- 客户端已经播放结束。
收到 response.done,不代表用户已经听完全部声音。
九、字幕为什么应该与音频分开管理?
字幕事件和音频事件的到达时间可能不同。如果简单按照收到事件的先后顺序追加消息,就容易出现顺序错乱。
建议按轮次组织数据:
一个用户轮次
├─ 用户转写
└─ AI 响应
├─ 回复文字
├─ 回复音频
└─ 完成或打断状态
字幕负责展示,音频队列负责播放。
已经播放模型原生音频时,不应再把同一份字幕送入 TTS 播放,否则可能出现重复声音。
因此,删除字幕组件通常不能修复串音。真正需要检查的是响应归属、播放队列和打断逻辑。
十、语音升级为视频时,如何保持上下文?
如果应用最初使用 Audio 模型进行纯语音交流,用户随后开启摄像头,就需要切换到能够处理视觉输入的模型。
一种可行的设计是:
暂停旧连接的输入与播放
↓
建立 Omni 会话
↓
传入必要的文字上下文
↓
开始发送麦克风与画面
↓
恢复交流
上下文可以包括近期问题、必要背景和未完成事项,但不应假设两个模型连接会自动共享记忆。
应用应明确显示"正在切换",避免用户以为画面已经送达,而实际上新连接尚未就绪。
如果一开始就使用 Omni,则可以围绕同一个视觉模型会话管理摄像头和共享屏幕。无论采用哪种方案,都应清晰显示 AI 当前正在查看哪个来源。
十一、上线前应该验证什么?
建议分别验证模型能力、媒体链路和交互体验:
| 验证项 | 关注点 |
|---|---|
| 麦克风输入 | 设备正确、音量正常、编码符合协议 |
| 回复播放 | 连续、无明显丢帧、无重复播放 |
| 摄像头理解 | 问题与当前画面一致 |
| 屏幕理解 | 文字清晰、范围明确、不使用旧画面 |
| 打断 | 旧语音及时停止,不再续播 |
| 画面切换 | 摄像头与屏幕不串源 |
| 网络恢复 | 不重放旧音频、不重复执行操作 |
| 识别质量 | 用人工标注样例检查,而不只看请求成功 |
| 用量记录 | 分别观察音频、视觉和额外分析请求 |
成本优化也应结合任务。静态页面反复上传相同画面,未必比按需提交清晰截图更有效;快速变化的场景则需要评估采样频率是否足以保留关键信息。
结语
使用 qwen-audio-3.0-realtime-plus,可以构建以声音为核心的实时交流;使用 qwen3.8-omni-flash-realtime,可以进一步让 AI 理解摄像头和共享屏幕中的内容。
摄像头视频聊天与屏幕共享,虽然采集入口不同,但都需要把视觉信息与当前语音问题正确关联。
实现一个稳定的实时 AI 应用,需要同时做好音频处理、画面采集、协议传输、响应管理和识别验证。模型负责理解与生成,应用则负责让输入可信、输出有序,并让用户清楚知道 AI 此刻听到了什么、看到了什么。
14:32