• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

node.js使用ffmpegRTSP转码服务

武飞扬头像
柯
帮助1

因为最近项目有直接连接海康摄像头的实时视频播放需求,海康的子码流是RTSP是不能自己在浏览器中播放,之前也没接触过此方面的技术和需求,特此记录一下,node.js使用ffmpeg将海康的子码流进行转码播放。话不多说,直接上代码

node.js

var express =  require("express");
var expressWebSocket = require("express-ws");
var ffmpeg = require("fluent-ffmpeg");
const fs = require('fs');
const path = require('path');
const http = require('http');
ffmpeg.setFfmpegPath("/opt/homebrew/Cellar/ffmpeg/6.0/bin/ffmpeg");
var webSocketStream = require("websocket-stream/stream");
function localServer() {
    let app = express();
    app.use(express.static(__dirname));
    expressWebSocket(app, null, {
        perMessageDeflate: true
    });
    app.ws("/rtsp/:id/", requestHandle)
    app.listen(8888);
    console.log("express listened")
}
function requestHandle(ws, req) {

    // 这里是针对回放视频的条件参数
    let url = req.query.url;
    if(req.query.starttime && req.query.endtime){
        url = url   '?starttime='   req.query.starttime   '&endtime='   req.query.endtime
    }
    console.log("rtsp url:", url);
    console.log("rtsp params:", req.params);

    if(url.indexOf('rtsp') > -1){
        rtspRequestHandle(url, ws)
    }else {
        videoRequestHandle(url)
    }

}

/**
 * 将视频文件输出为指定码流
 * 并分为主子码流,主码流传给net服务,子码流传给vue
  */
function videoRequestHandle(url){
    // 指定格式主码流配置
    const mainStreamConfig = {
        codec: 'h264',
        bitRate: '3000k',
        resolution: '1280x720',
        fps: 25,
    };

    // 指定格式子码流配置
    const subStreamConfig = {
        codec: 'h264',
        bitRate: '1000k',
        resolution: '640x360',
        fps: 25,
    };

    // 循环处理每个视频文件
    fs.readdir(url, (err, files) => {
        if (err) {
            console.error(err);
            return;
        }

        files.forEach(file => {
            const filePath = path.join(url, file);

            // 使用 fluent-ffmpeg 进行视频编解码处理
            ffmpeg(filePath)
                // 主码流
                .outputOptions(
                    `-c:v ${mainStreamConfig.codec}`,
                    `-b:v ${mainStreamConfig.bitRate}`,
                    `-s ${mainStreamConfig.resolution}`,
                    `-r ${mainStreamConfig.fps}`
                )
                .output(`${filePath}_main.mp4`)
                // 子码流
                .outputOptions(
                    `-c:v ${subStreamConfig.codec}`,
                    `-b:v ${subStreamConfig.bitRate}`,
                    `-s ${subStreamConfig.resolution}`,
                    `-r ${subStreamConfig.fps}`
                )
                .output(`${filePath}_sub.mp4`)
                .on('end', () => {
                    // 处理完成后发送主码流给 .net 服务
                    sendToDotNet(`${filePath}_main.mp4`);

                    // 读取子码流文件并发送给 Vue 客户端
                    const subStream = fs.createReadStream(`${filePath}_sub.mp4`);
                    sendToVue(subStream, `${file}_sub.mp4`);
                })
                .run();
        });
    });
}

function sendToDotNet(filePath) {
    // 将文件以Buffer对象形式读取
    const fileBuffer = fs.readFileSync(filePath);

    // 构造HTTP请求选项
    const options = {
        hostname: 'your.net.service.host',
        port: 80,
        path: '/api/analyze_video',
        method: 'POST',
        headers: {
            'Content-Type': 'application/octet-stream', // 暂时以二进制流方式发送
            'Content-Length': fileBuffer.length,
        }
    };

    // 发送HTTP请求
    const req = http.request(options, res => {
        console.log(`statusCode: ${res.statusCode}`);
        res.on('data', d => {
            process.stdout.write(d);
        });
    });

    req.on('error', error => {
        console.error(error);
    });

    // 将文件数据写入请求主体
    req.write(fileBuffer);
    req.end();
}
// 实现将子码流传输给 Vue 客户端的逻辑代码
// function sendToVue(stream, fileName) {
// }

// 针对摄像头rtsp转码
function rtspRequestHandle(url, ws){
    const stream = webSocketStream(ws, {
        binary: true,
        browserBufferTimeout: 1000000
    }, {
        browserBufferTimeout: 1000000
    });
    try {
        ffmpeg(url)
            .addInputOption('-analyzeduration', '100000', '-max_delay', '1000000')
            // .addInputOption("-rtsp_transport", "tcp", "-buffer_size", "1024000")  // 这里可以添加一些 RTSP 优化的参数
            .on("start", function () {
                console.log(url, "Stream started.");
            })
            .on("codecData", function () {
                console.log(url, "Stream codecData.")
                // 摄像机在线处理
            })
            .on("error", function (err, stdout, stderr) {
                console.log(url, "An error occured: ", err.message);
                console.error(stdout);
                console.error(stderr);
            })
            .on("end", function () {
                console.log(url, "Stream end!");
                // 摄像机断线的处理
            })
            .outputFormat("flv").videoCodec("copy").noAudio().pipe(stream);
    } catch (error) {
        console.log(error);
    }
}

localServer();

vue中调用

实时播放

<template>
  <div class="wrap">
    <video v-show="isShow" class="video" muted autoplay ref="player"></video>
  </div>
</template>

export default {
  name: 'RTSPPlayer',
  props: {},
  data() {
    return {}
  },
  mounted () {
    // 如果浏览器支持flvjs,则执行相应的程序
    if (flvjs.isSupported()) {
      // 准备监控设备流地址
      let url = this.videoUrl
      if(this.videoUrl.indexOf('rtsp') != -1){
        url = localStorage.getItem('VUE_APP_VIDEO_SOCKET')   'hikvision/?url='   this.videoUrl
      }
      
      console.log('ws:url------>', url)
      // 创建一个flvjs实例
      // 下面的ws://localhost:8888换成你搭建的websocket服务地址,后面加上设备流地址
      this.player = flvjs.createPlayer({
        type: 'flv',
        isLive: true,
        url: url
      })
      this.player.on('error', (e) => {
        console.log('transcoding error: ', e)
        return false
      })

     // 将实例挂载到video元素上面
      this.player.attachMediaElement(this.$refs.player)

      try {
        // 开始运行加载 只要流地址正常 就可以在h5页面中播放出画面了
        this.player.load()
        this.player.play()
      } catch (error) {
        console.log('play error: ', error)
      }
    }
  },
  beforeDestroy () {
    // 页面销毁前 关闭flvjs
    this.player.destroy()
  }
};
</script>

历史视频回放

与实时视频播放类似路径不同,需带时间区间参数,这些网上有说明,下面代码仅供参考,具体业务具体分析。
注:这里回放可能视频不会显示,需检查视频后台的视频流格式,需改为H264

getVideo(rtspUrl){
      // 如果浏览器支持flvjs,则执行相应的程序
      if (flvjs.isSupported()) {
        if(rtspUrl.indexOf("rtsp") != -1){
          rtspUrl = localStorage.getItem('VUE_APP_VIDEO_SOCKET')   'hikvision/?url='  
                    rtspUrl.replace("Channels", "tracks").slice(0, -1) 
                      '1&starttime='   this.beginTime   '&endtime='   this.endTime
        }
        console.log('ws:url------>', rtspUrl)
        // 创建一个flvjs实例
        // 下面的ws://localhost:8888换成你搭建的websocket服务地址,后面加上设备流地址
        this.player = flvjs.createPlayer({
          type: 'flv',
          isLive: true,
          url: rtspUrl
        })
        this.player.on('error', (e) => {
          console.log(e)
        })

        // 将实例挂载到video元素上面
        this.player.attachMediaElement(this.$refs.player)

        try {
          // 开始运行加载 只要流地址正常 就可以在h5页面中播放出画面了
          this.player.load()
          this.player.play()
        } catch (error) {
          console.log(error)
        }
      }
    },

后期还会更新使用node-media-server搭建直播流服务
直播流地址已更新
若代码有什么不足可指出优化,有什么疑问,欢迎评论区讨论

这篇好文章是转载于:编程之路

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 编程之路
  • 本文地址: /boutique/detail/tanhhgahac
系列文章
更多 icon
同类精品
更多 icon
继续加载