- ブログ
- 近日公開
近日公開
編集チーム
2025/9/17
記事
M3U8
搭建自己的M3U8视频流服务器教程
搭建一个专业的M3U8视频流服务器并不复杂。本教程将手把手教您如何使用FFmpeg、Nginx等开源工具,构建一个功能完整的流媒体服务器,并通过M3U8 Player进行测试。
系统架构概览
graph TB
A[视频源] --> B[FFmpeg转码]
B --> C[切片生成]
C --> D[Nginx服务器]
D --> E[CDN分发]
E --> F[客户端播放器]
G[直播源] --> H[FFmpeg实时转码]
H --> D
一、环境准备
系统要求
- 操作系统:Ubuntu 20.04/22.04 或 CentOS 7/8
- CPU:至少4核(转码需要)
- 内存:8GB以上
- 存储:100GB以上(视频存储)
- 带宽:上行至少10Mbps
软件安装
1. 安装FFmpeg
# Ubuntu/Debian
sudo apt update
sudo apt install ffmpeg
# CentOS/RHEL
sudo yum install epel-release
sudo yum install ffmpeg
# 或从源码编译(推荐,支持更多编码器)
wget https://ffmpeg.org/releases/ffmpeg-5.1.2.tar.xz
tar xvf ffmpeg-5.1.2.tar.xz
cd ffmpeg-5.1.2
./configure --enable-gpl --enable-libx264 --enable-libx265 --enable-libvpx --enable-libfdk-aac --enable-libmp3lame --enable-nonfree
make -j$(nproc)
sudo make install
2. 安装Nginx with RTMP模块
# 安装依赖
sudo apt install build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev
# 下载Nginx和RTMP模块
wget http://nginx.org/download/nginx-1.24.0.tar.gz
wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
# 解压
tar -zxvf nginx-1.24.0.tar.gz
unzip master.zip
# 编译安装
cd nginx-1.24.0
./configure --prefix=/usr/local/nginx \
--add-module=../nginx-rtmp-module-master \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_secure_link_module
make -j$(nproc)
sudo make install
二、FFmpeg转码配置
基础转码命令
1. 单码率转码
#!/bin/bash
# basic_transcode.sh
INPUT="input.mp4"
OUTPUT_DIR="/var/www/hls"
STREAM_NAME="stream"
# 创建输出目录
mkdir -p $OUTPUT_DIR
# 基础HLS转码
ffmpeg -i $INPUT \
-c:v libx264 \
-c:a aac \
-b:v 2800k \
-b:a 128k \
-vf "scale=1280:720" \
-f hls \
-hls_time 10 \
-hls_list_size 0 \
-hls_segment_filename "$OUTPUT_DIR/${STREAM_NAME}_%03d.ts" \
$OUTPUT_DIR/${STREAM_NAME}.m3u8
2. 多码率自适应流
#!/bin/bash
# adaptive_transcode.sh
INPUT="input.mp4"
OUTPUT_DIR="/var/www/hls"
# 创建输出目录
mkdir -p $OUTPUT_DIR
# 多码率转码
ffmpeg -i $INPUT \
-filter_complex \
"[0:v]split=4[v1][v2][v3][v4]; \
[v1]scale=w=3840:h=2160[v1out]; \
[v2]scale=w=1920:h=1080[v2out]; \
[v3]scale=w=1280:h=720[v3out]; \
[v4]scale=w=854:h=480[v4out]" \
-map "[v1out]" -c:v:0 libx264 -b:v:0 15000k -maxrate:v:0 15000k -bufsize:v:0 30000k -preset slow -g 48 -keyint_min 48 -sc_threshold 0 \
-map "[v2out]" -c:v:1 libx264 -b:v:1 5000k -maxrate:v:1 5000k -bufsize:v:1 10000k -preset slow -g 48 -keyint_min 48 -sc_threshold 0 \
-map "[v3out]" -c:v:2 libx264 -b:v:2 2800k -maxrate:v:2 2800k -bufsize:v:2 5600k -preset slow -g 48 -keyint_min 48 -sc_threshold 0 \
-map "[v4out]" -c:v:3 libx264 -b:v:3 1400k -maxrate:v:3 1400k -bufsize:v:3 2800k -preset slow -g 48 -keyint_min 48 -sc_threshold 0 \
-map 0:a -c:a aac -b:a 128k -ar 44100 \
-f hls \
-hls_time 10 \
-hls_list_size 0 \
-hls_flags independent_segments \
-hls_segment_type mpegts \
-hls_segment_filename "$OUTPUT_DIR/stream_%v_%03d.ts" \
-master_pl_name master.m3u8 \
-var_stream_map "v:0,a:0,name:4k v:1,a:0,name:1080p v:2,a:0,name:720p v:3,a:0,name:480p" \
$OUTPUT_DIR/stream_%v.m3u8
直播流转码
RTMP转HLS
#!/bin/bash
# rtmp_to_hls.sh
RTMP_URL="rtmp://localhost/live/stream"
OUTPUT_DIR="/var/www/hls/live"
# 创建输出目录
mkdir -p $OUTPUT_DIR
# RTMP转HLS(低延迟配置)
ffmpeg -i $RTMP_URL \
-c:v libx264 -preset veryfast \
-tune zerolatency \
-c:a aac \
-f hls \
-hls_time 2 \
-hls_list_size 5 \
-hls_flags delete_segments+append_list \
-hls_segment_type mpegts \
-hls_segment_filename "$OUTPUT_DIR/segment_%d.ts" \
$OUTPUT_DIR/index.m3u8
高级转码选项
GPU加速转码(NVIDIA)
# 使用NVIDIA GPU加速
ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i input.mp4 \
-c:v h264_nvenc \
-preset p4 \
-tune hq \
-b:v 5M \
-maxrate 5M \
-bufsize 10M \
-c:a copy \
-f hls \
output.m3u8
添加水印
# 添加图片水印
ffmpeg -i input.mp4 -i watermark.png \
-filter_complex "overlay=W-w-10:H-h-10" \
-c:v libx264 -c:a copy \
-f hls output.m3u8
# 添加文字水印
ffmpeg -i input.mp4 \
-vf "drawtext=text='© M3U8 Player':x=10:y=10:fontsize=24:fontcolor=white:shadowcolor=black:shadowx=2:shadowy=2" \
-c:v libx264 -c:a copy \
-f hls output.m3u8
三、Nginx配置
基础配置
# /usr/local/nginx/conf/nginx.conf
worker_processes auto;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 10240;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
# HLS配置
server {
listen 80;
server_name stream.example.com;
# HLS目录
location /hls {
# 禁用缓存(直播)
add_header Cache-Control no-cache;
# CORS配置
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
# HLS类型
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /var/www;
}
# 统计页面
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root /usr/local/nginx/html;
}
}
}
# RTMP配置
rtmp {
server {
listen 1935;
chunk_size 4096;
# 直播应用
application live {
live on;
# 录制
record all;
record_path /var/recordings;
record_unique on;
# 转码为HLS
exec ffmpeg -i rtmp://localhost/live/$name
-c:v libx264 -preset veryfast -b:v 2800k
-c:a aac -b:a 128k -ar 44100
-f hls -hls_time 2 -hls_list_size 5
-hls_flags delete_segments
/var/www/hls/$name.m3u8 2>>/var/log/ffmpeg.log;
# HLS配置
hls on;
hls_path /var/www/hls;
hls_fragment 2s;
hls_playlist_length 10s;
# 允许从任何地方推流
allow publish all;
# 允许从任何地方播放
allow play all;
}
}
}
SSL配置
server {
listen 443 ssl http2;
server_name stream.example.com;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# SSL优化
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
location /hls {
# HLS配置(同上)
}
}
负载均衡配置
upstream hls_backends {
least_conn;
server backend1.example.com:80 weight=5;
server backend2.example.com:80 weight=5;
server backend3.example.com:80 backup;
# 健康检查
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "HEAD /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
server {
listen 80;
server_name stream.example.com;
location /hls {
proxy_pass http://hls_backends;
proxy_cache hls_cache;
proxy_cache_valid 200 302 1m;
proxy_cache_valid 404 1s;
}
}
四、CDN加速配置
使用Cloudflare CDN
# 信任Cloudflare IP
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
# ... 更多Cloudflare IP
real_ip_header CF-Connecting-IP;
# 缓存规则
location ~ \.(m3u8)$ {
add_header Cache-Control "public, max-age=2";
add_header X-Content-Type-Options nosniff;
}
location ~ \.(ts)$ {
add_header Cache-Control "public, max-age=3600";
add_header X-Content-Type-Options nosniff;
}
自建CDN节点
#!/bin/bash
# cdn_sync.sh - 同步内容到CDN节点
ORIGIN="/var/www/hls"
NODES=("cdn1.example.com" "cdn2.example.com" "cdn3.example.com")
for node in "${NODES[@]}"; do
echo "Syncing to $node..."
rsync -avz --delete $ORIGIN/ user@$node:/var/www/hls/
done
智能DNS配置
# dns_routing.py - 基于地理位置的DNS路由
import geoip2.database
from flask import Flask, request, jsonify
app = Flask(__name__)
reader = geoip2.database.Reader('GeoLite2-City.mmdb')
CDN_NODES = {
'US': 'us.cdn.example.com',
'EU': 'eu.cdn.example.com',
'AS': 'as.cdn.example.com',
'DEFAULT': 'global.cdn.example.com'
}
@app.route('/get_cdn_node')
def get_cdn_node():
client_ip = request.remote_addr
try:
response = reader.city(client_ip)
continent = response.continent.code
cdn_node = CDN_NODES.get(continent, CDN_NODES['DEFAULT'])
except:
cdn_node = CDN_NODES['DEFAULT']
return jsonify({'cdn_node': cdn_node})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
五、监控和优化
系统监控脚本
#!/bin/bash
# monitor.sh - 流媒体服务器监控
# CPU使用率
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
# 内存使用
MEMORY_USAGE=$(free -m | awk 'NR==2{printf "%.2f", $3*100/$2}')
# 磁盘使用
DISK_USAGE=$(df -h /var/www/hls | awk 'NR==2{print $5}' | cut -d'%' -f1)
# 网络带宽
NETWORK_IN=$(vnstat -tr 2 | grep "rx" | awk '{print $2,$3}')
NETWORK_OUT=$(vnstat -tr 2 | grep "tx" | awk '{print $2,$3}')
# FFmpeg进程
FFMPEG_PROCESSES=$(ps aux | grep -c "[f]fmpeg")
# Nginx连接数
NGINX_CONNECTIONS=$(netstat -an | grep :80 | wc -l)
# 告警阈值检查
if (( $(echo "$CPU_USAGE > 80" | bc -l) )); then
echo "WARNING: High CPU usage: ${CPU_USAGE}%"
# 发送告警邮件或消息
fi
if (( $(echo "$MEMORY_USAGE > 90" | bc -l) )); then
echo "WARNING: High memory usage: ${MEMORY_USAGE}%"
fi
if [ "$DISK_USAGE" -gt 85 ]; then
echo "WARNING: High disk usage: ${DISK_USAGE}%"
# 清理旧文件
find /var/www/hls -name "*.ts" -mtime +1 -delete
fi
# 输出监控数据
cat << EOF
=== 流媒体服务器状态 ===
CPU使用率: ${CPU_USAGE}%
内存使用率: ${MEMORY_USAGE}%
磁盘使用率: ${DISK_USAGE}%
网络流入: ${NETWORK_IN}
网络流出: ${NETWORK_OUT}
FFmpeg进程数: ${FFMPEG_PROCESSES}
Nginx连接数: ${NGINX_CONNECTIONS}
========================
EOF
性能优化
1. 系统优化
# /etc/sysctl.conf - 内核参数优化
# 网络优化
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 0
# 文件描述符
fs.file-max = 1000000
fs.nr_open = 1000000
# 应用配置
sudo sysctl -p
2. FFmpeg优化
# optimize_encoding.py - 动态编码参数优化
import subprocess
import json
def analyze_video(input_file):
"""分析视频特征"""
cmd = [
'ffprobe', '-v', 'quiet',
'-print_format', 'json',
'-show_streams', input_file
]
result = subprocess.run(cmd, capture_output=True, text=True)
data = json.loads(result.stdout)
video_stream = next((s for s in data['streams'] if s['codec_type'] == 'video'), None)
return {
'width': int(video_stream['width']),
'height': int(video_stream['height']),
'fps': eval(video_stream['r_frame_rate']),
'bitrate': int(video_stream.get('bit_rate', 0))
}
def generate_encoding_ladder(video_info):
"""生成编码梯度"""
base_bitrate = video_info['bitrate'] or 5000000
ladder = []
resolutions = [
(3840, 2160, 1.0), # 4K
(1920, 1080, 0.5), # 1080p
(1280, 720, 0.3), # 720p
(854, 480, 0.15), # 480p
(640, 360, 0.08) # 360p
]
for width, height, bitrate_factor in resolutions:
if width <= video_info['width'] and height <= video_info['height']:
ladder.append({
'resolution': f'{width}x{height}',
'bitrate': int(base_bitrate * bitrate_factor),
'preset': 'fast' if height <= 720 else 'medium'
})
return ladder
def generate_ffmpeg_command(input_file, output_dir, ladder):
"""生成FFmpeg命令"""
cmd = ['ffmpeg', '-i', input_file]
# 添加过滤器
filter_complex = []
for i, level in enumerate(ladder):
w, h = level['resolution'].split('x')
filter_complex.append(f'[0:v]scale={w}:{h}[v{i}]')
cmd.extend(['-filter_complex', ';'.join(filter_complex)])
# 添加输出
for i, level in enumerate(ladder):
cmd.extend([
f'-map', f'[v{i}]',
f'-c:v:{i}', 'libx264',
f'-b:v:{i}', f"{level['bitrate']}",
f'-preset', level['preset']
])
# 音频
cmd.extend(['-map', '0:a', '-c:a', 'aac', '-b:a', '128k'])
# HLS参数
cmd.extend([
'-f', 'hls',
'-hls_time', '10',
'-hls_list_size', '0',
'-hls_segment_filename', f'{output_dir}/segment_%v_%03d.ts',
'-master_pl_name', 'master.m3u8'
])
# 变体流映射
var_stream_map = ' '.join([f'v:{i},a:0' for i in range(len(ladder))])
cmd.extend(['-var_stream_map', var_stream_map])
cmd.append(f'{output_dir}/stream_%v.m3u8')
return cmd
# 使用示例
if __name__ == '__main__':
input_file = 'input.mp4'
output_dir = '/var/www/hls'
video_info = analyze_video(input_file)
ladder = generate_encoding_ladder(video_info)
cmd = generate_ffmpeg_command(input_file, output_dir, ladder)
print('生成的编码梯度:')
for level in ladder:
print(f" {level['resolution']}: {level['bitrate']} bps")
print('\nFFmpeg命令:')
print(' '.join(cmd))
# 执行转码
subprocess.run(cmd)
六、安全配置
防盗链设置
# Nginx防盗链配置
location /hls {
# 检查Referer
valid_referers none blocked server_names
*.example.com
m3u8-player.net;
if ($invalid_referer) {
return 403;
}
# Secure Link模块(URL签名)
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr secret";
if ($secure_link = "") {
return 403;
}
if ($secure_link = "0") {
return 410;
}
}
访问控制
# access_control.py - Token验证服务
from flask import Flask, request, abort
import jwt
import time
app = Flask(__name__)
SECRET_KEY = 'your-secret-key'
@app.route('/auth')
def authenticate():
token = request.args.get('token')
if not token:
abort(403)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
# 检查过期时间
if payload['exp'] < time.time():
abort(401)
# 检查IP地址
if payload.get('ip') != request.remote_addr:
abort(403)
return '', 200
except jwt.InvalidTokenError:
abort(403)
@app.route('/generate_token')
def generate_token():
user_id = request.args.get('user_id')
duration = int(request.args.get('duration', 3600))
payload = {
'user_id': user_id,
'ip': request.remote_addr,
'exp': time.time() + duration
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return {'token': token, 'expires_in': duration}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
七、测试和验证
测试脚本
#!/bin/bash
# test_streaming.sh - 流媒体服务器测试
SERVER_URL="https://stream.example.com"
TEST_STREAM="/hls/master.m3u8"
echo "=== 流媒体服务器测试 ==="
# 1. 连通性测试
echo -n "1. 测试服务器连通性... "
if curl -s -o /dev/null -w "%{http_code}" $SERVER_URL | grep -q "200\|302"; then
echo "✓ 成功"
else
echo "✗ 失败"
exit 1
fi
# 2. M3U8文件测试
echo -n "2. 测试M3U8文件访问... "
if curl -s $SERVER_URL$TEST_STREAM | grep -q "#EXTM3U"; then
echo "✓ 成功"
else
echo "✗ 失败"
exit 1
fi
# 3. CORS测试
echo -n "3. 测试CORS配置... "
CORS_HEADER=$(curl -s -I $SERVER_URL$TEST_STREAM | grep -i "access-control-allow-origin")
if [ -n "$CORS_HEADER" ]; then
echo "✓ 成功 ($CORS_HEADER)"
else
echo "✗ 失败"
fi
# 4. SSL证书测试
echo -n "4. 测试SSL证书... "
if echo | openssl s_client -connect stream.example.com:443 2>/dev/null | grep -q "Verify return code: 0"; then
echo "✓ 成功"
else
echo "✗ 警告:证书验证失败"
fi
# 5. 性能测试
echo "5. 性能测试..."
ab -n 100 -c 10 $SERVER_URL$TEST_STREAM | grep "Requests per second"
echo "=== 测试完成 ==="
使用M3U8 Player测试
访问M3U8 Player,输入您的流媒体服务器地址进行测试:
-
基础播放测试
- 输入:
https://your-server.com/hls/master.m3u8
- 检查:是否正常播放
- 输入:
-
自适应码率测试
- 观察质量切换是否流畅
- 检查不同网络条件下的表现
-
错误恢复测试
- 模拟网络中断
- 检查恢复机制
八、生产环境部署清单
部署前检查
- SSL证书已配置
- 防火墙规则已设置
- 备份策略已制定
- 监控告警已配置
- CDN节点已部署
- 负载均衡已测试
- 安全策略已实施
- 文档已完善
运维脚本
#!/bin/bash
# deploy.sh - 自动化部署脚本
set -e
echo "开始部署流媒体服务器..."
# 1. 更新系统
sudo apt update && sudo apt upgrade -y
# 2. 安装依赖
./install_dependencies.sh
# 3. 配置服务
sudo cp nginx.conf /usr/local/nginx/conf/
sudo cp ffmpeg.service /etc/systemd/system/
# 4. 启动服务
sudo systemctl daemon-reload
sudo systemctl enable nginx ffmpeg
sudo systemctl start nginx ffmpeg
# 5. 健康检查
sleep 5
if curl -s http://localhost/health | grep -q "OK"; then
echo "✓ 部署成功!"
else
echo "✗ 部署失败,请检查日志"
exit 1
fi
# 6. 配置自动清理
(crontab -l 2>/dev/null; echo "0 3 * * * /usr/local/bin/cleanup.sh") | crontab -
echo "部署完成!"
echo "访问地址:https://stream.example.com"
echo "监控地址:https://stream.example.com/stat"
九、故障恢复
自动故障转移
# failover.py - 自动故障转移
import subprocess
import time
import requests
PRIMARY_SERVER = "stream1.example.com"
BACKUP_SERVER = "stream2.example.com"
CHECK_INTERVAL = 30 # 秒
def check_server(server):
"""检查服务器状态"""
try:
response = requests.get(f"http://{server}/health", timeout=5)
return response.status_code == 200
except:
return False
def switch_to_backup():
"""切换到备用服务器"""
print(f"主服务器故障,切换到备用服务器 {BACKUP_SERVER}")
# 更新DNS记录
subprocess.run([
"aws", "route53", "change-resource-record-sets",
"--hosted-zone-id", "Z1234567890ABC",
"--change-batch", '{"Changes":[{"Action":"UPSERT","ResourceRecordSet":{"Name":"stream.example.com","Type":"A","TTL":60,"ResourceRecords":[{"Value":"' + BACKUP_SERVER + '"}]}}]}'
])
# 发送告警
send_alert("主服务器故障,已切换到备用服务器")
def send_alert(message):
"""发送告警通知"""
# 实现告警逻辑(邮件、短信、Slack等)
pass
def main():
while True:
if not check_server(PRIMARY_SERVER):
print(f"主服务器 {PRIMARY_SERVER} 无响应")
# 重试3次
for i in range(3):
time.sleep(10)
if check_server(PRIMARY_SERVER):
print("主服务器恢复")
break
else:
switch_to_backup()
time.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
main()
总结
搭建一个专业的M3U8视频流服务器需要考虑多个方面:
- 基础架构:选择合适的硬件和网络
- 转码策略:平衡质量和性能
- 分发优化:使用CDN和缓存
- 安全防护:实施访问控制和防盗链
- 监控运维:确保服务稳定可靠
通过本教程的详细指导,您应该能够搭建一个功能完整、性能优秀的M3U8流媒体服务器。记住,实践是最好的老师,建议先在测试环境中验证所有配置。
使用M3U8 Player可以方便地测试您的流媒体服务器,它提供了专业的诊断工具和播放分析,帮助您快速发现和解决问题。
祝您搭建成功!如有问题,欢迎访问M3U8 Player获取更多技术支持。