nginx和ffmpeg 的安裝請參考我的另一篇文章
Nginx+rtmp+ffmpeg搭建視頻轉碼服務_nginx-rtmp-module-master-CSDN博客
目錄
1、整體方案設計如圖
2、nginx下目錄創建和配置文件創建
3、創建視頻流生成腳本
4、修改nginx配置
5、管理界面 (video.html)
6、ffmpeg后臺啟動
方案1:使用nohup和后臺運行
6.1啟動腳本編寫
6.2停止腳本編寫
方案2:使用systemd服務(推薦生產環境使用)
1、整體方案設計如圖
其中config下是視頻配置文件,live存放視頻實時流,archive存放視頻歷史流
2、nginx下目錄創建和配置文件創建
cd /usr/local/nginx/html
mkdir streams
cd streams
mkdir config
vi cameras.json在json文件中填充如下內容{"cameras": [{"id": "zl","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/101","name": "走廊監控"},{"id": "blm","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/201","name": "玻璃門監控"},{"id": "cg","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/301","name": "采購監控"},{"id": "yf","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/401","name": "研發監控"},{"id": "qt","rtsp": "rtsp://admin:123456@172.168.2.11:554/Streaming/Channels/501","name": "前臺監控"}],"hls_time": 2,"max_archive_hours": 24,"live_segments": 2
}
? ? 1:"hls_time": 2,#每個切片時長 2s
? ? 2:"max_archive_hours": 24,#歷史數據保留24小時
? ? 3:live_segments?指定播放列表(m3u8文件)中保留的最新TS視頻分片數量
? ? 3.1:當設置為5時:
-
播放列表始終保留最新的5個TS分片
-
當第6個分片生成時,最舊的分片會被移除
-
例如:segment_001.ts 到 segment_005.ts → 新分片產生 → segment_002.ts 到 segment_006.ts
? ? 3.2:計算公式
-
實時流延遲 ≈?
live_segments
?×?hls_time
-
上面示例:5 × 2秒 = 約10秒延遲
3、創建視頻流生成腳本
創建start_streams.sh腳本vi start_streams.sh在腳本中填充如下內容#!/bin/bashCONFIG_FILE="/usr/local/nginx/html/streams/config/cameras.json"
STREAMS_DIR="/home/streams" #流媒體存儲目錄
#STREAMS_DIR="/usr/local/nginx/html/streams"
NGINX_USER="nginx" # Nginx運行用戶# 創建目錄并設置權限
mkdir -p $STREAMS_DIR/{live,archive}
chown -R $NGINX_USER:$NGINX_USER $STREAMS_DIR
chmod -R 755 $STREAMS_DIR# 讀取配置
CAMERAS=$(jq -r '.cameras[] | .id' $CONFIG_FILE)
HLS_TIME=$(jq -r '.hls_time' $CONFIG_FILE)
MAX_HOURS=$(jq -r '.max_archive_hours' $CONFIG_FILE)
LIVE_SEGMENTS=$(jq -r '.live_segments' $CONFIG_FILE)# 為每個攝像頭啟動FFmpeg進程
for CAMERA in $CAMERAS; doRTSP_URL=$(jq -r --arg id "$CAMERA" '.cameras[] | select(.id==$id) | .rtsp' $CONFIG_FILE)# 創建目錄mkdir -p $STREAMS_DIR/live/$CAMERAmkdir -p $STREAMS_DIR/archive/$CAMERA# 啟動實時流ffmpeg -i "$RTSP_URL" \-c copy \-f hls \-hls_time $HLS_TIME \-hls_list_size $LIVE_SEGMENTS \-hls_flags delete_segments \-hls_segment_filename "$STREAMS_DIR/live/$CAMERA/segment_%03d.ts" \"$STREAMS_DIR/live/$CAMERA/live.m3u8" &# 啟動歷史流ffmpeg -i "$RTSP_URL" \-c copy \-f hls \-hls_time $HLS_TIME \-hls_list_size 0 \-hls_flags append_list \-hls_segment_filename "$STREAMS_DIR/archive/$CAMERA/%Y%m%d_%H%M%S.ts" \-strftime 1 \"$STREAMS_DIR/archive/$CAMERA/archive.m3u8" &
done# 定時清理舊歷史文件
while true; dofor CAMERA in $CAMERAS; dofind "$STREAMS_DIR/archive/$CAMERA" -name "*.ts" -mmin +$(($MAX_HOURS*60)) -deletedonesleep 3600 # 每小時清理一次
done
該腳本啟動時如果系統沒有安裝 jq,啟動腳本提示提示 jq: 未找到命令
需要安裝一下jq
wget https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
mv jq-1.6 jq
chmod +x jq # 添加可執行權限
sudo mv jq /usr/local/bin/ # 移動到系統路徑查看jq是否安裝成功
jq --version
腳本如果繼續執行報錯 這行報錯parse error: Invalid numeric literal
把json配置文件中的#注釋去掉
啟動腳本
4、修改nginx配置
nginx全局配置如下
#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;
}rtmp { server { listen 1935; #監聽的端口號application myapp { #自定義的名字live on; } application hls { live on; hls on; hls_path /tmp/hls; hls_fragment 1s;hls_playlist_length 3s; } }
}http {include mime.types;default_type application/octet-stream;#log_format main '$remote_addr - $remote_user [$time_local] "$request" '# '$status $body_bytes_sent "$http_referer" '# '"$http_user_agent" "$http_x_forwarded_for"';#access_log logs/access.log main;sendfile on;#tcp_nopush on;#keepalive_timeout 0;keepalive_timeout 65;#gzip on;server {listen 80;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;location / {root html;index index.html index.htm;}# 實時流訪問location /live {#alias /usr/local/nginx/html/streams/live;#根據自己文視頻流生成的位置配置,我這邊因為要存歷史視頻,占用空間大,所以把視頻流放到/home/streams 目錄下去了alias /home/streams/live;types {application/vnd.apple.mpegurl m3u8;video/mp2t ts;}add_header Cache-Control no-cache;}# 歷史流訪問location /archive {#alias /usr/local/nginx/html/streams/archive;alias /home/streams/archive;types {application/vnd.apple.mpegurl m3u8;video/mp2t ts;}add_header Cache-Control no-cache;}# 配置API 訪問json配置文件location /api/cameras {alias /usr/local/nginx/html/streams/config/cameras.json;default_type application/json;}#error_page 404 /404.html;# redirect server error pages to the static page /50x.html#error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}# proxy the PHP scripts to Apache listening on 127.0.0.1:80##location ~ \.php$ {# proxy_pass http://127.0.0.1;#}# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000##location ~ \.php$ {# root html;# fastcgi_pass 127.0.0.1:9000;# fastcgi_index index.php;# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;# include fastcgi_params;#}# deny access to .htaccess files, if Apache's document root# concurs with nginx's one##location ~ /\.ht {# deny all;#}}# another virtual host using mix of IP-, name-, and port-based configuration##server {# listen 8000;# listen somename:8080;# server_name somename alias another.alias;# location / {# root html;# index index.html index.htm;# }#}# HTTPS server##server {# listen 443 ssl;# server_name localhost;# ssl_certificate cert.pem;# ssl_certificate_key cert.key;# ssl_session_cache shared:SSL:1m;# ssl_session_timeout 5m;# ssl_ciphers HIGH:!aNULL:!MD5;# ssl_prefer_server_ciphers on;# location / {# root html;# index index.html index.htm;# }#}}
配置完成后,啟動nginx
5、管理界面 (video.html)
自己通過一個html頁面查看視頻是否正常播放,或者通過流媒體播放軟件查看
video.html頁面內容如下
<!DOCTYPE html>
<html>
<head><title>多路視頻監控</title><!-- 替換為國內CDN -->
<script src="https://cdn.bootcdn.net/ajax/libs/hls.js/1.1.5/hls.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script><style>.video-container {display: inline-block;margin: 10px;vertical-align: top;}.video-title {text-align: center;font-weight: bold;}</style>
</head>
<body><h1>視頻監控系統</h1><div id="cameras-container"></div><script>$(document).ready(function() {// 獲取攝像頭列表$.getJSON("http://192.168.3.35/api/cameras", function(data) {const cameras = data.cameras;cameras.forEach(camera => {// 創建視頻容器const container = $(`<div class="video-container"><div class="video-title">${camera.name}</div><div><button onclick="playLive('${camera.id}')">實時</button><input type="datetime-local" id="${camera.id}-start"><input type="datetime-local" id="${camera.id}-end"><button onclick="playArchive('${camera.id}')">回放</button></div><video id="${camera.id}-video" controls width="640" height="360"></video></div>`);$("#cameras-container").append(container);// 默認播放實時視頻playLive(camera.id);});});});function playLive(cameraId) {const video = document.getElementById(`${cameraId}-video`);if(Hls.isSupported()) {const hls = new Hls();hls.loadSource(`/live/${cameraId}/live.m3u8`);hls.attachMedia(video);video.play();} else if (video.canPlayType('application/vnd.apple.mpegurl')) {video.src = `/live/${cameraId}/live.m3u8`;video.play();}}function playArchive(cameraId) {const start = document.getElementById(`${cameraId}-start`).value;const end = document.getElementById(`${cameraId}-end`).value;const video = document.getElementById(`${cameraId}-video`);// 簡單實現 - 實際項目中應該調用后端API篩選時間范圍if(Hls.isSupported()) {const hls = new Hls();hls.loadSource(`/archive/${cameraId}/archive.m3u8`);hls.attachMedia(video);video.play();}}</script>
</body>
</html>
在nginx html下面創建video文件夾,把video.html放進去,啟動nginx,訪問頁面內容如下
6、ffmpeg后臺啟動
上面的啟動腳本不是后臺啟動,關閉ssh連接后,服務會中斷,
方案1:使用nohup和后臺運行
6.1啟動腳本編寫
注意:腳本啟動后,不要通過ctrl+c 方式退出啟動命令,程序會終止,直接通過叉掉ssh頁面即可
#!/bin/bashCONFIG_FILE="/usr/local/nginx/html/streams/config/cameras.json"
STREAMS_DIR="/home/streams" #流媒體存儲目錄
#STREAMS_DIR="/usr/local/nginx/html/streams"
NGINX_USER="nginx" # Nginx運行用戶
PID_FILE="$STREAMS_DIR/process_ids.txt" # PID記錄文件# 清空或創建PID文件
> "$PID_FILE"# 創建目錄并設置權限
mkdir -p $STREAMS_DIR/{live,archive,logs}
chown -R $NGINX_USER:$NGINX_USER $STREAMS_DIR
chmod -R 755 $STREAMS_DIR# 讀取配置
CAMERAS=$(jq -r '.cameras[] | .id' $CONFIG_FILE)
HLS_TIME=$(jq -r '.hls_time' $CONFIG_FILE)
MAX_HOURS=$(jq -r '.max_archive_hours' $CONFIG_FILE)
LIVE_SEGMENTS=$(jq -r '.live_segments' $CONFIG_FILE)# 為每個攝像頭啟動FFmpeg(使用nohup)
for CAMERA in $CAMERAS; doRTSP_URL=$(jq -r --arg id "$CAMERA" '.cameras[] | select(.id==$id) | .rtsp' $CONFIG_FILE)# 為每個攝像創建目錄mkdir -p $STREAMS_DIR/live/$CAMERAmkdir -p $STREAMS_DIR/archive/$CAMERA# 實時流 - 使用nohup和后臺運行nohup ffmpeg -i "$RTSP_URL" \-c copy \-f hls \-hls_time $HLS_TIME \-hls_list_size $LIVE_SEGMENTS \-hls_flags delete_segments \-hls_segment_filename "$STREAMS_DIR/live/$CAMERA/segment_%03d.ts" \"$STREAMS_DIR/live/$CAMERA/live.m3u8" > "$STREAMS_DIR/logs/$CAMERA-live.log" 2>&1 &echo "$! camera_$CAMERA live" >> "$PID_FILE" # 記錄PID# 歷史流 - 使用nohup和后臺運行nohup ffmpeg -i "$RTSP_URL" \-c copy \-f hls \-hls_time $HLS_TIME \-hls_list_size 0 \-hls_flags append_list \-hls_segment_filename "$STREAMS_DIR/archive/$CAMERA/%Y%m%d_%H%M%S.ts" \-strftime 1 \"$STREAMS_DIR/archive/$CAMERA/archive.m3u8" > "$STREAMS_DIR/logs/$CAMERA-archive.log" 2>&1 &echo "$! camera_$CAMERA archive" >> "$PID_FILE" # 記錄PID
done# 定時清理舊歷史文件
while true; dofor CAMERA in $CAMERAS; dofind "$STREAMS_DIR/archive/$CAMERA" -name "*.ts" -mmin +$(($MAX_HOURS*60)) -deletedonesleep 3600 # 每小時清理一次
done
腳本執行如果提示 無效的用戶: "nginx:nginx"? 則創建nginx用戶
# 創建nginx用戶和組
sudo groupadd nginx
sudo useradd -g nginx -s /sbin/nologin -d /var/nginx -M nginx# 驗證用戶
id nginx
6.2停止腳本編寫
在home目錄創建停止腳本 vi?stop_streams.sh
#!/bin/bash# 文件用來存儲進程ID
PID_FILE="/home/streams/process_ids.txt"
LOG_FILE="/home/streams/stop.log"# 記錄日志函數
log() {echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}# 腳本結束前,關閉所有記錄的進程
cleanup() {if [ ! -f "$PID_FILE" ]; thenlog "錯誤:PID文件 $PID_FILE 不存在"return 1filog "開始停止所有FFmpeg進程..."total=0killed=0while read -r line; do# 解析PID和描述信息pid=$(echo "$line" | awk '{print $1}')desc=$(echo "$line" | cut -d' ' -f2-)((total++))# 檢查PID是否有效if kill -0 "$pid" 2>/dev/null; thenlog "正在停止進程 $pid ($desc)"if kill "$pid"; then((killed++))log "成功停止進程 $pid"elselog "警告:無法停止進程 $pid"fielselog "進程 $pid 已停止或不存在"fidone < "$PID_FILE"log "操作完成:共找到 $total 個記錄,成功停止 $killed 個進程"# 清空PID文件(可選)> "$PID_FILE"
}# 確保腳本退出時調用cleanup函數
trap cleanup EXIT# 主執行
log "====== 開始執行停止腳本 ======"
cleanup
exit 0
方案2:使用systemd服務(推薦生產環境使用)
-
創建systemd服務文件?
/etc/systemd/system/rtsp_to_hls.service
:
[Unit]
Description=RTSP to HLS Stream Service
After=network.target[Service]
Type=forking
User=nginx
WorkingDirectory=/home/streams
ExecStart=/path/to/your/start_streams.sh
Restart=always
RestartSec=10
StandardOutput=append:/home/streams/logs/service.log
StandardError=append:/home/streams/logs/service-error.log[Install]
WantedBy=multi-user.target
-
建日志目錄并設置權限:
sudo mkdir -p /home/streams/logs sudo chown nginx:nginx /home/streams/logs
-
啟用并啟動服務:
sudo systemctl daemon-reload
sudo systemctl enable rtsp_to_hls.service
sudo systemctl start rtsp_to_hls.service
驗證服務是否正常運行
# 檢查systemd服務狀態
systemctl status rtsp_to_hls.service# 檢查進程
pgrep -a ffmpeg# 檢查日志
tail -f /home/streams/logs/*.log
停止服務的正確方式
sudo systemctl stop rtsp_to_hls.service