视频转码
背景
在看抖音网页版的时候,发现抖音的视频是这种形式的:blob:http://asset.chenbobo.top/3d300215-bbcc-4e85-a766-c1568b70a338,但是当访问 http://asset.chenbobo.top/3d300215-bbcc-4e85-a766-c1568b70a338时,却发现,这个是一个 404,这是为了防止自己的网页被爬虫爬取实现的一个技术,后端返回前端的并不是一个 mp4 的 url,而是一个 .m3u8 的地址,通过 HLS(HTTP Live Streaming) 进行加载
流程
在一个短视频平台中,用户上传的视频可能是各种不同的格式,为了统一管理,一般来说,都会将用户上传的视频转换为统一格式,比如:mp4,同时对用户上传的视频进行分片,这样就实现了加载功能,并对用户的视频生成不同的清晰度,这样就是实现了切换清晰度的功能。

技术
FFmpeg
简介
FFmpeg 是一个开源的多媒体处理工具套件,它包含很多命令行工具,最著名的是
ffmpeg命令,用于音视频转码、剪辑、格式转换、切片、合成等操作它的功能非常强大,支持几乎所有主流音视频格式和编解码器。
官方最主流的用法是通过命令行终端执行,比如:
ffmpeg -i input.mp4 -c:v libx264 output.mp4这是最直接、最灵活、也最普遍的使用方式。
安装
安装 EPEL 和 RPM Fusion 仓库
sudo dnf install epel-release -y
sudo dnf install https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm -y安装 FFmpeg
sudo dnf install ffmpeg ffmpeg-devel -y验证安装
ffmpeg -version
使用
将一个输入视频转码
ffmpeg -i input.mp4 -c:v libx264 -preset fast -crf 23 output.mp4生成多个分辨率
# 720p
ffmpeg -i input.mp4 -vf scale=-2:720 -c:v libx264 -preset fast -crf 23 output_720p.mp4
# 480p
ffmpeg -i input.mp4 -vf scale=-2:480 -c:v libx264 -preset fast -crf 23 output_480p.mp4
# 360p
ffmpeg -i input.mp4 -vf scale=-2:360 -c:v libx264 -preset fast -crf 23 output_360p.mp4-vf scale=-2:720表示保持宽高比,把高度设置为 720,宽度自动调整为 2 的倍数(ffmpeg 要求)适合用于多码率视频适配播放器分辨率切换
切片成 HLS(实现在线播放、秒切分辨率)
ffmpeg -i input.mp4 \
-profile:v baseline -level 3.0 -start_number 0 \
-hls_time 5 -hls_list_size 0 -f hls output.m3u8生成
output.m3u8索引文件,和一堆.ts视频切片可配合 Nginx 播放
想做多分辨率自适应 HLS,我也可以教你
分离音视频轨道
拆分音频:
ffmpeg -i input.mp4 -vn -acodec copy audio.aac拆分视频(无声):
ffmpeg -i input.mp4 -an -vcodec copy video.mp4给视频加水印
ffmpeg -i input.mp4 -i watermark.png -filter_complex "overlay=10:10" output.mp4overlay=10:10表示将图片放在左上角 (10px, 10px)
裁剪视频时间段
ffmpeg -ss 00:00:05 -i input.mp4 -t 10 -c copy output_clip.mp4-ss:起始时间-t:持续时间(10 秒)-c copy:不转码,快速剪切
合并多个视频
生成一个 file_list.txt:
file 'part1.mp4'
file 'part2.mp4'
file 'part3.mp4'合并
ffmpeg -f concat -safe 0 -i file_list.txt -c copy merged.mp4
Demo
项目结构:
/var
/www
/assets
/videos
index.html
/outputs
/scripts
transcode.sh新建脚本 /scripts/transcode.sh
#!/bin/bash
# 参数:输入文件路径(MP4)
INPUT=$1
BASENAME=$(basename "$INPUT" .mp4)
OUTPUT_DIR="../outputs/$BASENAME"
mkdir -p "$OUTPUT_DIR"
# 生成多个分辨率的 HLS 文件
for RES in 360 480 720; do
ffmpeg -i "$INPUT" \
-vf scale=-2:$RES \
-c:a aac -ar 48000 -c:v libx264 -profile:v main -crf 20 -sc_threshold 0 \
-g 48 -keyint_min 48 \
-hls_time 4 -hls_playlist_type vod \
-b:v 1000k \
-hls_segment_filename "$OUTPUT_DIR/${RES}p_%03d.ts" \
"$OUTPUT_DIR/${RES}p.m3u8"
done
# 产生切片
cat > "$OUTPUT_DIR/master.m3u8" <<EOF
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=854x480
480p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
720p.m3u8
EOF
echo "✅ 转码完成:$OUTPUT_DIR"编写 nginx 配置
server {
listen 80;
server_name asset.chenbobo.top;
location /assets/ {
alias /var/www/assets/videos/;
autoindex on;
}
location /videos/ {
alias /var/www/assets/videos/outputs/;
add_header Cache-Control no-cache;
autoindex on;
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
}
}执行脚本
./transcode.sh ./${filename}.mp4这会在 outputs/ 中生成一个 ${filename} 文件夹,并在其中有很多文件
.png)
编写 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HLS 手动切换清晰度</title>
</head>
<body>
<h2>HLS 视频播放器 - 手动清晰度选择</h2>
<video id="video" controls width="640" height="360"></video>
<br>
<label for="qualitySelect">清晰度:</label>
<select id="qualitySelect">
<option value="-1">自适应</option>
</select>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
const video = document.getElementById('video');
const qualitySelect = document.getElementById('qualitySelect');
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource('http://asset.chenbobo.top/videos/IMG_8633.MOV/master.m3u8'); // ← 这里替换为你的实际地址
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
const levels = hls.levels;
levels.forEach((level, i) => {
const option = document.createElement('option');
option.value = i;
option.text = `${level.height}p`;
qualitySelect.appendChild(option);
});
qualitySelect.addEventListener('change', () => {
const level = parseInt(qualitySelect.value, 10);
hls.currentLevel = level;
});
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// iOS Safari 原生支持 HLS
video.src = 'http://asset.chenbobo.top/videos/IMG_8633.MOV/master.m3u8'; // ← 替换为实际地址
}
</script>
</body>
</html>
然后访问:http://asset.chenbobo.top/assets/ 即可看到效果