From c1e116f65d5ce23761a6844781efcd3a11c40a1b Mon Sep 17 00:00:00 2001 From: 86156 <823267011@qq.com> Date: Thu, 2 Oct 2025 15:35:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E9=AB=98=E8=A7=86=E9=A2=91=E5=B8=A7?= =?UTF-8?q?=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/device/component/jpegPlayer.vue | 79 +++++++++---------- .../thread/MediaTransferFlvByJavacv.java | 35 +++----- 2 files changed, 49 insertions(+), 65 deletions(-) diff --git a/rtsp-vue/src/views/video/device/component/jpegPlayer.vue b/rtsp-vue/src/views/video/device/component/jpegPlayer.vue index 13ef47c..0a29e85 100644 --- a/rtsp-vue/src/views/video/device/component/jpegPlayer.vue +++ b/rtsp-vue/src/views/video/device/component/jpegPlayer.vue @@ -41,19 +41,14 @@ export default { methods: { createPlayer(url) { if (!url) { - console.error('❌ 播放地址为空'); return; } // 检查是否是 WebSocket URL if (!url.startsWith('ws://') && !url.startsWith('wss://')) { - console.error('❌ JpegPlayer 只支持 WebSocket URL'); return; } - console.log('🎬 创建 JPEG WebSocket 播放器'); - console.log('📺 WebSocket URL:', url); - // 关闭旧连接 if (this.ws) { this.ws.close(); @@ -67,7 +62,6 @@ export default { this.ws.binaryType = 'arraybuffer'; this.ws.onopen = () => { - console.log('✅ WebSocket 连接已建立'); this.lastFrameTime = Date.now(); }; @@ -76,11 +70,10 @@ export default { }; this.ws.onerror = (error) => { - console.error('❌ WebSocket 错误:', error); + // 静默处理错误 }; this.ws.onclose = () => { - console.log('⚠️ WebSocket 连接已关闭'); this.isPlay = false; }; }, @@ -95,39 +88,46 @@ export default { this.latency = now - this.lastFrameTime; this.lastFrameTime = now; - // 将 ArrayBuffer 转换为 Blob - const blob = new Blob([data], { type: 'image/jpeg' }); - const url = URL.createObjectURL(blob); - - // 创建图片对象 - const img = new Image(); - img.onload = () => { - // 调整 canvas 尺寸 - if (this.canvas.width !== img.width || this.canvas.height !== img.height) { - this.canvas.width = img.width; - this.canvas.height = img.height; - console.log('📐 Canvas 尺寸:', img.width, 'x', img.height); - } - - // 绘制图片 - this.ctx.drawImage(img, 0, 0); + // 使用 createImageBitmap 实现更快的解码(如果浏览器支持) + if (window.createImageBitmap) { + const blob = new Blob([data], { type: 'image/jpeg' }); + createImageBitmap(blob).then(imageBitmap => { + // 只在尺寸变化时调整 canvas + if (this.canvas.width !== imageBitmap.width || this.canvas.height !== imageBitmap.height) { + this.canvas.width = imageBitmap.width; + this.canvas.height = imageBitmap.height; + } + + // 直接绘制 imageBitmap(比 Image 对象更快) + this.ctx.drawImage(imageBitmap, 0, 0); + imageBitmap.close(); // 释放内存 + }).catch(() => { + // 静默处理错误 + }); + } else { + // 降级方案:使用传统的 Image 对象 + const blob = new Blob([data], { type: 'image/jpeg' }); + const url = URL.createObjectURL(blob); + const img = new Image(); - // 释放 URL - URL.revokeObjectURL(url); - - // 只在前几帧输出日志 - if (this.frameCount <= 3 || this.frameCount % 100 === 0) { - console.log(`📹 接收帧 #${this.frameCount}: ${data.byteLength} bytes, 分辨率: ${img.width}x${img.height}`); - } - }; - img.onerror = () => { - console.error('❌ 图片解码失败'); - URL.revokeObjectURL(url); - }; - img.src = url; + img.onload = () => { + if (this.canvas.width !== img.width || this.canvas.height !== img.height) { + this.canvas.width = img.width; + this.canvas.height = img.height; + } + this.ctx.drawImage(img, 0, 0); + URL.revokeObjectURL(url); + }; + + img.onerror = () => { + URL.revokeObjectURL(url); + }; + + img.src = url; + } } catch (error) { - console.error('❌ 处理帧数据失败:', error); + // 静默处理错误 } }, @@ -147,8 +147,7 @@ export default { this.fpsTimer = null; } - // 清空 canvas - if (this.ctx) { + if (this.ctx && this.canvas) { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } } diff --git a/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java b/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java index cfbba74..1215280 100644 --- a/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java +++ b/ruoyi-video/src/main/java/com/ruoyi/video/thread/MediaTransferFlvByJavacv.java @@ -286,13 +286,9 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable totalFrames++; long now = System.currentTimeMillis(); - // 前几帧输出详细日志 - if (totalFrames <= 5 || totalFrames % 100 == 0) { - log.info("📹 抓取帧 #{}: 类型={}, 有图像={}, 有音频={}", - totalFrames, - frame.image != null ? "视频帧" : (frame.samples != null ? "音频帧" : "未知"), - frame.image != null, - frame.samples != null); + // 只在开始时和每1000帧输出一次日志 + if (totalFrames == 1 || totalFrames % 1000 == 0) { + log.info("📹 已抓取 {} 帧", totalFrames); } // 检查是否需要录制视频 @@ -313,8 +309,8 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable } } - // 避免CPU占用过高 - LockSupport.parkNanos(1_000_000L); // 1毫秒 + // 优化:减少延迟,提高推流帧率 + // LockSupport.parkNanos(1_000_000L); // 移除 1ms 延迟,让帧处理更快 } log.warn("⚠️ 主处理循环退出: 总帧数={}, 丢帧数={}", totalFrames, droppedFrames); @@ -343,9 +339,6 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable Map contexts = MediaService.clientConnections.get(mediaKey); if (contexts == null || contexts.isEmpty()) { - if (totalFrames == 1 || totalFrames % 100 == 0) { - log.debug("⚠️ 无客户端连接,跳过推流 (帧 #{})", totalFrames); - } return; } @@ -357,7 +350,6 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable // 转换为JPEG字节 BufferedImage bufferedImage = toImage.convert(frame); if (bufferedImage == null) { - log.warn("❌ 帧转换失败: bufferedImage 为 null (帧 #{})", totalFrames); return; } @@ -365,11 +357,10 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable ImageIO.write(bufferedImage, "jpg", outputStream); byte[] imageBytes = outputStream.toByteArray(); - if (totalFrames <= 3 || totalFrames % 100 == 0) { - log.info("📤 推送帧 #{}: 大小={} bytes, 分辨率={}x{}, 客户端数={}", - totalFrames, imageBytes.length, - bufferedImage.getWidth(), bufferedImage.getHeight(), - contexts.size()); + // 每1000帧输出一次统计 + if (totalFrames % 1000 == 0) { + log.info("📤 推送统计: 已推送 {} 帧, 平均大小={} KB, 客户端数={}", + totalFrames, imageBytes.length / 1024, contexts.size()); } // 推送给所有客户端 @@ -380,20 +371,14 @@ public class MediaTransferFlvByJavacv extends MediaTransfer implements Runnable BinaryWebSocketFrame binaryWebSocketFrame = new BinaryWebSocketFrame( Unpooled.wrappedBuffer(imageBytes)); ctx.writeAndFlush(binaryWebSocketFrame); - } else if (type == ClientType.HTTP) { - // HTTP-FLV 推流 (JPEG 方式不适用,应该推送 FLV 数据) - log.warn("⚠️ HTTP 客户端不支持 JPEG 推流,需要 FLV 格式"); - } else { - log.warn("⚠️ 未知客户端类型: {}", type); } } catch (Exception e) { - log.error("❌ 推流失败 ({}): {}", clientId, e.getMessage()); MediaService.clients.remove(clientId); contexts.remove(clientId); } }); } catch (Exception e) { - log.error("❌ 帧转换失败: {}", e.getMessage(), e); + // 静默处理错误 } }