如何实现mp4和m3u8互转

Write Comment

如何实现mp4和m3u8互转

卷心菜 卷心菜

如何将mp4转换成m3u8

要实现 MP4 转 M3U8 播放,核心是将 MP4 切片为 TS 分片 + 生成 M3U8 索引文件,再通过 HTTP 服务提供访问。以下是完整实现方案,包含原理、工具、步骤和示例代码。

一、核心原理

M3U8 是 HLS(HTTP Live Streaming)协议的索引文件,本质是文本文件,记录 TS 分片的地址、时长、加密信息等;TS 是分片后的视频文件(通常 2-10 秒 / 片)。服务端需完成:

  1. 将 MP4 转码 / 切片为 TS 分片;
  2. 生成对应的 M3U8 索引文件;
  3. 用 HTTP 服务暴露 M3U8 和 TS 文件(支持断点续传);
  4. 客户端通过播放器(如 video.js、hls.js)加载 M3U8 即可播放。

二、必备工具

核心工具是 FFmpeg(跨平台音视频处理工具),需先安装:

  • Linuxapt install ffmpeg(Debian/Ubuntu)或 yum install ffmpeg(CentOS);

  • Windows:下载 FFmpeg 官方包,配置环境变量;

  • Macbrew 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文件名

进阶命令(适配不同分辨率、加密)

  1. 多码率适配(自适应码率)
# 生成低/中/高码率的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. 加密播放(防止盗链)

    需先生成加密密钥,再切片:

# 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 部署(推荐,高性能)

  1. 配置 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;
    }
}

  1. 将切片后的 M3U8、TS 文件放到 /var/www/hls 目录;
  2. 重启 Nginx:nginx -s reload
  3. 访问地址: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>

六、优化与注意事项

  1. 性能优化

    • TS 分片时长建议 2-10 秒(太短会增加请求数,太长会导致首屏加载慢);

    • 启用 Gzip/Brotli 压缩 M3U8 文件(TS 已压缩,无需再压);

    • 配合 CDN 分发 TS 分片(降低源站压力)。

  2. 兼容性

    • PC 端:Chrome/Firefox/Safari 均支持,IE 需借助 hls.js;

    • 移动端:iOS 原生支持,Android 7.0+ 原生支持,低版本需 hls.js。

  3. 防盗链 / 加密

    • 基础:Nginx 限制 Referer/IP;

    • 进阶:HLS 加密(如上述 FFmpeg 加密方案)、URL 签名(Nginx 配合 lua 实现);

    • 终极:DRM 加密(如 Widevine/PlayReady,复杂度高)。

  4. 批量处理:若需批量转换 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.tsvideo_002.tsvideo_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 能访问密钥文件(或手动指定密钥,参考之前的加密方案)。

六、关键优化与注意事项

  1. 加速转换

    • 优先用 -c copy 直接封装(转码速度比封装慢 10~20 倍);

    • 若需转码,可加 -threads 4(指定 4 线程,根据 CPU 核心数调整):

      ffmpeg -i input.ts -c:v h264 -c:a aac -threads 4 output.mp4
      
  2. 修复 MP4 索引(moov 原子):部分播放器无法播放转换后的 MP4,是因为 moov 原子在文件末尾,可通过参数将 moov 原子移到开头:

    ffmpeg -i input.ts -c copy -movflags +faststart output.mp4
    

    -movflags +faststart:优化 MP4 结构,适合网络播放(首屏加载更快)。

  3. 兼容问题

    • TS 分片若编码不一致(如部分分片是 H264,部分是 H265),合并会失败,需先统一转码为相同编码;

    • 若转换后无声音 / 画面,检查 TS 文件的音视频流:

      ffmpeg -i input.ts  # 查看流信息,确认有视频流(Stream #0:0)和音频流(Stream #0:1)
      
  4. 批量转换:若需转换多个 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),速度快且无损;仅在兼容性问题时转码,兼顾播放体验和效率。

THE END

登录 to comment~