如何使用FFmpeg6的API进行HLS开发呢?

目录

[前言:为什么 FFmpeg 6 的 HLS 开发才值得写](#前言:为什么 FFmpeg 6 的 HLS 开发才值得写)

[一、先建立心智模型:HLS 在 FFmpeg API 里是什么?](#一、先建立心智模型:HLS 在 FFmpeg API 里是什么?)

[二、最小可跑的 FFmpeg 6 HLS 初始化](#二、最小可跑的 FFmpeg 6 HLS 初始化)

[1️. 找 hls muxer(不是 flv / mp4)](#1️. 找 hls muxer(不是 flv / mp4))

[2️. 核心 HLS 选项](#2️. 核心 HLS 选项)

[3️. 打开输出(playlist 名)](#3️. 打开输出(playlist 名))

[4️. 喂流(和 mp4 一模一样)](#4️. 喂流(和 mp4 一模一样))

[5️. 结束 / 重推](#5️. 结束 / 重推)

[三、FFmpeg 6 里 HLS 切片的"真正触发条件"](#三、FFmpeg 6 里 HLS 切片的“真正触发条件”)

实际触发顺序

[四、进阶:接管 segment IO](#四、进阶:接管 segment IO)

[用 io_openhook segment 写入](#用 io_openhook segment 写入)

[五、多码率 Master Playlist](#五、多码率 Master Playlist)

[Step 1:生成多个 variant](#Step 1:生成多个 variant)

[Step 2:自己写 master.m3u8](#Step 2:自己写 master.m3u8)

[六、fmp4 + LL-HLS](#六、fmp4 + LL-HLS)

参数差异

[七、直播HLS的5个API 级坑](#七、直播HLS的5个API 级坑)

[坑 1:没 keyframe → ts 无限长](#坑 1:没 keyframe → ts 无限长)

[坑 2:avformat_write_header 后立刻断流](#坑 2:avformat_write_header 后立刻断流)

[坑 3:segment_filename 没 %d](#坑 3:segment_filename 没 %d)

[坑 4:pts/dts 不连续没 discontinuity](#坑 4:pts/dts 不连续没 discontinuity)

[坑 5:Windows 路径 UTF-8 问题](#坑 5:Windows 路径 UTF-8 问题)

[八、调试 HLS API 的黄金三板斧](#八、调试 HLS API 的黄金三板斧)

[九、什么时候该用 API,什么时候用命令?](#九、什么时候该用 API,什么时候用命令?)

十、总结


觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。

由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。

FFmpeg 是一个非常强大的开源库,用于处理视频和音频文件,包括编码、转码、流媒体等。使用 FFmpeg 的 API 来开发 HLS(HTTP Live Streaming)流媒体服务,你可以将视频实时编码并封装成 HLS 格式,使其能够在支持 HLS 的播放器中播放。以下是如何使用 FFmpeg 的 API 来实现 HLS 开发的基本步骤和示例。

前言:为什么 FFmpeg 6 的 HLS 开发才值得写

很多人对 FFmpeg + HLS 的认知还停在:

复制代码
ffmpeg -i x.mp4 -f hls out.m3u8

但真实工程里(播放器 / 推流服务器 / 边缘录制 / 本地 OSD 回看):

  • 你要 自己 mux → segment → playlist

  • 要控制 切片时机 / discontinuity / init segment

  • 要对接 CDN 回调 / 磁盘回收 / 加密

FFmpeg 6.x 的 libavformat 对 HLS / Segment muxer 做了不少收敛:

  • hlsenc内部逻辑更稳

  • AVFormatContext选项体系完整

  • io_open / io_close钩子成熟,适合"自己接管 ts 写文件 / 发内存"

这篇不讲命令行,只讲 API 层:用 FFmpeg 6 写一套"能上生产的 HLS 切片器"


一、先建立心智模型:HLS 在 FFmpeg API 里是什么?

API 视角下,HLS 不是"格式",而是:

复制代码
AVFormatContext
   │
   ├── oformat = av_guess_format("hls", NULL, NULL)
   │
   ├── priv_options ──► HLS muxer internal state
   │
   ├── avio_open ──► playlist (.m3u8)
   │
   └── segment_write ──► ts / fmp4 + 滚动删除

一句话:

HLS muxer = 一个"会定时切片的 Muxer",你只负责往里喂 AVPacket

切片、命名、m3u8 维护,全是 libavformat/hlsenc.c帮你做的。


二、最小可跑的 FFmpeg 6 HLS 初始化

1️. 找 hls muxer(不是 flv / mp4)

复制代码
AVFormatContext *oc = avformat_alloc_context();
oc->oformat = av_guess_format("hls", NULL, NULL);
if (!oc->oformat) {
    // FFmpeg 没编进 hls muxer
}

2️. 核心 HLS 选项

FFmpeg 6 推荐用 AVDictionary设置 priv options:

复制代码
AVDictionary *opts = nullptr;

av_dict_set(&opts, "hls_time", "4", 0);              // segment 建议时长
av_dict_set(&opts, "hls_list_size", "5", 0);          // 直播窗口
av_dict_set(&opts, "hls_flags", "delete_segments+omit_endlist", 0);
av_dict_set(&opts, "hls_segment_filename", "seg_%05d.ts", 0);
av_dict_set(&opts, "hls_segment_type", "mpegts", 0);  // 或 fmp4

注意:

  • 不是 AVCodecContext 参数

  • hlsenc的 priv_class options


3️. 打开输出(playlist 名)

复制代码
if (avformat_write_header(oc, &opts) < 0)
    // 失败多半是 hls_segment_filename 路径非法

FFmpeg 6 里:

  • oc->url = "index.m3u8"(playlist)

  • ts 文件名由 hls_segment_filename控制


4️. 喂流(和 mp4 一模一样)

复制代码
AVPacket *pkt = av_packet_alloc();
// fill pkt (stream_index / pts / dts / flags)
pkt->pts = av_rescale_q(pts, tb_in, oc->streams[0]->time_base);
pkt->dts = av_rescale_q(dts, tb_in, oc->streams[0]->time_base);

av_interleaved_write_frame(oc, pkt);
av_packet_unref(pkt);

关键认知

  • GOP / keyframe 决定真实切片点

  • 你不用管"切不切",hls muxer 自己 watch pkt->flags


5️. 结束 / 重推

复制代码
av_write_trailer(oc);
avformat_free_context(oc);

直播场景一般 不写 trailer,进程退出前 flush 即可。


三、FFmpeg 6 里 HLS 切片的"真正触发条件"

很多人 API 层踩坑,以为 hls_time=4就是 4 秒必切。

实际触发顺序

复制代码
av_write_frame()
 → hls_write_packet()
   → cur_segment_duration >= hls_time ?
      AND pkt->flags & AV_PKT_FLAG_KEY ?
        → hls_start_new_segment()

所以你必须保证:

  • 视频流 定期 keyframe

  • 或强制:

    oc->oformat->video_codec = AV_CODEC_ID_H264;
    // 编码器 side-data 给 keyint

FFmpeg 6 官方建议:

HLS 切片时长 = hls_time + GOP 对齐


四、进阶:接管 segment IO

这是工程级差异点。

io_openhook segment 写入

复制代码
static int my_io_open(AVFormatContext *s, AVIOContext **pb,
                      const char *url, int flags, AVDictionary **opts)
{
    // url = "seg_00001.ts"
    FILE *f = fopen(url, "wb");
    avio_alloc_context(...);
    return 0;
}

oc->io_open = my_io_open;
oc->io_close = my_io_close;

能干什么:

  • ts 直接写内存

  • 写 S3 / 本地缓存

  • segment 写完后回调 CDN flush

  • 统计每个 ts 大小 / 码率


五、多码率 Master Playlist

FFmpeg 不会自动生成 master.m3u8,API 层标准做法:

Step 1:生成多个 variant

复制代码
hls_720p/index.m3u8
hls_480p/index.m3u8

Step 2:自己写 master.m3u8

复制代码
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
hls_720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=854x480,CODECS="avc1.64001e,mp4a.40.2"
hls_480p/index.m3u8

FFmpeg 6 官方示例也是这么干的

别试图用 avformat_write_header自动生成(不支持)


六、fmp4 + LL-HLS

参数差异

复制代码
av_dict_set(&opts, "hls_segment_type", "fmp4", 0);
av_dict_set(&opts, "hls_fmp4_init_filename", "init.mp4", 0);
av_dict_set(&opts, "hls_time", "2", 0);
av_dict_set(&opts, "hls_flags", "independent_segments+omit_endlist", 0);

生成结构:

复制代码
init.mp4
seg_00000.m4s
seg_00001.m4s
index.m3u8

优势:

  • 头小

  • Apple LL-HLS 友好

  • 适合 H.265 / AV1


七、直播HLS的5个API 级坑

坑 1:没 keyframe → ts 无限长

解决:

复制代码
x264_param.rc.i_keyint_max = fps * 2;
x264_param.b_repeat_headers = 1;

坑 2:avformat_write_header 后立刻断流

→ 没喂一帧就 write_trailer

→ m3u8 只有 header + ENDLIST


坑 3:segment_filename 没 %d

→ 所有 ts 同名 → CDN 脏缓存


坑 4:pts/dts 不连续没 discontinuity

API 层解决:

复制代码
av_dict_set(&opts, "hls_flags", "discont_start", 0);

或 source 重连后手动:

复制代码
av_write_frame(NULL); // hlsenc 内部识别 discontinuity

坑 5:Windows 路径 UTF-8 问题

建议:

复制代码
av_dict_set(&opts, "hls_segment_filename", "./seg_%05d.ts", 0);

或 hook io_open 自己 fopen_s + utf8 转 wchar


八、调试 HLS API 的黄金三板斧

复制代码
av_log_set_level(AV_LOG_DEBUG);
av_dump_format(oc, 0, oc->url, 1);
  • 直接看 m3u8:

    cat index.m3u8 | grep -A5 "#EXTINF"


九、什么时候该用 API,什么时候用命令?

场景 推荐
播放器本地录制 API
边缘节点切片 API
服务器转码 命令
快速验证 命令

金句:

命令行是 demo,API 才是产品。


十、总结

cpp 复制代码
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libavutil/time.h>
}

#include <cstdio>
#include <cstdlib>

static AVFrame* alloc_yuv_frame(int w, int h, AVPixelFormat fmt)
{
    AVFrame *f = av_frame_alloc();
    f->format = fmt;
    f->width  = w;
    f->height = h;
    av_image_alloc(f->data, f->linesize, w, h, fmt, 32);
    return f;
}

int main()
{
    av_log_set_level(AV_LOG_INFO);

    const int W = 1280, H = 720, FPS = 25;
    const char *out_m3u8 = "out/index.m3u8";

    avformat_alloc_output_context2(&oc, nullptr, "hls", out_m3u8);
    if (!oc) return -1;

    /* ===== HLS 核心选项 ===== */
    AVDictionary *opts = nullptr;
    av_dict_set(&opts, "hls_time", "4", 0);               // 建议切片时长
    av_dict_set(&opts, "hls_list_size", "5", 0);           // 直播窗口
    av_dict_set(&opts, "hls_flags", "delete_segments+omit_endlist", 0);
    av_dict_set(&opts, "hls_segment_filename", "out/seg_%05d.ts", 0);
    av_dict_set(&opts, "hls_segment_type", "mpegts", 0);

    /* ===== 视频编码器 ===== */
    const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    AVStream *st = avformat_new_stream(oc, codec);
    AVCodecContext *cc = avcodec_alloc_context3(codec);

    cc->codec_type   = AVMEDIA_TYPE_VIDEO;
    cc->width        = W;
    cc->height       = H;
    cc->framerate    = {FPS, 1};
    cc->time_base    = {1, FPS};
    cc->gop_size     = FPS * 2;          // ★ 关键:GOP=2s
    cc->keyint_min   = cc->gop_size;
    cc->max_b_frames = 0;
    cc->pix_fmt      = AV_PIX_FMT_YUV420P;
    av_opt_set(cc, "preset", "ultrafast", 0);
    av_opt_set(cc, "tune", "zerolatency", 0);

    avcodec_open2(cc, codec, nullptr);
    avcodec_parameters_from_context(st->codecpar, cc);
    st->time_base = cc->time_base;

    /* ===== 打开 HLS 输出 ===== */
    avformat_write_header(oc, &opts);

    /* ===== 生成测试画面 ===== */
    AVFrame *frame = alloc_yuv_frame(W, H, AV_PIX_FMT_YUV420P);
    AVPacket *pkt = av_packet_alloc();

    for (int i = 0; i < FPS * 20; i++) {   // 录 20 秒
        frame->pts = i;

        // 简单灰阶动画
        uint8_t v = (i * 10) % 255;
        memset(frame->data[0], v, W * H);
        memset(frame->data[1], 128, W/2 * H/2);
        memset(frame->data[2], 128, W/2 * H/2);

        avcodec_send_frame(cc, frame);
        while (avcodec_receive_packet(cc, pkt) == 0) {
            av_packet_rescale_ts(pkt, cc->time_base, st->time_base);
            pkt->stream_index = st->index;
            av_interleaved_write_frame(oc, pkt);
            av_packet_unref(pkt);
        }

        av_usleep(1000000 / FPS);
    }

    /* ===== 收尾 ===== */
    av_write_trailer(oc);

    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&cc);
    avformat_free_context(oc);

    printf("HLS 生成完成:%s\n", out_m3u8);
    return 0;
}

FFmpeg 6 的 HLS相关API并不复杂,复杂的是你以为"切片是时间问题",其实它是 GOP / keyframe / playlist 窗口 / CDN 语义​ 的组合拳。

用 API 写一次 HLS,你会突然明白:

为什么以前用命令行的直播,总是慢那么几秒。

相关推荐
林墨聊AIGC6 小时前
动漫AI视频创作工具在哪找到的?2026年最新动漫AI视频平台与软件指南
大数据·人工智能·ai作画·aigc·音视频
FlightYe8 小时前
音视频修炼之基础理论(五):AAC格式与解析
android·linux·c++·音视频·aac
cnnews9 小时前
Ubuntu 编译 postmarketOS
linux·运维·arm开发·ubuntu·github·音视频
小五兄弟13 小时前
AI仿真人剧出海创收指南
经验分享·音视频·媒体
音视频牛哥13 小时前
从AV2到H.266/VVC:下一代视频编码的技术分野与SmartMediaKit的演进价值
音视频·smartmediakit·av2视频编码·h.266 vvc·av2与vvc区别·av1视频编码·lcevc增强编码
嘟哩DuliDuli14 小时前
AI 视频局部重做,怎么避免破坏成片
人工智能·安全·ai·音视频·软件工程
m0_6145235514 小时前
工程工作流:一句需求做视频封面,先拆主题再做小图验收
人工智能·音视频
沐禾安信15 小时前
抖音的bgm 想保存? 3个音频提取工具,一键分离
音视频·音频·音频转换·视频转换
Peter-Code16 小时前
视频人脸马赛克逐帧处理技术方案解析
音视频
TechVoyager_824616 小时前
从TMDS输入到音视频分离:IT66021FN接收芯片技术详解
音视频·色彩空间转换·it66021·hdmi1.4b·tmds·音视频分离·hdcp1.4