Skip to content

HTML5 播放 RTSP/RTMP 方案

RTSP

RTSP(Real-Time Stream Protocol)协议是一个基于文本的多媒体播放控制协议,属于应用层。RTSP 以客户端方式工作,对流媒体提供播放、暂停、后退、前进等操作。该标准由 IETF 指定,对应的协议是 RFC2326。

RTSP 传输的一般是 TS、MP4 格式的流,其传输一般需要 2~3 个通道,命令和数据通道分离。使用 RTSP 协议传输流媒体数据需要有专门的媒体播放器和媒体服务器,也就是需要支持 RTSP 协议的客户端和服务器。

RTMP

RTMP 协议是 Real Time Message Protocol(实时信息传输协议)的缩写,它是由 Adobe 公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing)和分包(packetizing)的问题。

轻量方案,使用 NodeJS 和 ffmpeg 生成 HLS

服务端

javascript
const streamList = [
  {
    url: "rtmp://58.200.131.2:1935/livetv/gxtv",
    name: "gxtv",
  },
];
module.exports = streamList;
const streamList = [
  {
    url: "rtmp://58.200.131.2:1935/livetv/gxtv",
    name: "gxtv",
  },
];
module.exports = streamList;
javascript
const express = require("express");
const path = require("path");
const os = require("os");
const { spawn } = require("child_process");
const cors = require("cors");
const makeDir = require("make-dir");
const streamList = require("./device/streamList");

const platform = os.platform(); // darwin,win32
const ffmpegPath = path.resolve(__dirname, `./ffmpeg/ffmpeg-${platform}`);
console.log("ffmpegPath:", ffmpegPath);

const app = express();
app.use(cors());
app.use(express.static(path.resolve(__dirname, "./media")));

function process(stream) {
  // ffmpeg参数
  let paramList = [
    "-re",
    "-r",
    "25",
    "-i",
    stream.url,
    "-strict",
    "-2",
    "-max_delay",
    "500000",
    "-fflags",
    "flush_packets",
    "-an", // 无音频
    "-flags",
    "-global_header",
    "-hls_time",
    "1",
    "-hls_list_size",
    "10",
    "-hls_wrap",
    "10",
    "-vcodec",
    "copy", // 不重新编码
    "-hls_flags",
    "delete_segments",
    "-y", // 覆盖输出文件
    path.resolve(__dirname, `./media/${stream.name}.m3u8`),
    // "-loglevel",
    // "quiet"
  ];
  if (stream.url.indexOf("rtsp://") === 0) {
    // RTSP私有参数
    let rtspParamList = ["-stimeout", "60" + "000000", "-rtsp_transport", "tcp"];
    paramList = rtspParamList.concat(paramList);
  }
  // 开启切片进程
  let p = spawn(ffmpegPath, paramList, { encoding: "utf-8" });
  // 监听进程输出
  p.stderr.on("data", (data) => {
    const d = data.toString();
    console.log(d);
    if (d.indexOf("unknown") !== -1 || d.indexOf("Operation not") !== -1 || d.indexOf("404 Not Found") !== -1) {
      // 如果出现指定的异常,5s后重启进程
      console.log("异常", stream.url);
      p.kill();
      setTimeout(() => {
        p = process(stream);
      }, 5000);
    }
  });
  return p;
}

function start() {
  makeDir(path.resolve(__dirname, `./media`)).then(() => {
    streamList.forEach((stream) => {
      console.log(stream.url);
      process(stream);
    });
  });
}

start();

let port = 7000;
for (let i = 0; i < streamList.length; i++) {
  // 按照视频数量启动多个端口,防止浏览器在同一域下只支持6个HTTP并发。
  app.listen(port);
  port++;
}
const express = require("express");
const path = require("path");
const os = require("os");
const { spawn } = require("child_process");
const cors = require("cors");
const makeDir = require("make-dir");
const streamList = require("./device/streamList");

const platform = os.platform(); // darwin,win32
const ffmpegPath = path.resolve(__dirname, `./ffmpeg/ffmpeg-${platform}`);
console.log("ffmpegPath:", ffmpegPath);

const app = express();
app.use(cors());
app.use(express.static(path.resolve(__dirname, "./media")));

function process(stream) {
  // ffmpeg参数
  let paramList = [
    "-re",
    "-r",
    "25",
    "-i",
    stream.url,
    "-strict",
    "-2",
    "-max_delay",
    "500000",
    "-fflags",
    "flush_packets",
    "-an", // 无音频
    "-flags",
    "-global_header",
    "-hls_time",
    "1",
    "-hls_list_size",
    "10",
    "-hls_wrap",
    "10",
    "-vcodec",
    "copy", // 不重新编码
    "-hls_flags",
    "delete_segments",
    "-y", // 覆盖输出文件
    path.resolve(__dirname, `./media/${stream.name}.m3u8`),
    // "-loglevel",
    // "quiet"
  ];
  if (stream.url.indexOf("rtsp://") === 0) {
    // RTSP私有参数
    let rtspParamList = ["-stimeout", "60" + "000000", "-rtsp_transport", "tcp"];
    paramList = rtspParamList.concat(paramList);
  }
  // 开启切片进程
  let p = spawn(ffmpegPath, paramList, { encoding: "utf-8" });
  // 监听进程输出
  p.stderr.on("data", (data) => {
    const d = data.toString();
    console.log(d);
    if (d.indexOf("unknown") !== -1 || d.indexOf("Operation not") !== -1 || d.indexOf("404 Not Found") !== -1) {
      // 如果出现指定的异常,5s后重启进程
      console.log("异常", stream.url);
      p.kill();
      setTimeout(() => {
        p = process(stream);
      }, 5000);
    }
  });
  return p;
}

function start() {
  makeDir(path.resolve(__dirname, `./media`)).then(() => {
    streamList.forEach((stream) => {
      console.log(stream.url);
      process(stream);
    });
  });
}

start();

let port = 7000;
for (let i = 0; i < streamList.length; i++) {
  // 按照视频数量启动多个端口,防止浏览器在同一域下只支持6个HTTP并发。
  app.listen(port);
  port++;
}

客户端

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <link rel="stylesheet" href="video-js.min.css" />
    <script src="video.min.js"></script>
  </head>
  <body>
    <video id="v" class="video-js vjs-default-skin" muted>
      <source src="http://127.0.0.1:7000/gxtv.m3u8" type="application/x-mpegURL" />
    </video>
    <script>
      var player = videojs(document.getElementById("v"), {}, function () {
        player.play();
      });
    </script>
  </body>
</html>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <link rel="stylesheet" href="video-js.min.css" />
    <script src="video.min.js"></script>
  </head>
  <body>
    <video id="v" class="video-js vjs-default-skin" muted>
      <source src="http://127.0.0.1:7000/gxtv.m3u8" type="application/x-mpegURL" />
    </video>
    <script>
      var player = videojs(document.getElementById("v"), {}, function () {
        player.play();
      });
    </script>
  </body>
</html>

海康大华设备对接笔记

海康摄像机/硬盘录像机

老版本:

bash
rtsp://username:password@<ipaddress>/<videotype>/ch<number>/<streamtype>
rtsp://username:password@<ipaddress>/<videotype>/ch<number>/<streamtype>

DS-9016HF-ST 的 IP 通道 01 主码流:

bash
rtsp://admin:[email protected]:554/h264/ch33/main/av_stream
rtsp://admin:[email protected]:554/h264/ch33/main/av_stream

DS-9016HF-ST 的模拟通道 01 子码流:

bash
rtsp://admin:[email protected]:554/h264/ch1/sub/av_stream
rtsp://admin:[email protected]:554/h264/ch1/sub/av_stream

老 URL,NVR(>=64 路的除外)的 IP 通道从 33 开始;新 URL,通道号全部按顺序从 1 开始。

新版本(硬盘录像机):

bash
rtsp://username:password@<address>:<port>/Streaming/channels/<id>(?parm1=value1&parm2-=value2…)
rtsp://username:password@<address>:<port>/Streaming/channels/<id>(?parm1=value1&parm2-=value2…)

录像机 172.30.12.170 的通道 1 的子码流。

ID 命名规则:

通道 1 的主码流:101 通道 1 的子码流:102 通道 10 的主码流:1001 通道 10 的子码流:1002 通道 10 的第三码流:1003

bash
rtsp://admin:[email protected]:554/Streaming/channels/102
rtsp://admin:[email protected]:554/Streaming/channels/102

大华摄像机

bash
rtsp://admin:[email protected]:554/cam/realmonitor?channel=1&subtype=1
rtsp://admin:[email protected]:554/cam/realmonitor?channel=1&subtype=1

channel: 通道号,起始为 1。例如通道 2,则为 channel=2。 subtype: 码流类型,主码流为 0(即 subtype=0),辅码流为 1(即 subtype=1)。

最后编辑时间:

Version 4.0 (framework-1.0.0-rc.20)