node实现简单的websocket服务器,基于ffmpeg将rtmp、rtsp流转码为ws流

【前言】 由于之前使用node-rtsp-stream这个插件每个播放流都需要开启一个端口来实现websocket服务,但是项目有需要播放很多视频所以自己写了一个工具类实现只用一个端口实现多个视频的转码播放

之前的文章:链接: node通过ffmpeg将多路rtsp、rtmp流媒体转换为多端口websocket流供前端播放

直接上代码

1 安装ffmpeg

ffmpeg官网下载:https://ffmpeg.org/
github下载:https://github.com/FFmpeg/FFmpeg

2 node代码(必须要提前安装ffmpeg)

注意 需提前安装FFmpeg,这是转码核心
注意logger 这个是我写的日志工具,实际使用时请把logger 全部删除

安装基本插件
npm i ws
npm i child_process
const WebSocket = require('ws')
const { spawn } = require('child_process')
const logger = require('./log')

class WebSocketServer {
  constructor(port = 20000) {
    this.port = port // WebSocket 服务器端口
    this.ffmpegServerList = [] // 保存所有 FFmpeg 进程以及wss实例的映射关系
    this.wss = null // WebSocket 服务器实例
    this.init(port) // 初始化 WebSocket 服务器
  }
  init(port = this.port) {
    this.wss = new WebSocket.Server({ port }) // 创建 WebSocket 服务器
    logger.log('websocket服务启动') // 打印日志
    // 监听 WebSocket 连接
    this.wss.on('connection', (ws, req) => {
      try {
        const path = decodeURIComponent(req.url)?.split('?')[1]?.split('=')[1] // 获取rtsp/rtmp请求路径
        let ffmpegServer
        let ffmpeg // FFmpeg 进程实例
        // 检查是否已经存在与相同路径的 FFmpeg 进程
        if (this.ffmpegServerList.filter((item) => item.path === path).length > 0) {
          ffmpegServer = this.ffmpegServerList.filter((item) => item.path === path)[0] // 获取当前路径的包含ffmpeg、计数器的object
          ffmpeg = ffmpegServer.ffmpeg // 获取当前路径的ffmpeg实例
          ffmpegServer['activeConnections'] = ffmpegServer?.activeConnections + 1 // 计数器加一,计数器指的是目前调用同一视频流的websocket连接数,类似与两个用户同时观看
        } else {
          // 如果ffmpegServerList不存在任务activeConnections==0,那么创建一个对象并保存至ffmpegServerList中
          console.log('websocket服务进程启动path', path)
          // 启动 FFmpeg 进程
          ffmpeg = spawn('ffmpeg', ['-i', path, '-c:v', 'mpeg1video', '-c:a', 'mp2', '-f', 'mpegts', '-'])
          this.ffmpegServerList.push({
            activeConnections: 1,
            path,
            ws,
            ffmpeg
          })
        }

        // 将 FFmpeg 输出通过 WebSocket 发送
        ffmpeg.stdout.on('data', (data) => {
          // 发送 JSON 消息
          ws.send(data)
        })

        // 监听子进程的标准错误
        ffmpeg.stderr.on('data', (data) => {
          // console.error(`转码异常警告: ${data}`)
        })

        // 监听子进程的关闭事件
        ffmpeg.on('close', (code) => {
          logger.log('ffmpeg关闭,关闭服务:code' + code + ' path:' + path)
          this.ffmpegServerList = this.ffmpegServerList.filter((item) => item.path !== path)
        })
        ffmpeg.on('error', (error) => {
          logger.log('ffmpeg异常终止,关闭服务:' + path + ' error:' + error)
          ffmpeg.kill()
          this.ffmpegServerList = this.ffmpegServerList.filter((item) => item.path !== path)
        })
        ws.on('close', () => {
          console.log('websocket数据传输进程关闭')
          ffmpegServer = this.ffmpegServerList.filter((item) => item.path === path)[0] // 获取当前路径的ffmpeg实例
          if (ffmpegServer) {
            ffmpegServer['activeConnections'] = ffmpegServer?.activeConnections - 1 // 计数器减一
            if (ffmpegServer['activeConnections'] === 0) {
              // 如果计数器为0,则关闭ffmpeg实例
              ffmpeg.kill()
              logger.log('终止一个ffmpeg转码' + ffmpegServer.path)
              this.ffmpegServerList = this.ffmpegServerList.filter((item) => item.path !== path)
            }
          }
        })
      } catch (error) {
        console.error('websocket服务异常,请检查代码(util/WebSocket.js):' + error)
        logger.log('websocket服务异常,请检查代码(util/WebSocket.js)' + error)
      }
    })
  }
  // 清除所有连接
  clear() {
    this.ffmpegServerList.forEach(async (item) => {
      // 关闭 WebSocket 连接
      await item.ws.close()
    })
    // 清空列表
    this.ffmpegServerList = []
  }
  restart() {
    this.clear()
    this.wss.close()
    this.init()
  }
}

module.exports = WebSocketServer

找个项目启动时的文件里加入该代码,注意WebSocket.js路径,port默认20000,自己定义

const WebSocketServer = require('../util/WebSocket.js')
SocketServer = new WebSocketServer(port)

3 前端代码

安装jsmpeg-player插件

npm i jsmpeg-player

设置rtsp-video.vue组件:

说明一下ws://${currentPageIP}:20000/socket?path=${encodeURIComponent(url)}这个中currentPageIP是node服务地址ip,20000是端口号与node服务你写入的一致,url是rtmp和rtsp的地址url

<script setup>
import { defineProps, onMounted, onBeforeUnmount, watch } from 'vue'
/* api */
import { getWebsocketPort } from '@/api/index'
// jsmpeg播放器
import JSMpeg from 'jsmpeg-player'

// import WebSocket from 'ws';
const currentPageIP = window.location.hostname
const props = defineProps({
  url: {
    type: String,
    default: ''
  },
  name: {
    type: String,
    default: 'name1'
  },
  show: {
    type: Boolean,
    default: false
  }
})
let videoDom = null

/* 监听url变化 */
watch(
  () => props.url,
  (newVal) => {
    if (newVal) {
      // 创建 WebSocket 连接
      props.url && initWebSocket(props.url, props.name)
    }
  }
)
const initWebSocket = (url, name) => {
  getWebsocketPort().then((res) => {
    // 获取 canvas 元素
    const canvas = document.getElementById(name)
    // 创建 JSMpeg.Player 实例
    videoDom = new JSMpeg.Player(`ws://${currentPageIP}:20000/socket?path=${encodeURIComponent(url)}`, {
      loop: true,
      autoplay: true,
      audio: false,
      videoBufferSize: 512 * 1024, // 512KB
      canvas
    })
  })
}
onMounted(() => {
  props.url && initWebSocket(props.url, props.name)
})
onBeforeUnmount(() => {
  if (videoDom) {
    videoDom.stop()
    videoDom.destroy()
    console.log('视频连接已关闭')
  }
})
</script>

<template>
  <div class="video-container">
    <canvas :id="props.name" style="width: 100%; height: 100%"></canvas>
    <div class="tooltip">132</div>
  </div>
</template>
<style scoped lang="scss">
.video-container {
  position: relative;
  width: 100%;
  height: 100%;
  .video-buttom {
    display: none;
    .icon {
      padding: 0 15px;
      width: 50px;
      height: 20px;
      cursor: pointer;
    }
  }
  .tooltip {
    width: 100%;
    height: 40px;
    display: none;
    position: absolute;
    top: 20px; /* 置于文本容器下方 */
    right: 20px;
    padding: 0 5px;
    background-color: rgba($color: #3a3a3a, $alpha: 0.6);
    color: #fff;
    border-radius: 3px;
    white-space: normal; /* 允许换行 */
    z-index: 0; /* 确保tooltip在文本容器之下 */
    font-size: 14px; /* 可以根据需要调整 */
    line-height: 20px;
  }
}
</style>

调用

<script setup>
/* 引入videojs样式 */
import 'video.js/dist/video-js.css'

import rtspVideo from '@/components/main/videowall/rtsp-video.vue'
</script>
<template>
	<div style="width: 100%; height: 100%">
            <rtsp-video :url="你的rtmp或rtsp地址" :name="name1"></rtsp-video>
	</div>
</template>

<think>好的,我需要帮助用户使用Node.jsFFmpeg处理多个摄像头RTSP视频并转以便播放。首先,我得回忆一下相关的技术点,比如RTSP协议、FFmpegNode.js如何调用外部进程,以及如何管理多个。 用户提到之前有文章说明如何获取RTSP,所以我可能不需要详细讲这部分,但需要确保他们配置好了媒体服务器,比如Nginx。根据引用[2]和[3],Nginx的RTMP模块可能有用,或者使用node-media-server这样的库。我需要考虑如何处理多个摄像头,可能需要为每个摄像头启动一个FFmpeg进程。 接下来,我需要确保用户正确安装FFmpeg并将其添加到环境变量中,这样Node.js才能调用。然后,在Node.js中,可能需要使用child_process模块来生成FFmpeg命令。每个摄像头的RTSP对应一个FFmpeg实例,转RTMP或HLS等格式,再通过前端播放器如flv.js或video.js播放。 另外,需要考虑如何管理多个进程,防止资源竞争或端口冲突。例如,每个转可能需要不同的输出地址或端口。引用[3]提到node-media-server,可能可以用来作为媒体服务器,接收FFmpeg后的并分发。 可能需要分步骤指导:安装依赖、配置FFmpeg、编写Node.js脚本启动多个FFmpeg进程、设置媒体服务器、前端播放。同时,要处理可能的错误,比如摄像头连接失败或转错误,确保进程能够重启或记录日志。 最后,根据用户需求生成相关问题,比如性能优化、不同协议对比或延迟处理。需要确保引用正确,比如引用[2]关于FFmpeg和Nginx的配置,引用[3]关于node-media-server的使用。</think>以下是通过Node.jsFFmpeg处理多摄像头RTSP视频实现方案: ### 一、技术架构 $$程图$$ 摄像头RTSPNode.js进程管理 → FFmpeg媒体服务器分发 → 前端播放器渲染 [^2][^3] ### 二、实现步骤 1. **环境准备**: - 安装FFmpeg并配置环境变量(Windows需设置PATH到C:\ffmpeg\bin)[^2] - 安装Node.js环境(v14+) ```bash npm install fluent-ffmpeg node-media-server ``` 2. **核心代实现**: ```javascript const { spawn } = require('child_process'); const NodeMediaServer = require('node-media-server'); // 媒体服务器配置 const nmsConfig = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 60, ping_timeout: 30 } }; // 摄像头配置列表 const cameras = [ { name: 'cam1', rtsp: 'rtsp://admin:123456@192.168.1.101/stream1' }, { name: 'cam2', rtsp: 'rtsp://admin:123456@192.168.1.102/stream2' } ]; // 启动媒体服务器 const nms = new NodeMediaServer(nmsConfig); nms.run(); // 处理多路视频 cameras.forEach(cam => { const ffmpeg = spawn('ffmpeg', [ '-rtsp_transport', 'tcp', '-i', cam.rtsp, '-c:v', 'libx264', '-preset', 'ultrafast', '-tune', 'zerolatency', '-f', 'flv', `rtmp://localhost/live/${cam.name}` ]); ffmpeg.stderr.on('data', (data) => { console.error(`[${cam.name}] FFmpeg错误: ${data}`); }); ffmpeg.on('close', (code) => { console.warn(`[${cam.name}] FFmpeg进程终止,重启中...`); // 可添加自动重启逻辑 }); }); ``` 3. **前端播放示例**: ```html <script src="https://cdn.bootcss.com/flv.js/1.5.0/flv.min.js"></script> <video id="cam1" controls></video> <script> if (flvjs.isSupported()) { const player = flvjs.createPlayer({ type: 'flv', url: 'ws://your-server:8000/live/cam1.flv' }); player.attachMediaElement(document.getElementById('cam1')); player.load(); player.play(); } ``` ### 三、关键优化点 1. 使用`-rtsp_transport tcp`参数增强RTSP连接稳定性 2. 采用`zerolatency`编参数降低延迟至0.5-1秒 3. 通过`node-media-server`实现低延迟的WS-FLV播放(相比RTMP协议延迟降低30%)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PlanAPlanB

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值