如何使用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,你会突然明白:

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

相关推荐
程序员老陆1 小时前
Qt的QThread::usleep和FFmpeg的libavutil模块的av_usleep哪个精度高一些?
开发语言·qt·ffmpeg·音视频
怪奇云呼军3 小时前
从声音特征到 CRM 回流:闪电智能 Voice Agent 沟通策略自适应系统 v1 实战
android·人工智能·python·音视频·语音识别
The moon forgets6 小时前
Qwen团队提出Ego2Robot, 第一人称视频助力具身VLA训练新数据
人工智能·机器学习·音视频
AI服务老曹8 小时前
AI视频分析API完整流程:设备、算法与告警接口接入指南
人工智能·算法·音视频
AImoon11.18 小时前
MiniMax H3开源引发视频赛道变局,客易云关注AI模型从生成工具迈向生产力工具
人工智能·音视频
乐橙开放平台9 小时前
一周上线校园透明化:listDeviceDetailsByPage 台账 + getKitToken + ImouPlayer 多路墙
后端·物联网·安全·音视频·智能家居
美狐美颜sdk9 小时前
直播APP开发完整流程:需求规划、UI设计、功能开发、美颜SDK接入全解析
大数据·人工智能·音视频·美颜sdk·美颜api
ai产品老杨9 小时前
AI视频分析API项目实战记录
人工智能·音视频
FriendshipT10 小时前
ComfyUI 使用 MiniMax H3 进行文生视频
人工智能·pytorch·python·深度学习·音视频