因为最近项目有直接连接海康摄像头的实时视频播放需求,海康的子码流是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搭建直播流服务
直播流地址已更新
若代码有什么不足可指出优化,有什么疑问,欢迎评论区讨论