AI摘要
北海のAI

HLS协议

当我一开始在我的电影网站中直接使用minio直连的方式通过·<video>标签去播放的时候会发现还是很卡顿的,后面打开腾讯视频等网站站时候会发现在看视频的时候控制台会是不是的蹦出一些请求,后面发现这是HLS协议,后面研究了一下放到我自己网站测通了,做个笔记记录下。

总结全流程:

  1. 先将视频切成多档清晰度比如1080p、720p等并且生成主清单(master.m3u8)
  2. 每一档再切成 m 秒的 小片段(.ts) 文件,并且生成对应的 视频清单(index.m3u8)
  3. 浏览器使用 HLS.js 去先拉取master.m3u8,拉取之后选择对应的清晰度获取index.m3u8,然后获取对应的ts分片地址,然后将ts转成浏览器偏向的 fMP4 格式,最终通过 MSE 喂给 <video> 标签
sequenceDiagram
    autonumber
    actor U as 用户
    participant V as 浏览器

一、所用技术

  • 视频、音频分片:FFmpeg

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #!/bin/bash
    set -e
    # 依赖:ffmpeg 4.3+ ,bash

    SRC=${1:-original.mp4} # 默认源片
    IMDB=tt1911658 # 你给的目录前缀
    VER=v1 # 版本号
    ROOT="movie-resource-test/${IMDB}/${VER}"
    mkdir -p "$ROOT"/{video/{1080p,720p,480p},audio/{zh-CN,en-US},subtitles/{zh-CN,en-US},manifest,origin}

    # 1. 原片入库
    cp "$SRC" "$ROOT/origin/"

    # 2. 整片元数据
    ffprobe -v quiet -print_format json -show_format -show_streams "$SRC" > "$ROOT/manifest/metadata.json"

    ########### 视频 ###########
    for res in 1080p 720p 480p; do
    case $res in
    1080p) W=1920;H=1080;BV=2800k;;
    720p) W=1280;H=720; BV=1400k;;
    480p) W=854; H=480; BV=800k;;
    esac
    ffmpeg -hide_banner -i "$SRC" \
    -map 0:v:0 -c:v libx264 -preset fast -s ${W}x${H} -b:v $BV -c:a aac -b:a 128k \
    -hls_time 6 -hls_segment_filename "$ROOT/video/$res/%03d.ts" -f hls "$ROOT/video/$res/index.m3u8"
    done

    ########### 音频(假设原片 track1=中文 track2=英文) ###########
    # 中文音轨
    ffmpeg -i "$SRC" -map 0:a:0 -c:a aac -b:a 96k -hls_time 6 -hls_segment_filename "$ROOT/audio/zh-CN/%03d.ts" -f hls "$ROOT/audio/zh-CN/index.m3u8"
    # 英文音轨
    ffmpeg -i "$SRC" -map 0:a:1 -c:a aac -b:a 96k -hls_time 6 -hls_segment_filename "$ROOT/audio/en-US/%03d.ts" -f hls "$ROOT/audio/en-US/index.m3u8"

    ########### 字幕(如原片带软字幕) ###########
    ffmpeg -i "$SRC" -map 0:s:0 "$ROOT/subtitles/zh-CN/subtitle.vtt" 2>/dev/null || true
    ffmpeg -i "$SRC" -map 0:s:1 "$ROOT/subtitles/en-US/subtitle.vtt" 2>/dev/null || true
    # 若无软字幕,可把外挂 srt 拷进去即可
    # cp my.zh.srt "$ROOT/subtitles/zh-CN/subtitle.vtt"

    ########### master m3u8 ###########
    cat > "$ROOT/manifest/master.m3u8" <<EOF
    #EXTM3U
    #EXT-X-VERSION:6
    #EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
    ../video/1080p/index.m3u8
    #EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=1280x720,CODECS="avc1.4d401f,mp4a.40.2"
    ../video/720p/index.m3u8
    #EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=854x480,CODECS="avc1.42e00a,mp4a.40.2"
    ../video/480p/index.m3u8
    EOF

    echo "✅ 完成,目录结构:"
    tree "$ROOT" -L 5
  • 字幕:whisper

    1
    whisper input.mp4 --model medium --language zh --output_format vtt
  • 元数据json:ffprobe

二、目录格式

最终在Minio的目录类似于下面结构,可以进行参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
movie-resource-test/tt1911658/v1/
├── video/
│ ├── 1080p/
│ │ ├── index.m3u8
│ │ ├── 000.ts
│ │ ├── 001.ts
│ │ └── ...
│ ├── 720p/
│ │ ├── index.m3u8
│ │ ├── 000.ts
│ │ ├── 001.ts
│ │ └── ...
│ └── 480p/
│ ├── index.m3u8
│ ├── 000.ts
│ ├── 001.ts
│ └── ...
├── audio/
│ ├── zh-CN/
│ │ ├── index.m3u8
│ │ ├── 000.ts
│ │ └── ...
│ └── en-US/
│ ├── index.m3u8
│ ├── 000.ts
│ └── ...
├── subtitles/
│ ├── zh-CN/
│ │ ├── subtitle.vtt
│ │ └── ...
│ └── en-US/
│ ├── subtitle.vtt
│ └── ...
├── manifest/
│ ├── master.m3u8
│ └── metadata.json
└── origin/
└── original.mp4

三、对应文件

  • 主清单(master.m3u8)

    1
    2
    3
    4
    5
    6
    7
    8
    #EXTM3U
    #EXT-X-VERSION:6
    #EXT-X-STREAM-INF:BANDWIDTH=514000,RESOLUTION=960x540,CODECS="avc1.42e00a,mp4a.40.2"
    1080p/index.m3u8
    #EXT-X-STREAM-INF:BANDWIDTH=364000,RESOLUTION=720x404,CODECS="avc1.42e00a,mp4a.40.2"
    720p/index.m3u8
    #EXT-X-STREAM-INF:BANDWIDTH=264000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2"
    480p/index.m3u8
  • 媒体清单(index.m3u8)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #EXTM3U
    #EXT-X-VERSION:3
    #EXT-X-TARGETDURATION:12
    #EXT-X-MEDIA-SEQUENCE:0
    #EXT-X-PLAYLIST-TYPE:VOD
    #EXTINF:12.500000,
    000.ts
    #EXTINF:12.500000,
    001.ts
    #EXTINF:12.500000,
    002.ts
    #EXTINF:4.650000,
    003.ts
    #EXT-X-ENDLIST
  • 元数据文件(metadata.json)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // metadata.json
    {
    "movieId": "tt1911658",
    "version": "v1",
    "createdAt": "2023-12-01T10:00:00Z",
    "duration": 7200,
    "videoStreams": [
    {
    "resolution": "1080p",
    "width": 1920,
    "height": 1080,
    "bitrate": 5000,
    "codec": "h264"
    },
    {
    "resolution": "720p",
    "width": 1280,
    "height": 720,
    "bitrate": 2500,
    "codec": "h264"
    }
    ],
    "audioStreams": [
    {
    "language": "zh-CN",
    "bitrate": 128,
    "channels": 2,
    "codec": "aac"
    }
    ]
    }

四、关键字速查

标签 含义
#EXTM3U 必须是第一行,声明 HLS 文件
#EXT-X-VERSION 协议版本,6 以上支持多码率
#EXT-X-TARGETDURATION 最大片长(秒),缓冲策略参考
#EXT-X-MEDIA-SEQUENCE 直播用,当前清单第一段的序号
#EXTINF:<duration>, 后面紧跟的 .ts 时长
#EXT-X-ENDLIST 点播结束符;直播没有这行
#EXT-X-STREAM-INF 主清单用,标明一条清晰度流的带宽、分辨率、编码器
#EXT-X-KEY 加密信息(AES-128 钥匙地址)
#EXT-X-DISCONTINUITY 编码参数突变点(插广告/换码率)
#EXT-X-PLAYLIST-TYPE VOD=点播不会变;EVENT=直播可延长
#EXT-X-MAP fMP4 格式必须先下载的“初始化段”
#EXT-X-PART 低延迟 HLS 的 1 秒以内子切片
#EXT-X-SKIP Delta 更新,只传变化部分,省流量
#EXT-X-START 起播时间偏移(秒),可跳过片头
#EXT-X-ALLOW-CACHE 是否允许 CDN 缓存(YES/NO)

五、闲聊

目前直播、点播、网页、手机、电视,只要 HTTP 能到,就能看到 HLS 身影,不过我看抖音直播主链路用自研 RTMP/WebRTC 低延迟协议(端到端 1 秒级),后面有机会再看看直播这种用的什么技术吧