目录前言为什么 FFmpeg 6 的 HLS 开发才值得写一、先建立心智模型HLS 在 FFmpeg API 里是什么二、最小可跑的 FFmpeg 6 HLS 初始化1️. 找 hls muxer不是 flv / mp42️. 核心 HLS 选项3️. 打开输出playlist 名4️. 喂流和 mp4 一模一样5️. 结束 / 重推三、FFmpeg 6 里 HLS 切片的“真正触发条件”实际触发顺序四、进阶接管 segment IO用 io_openhook segment 写入五、多码率 Master PlaylistStep 1生成多个 variantStep 2自己写 master.m3u8六、fmp4 LL-HLS参数差异七、直播HLS的5个API 级坑坑 1没 keyframe → ts 无限长坑 2avformat_write_header 后立刻断流坑 3segment_filename 没 %d坑 4pts/dts 不连续没 discontinuity坑 5Windows 路径 UTF-8 问题八、调试 HLS API 的黄金三板斧九、什么时候该用 API什么时候用命令十、总结觉得有用就请您帮忙点赞转发收藏吧您的鼓励是我创作的动力多谢看官。由于能力水平有限文中的错误或不严谨的地方在所难免还请批评指正。FFmpeg 是一个非常强大的开源库用于处理视频和音频文件包括编码、转码、流媒体等。使用 FFmpeg 的 API 来开发 HLSHTTP 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 / mp4AVFormatContext *oc avformat_alloc_context(); oc-oformat av_guess_format(hls, NULL, NULL); if (!oc-oformat) { // FFmpeg 没编进 hls muxer }2️. 核心 HLS 选项FFmpeg 6 推荐用AVDictionary设置 priv optionsAVDictionary *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_segmentsomit_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 options3️. 打开输出playlist 名if (avformat_write_header(oc, opts) 0) // 失败多半是 hls_segment_filename 路径非法FFmpeg 6 里oc-url index.m3u8playlistts 文件名由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-flags5️. 结束 / 重推av_write_trailer(oc); avformat_free_context(oc);直播场景一般不写 trailer进程退出前 flush 即可。三、FFmpeg 6 里 HLS 切片的“真正触发条件”很多人 API 层踩坑以为hls_time4就是 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 给 keyintFFmpeg 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 PlaylistFFmpeg不会自动生成 master.m3u8API 层标准做法Step 1生成多个 varianthls_720p/index.m3u8 hls_480p/index.m3u8Step 2自己写 master.m3u8#EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:BANDWIDTH1500000,RESOLUTION1280x720,CODECSavc1.64001f,mp4a.40.2 hls_720p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH800000,RESOLUTION854x480,CODECSavc1.64001e,mp4a.40.2 hls_480p/index.m3u8FFmpeg 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_segmentsomit_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;坑 2avformat_write_header 后立刻断流→ 没喂一帧就write_trailer→ m3u8 只有 header ENDLIST坑 3segment_filename 没 %d→ 所有 ts 同名 → CDN 脏缓存坑 4pts/dts 不连续没 discontinuityAPI 层解决av_dict_set(opts, hls_flags, discont_start, 0);或 source 重连后手动av_write_frame(NULL); // hlsenc 内部识别 discontinuity坑 5Windows 路径 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);直接看 m3u8cat index.m3u8 | grep -A5 #EXTINF九、什么时候该用 API什么时候用命令场景推荐播放器本地录制API边缘节点切片API服务器转码命令快速验证命令金句命令行是 demoAPI 才是产品。十、总结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_segmentsomit_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; // ★ 关键GOP2s 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你会突然明白为什么以前用命令行的直播总是慢那么几秒。