基于nginx+ffmpeg+vue3+TypeScript在網(wǎng)頁上顯示監(jiān)控的實(shí)時(shí)畫面
共 11808字,需瀏覽 24分鐘
·
2024-04-17 08:49
大廠技術(shù) 高級(jí)前端 Node進(jìn)階
點(diǎn)擊上方 程序員成長指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
一、心路歷程
寫在前面
最近在忙比賽的項(xiàng)目,項(xiàng)目有一個(gè)實(shí)時(shí)預(yù)覽監(jiān)控?cái)z像頭的畫面的需求。按道理說我一個(gè)臭前端不負(fù)責(zé)這一塊的東西,但是沒辦法,時(shí)間緊任務(wù)重,只好硬著頭皮上了。剛做到這一塊的業(yè)務(wù)的時(shí)候,毫無頭緒,因?yàn)閿z像頭用的是海康的,然后就四處查閱文檔,四處碰壁。并且對(duì)攝像頭這一塊完全不了解,花了三天時(shí)間才做出一個(gè)「demo」。如果你的攝像頭配置比較好,如果可以通過各大攝像頭廠商給demo連接上,那就會(huì)省下很多麻煩事。一定一定在做之前看攝像頭的配置,支持什么,不支持什么?。?!因?yàn)闀r(shí)間肥腸的寶貴。
一些必要的認(rèn)知
-
一些常見的流媒體傳輸協(xié)議:「RTSP、RTMP、HLS、HTTP-FLV」
當(dāng)時(shí)我查閱文檔的時(shí)候也很蒙b,這些都是什么啊,這么多協(xié)議,而且都用來干啥的。反正就是一臉懵逼,
經(jīng)過好多番的學(xué)習(xí)。我這邊提供的攝像頭是支持「RTSP」取流的,所以打算在服務(wù)器上通過「ffmpeg」進(jìn)行取流,然后推流到「Nginx」上,「Nginx」將流處理成對(duì)前端友好的傳輸格式「HLS」(「m3u8」格式的文件),然后前端再拉流就行了。這里實(shí)現(xiàn)的流程是這樣的
什么你說什么拉流推流,根本聽不懂誒!
說實(shí)話,我也不懂,我三天的摸索下來,似乎還不能正確的理解推流和拉流。最后我是這么理解的:
「推流」:女主播把畫面推到服務(wù)器上
「拉流」:我點(diǎn)開女主播的直播間,看女主播跳舞(doge)
RTSP協(xié)議
當(dāng)然沒這么簡單,媒體文件的傳輸肯定是要基于某個(gè)協(xié)議進(jìn)行傳輸。由于這里攝像頭提供了RTSP協(xié)議的地址(很多監(jiān)控?cái)z像頭廠商都有的),手機(jī)移動(dòng)攝像頭我不懂。
RTSP協(xié)議,RTSP(實(shí)時(shí)流傳輸協(xié)議)是一個(gè)網(wǎng)絡(luò)控制協(xié)議,用于在線實(shí)時(shí)觀看和控制流媒體服務(wù)器。它的作用類似于流媒體服務(wù)器的遠(yuǎn)程控制 (https://zhuanlan.zhihu.com/p/478736595)這里說的比較清楚。
調(diào)試工具
學(xué)習(xí)了這些知識(shí),我對(duì)視頻流的傳輸漸漸有了一些理解,上文提到我們監(jiān)控?cái)z像機(jī)提供了RTSP流的地址常見攝像機(jī)廠商RTSP地址格式,我們可以通過一些工具去播放這個(gè)流,比如VLC、potPlayer播放器。這里建議使用VLC,因?yàn)樗娴暮茌p量!potPlayers也行,兩個(gè)都是究極好用的媒體播放器
VLC:打開軟件**-->「媒體」-->**打開網(wǎng)絡(luò)串流
potPlayer:瀏覽器-->打開鏈接
這里把他們當(dāng)作調(diào)試工具用就行啦,因?yàn)椴还苁荝TSP流、RTMP流、FLV流、HLS流都能播放。在搭建服務(wù)之前得保證自己的攝像機(jī)正常的在工作。
然后就是重頭戲了,「nginx」和「ffmpeg」。疊個(gè)甲,對(duì)ffmpeg我是第一次用,nginx也僅限于了解,平時(shí)部署項(xiàng)目是寶塔面板一鍵部署的。
ffmpeg
這個(gè)就不多說了(因?yàn)槲艺f不來哈哈哈),是一個(gè)開源的程序庫,通過命令行的方式來使用他的功能,就專門用來處理媒體文件的,這里掛一個(gè)官網(wǎng)的下載地址。如果使用的是寶塔面板,軟件商店就有,一鍵安裝就行。什么?命令行的方式?當(dāng)然,我想,你在找文檔,而且最好是中文文檔。這里也準(zhǔn)備好了ffmpeg中文文檔。
把他當(dāng)作一個(gè)中間工具就行了。
Nginx
這位才是重量級(jí),真正讓服務(wù)跑起來的還得是nginx,因?yàn)椴皇欤緛聿淮蛩阕哌@條路(原本想用Node來搭),到頭來還是避不開Nginx(踩坑過后,嗯Nginx真香)
很重要的一點(diǎn)!一定要給nginx添加rtmp模塊,在這里踩了很多坑,什么模塊安裝不上、配置文件不生效....em反正就踩了很多坑。
二、實(shí)戰(zhàn)
RTSP地址
這邊老師給提供的是海康威視的攝像頭,地址格式是rtsp://攝像頭用戶名:攝像頭密碼@攝像頭ip:rtsp端口號(hào)/h264/ch1/main/av_stream
畫面測(cè)試
有了上文的地址,可以先在vlc和potPlayer里看一看,畫面是否能正常預(yù)覽畫面,這里放一個(gè)正常取流的結(jié)果
安裝ffpemg
這里給出了兩種方式安裝
-
「寶塔面板」
我是用寶塔面板安裝的,因?yàn)榉奖懵铮?br>
-
「手動(dòng)安裝」
來到ffmpeg中文官網(wǎng),選擇靜態(tài)構(gòu)建
點(diǎn)擊sorce
下載第一個(gè)就行
下載完了之后把他扔到服務(wù)器上面去
這里先不著急,ffmpeg安裝還依賴一些東西,nasm
同樣也是下載完了扔到服務(wù)器上就行
萬事俱備,解壓編譯安裝
先是解壓nasm
tar -xvf /www/server/mypack/nasm-2.16.01.tar.gz #解壓到當(dāng)前目錄
# tar -xvf /www/server/mypack/nasm-2.16.01.tar.gz -C /指定目錄
這里我解壓到了/www/server/nasm目錄下
「進(jìn)入該目錄后」配置makeFile然后進(jìn)行編譯安裝
./configure --prefix=[你的安裝路徑]
make && make install
nasm安裝完了之后就可以安裝ffmpeg了
還是一樣,解壓,配置makeFile,編譯,安裝即可tips我習(xí)慣給安裝的軟件配置一個(gè)軟鏈接(它很像windows中的快捷方式),方便全局使用,一般是這樣的
ln -s [軟件安裝目錄下的bin目錄或者sbin] [自己機(jī)器的sbin]
以nasm為例,我的nasm是安裝在/usr/local/nasm下面
因?yàn)槲疫@里已經(jīng)配置過了,所以whereis顯示/usr/local/sbin/nasm
來看看/usr/local/nasm目錄下有什么,一個(gè)bin目錄(用于存放該軟件的指令,有些軟件是sbin)
命令如下:
ln -s /usr/local/nasm/sbin /usr/local/sbin/nasm
這里我已經(jīng)建立過了,所以會(huì)顯示文件已經(jīng)存在
照葫蘆畫瓢,ffmpeg也是如此安裝
安裝完了并建立了軟鏈接,使用ffmpeg -version檢查是否安裝上了。
安裝Nginx
到這里開始踩坑了,nginx-rtmp-module模塊的安裝,因?yàn)槲业臋C(jī)器原本就安裝了nginx,按道理說這并不麻煩,不就是添加一個(gè)功能模塊嘛,嘗試過各種辦法老是裝不上。這里有兩種情況,這兩種情況都是要下載rtmp和nginx-http-flv模塊的,先下載它吧,有可能會(huì)出現(xiàn)一些「網(wǎng)絡(luò)」問題,這里自己解決啦
git clone https://github.com/arut/nginx-rtmp-module.git 模塊存放路徑/默認(rèn)當(dāng)前
git clone https://gitcode.com/winshining/nginx-http-flv-module.git 模塊存放路徑/默認(rèn)當(dāng)前
-
「機(jī)器沒有nginx」
沒有就裝唄,這里我就不寫了,因?yàn)椴煌腖inux發(fā)行版包管理工具都不一樣
這里給出Centos7的安裝歷程
-
安裝一些依賴
yum install -y gcc-c++ #因?yàn)橐ㄟ^編譯安裝nginx,所以這里要安裝c/c++編譯器
yum install -y pcre pcre-devel #nginx的http模塊需要使用pcre來解析正則表達(dá)式
yum install -y zlib zlib-devel #nginx使用zlib對(duì)http包的內(nèi)容進(jìn)行g(shù)zip
yum install -y openssl openssl-devel #可以在你的應(yīng)用程序中使用 OpenSSL 提供的加密功能
-
下載nginx
下載nginx有很多方式,你可以在windows上下載,然后再扔到Linux上,也可以用包管理工具安裝,這里選擇前者。
找個(gè)工具扔上去就行
# 這里選擇解壓到/usr/local目錄下
tar -xvf nginx-1.14.0.tar.gz -C /usr/local
ls /usr/local/查看解壓出來的nginx
發(fā)現(xiàn)已經(jīng)解壓好了,然后進(jìn)入該目錄cd /usr/local/nginx-1.24.0
配置./configure 腳本
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi \
--with-http_stub_status_module \
--with-http_ssl_module \
--with-file-aio \
--with-http_realip_module \
--add-module=/www/server/nginx-http-flv-module # 指定添加flv模塊
「后期實(shí)踐證明,壓根不需要rtmp模塊,要http-flv模塊就行了,昨晚復(fù)盤的時(shí)候發(fā)現(xiàn)不裝nginx-rtmp-module也能跑通」
mmp,特別像這根水管一樣,去網(wǎng)上找各種文章,然后東拼西湊,居然能跑起來,你就說能不能用吧
編譯安裝nginx
make && make install #如果你想看編譯是否通過,建議是make和make install分開執(zhí)行
安裝完并且軟連接建立,nginx -t檢查
nginx默認(rèn)對(duì)應(yīng)的是80端口,在啟動(dòng)nginx之前檢查一下自己的防火墻,看看80端口有沒有放行
firewall-cmd --list-ports
如果沒有放行
# --zone #作用域 --add-port=80/tcp #添加端口,格式為:端口/通訊協(xié)議
# --permanent #永久生效,沒有此參數(shù)重啟后失效
firewall-cmd --zone=public --add-port=80/tcp --permanent
# reload一下防火墻
firewall-cmd --reload
這個(gè)時(shí)候啟動(dòng)nginx
nginx
打開瀏覽器,輸入你的服務(wù)器ip就能看到這個(gè)默認(rèn)界面了,要是出現(xiàn)其他的錯(cuò)誤,仔細(xì)看終端的錯(cuò)誤信息。
nginx配置
編輯nginx的配置文件nginx.conf
內(nèi)容如下
#user nobody;
worker_processes 1;
error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 8888;
location /stat { # http://ip:1000/stat, 監(jiān)控流的地址
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /hls { # http拉流的地址,http://ip:1000/hls/密鑰.m3u8
# Serve HLS fragments
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /www/tmp;
expires -1;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
}
}
rtmp {
server {
listen 1935;
ping 30s;
chunk_size 4000;
notify_method get;
application live { # 推流地址rtmp://ip:1935/live/密鑰,同拉流播放地址
live on;
record all; # 是否開啟記錄 alloff, all,用于錄制直播視頻以便回放重播
record_unique on; # 記錄值唯一
record_max_size 200M; # 記錄文件大小
record_path "/www/tmp/video"; # 記錄文件位置
record_suffix -%Y-%m-%d-%H_%M_%S.flv; # 記錄文件命名
# on_publish http://127.0.0.1:8686/auth; # 開始推流的回調(diào)地址
#on_done 'http://when live stop call this url'; # 結(jié)束推流的回調(diào)地址
#on_play http://127.0.0.1:8686/auth; # 開始播放的回調(diào)地址
}
application hls { # 推流地址rtmp://ip:1935/hls/密鑰,開啟HLS協(xié)議進(jìn)行m3u8直播
live on;
hls on; # 開啟hls, hls的推流會(huì)產(chǎn)生一個(gè)m3u8的ts視頻文件索引,同時(shí)保存一個(gè)個(gè)視頻片段緩存,可以拿到再次播放。
hls_path /www/tmp/hls; # 視頻切片ts文件存放的位置
hls_sync 100ms;
hls_fragment 5s; # 視頻切片的大小,ts文件大小
hls_cleanup on; #對(duì)多余的切片進(jìn)行刪除
hls_playlist_length 60s; #保存m3u8列表長度時(shí)間,默認(rèn)是30秒
}
#application vod { # 用于視頻點(diǎn)播flv/mp4
# play /www/tmp/videos; # 本地視頻MP4文件存放地址,作為流播放視頻: rtmp://ip:1935/vod/視頻名稱.mp4
#}
#application vod_http { # 播放遠(yuǎn)程http鏈接的視頻,rtmp://ip:1935/vod_http/視頻名稱.mp4
# play http://localhost:8080/vod/;
#}
}
}
使用ffpemg進(jìn)行拉流轉(zhuǎn)碼
tips如果拉流轉(zhuǎn)碼的服務(wù)不在本機(jī)上運(yùn)行,命令會(huì)有一些改動(dòng)。具體怎么改請(qǐng)查閱ffmpeg的官方文檔
ffmpeg -re -rtsp_transport tcp -i rtsp://admin:123456@ip:port/h264/ch1/main/av_stream -c copy -f hls -hls_time 10 -hls_list_size 0 /www/tmp/hls/test.m3u8
跑起來是這樣的
不用擔(dān)心ts片段會(huì)堆滿你的磁盤,因?yàn)橹耙呀?jīng)在nginx的nginx.conf文件配置過了,多余的ts片段會(huì)直接丟掉。
測(cè)試
要是沒什么問題那么現(xiàn)在用VLC訪問http://ip:8888/hls/test.m3u8是可以看到監(jiān)控畫面的,
要是有問題 那多半是對(duì)應(yīng)的端口沒放行
成功!
有了這個(gè)hls流的地址,就可以很方便的將監(jiān)控畫面放進(jìn)移動(dòng)端頁面,網(wǎng)頁端頁面了,這里我就不過多的介紹了,
vue3中使用
用的video.js這個(gè)插件,這個(gè)自行學(xué)習(xí)安裝了
<script setup lang="ts">
import {onMounted, onUnmounted, ref} from 'vue'
import 'video.js/dist/video-js.css'
import videojs from 'video.js'
const src = ref('http://ip:8888/hls/test.m3u8')
const player = ref<any>(null)
const videoRef = ref('')
const videoInit = () => {
if(player.value) {
return
}
player.value = videojs(videoRef.value, {
autoplay: false,
controls: true,
fluid: true, // 自適應(yīng)寬高
sources: [
{
src: src.value,
type: 'application/x-mpegURL'
}
]
}, () => {
console.log('player init success')
})
}
onMounted(() => {
videoInit()
})
onUnmounted(() => {
if (player.value) {
player.value.dispose()
console.log('player dispose success')
}
})
</script>
<template>
<video
ref="videoRef"
id="my-video"
class="video-js vjs-default-skin vjs-big-play-centered vjs-16-9"
controls
>
<source :src="src"/>
</video>
</template>
<style scoped lang="scss">
</style>
結(jié)果:
三、總結(jié)
幾天的試驗(yàn)與探索,收獲很多,我也想過為什么不能讓rtsp流直接在web網(wǎng)頁中顯示,那得具體的問問研究流媒體傳輸協(xié)議的大佬了,究其原因還是瀏覽器不支持直接播放rtsp流。所以沒辦法還是要轉(zhuǎn)碼,轉(zhuǎn)碼就會(huì)花時(shí)間,延遲自然就出現(xiàn)了。而且這個(gè)demo轉(zhuǎn)碼是直接在本機(jī)進(jìn)行的,并不需要再推流到服務(wù)器上了,實(shí)際情況可能轉(zhuǎn)碼和流媒體服務(wù)器是分開的,延遲會(huì)更高,假設(shè)又拋一個(gè)回放的需求....要回放XXXX年XX月XX日,某某時(shí)間段的錄像,好了我的服務(wù)器已經(jīng)宕機(jī)了。有錯(cuò)誤的地方請(qǐng)指正
性能問題(實(shí)際環(huán)境中)
我的機(jī)器比較垃圾,一個(gè)ffmpeg進(jìn)程已經(jīng)負(fù)載累累了,還有一個(gè)問題是畫面延遲,hls方案延遲會(huì)比較高,我沒做過其他的解決方案。這個(gè)demo的延遲大概10S這樣
平均負(fù)載
IO
暫時(shí)沒有想到優(yōu)化的解決方案。如果有好的優(yōu)化方案可以聊一聊,我也想學(xué)!
ffmpeg后臺(tái)24小時(shí)運(yùn)行
這個(gè)應(yīng)該很簡單了,我就直接把指令貼出來了
ffmpeg -nostdin -re -rtsp_transport tcp -i rtsp://admin:123456@ip:554/h264/ch1/main/av_stream -c copy -f hls -hls_time 10 -hls_list_size 0 /www/tmp/hls/test.m3u8 2> /dev/null &
為了防止拉流轉(zhuǎn)碼服務(wù)掛掉,可以寫一個(gè)shell腳本,每隔一段時(shí)間去檢查一下ffmpeg進(jìn)程是否還在,不在重啟就可以了
重啟腳本restart.sh
ffmpeg -nostdin -re -rtsp_transport tcp -i rtsp://admin:123456@ip:554/h264/ch1/main/av_stream -c copy -f hls -hls_time 10 -hls_list_size 0 /www/tmp/hls/test.m3u8 2> /dev/null &
檢查腳本check.sh這里我沒有在腳本中寫定時(shí)器,而是通過寶塔面板計(jì)劃任務(wù)去實(shí)現(xiàn)的,方便嘛
#!/bin/bash
# 定義一個(gè)函數(shù)來檢查并重啟ffmpeg進(jìn)程
check_and_restart_ffmpeg() {
# 使用pgrep查找ffmpeg進(jìn)程的PID,如果不存在則返回空
ffmpeg_pids=$(pgrep -f ffmpeg)
# 如果沒有找到ffmpeg進(jìn)程,則執(zhí)行重啟腳本
if [ -z "$ffmpeg_pids" ]; then
echo "$(date): No ffmpeg processes found, restarting..."
/root/restart.sh
else
echo "$(date): ffmpeg processes are running with PIDs: $ffmpeg_pids"
fi
}
check_and_restart_ffmpeg
最后
Node 社群
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
“分享、點(diǎn)贊、在看” 支持一下
