FFmpeg + nginx + rtmp 实现推流

项目目录:/www/ffmpeg
环境要求:ffmpeg + nginx + php + nginx-rtmp-module
H5直播要求:需运行在线上服务器,并申请SSL证书 ( getUserMedia需要https支持 )
目前FFmpeg推流及H5录制已初步实现,但是FFmpeg如何实时推送H5录制的视频,还未实现

rtmp 安装 及配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#1. 编辑 nginx.conf
vi /usr/local/nginx/conf/nginx.conf

#2. 尾部添加配置
#协议名称
rtmp {
#服务器相关配置
server {
#监听的端口,rtmp协议的默认端口号是1935
listen 1935;
chunk_size 4000;

#rtmp推流请求路径
application hls {
#开启实时
live on;
#不记录数据
record off;
}
}
}

#3. 重启nginx
systemctl restart nginx

#4. 开放1935端口
iptables -I INPUT 4 -p tcp -m state --state NEW -m tcp --dport 1935 -j ACCEPT
service iptables save
配置 HLS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#1. 编辑 nginx.conf
vi /usr/local/nginx/conf/nginx.conf

#2. 修改 rtmp 配置
#协议名称
rtmp {
#服务器相关配置
server {
#监听的端口,rtmp协议的默认端口号是1935
listen 1935;
chunk_size 4000;

#rtmp推流请求路径
application hls {
#开启实时
live on;
#不记录数据
record off;

hls on;
#视频流文件目录
hls_path /data/ffmpeg/hls/test;
hls_fragment 3s;
}
}
}

#3. 修改 server 的配置
server {
#加入hls支持
location /hls {
types {
application/x-mpegURL
video/mp2t ts;
#application/vnd.apple.mpegurl m3u8;
}
alias /data/ffmpeg/hls/test;
expires -1;
add_header Cache-Control no-cache;
}
}

#4. 重启nginx
systemctl restart nginx
FFmepg 推流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#第一种:基于已有视频
1. 将 test.mp4 上传至 /data/ffmpeg 目录下
2. ffmpeg 目录下新建 hls 目录

#第二种:基于可用的 rtmp 直播源
1. rtmp://live.hkstv.hk.lxdns.com/live/hks
2. rtmp://live.hkstv.hk.lxdns.com/live/hks1
3. rtmp://live.hkstv.hk.lxdns.com/live/hks2

#推流
ffmpeg -re -i /data/ffmpeg/test.mp4 -vcodec copy -f flv rtmp://127.0.0.1:1935/hls/test
ffmpeg -re -i rtmp://live.hkstv.hk.lxdns.com/live/hks -vcodec copy -f flv rtmp://127.0.0.1:1935/hls/test
#若视频没有声音,则使用以下命令推流
ffmpeg -re -i /data/ffmpeg/test.mp4 -acodec copy -vcodec copy -f flv rtmp://127.0.0.1:1935/hls/test
ffmpeg -re -i rtmp://live.hkstv.hk.lxdns.com/live/hks1 -acodec copy -vcodec copy -f flv rtmp://127.0.0.1:1935/hls/test
HTML 实时播放 index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<!--<link href="https://unpkg.com/video.js/dist/video-js.css" rel="stylesheet">-->
<link href="video-js.css" rel="stylesheet">

<!--<script src="https://unpkg.com/video.js/dist/video.js"></script>-->
<!--<script src="https://unpkg.com/@videojs/http-streaming/dist/videojs-http-streaming.js"></script>-->
<script src="video.js"></script>
<script src="videojs-http-streaming.js"></script>
</head>
<body>
<video-js id="my-video" controls preload="auto" width="640" height="268">
<source src="/hls/test.m3u8" type="application/x-mpegURL" />
</video-js>
<script>
var player = videojs('my-video');
</script>
</body>
</html>

H5 录制视频并通过websocket上传,服务端将上传的数据保存为视频

  1. nginx 的 SSL/PHP 配置
  2. composer require workerman/workerman
  3. websocket 及 nginx 反向代理配置
  4. worker.php、recording.html 都在 /www/ffmpeg 目录下
第一步:服务端监听 8181 端口 worker.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

use Workerman\Worker;
use Workerman\Protocols\Websocket;

require_once '../vendor/autoload.php';

$context = array(
'ssl' => array(
'local_cert' => '/usr/local/nginx/conf/cert/cert.pem',
'local_pk' => '/usr/local/nginx/conf/cert/cert.key',
'verify_peer' => false,
)
);
$worker = new Worker('websocket://0.0.0.0:8181', $context);
$worker->transport = 'ssl';

$worker->onConnect = function($connection)
{
$connection->onWebSocketConnect = function($connection , $httpHeader)
{
// 二进制数据,则需要指定传输的数据类型
$connection->websocketType = Websocket::BINARY_TYPE_ARRAYBUFFER;
};
};

/** @var Workerman\Connection\TcpConnection $connection */
$worker->onMessage = function($connection, $data)
{
// 接收websocket传递的视频流并保存为webm
file_put_contents('test.webm', $data, FILE_APPEND);
};

Worker::runAll();
第二步:nginx 反向代理配置 websocket (完整的SSL配置)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#编辑配置文件
vi /usr/local/nginx/conf/nginx.conf

server {
listen 443 ssl;
server_name www.xxx.com;

ssl_certificate cert/cert.pem;
ssl_certificate_key cert/cert.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

location / {
root /www/ffmpeg;
index index.html index.htm;
}

#加入hls支持
location /hls {
types {
application/x-mpegURL
video/mp2t ts;
}
alias /data/ffmpeg/hls/test;
expires -1;
add_header Cache-Control no-cache;
}

location /wss {
proxy_pass https://127.0.0.1:8181;
proxy_http_version 1.1;
proxy_set_header X-Client-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
}

#重启
service nginx restart
第三步:编写 H5 recording.html
1
2
3
4
H5 的视频录制主要基于以下三个API
getUserMedia - https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia
MediaRecorder - https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder
FileReader - https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title></title>
<style>
body {
padding-top: 40px;
text-align: center;
}
button {
height: 40px;
line-height: 40px;
}
</style>
</head>
<body>
<button id="start">Start Recording</button>
<button id="stop" disabled="">Stop Recording</button>
<hr>
<video controls></video>
<a id="downloadLink" download="mediarecorder.webm" name="mediarecorder.webm" href></a>
<p id="data"></p>
<script>
'use strict';

// 避免重复连接
let lockReconnect = false;
let ws = null;
let wsUrl = 'wss://www.xxx.com/wss/';
createWebSocket(wsUrl);

// 建立websocket连接
function createWebSocket() {
try {
ws = new WebSocket(wsUrl);
initEventHandle();
} catch (e) {
reconnect(wsUrl);
}
}

// 重连
function reconnect() {
if (lockReconnect) {
return;
}

lockReconnect = true;
setTimeout(function () {
createWebSocket(wsUrl);
console.log('正在重连,当前时间:' + new Date());
lockReconnect = false;
}, 3000);
}

// 初始化
function initEventHandle() {
// 连接成功后
ws.onopen = function() {
// 设置发送的信息类型为 ArrayBuffer
ws.binaryType = 'arraybuffer';
console.log('连接成功');
};

// 连接关闭后
ws.onclose = function() {
console.log('关闭连接');
reconnect(wsUrl);
};

ws.onerror = function () {
reconnect();
};
}

var video = document.querySelector('video');
var startBtn = document.querySelector('button#start');
var stopBtn = document.querySelector('button#stop');
var dataElement = document.querySelector('#data');
var downloadLink = document.querySelector('a#downloadLink')
var mediaRecorder;
var chunks = [];

// 开始录制
startBtn.onclick = function () {
let constraints = {
audio: true,
video: {
width: 300,
height: 300,
// 优先前置:user, 优先后置: exact: "environment"
//facingMode: 'user'
facingMode: {exact: "environment"}

}
};
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
video.srcObject = stream;
video.onloadedmetadata = function(e) {
video.play();
};

let timeslice = 10;
mediaRecorder = new MediaRecorder(stream);
// 启动视频捕获,若设置毫秒值,则录制的媒体会分割成一个个单独的区块,而不是一个非常大的整块内容
mediaRecorder.start(timeslice);

// 视频数据捕获之后触发事件,若MediaRecorder.start()设置了timeslice,则dataavailable每隔timeslice毫秒触发一次事件
mediaRecorder.ondataavailable = function(e) {
chunks.push(e.data);

var reader = new FileReader();
// 当读取操作完成时,触发loadend事件,同时result将包含一个ArrayBuffer对象(Blob文件中的数据)
reader.addEventListener('loadend', function() {
// 创建视图
var buf = new Uint8Array(reader.result);
if (reader.result.byteLength > 0) {
ws.send(buf);
}
});
// 读取Blob对象并转化为ArrayBuffer
reader.readAsArrayBuffer(e.data);
};
}).catch(function (error) {
alert('Unable to capture your camera. Please check console logs.');
console.log(error);
});
};

function log(message) {
dataElement.innerHTML = dataElement.innerHTML + '<br />' + message;
}
</script>
</body>
</html>
客户端视频播放页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<link href="https://unpkg.com/video.js/dist/video-js.css" rel="stylesheet">

<script src="https://unpkg.com/video.js/dist/video.js"></script>
<script src="https://unpkg.com/@videojs/http-streaming/dist/videojs-http-streaming.js"></script>
</head>
<body>
<video-js id="my-video" controls preload="auto" width="640" height="268">
<source src="/hls/test.m3u8" type="application/x-mpegURL" />
</video-js>

<script>
var player = videojs('my-video');
</script>
</body>
</html>
测试流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 启动 worker.php
php worker.php start

2. 手机打开链接 https://www.xxx.com/recording.html, 并点击 `Start Recording` 按钮

3. 查看 /www/ffmpeg 目录, 会生成一个新的文件 test.webm 文件 ( H5 录制的内容 )

4. 将 test.webm 通过 ffmpeg 转为 mp4
ffmpeg -i test.webm -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" test.mp4

5. 转换后的 mp4 就可以使用 ffmpeg 推流
ffmpeg -re -i test.mp4 -acodec copy -vcodec copy -f flv rtmp://127.0.0.1:1935/hls/test

6. 访问 https://www.xxx.com/index.html

参考资料

基于H5的摄像头视频数据流采集
基于Nginx搭建RTMP/HLS视频直播服务器
H5直播起航
H5视频直播扫盲


网页播放m3u8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#1. 准备好视频 input.mp4

#2. 将视频文件转为视频编码h.264, 音频编码aac格式的mp4文件 (使用ffprobe查看文件编码方式)
ffprobe input.mp4
#若不是mp4,运行以下命令转换
ffmpeg -i input.mkv -acodec copy -vcodec copy input.mp4

#3. 将mp4转为完整的ts
ffmpeg -i input.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb output.ts

#4. 将ts切片,并生成m3u8文件 (segment_time 隔几秒切一个文件)
ffmpeg -i output.ts -c copy -map 0 -f segment -segment_list play.m3u8 -segment_time 5 output%03d.ts

#5. 新建 index.html 文件,代码如下
<!DOCTYPE html>
<html>
<head>
<link href="https://unpkg.com/video.js/dist/video-js.css" rel="stylesheet">
</head>
<body>
<video-js id="my-video" controls preload="auto" width="640" height="268">
<source src="play.m3u8" type="application/x-mpegURL">
</video-js>

<script src="https://unpkg.com/video.js/dist/video.js"></script>
<script src="https://unpkg.com/@videojs/http-streaming/dist/videojs-http-streaming.js"></script>

<script>
var player = videojs('my-video');
</script>
</body>
</html>
0%