如何将mp4转换成m3u8
要实现 MP4 转 M3U8 播放,核心是将 MP4 切片为 TS 分片 + 生成 M3U8 索引文件,再通过 HTTP 服务提供访问。以下是完整实现方案,包含原理、工具、步骤和示例代码。
一、核心原理
M3U8 是 HLS(HTTP Live Streaming)协议的索引文件,本质是文本文件,记录 TS 分片的地址、时长、加密信息等;TS 是分片后的视频文件(通常 2-10 秒 / 片)。服务端需完成:
- 将 MP4 转码 / 切片为 TS 分片;
- 生成对应的 M3U8 索引文件;
- 用 HTTP 服务暴露 M3U8 和 TS 文件(支持断点续传);
- 客户端通过播放器(如 video.js、hls.js)加载 M3U8 即可播放。
二、必备工具
核心工具是 FFmpeg(跨平台音视频处理工具),需先安装:
-
Linux:
apt install ffmpeg(Debian/Ubuntu)或yum install ffmpeg(CentOS); -
Windows:下载 FFmpeg 官方包,配置环境变量;
-
Mac:
brew install ffmpeg。
三、步骤 1:MP4 转 M3U8(FFmpeg 命令)
基础命令(无加密、通用)
# 基本切片(2秒/片,生成 M3U8 + TS 分片)
ffmpeg -i input.mp4 \
-c:v h264 -c:a aac \ # 编码格式(HLS 推荐 H264+AAC)
-hls_time 2 \ # 每个TS分片时长(秒)
-hls_list_size 0 \ # 0 表示生成完整的M3U8(非直播)
-hls_segment_filename "video_%03d.ts" \ # TS分片命名规则
output.m3u8 # 输出的M3U8文件名
进阶命令(适配不同分辨率、加密)
- 多码率适配(自适应码率):
# 生成低/中/高码率的M3U8(主索引文件 master.m3u8)
ffmpeg -i input.mp4 \
-filter_complex \
"[0:v]split=3[v1][v2][v3]; \
[v1]scale=640:360[v1_out]; \
[v2]scale=1280:720[v2_out]; \
[v3]scale=1920:1080[v3_out]" \
-map "[v1_out]" -map 0:a -b:v:0 800k -b:a:0 128k \
-map "[v2_out]" -map 0:a -b:v:1 2500k -b:a:1 128k \
-map "[v3_out]" -map 0:a -b:v:2 5000k -b:a:2 128k \
-c:v h264 -c:a aac \
-hls_time 2 -hls_list_size 0 \
-hls_segment_filename "video_%v_%03d.ts" \
-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
-master_pl_name master.m3u8 \
output_%v.m3u8
-
加密播放(防止盗链):
需先生成加密密钥,再切片:
# 1. 生成加密密钥和IV
openssl rand 16 > key.key # 16位密钥
openssl rand -hex 16 > iv.txt # 16进制IV
# 2. 加密切片
ffmpeg -i input.mp4 \
-c:v h264 -c:a aac \
-hls_time 2 -hls_list_size 0 \
-hls_segment_filename "video_%03d.ts" \
-hls_key_info_file key.info \ # 密钥配置文件
output.m3u8
key.info 内容示例:
http://your-server.com/key.key # 密钥访问地址
key.key # 本地密钥文件路径
iv_value # IV值(如openssl生成的)
四、步骤 2:服务端部署(暴露文件)
需用 HTTP 服务(如 Nginx、Node.js、Python)暴露 M3U8 和 TS 文件,核心要求:
-
支持
GET请求; -
支持字节范围请求(Range,播放器分片加载需要);
-
MIME 类型配置正确(关键)。
方案 1:Nginx 部署(推荐,高性能)
- 配置 Nginx:
server {
listen 80;
server_name your-domain.com;
# 视频文件根目录
root /var/www/hls;
# 启用字节范围请求
mp4; # 或 aio on; (通用范围请求)
# 配置MIME类型(关键)
types {
application/x-mpegURL m3u8;
video/MP2T ts;
video/mp4 mp4;
}
# 可选:防盗链(限制Referer)
valid_referers none blocked your-domain.com;
if ($invalid_referer) {
return 403;
}
}
- 将切片后的 M3U8、TS 文件放到
/var/www/hls目录; - 重启 Nginx:
nginx -s reload; - 访问地址:
http://your-domain.com/output.m3u8。
方案 2:Node.js 简易服务(快速测试)
const http = require('http');
const fs = require('fs');
const path = require('path');
const mime = require('mime-types'); // 需安装:npm i mime-types
const server = http.createServer((req, res) => {
const filePath = path.join(__dirname, req.url === '/' ? 'output.m3u8' : req.url);
// 检查文件是否存在
if (!fs.existsSync(filePath)) {
res.writeHead(404);
res.end('File not found');
return;
}
// 设置MIME类型
const mimeType = mime.lookup(filePath) || 'application/octet-stream';
res.setHeader('Content-Type', mimeType);
// 支持Range请求(核心)
const stats = fs.statSync(filePath);
const fileSize = stats.size;
const range = req.headers.range;
if (range) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunksize = (end - start) + 1;
const file = fs.createReadStream(filePath, { start, end });
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': mimeType,
};
res.writeHead(206, head);
file.pipe(res);
} else {
res.setHeader('Content-Length', fileSize);
fs.createReadStream(filePath).pipe(res);
}
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
方案 3:Python 简易服务(快速测试)
from http.server import HTTPServer, SimpleHTTPRequestHandler
import mimetypes
# 扩展MIME类型
mimetypes.add_type('application/x-mpegURL', '.m3u8')
mimetypes.add_type('video/MP2T', '.ts')
class RangeRequestHandler(SimpleHTTPRequestHandler):
# 支持Range请求
def send_head(self):
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
return self.list_directory(path)
ctype = self.guess_type(path)
try:
f = open(path, 'rb')
except OSError:
self.send_error(404, "File not found")
return None
try:
fs = os.fstat(f.fileno())
size = fs[6]
start = 0
end = size - 1
if 'Range' in self.headers:
start, end = self.headers['Range'].strip().split('=')[1].split('-')
start = int(start) if start else 0
end = int(end) if end else size - 1
length = end - start + 1
self.send_response(206)
self.send_header('Content-Type', ctype)
self.send_header('Content-Range', f'bytes {start}-{end}/{size}')
self.send_header('Content-Length', str(length))
self.send_header('Last-Modified', self.date_time_string(fs.st_mtime))
self.end_headers()
f.seek(start)
return f
except:
f.close()
raise
if __name__ == '__main__':
server_address = ('', 8000)
httpd = HTTPServer(server_address, RangeRequestHandler)
print('Server running on http://localhost:8000')
httpd.serve_forever()
五、步骤 3:客户端播放(测试)
使用支持 HLS 的播放器加载 M3U8 地址:
示例 1:video.js(前端通用)
<!DOCTYPE html>
<html>
<head>
<link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet">
<script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/videojs-contrib-hls@5.15.0/dist/videojs-contrib-hls.min.js"></script>
</head>
<body>
<video id="my-player" class="video-js vjs-big-play-centered" controls width="800">
<source src="http://your-server.com/output.m3u8" type="application/x-mpegURL">
</video>
<script>
var player = videojs('my-player');
player.play();
</script>
</body>
</html>
示例 2:原生 video 标签(部分浏览器支持)
<video controls width="800">
<source src="http://your-server.com/output.m3u8" type="application/x-mpegURL">
您的浏览器不支持HLS播放
</video>
六、优化与注意事项
-
性能优化:
-
TS 分片时长建议 2-10 秒(太短会增加请求数,太长会导致首屏加载慢);
-
启用 Gzip/Brotli 压缩 M3U8 文件(TS 已压缩,无需再压);
-
配合 CDN 分发 TS 分片(降低源站压力)。
-
-
兼容性:
-
PC 端:Chrome/Firefox/Safari 均支持,IE 需借助 hls.js;
-
移动端:iOS 原生支持,Android 7.0+ 原生支持,低版本需 hls.js。
-
-
防盗链 / 加密:
-
基础:Nginx 限制 Referer/IP;
-
进阶:HLS 加密(如上述 FFmpeg 加密方案)、URL 签名(Nginx 配合 lua 实现);
-
终极:DRM 加密(如 Widevine/PlayReady,复杂度高)。
-
-
批量处理:若需批量转换 MP4,可写 Shell 脚本:
bash
运行
# 遍历目录下所有MP4 for file in *.mp4; do ffmpeg -i "$file" -c:v h264 -c:a aac -hls_time 2 -hls_list_size 0 -hls_segment_filename "${file%.mp4}_%03d.ts" "${file%.mp4}.m3u8" done
总结
核心流程:FFmpeg 切片 MP4 为 M3U8+TS → HTTP 服务暴露文件(支持 Range) → 客户端播放器加载 M3U8。生产环境优先用 Nginx 部署,兼顾性能和兼容性;测试环境可快速用 Node.js/Python 服务验证。
将 TS 文件转换为 MP4
将 TS 文件转换为 MP4 是音视频处理中常见的需求,核心工具依然是 FFmpeg(跨平台、功能全面),操作简单且支持单 TS 文件、多个 TS 分片合并转 MP4 等场景。以下是完整实现方案,包含不同场景的命令和注意事项:
一、核心原理
TS(MPEG-2 Transport Stream)是流媒体分片格式,MP4 是封装格式;转换本质是重新封装(或转码)TS 中的音视频流到 MP4 容器,过程可分为「直接封装(无损)」和「转码(适配兼容)」两种方式。
二、前置准备
确保已安装 FFmpeg(参考之前的安装方法:Linux apt install ffmpeg、Mac brew install ffmpeg、Windows 配置环境变量)。
三、场景 1:单个 TS 文件转 MP4(最常用)
方案 1:直接封装(无损、速度快)
如果 TS 文件的编码是 H264+AAC(HLS 标准编码),可直接封装为 MP4,无需重新编码,速度极快(几秒完成):
ffmpeg -i input.ts -c copy output.mp4
-
-c copy:直接拷贝音视频流(copy mode),不转码,无损且高效; -
input.ts:待转换的 TS 文件路径; -
output.mp4:输出的 MP4 文件路径。
方案 2:转码(兼容老设备 / 播放器)
如果 TS 编码非 H264/AAC(如 MPEG-2 视频),或播放器不兼容直接封装的 MP4,需转码为通用编码:
ffmpeg -i input.ts -c:v h264 -c:a aac -b:v 2000k -b:a 128k output.mp4
-
-c:v h264:视频转码为 H264(通用编码); -
-c:a aac:音频转码为 AAC; -
-b:v 2000k:视频码率 2000kbps(可根据需求调整,如 1080P 用 5000k); -
-b:a 128k:音频码率 128kbps(标准值)。
四、场景 2:多个 TS 分片合并为单个 MP4
如果是从 M3U8 下载的多个 TS 分片(如 video_001.ts、video_002.ts、video_003.ts),需先合并再转 MP4,有两种方式:
方案 1:FFmpeg 直接合并(推荐)
# 方式1:通过concat协议合并(适合少量分片)
ffmpeg -i "concat:video_001.ts|video_002.ts|video_003.ts" -c copy output.mp4
# 方式2:通过文件列表合并(适合大量分片)
# 1. 先创建文件列表(list.txt)
echo "file 'video_001.ts'" > list.txt
echo "file 'video_002.ts'" >> list.txt
echo "file 'video_003.ts'" >> list.txt
# 2. 基于列表合并转MP4
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
-
-f concat:指定 concat 格式合并; -
-safe 0:允许访问任意路径的文件(避免 FFmpeg 限制); -
注意:TS 分片需是同编码、同分辨率、同帧率,否则合并会出错。
方案 2:先合并 TS 再转 MP4(Windows 批处理 / Linux Shell)
-
Linux/Mac:
# 合并所有TS分片为一个大TS,再转MP4 cat video_*.ts > all.ts ffmpeg -i all.ts -c copy output.mp4 -
Windows(CMD):
cmd
# 合并TS分片 copy /b video_001.ts+video_002.ts+video_003.ts all.ts # 转MP4 ffmpeg -i all.ts -c copy output.mp4
五、场景 3:从 M3U8 直接下载并转 MP4(一步到位)
如果有 M3U8 地址,无需手动下载 TS 分片,可直接通过 FFmpeg 下载并转换为 MP4:
ffmpeg -i http://your-server.com/output.m3u8 -c copy output.mp4
-
该命令会自动下载所有 TS 分片,合并后封装为 MP4,无需手动处理分片;
-
若遇到加密的 M3U8(带密钥),需确保 FFmpeg 能访问密钥文件(或手动指定密钥,参考之前的加密方案)。
六、关键优化与注意事项
-
加速转换:
-
优先用
-c copy直接封装(转码速度比封装慢 10~20 倍); -
若需转码,可加
-threads 4(指定 4 线程,根据 CPU 核心数调整):ffmpeg -i input.ts -c:v h264 -c:a aac -threads 4 output.mp4
-
-
修复 MP4 索引(moov 原子):部分播放器无法播放转换后的 MP4,是因为 moov 原子在文件末尾,可通过参数将 moov 原子移到开头:
ffmpeg -i input.ts -c copy -movflags +faststart output.mp4-movflags +faststart:优化 MP4 结构,适合网络播放(首屏加载更快)。 -
兼容问题:
-
TS 分片若编码不一致(如部分分片是 H264,部分是 H265),合并会失败,需先统一转码为相同编码;
-
若转换后无声音 / 画面,检查 TS 文件的音视频流:
ffmpeg -i input.ts # 查看流信息,确认有视频流(Stream #0:0)和音频流(Stream #0:1)
-
-
批量转换:若需转换多个 TS 文件,可写脚本:
-
Linux/Mac:
for file in *.ts; do ffmpeg -i "$file" -c copy "${file%.ts}.mp4" done -
Windows(批处理):
for %%i in (*.ts) do ffmpeg -i "%%i" -c copy "%%~ni.mp4"
-
总结
转换核心命令:
-
单 TS 无损转 MP4:
ffmpeg -i input.ts -c copy output.mp4; -
多 TS 合并转 MP4:
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4; -
M3U8 直接转 MP4:
ffmpeg -i http://xxx.m3u8 -c copy output.mp4。
优先使用「直接封装」(-c copy),速度快且无损;仅在兼容性问题时转码,兼顾播放体验和效率。