1365 字
4 分钟
WebSocket实时视频流传输技术实践
WebSocket实时视频流传输技术实践:从连接建立到画质控制
概述
本文将详细介绍一个物联网设备管理平台中WebSocket实时视频流传输功能的开发历程。通过四个关键Git提交的演进,我们将深入探讨WebSocket连接建立、图像质量控制、移动端适配以及连接模式优化等技术细节。
系统架构背景
该项目是一个基于Vue3 + TypeScript的物联网设备管理平台,需要实现以下核心功能:
- 实时视频流监控
- 温湿度传感器数据展示
- 设备管理与控制
- 支持桌面端和移动端双端访问
视频流传输采用WebSocket协议,实现低延迟的实时图像传输。
开发阶段详解
阶段一:WebSocket连接基础实现
主要变更:
- 实现WebSocket连接的基础架构
- 添加设备状态管理
- 配置Vite开发服务器代理
- 修改6个文件,新增404行代码
技术实现:
- WebSocket连接管理:
// 实时监控图片帧const currentFrameImage = ref<string>('');const lastFrameTime = ref<string>('');const connectionError = ref<string>('');const isStreamDisconnected = ref<boolean>(false);const currentFps = ref<number>(0);let lastFrameTimestamp = 0;let previousFrameTimestamp = 0;let streamCheckInterval: number | null = null;let ws: WebSocket | null = null;let currentImageUrl: string = '';- 连接建立流程:
const startRealtimeMonitoring = async () => { // 关闭旧的连接 stopRealtimeMonitoring();
if (!selectedDeviceId.value) { console.log('没有选择设备,无法启动实时监控'); return; }
try { // 先刷新token let token = await refreshToken(); if (!token) { connectionError.value = '未登录,请先登录'; return; }
// 建立WebSocket连接 const wsUrl = `/api/stream/viewer/ws/${selectedDeviceId.value}?token=${token}`; ws = new WebSocket(wsUrl);
// 监听连接打开事件 ws.onopen = () => { console.log('WebSocket连接已建立'); connectionError.value = ''; lastFrameTimestamp = Date.now(); isStreamDisconnected.value = false;
// 启动定时器检查图片流是否更新 streamCheckInterval = window.setInterval(() => { const now = Date.now(); if (now - lastFrameTimestamp > 2000 && currentFrameImage.value) { isStreamDisconnected.value = true; } }, 2500); }; // ... 其他事件监听 }};- 图片数据处理:
const handleFrameData = (data: any) => { try { let blob: Blob; if (typeof data === 'string') { // base64编码的图片数据 blob = base64ToBlob(data); } else if (data instanceof Blob) { blob = data; } else if (data instanceof ArrayBuffer) { blob = new Blob([data], { type: 'image/jpeg' }); } else if (data instanceof Uint8Array) { blob = new Blob([data as any], { type: 'image/jpeg' }); } else { throw new Error('不支持的数据类型'); }
// 释放旧的URL对象 if (currentImageUrl) { URL.revokeObjectURL(currentImageUrl); }
// 创建新的URL currentImageUrl = URL.createObjectURL(blob); currentFrameImage.value = currentImageUrl; lastFrameTime.value = new Date().toLocaleTimeString('zh-CN');
// 计算帧率 const now = Date.now(); if (previousFrameTimestamp > 0) { const frameInterval = now - previousFrameTimestamp; currentFps.value = Math.round(1000 / frameInterval); } previousFrameTimestamp = now; lastFrameTimestamp = now; isStreamDisconnected.value = false; } catch (error) { console.error('处理图片数据失败:', error); }};- Vite代理配置:
server: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, ws: true, // 启用WebSocket代理 }, },}关键挑战与解决方案:
| 挑战 | 解决方案 |
|---|---|
| WebSocket连接不稳定 | 实现自动重连机制和连接状态检测 |
| 内存泄漏 | 及时释放旧的Blob URL对象 |
| 多种数据格式支持 | 使用类型判断处理base64/Blob/ArrayBuffer/Uint8Array |
| 帧率计算 | 通过时间戳差值计算实时FPS |
阶段二:图像质量与分辨率控制
主要变更:
- 添加图像质量滑块控制(1-63)
- 实现视频分辨率切换功能
- 新增110行代码
技术实现:
- 画质控制实现:
// 视频质量控制const imageQuality = ref(32); // 默认画质为32const frameSize = ref('FRAMESIZE_VGA'); // 默认视频尺寸
// 发送画质设置到WebSocket连接const sendStreamQuality = async (quality: number) => { if (!ws || ws.readyState !== WebSocket.OPEN) { console.warn('WebSocket连接未建立,无法发送画质设置'); return; }
const message = JSON.stringify({ code: 1, item: 'camera', key: 'jpeg_quality', values: quality.toString() });
console.log('向ws连接发送画质设置:', message);
try { ws.send(message); } catch (error) { console.error('发送画质设置失败:', error); }};
// 监听滑块变化watch(imageQuality, (newQuality) => { // 双端统一:1-63 -> 63-1(反转值,滑块值越大质量越高) const invertedQuality = 63 - newQuality; sendStreamQuality(invertedQuality);});- 分辨率控制实现:
// 发送视频尺寸设置到WebSocket连接const sendFrameSize = async (size: string) => { if (!ws || ws.readyState !== WebSocket.OPEN) { console.warn('WebSocket连接未建立,无法发送视频尺寸设置'); return; }
const message = JSON.stringify({ code: 1, item: 'camera', key: 'frame_size', values: size });
console.log('向ws连接发送视频尺寸设置:', message);
try { ws.send(message); } catch (error) { console.error('发送视频尺寸设置失败:', error); }};
// 监听视频尺寸变化watch(frameSize, (newSize) => { sendFrameSize(newSize);});- UI界面实现:
<template> <div class="video-controls"> <div class="quality-control"> <span class="quality-label">视频质量</span> <el-slider v-model="imageQuality" :min="1" :max="63" :step="1" :show-tooltip="false" style="width: 120px" /> </div> <div class="quality-control"> <span class="quality-label">视频尺寸</span> <el-select v-model="frameSize" style="width: 120px" size="small"> <el-option label="128x128" value="FRAMESIZE_128X128" /> <el-option label="240x240" value="FRAMESIZE_240X240" /> <el-option label="320x320" value="FRAMESIZE_320X320" /> <el-option label="VGA" value="FRAMESIZE_VGA" /> <el-option label="SVGA" value="FRAMESIZE_SVGA" /> <el-option label="HD" value="FRAMESIZE_HD" /> <el-option label="FHD" value="FRAMESIZE_FHD" /> </el-select> </div> </div></template>技术亮点:
- 使用Vue的
watch监听实现实时响应 - 值反转逻辑(1-63映射为63-1)符合用户直觉(滑块右滑质量越高)
- 支持7种分辨率选项,从128x128到FHD
阶段三:移动端适配
主要变更:
- 为MobileData.vue添加视频控制功能
- 优化移动端UI布局
- 修改187行代码
移动端适配策略:
- 响应式控制面板:
<template> <!-- 移动端视频控制 --> <div class="video-settings"> <div class="setting-item"> <span class="setting-label">画质</span> <el-slider v-model="imageQuality" :min="1" :max="63" :step="1" :show-tooltip="false" size="small" style="flex: 1; margin-left: 8px;" /> </div> <div class="setting-item"> <span class="setting-label">视频尺寸</span> <el-select v-model="frameSize" style="flex: 1; margin-left: 8px;" size="small"> <el-option label="128x128" value="FRAMESIZE_128X128" /> <!-- ... 其他选项 --> </el-select> </div> </div></template>- 移动端布局优化:
- 垂直堆叠布局替代水平排列
- 使用flex布局自适应屏幕宽度
- 按钮尺寸适配触摸操作
移动端特有考虑:
- 网络环境可能不稳定,提供手动重连按钮
- 屏幕尺寸限制,简化控制面板
- 触摸友好的交互设计
阶段四:连接模式优化
主要变更:
- 优化WebSocket连接模式
- 修复设备切换时的连接问题
- 代码量减少10行(优化简化)
新连接模式特点:
- 单设备单连接:
// 新的连接模式:一个WebSocket对应一个设备const wsUrl = `/api/stream/viewer/ws/${selectedDeviceId.value}?token=${token}`;ws = new WebSocket(wsUrl);
// 新的连接模式不需要单独发送请求开启设备推流// 服务器自动根据URL中的设备ID开始推流- 改进的消息处理:
ws.onmessage = (event) => { try { // 检查是否是设备断开的文本消息 if (typeof event.data === 'string' && event.data.includes('设备已断开')) { console.log('收到设备断开消息:', event.data); connectionError.value = '设备已断开'; return; }
// 尝试解析JSON数据(控制命令响应) try { const data = JSON.parse(event.data); if (data.code === 1 && data.key === 'jpeg_quality') { console.log('画质设置成功:', data); } else if (data.code === 1 && data.key === 'frame_size') { console.log('视频尺寸设置成功:', data); } else if (data.code === 400) { console.error('订阅失败:', data.msg); connectionError.value = '订阅失败'; } } catch (jsonError) { // 如果不是JSON数据,处理为二进制图片数据 handleFrameData(event.data); } } catch (error) { console.error('解析WebSocket消息失败:', error); }};- Bug修复:
- 修复设备切换时WebSocket连接未正确关闭的问题
- 优化连接状态管理
- 改进错误处理逻辑
技术架构总结
WebSocket连接生命周期
1. 用户选择设备 ↓2. 获取访问token ↓3. 建立WebSocket连接 (/api/stream/viewer/ws/{deviceId}?token={token}) ↓4. 连接成功,开始接收视频帧 ↓5. 可发送控制命令(画质、分辨率) ↓6. 用户切换设备或离开页面时关闭连接消息协议设计
| 消息类型 | 方向 | 格式 | 说明 |
|---|---|---|---|
| 视频帧数据 | Server → Client | Binary (Blob/ArrayBuffer) | JPEG图像数据 |
| 画质设置 | Client → Server | JSON | {"code":1,"item":"camera","key":"jpeg_quality","values":"32"} |
| 分辨率设置 | Client → Server | JSON | {"code":1,"item":"camera","key":"frame_size","values":"FRAMESIZE_VGA"} |
| 控制响应 | Server → Client | JSON | {"code":1,"msg":"success"} 或 {"code":400,"msg":"error"} |
| 设备断开通知 | Server → Client | Text | ”设备已断开” |
性能优化策略
- 内存管理:
// 释放旧的URL对象防止内存泄漏if (currentImageUrl) { URL.revokeObjectURL(currentImageUrl);}currentImageUrl = URL.createObjectURL(blob);- 连接状态检测:
// 每2.5秒检查一次是否收到新帧streamCheckInterval = window.setInterval(() => { const now = Date.now(); if (now - lastFrameTimestamp > 2000 && currentFrameImage.value) { isStreamDisconnected.value = true; }}, 2500);- 动态画质调整:
- 根据网络状况调整图像质量
- 移动端默认较低画质以节省流量
- 支持实时调整无需重新连接
遇到的挑战及解决方案
挑战1:多种数据格式兼容
问题:WebSocket可能接收base64字符串、Blob、ArrayBuffer等多种格式的图片数据
解决方案:
const handleFrameData = (data: any) => { let blob: Blob; if (typeof data === 'string') { blob = base64ToBlob(data); } else if (data instanceof Blob) { blob = data; } else if (data instanceof ArrayBuffer) { blob = new Blob([data], { type: 'image/jpeg' }); } else if (data instanceof Uint8Array) { blob = new Blob([data as any], { type: 'image/jpeg' }); } // ...};挑战2:移动端性能优化
问题:移动端的网络环境和性能限制
解决方案:
- 默认使用较低分辨率(VGA)
- 提供画质调节滑块,让用户根据网络状况自主选择
- 优化UI布局,减少重绘
挑战3:连接稳定性
问题:WebSocket连接可能因网络波动断开
解决方案:
- 实现连接状态检测(2秒无新帧标记为断开)
- 提供手动重连按钮
- 设备切换时正确关闭旧连接
挑战4:用户直觉与数据映射
问题:画质滑块值越大应该质量越好,但设备接收的值是反的(值越小质量越高)
解决方案:
// 值反转:UI显示1-63(低-高),实际发送63-1watch(imageQuality, (newQuality) => { const invertedQuality = 63 - newQuality; sendStreamQuality(invertedQuality);});总结
通过这四个阶段的迭代开发,我们实现了一个稳定、高效的WebSocket实时视频流传输系统。关键经验包括:
- 渐进式开发:先实现基础连接,再添加控制功能,最后优化体验
- 多端统一:桌面端和移动端共享核心逻辑,仅UI层差异化
- 状态管理:完善的连接状态检测和错误处理机制
- 性能优先:内存管理、连接优化、动态画质调整
这套系统目前支持:
- 实时视频流传输
- 动态画质控制(1-63级)
- 7种分辨率选项
- 双端自适应UI
- 稳定的连接管理
WebSocket实时视频流传输技术实践
http://www.cyanbutterfly.top/posts/websocket_video_stream_development/ 部分信息可能已经过时









