FFmpeg 使用指南:基于 libffmpeg 实现 HLS 视频分片
摘要:本文全面介绍了 FFmpeg 核心库(libffmpeg)在 C++ 项目中的应用,重点讲解了如何使用 libavformat、libavcodec 等库实现 HLS 视频分片功能。内容涵盖 MP4 文件结构、HLS 协议原理、M3U8 文件格式、FFmpeg 关键数据结构和 API 接口,并提供了完整的代码示例和面向对象封装方案,帮助开发者从零掌握视频转码与流媒体处理的核心技术。
C++脚手架仓库地址 :https://gitee.com/chen-weifeng-cwf/developing-scaffolding-for-c
📑 目录
- [1. 什么是 libffmpeg?](#1. 什么是 libffmpeg?)
- [1.1 FFmpeg 项目与库的关系](#1.1 FFmpeg 项目与库的关系)
- [1.2 核心组件库](#1.2 核心组件库)
- [1.3 主要功能](#1.3 主要功能)
- [2. MP4 视频格式简介](#2. MP4 视频格式简介)
- [2.1 MP4 文件结构](#2.1 MP4 文件结构)
- [3. HLS 协议](#3. HLS 协议)
- [3.1 工作流程](#3.1 工作流程)
- [3.2 优缺点](#3.2 优缺点)
- [4. M3U8 文件格式](#4. M3U8 文件格式)
- [4.1 文件示例](#4.1 文件示例)
- [4.2 关键标签](#4.2 关键标签)
- [5. 开发环境准备](#5. 开发环境准备)
- [5.1 安装开发包](#5.1 安装开发包)
- [5.2 关键头文件](#5.2 关键头文件)
- [6. 关键数据结构](#6. 关键数据结构)
- [6.1 AVFormatContext](#6.1 AVFormatContext)
- [6.2 AVStream](#6.2 AVStream)
- [6.3 AVCodecParameters](#6.3 AVCodecParameters)
- [6.4 AVPacket](#6.4 AVPacket)
- [7. 关键 API 接口](#7. 关键 API 接口)
- [7.1 视频操作接口](#7.1 视频操作接口)
- [7.2 时间基转换](#7.2 时间基转换)
- [7.3 字典选项接口](#7.3 字典选项接口)
- [8. 完整实现:HLS 视频分片](#8. 完整实现:HLS 视频分片)
- [8.1 整体流程](#8.1 整体流程)
- [8.2 示例代码](#8.2 示例代码)
- [8.3 运行演示](#8.3 运行演示)
- [9. 面向对象封装](#9. 面向对象封装)
- [9.1 M3U8Info 类 --- M3U8 文件解析与生成](#9.1 M3U8Info 类 — M3U8 文件解析与生成)
- [9.2 HLSTranscoder 类 --- HLS 视频转码](#9.2 HLSTranscoder 类 — HLS 视频转码)
- [9.3 使用示例](#9.3 使用示例)
- [9.4 运行结果](#9.4 运行结果)
- [10. 总结](#10. 总结)
本文将介绍 FFmpeg 的核心库(libffmpeg)及其在 C++ 项目中的使用,并以 HLS 分片处理为例,带你从零到一掌握视频转码的核心流程。
1. 什么是 libffmpeg?
libffmpeg 是 FFmpeg 项目中包含的一系列核心共享库(Shared Libraries)的统称。这些库是 FFmpeg 强大功能的技术基石,开发者可以利用它们在自己的软件中实现复杂的多媒体处理任务。
1.1 FFmpeg 项目与库的关系
- FFmpeg 是一个领先的开源项目,主要处理音频、视频、字幕等多媒体流。其核心是命令行工具
ffmpeg、ffplay和ffprobe。 - libffmpeg 并非单个库,而是指 FFmpeg 项目构建并分发的一组底层库集合,提供了实现命令行工具功能的 API。
1.2 核心组件库
| 库名 | 功能 |
|---|---|
| libavcodec | 编解码器库,提供了数百种音视频编解码器的实现,是处理多媒体文件内容的核心引擎 |
| libavformat | 封装/解封装库,用于读取和写入各种媒体容器格式(MP4, MKV, AVI, MOV, FLV, WebM 等) |
| libavutil | 通用工具库,包含数学操作、数据结构、内存管理、错误处理、日志记录、时间戳处理等 |
| libswscale | 图像缩放和像素格式转换库(缩放、色彩空间转换) |
| libavfilter | 滤镜处理库,实现复杂的音视频滤镜链(裁剪、缩放、水印、降噪、混音等) |
| libavdevice | 设备输入/输出库,访问摄像头、麦克风、屏幕采集等外部设备 |
| libpostproc | 后期处理库,提供去块效应等特定后期处理滤镜 |
| libswresample | 音频重采样库,支持采样率转换、通道布局转换和样本格式转换 |
1.3 主要功能
- 多媒体解码:读取和解压几乎所有主流的音视频格式
- 多媒体编码:将原始或处理过的音视频数据压缩编码成各种格式
- 格式转换:在不同编码和封装格式之间转换
- 流媒体处理:支持直播流的输入/输出、转封装、协议处理(RTMP, RTSP, HLS, DASH, SRT 等)
- 音视频过滤与编辑:实现复杂的图像处理、音频效果、剪辑、合成等
- 设备捕获与播放:抓取摄像头、麦克风输入;在自定义界面播放音视频
- 媒体分析:提取媒体文件的元信息、流属性、帧信息等
2. MP4 视频格式简介
MP4(MPEG-4)是一种非常流行的数字多媒体容器格式,它将视频、音频、字幕、图片等各种媒体数据和元数据打包在一起。
2.1 MP4 文件结构
MP4 文件由称为 Box(或称 Atom) 的基本单元组成。每个 Box 包含 Header 和 Data 两部分。Header 指明 Box 的类型和大小,Data 承载实际内容或嵌套其他 Box。
一个 MP4 文件可以看作是由这些 Box 组成的树状结构,最顶层的 Box 有三个:

-
ftyp(File Type Box):总是位于文件开头,用于标识文件类型。
-
moov (Movie Box)和 mdat(Media Data Box):
moov在前、mdat在后:更利于流媒体播放(Fast Start),无需下载完整文件即可开始播放mdat在前、moov在后:常见于实时录制的视频,因为录制开始时无法预知媒体数据的最终大小
-
stbl(Sample Table Box):尤为关键,它像一个详细的索引目录,记录了媒体数据如何存储、如何解码以及时间信息如何映射,是播放器能够正确定位和播放媒体(尤其是随机拖拽)的核心。
3. HLS 协议
HLS(HTTP Live Streaming)是 Apple 提出的基于 HTTP 的流媒体传输协议,已被业界广泛采纳,成为主流的流媒体传输方案之一。
3.1 工作流程
- 编码与分片:原始视频被转码为多码率版本,并切割为 TS 分片(通常 2~10 秒)
- 生成 M3U8 播放列表:每个码率对应一个 M3U8 文件,主播放列表(Master Playlist)描述所有可用码率
- 客户端拉取:播放器根据网络状况选择合适码率,按顺序下载分片并播放
3.2 优缺点
优点:
- 基于 HTTP,兼容性强(无特殊服务器或端口要求)
- 支持自适应码率切换(ABR),提升弱网体验
- 天然支持 CDN 和缓存
缺点:
- 延迟较高(通常 10~30 秒),不适合实时交互场景
- 分片和 M3U8 更新机制对服务器有一定压力

4. M3U8 文件格式
4.1 文件示例
m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
http://192.168.65.130:9000/segment0.ts
#EXTINF:8.5,
segment1.ts
#EXTINF:9.2,
segment2.ts
#EXT-X-ENDLIST
4.2 关键标签
| 标签 | 说明 |
|---|---|
#EXTM3U |
文件头,标识 M3U8 格式 |
#EXT-X-VERSION |
指定 HLS 协议版本(如 3、6、7) |
#EXT-X-PLAYLIST-TYPE |
播放类型,VOD 表示当前视频为点播类型 |
#EXT-X-TARGETDURATION |
所有分片的最大时长(单位:秒) |
#EXT-X-MEDIA-SEQUENCE |
第一个分片的序列号(用于直播流滑动窗口) |
#EXTINF |
分片时长和路径 |
#EXT-X-ENDLIST |
标识点播流结束(直播流无此标签) |
5. 开发环境准备
5.1 安装开发包
bash
sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
5.2 关键头文件
cpp
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/error.h>
6. 关键数据结构
6.1 AVFormatContext
cpp
typedef struct AVFormatContext {
struct AVInputFormat *iformat; // 输入格式化对象
struct AVOutputFormat *oformat; // 输出格式化对象
unsigned int nb_streams; // 媒体流的数量(视频流、音频流、字幕流)
AVStream **streams; // 指向所有流的指针数组,通过索引访问
int64_t start_time; // 媒体文件的起始时间戳
int64_t duration; // 媒体文件的总时长
int64_t bit_rate; // 全局码率
// ...
};
6.2 AVStream
cpp
struct AVStream {
int id; // 流的唯一标识符
int64_t start_time; // 流的起始时间戳
int64_t duration; // 流的总时长
int64_t nb_frames; // 流的总帧数
AVRational time_base; // 时间基(时间戳单位),如 {1, 1000} 表示毫秒
AVRational avg_frame_rate; // 平均帧率(如 {25, 1} 表示 25 FPS)
AVRational r_frame_rate; // 标称帧率,用于关键帧间隔计算
AVCodecParameters *codecpar; // 编解码参数(编码格式、分辨率、采样率等)
// ...
};
6.3 AVCodecParameters
cpp
struct AVCodecParameters {
enum AVMediaType codec_type; // 媒体类型:AVMEDIA_TYPE_VIDEO / AVMEDIA_TYPE_AUDIO
enum AVCodecID codec_id; // 编解码器ID:AV_CODEC_ID_H264 / AV_CODEC_ID_AAC
uint32_t codec_tag; // 格式特定的编解码标签
int format; // 数据格式
int64_t bit_rate; // 码率(单位:bps)
int width; // 视频分辨率宽度(像素)
int height; // 视频分辨率高度(像素)
// ...
};
6.4 AVPacket
cpp
typedef struct AVPacket {
int64_t pts; // 显示时间戳,单位:stream->time_base
int64_t dts; // 解码时间戳,单位:stream->time_base
int size; // 数据包大小(字节数)
int stream_index; // 所属流的索引
int64_t duration; // 数据包持续时间
int64_t pos; // 数据包在输入文件中的字节偏移量
// ...
};
7. 关键 API 接口
7.1 视频操作接口
avformat_open_input --- 打开输入文件
cpp
int avformat_open_input(AVFormatContext **ps,
const char *url,
AVInputFormat *fmt,
AVDictionary **options);
- ps :指向
AVFormatContext指针的指针,函数内部分配并填充该结构体 - url :输入文件路径或 URL(如
"input.mp4"、"rtmp://example.com/live") - fmt :强制指定输入格式,通常设为
NULL自动探测 - options :附加选项(如设置超时、协议参数),可为
NULL - 返回值 :
< 0表示失败
avformat_find_stream_info --- 读取流信息
cpp
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
读取输入文件的流信息(如编码参数、帧率、时长)。
avformat_alloc_output_context2 --- 分配输出上下文
cpp
int avformat_alloc_output_context2(
AVFormatContext **ctx,
AVOutputFormat *oformat,
const char *format_name,
const char *filename);
- format_name :输出格式短名称(如
"mp4"、"hls") - filename :输出文件名或 URL。对于 HLS 输出,文件名应为
xx.m3u8
avformat_new_stream --- 创建新流
cpp
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
为输出上下文创建新的流。返回值 :NULL 表示失败。
avcodec_parameters_copy --- 复制编解码参数
cpp
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
从输入流复制编解码参数到输出流。返回值 :< 0 表示失败。
avformat_write_header --- 写入文件头部
cpp
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
写入输出文件的头部信息。返回值 :< 0 表示失败。
av_read_frame --- 读取数据包
cpp
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
从输入文件中读取一个数据包(AVPacket)。返回值 :< 0 表示失败或读取结束。
av_interleaved_write_frame --- 写入数据包
cpp
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
将数据包按时间戳顺序写入输出文件(自动处理交织)。返回值 :< 0 表示失败。
av_write_trailer --- 写入文件尾部
cpp
int av_write_trailer(AVFormatContext *s);
写入输出文件的尾部信息(如 MP4 的 moov 原子)。
资源释放
cpp
void av_packet_unref(AVPacket *pkt); // 释放 AVPacket 资源
void avformat_free_context(AVFormatContext *s); // 释放输出上下文
void avformat_close_input(AVFormatContext **s); // 关闭输入文件并释放资源
错误信息获取
cpp
int av_strerror(int errnum, char *errbuf, size_t errbuf_size);
获取接口执行失败的原因描述。
7.2 时间基转换
cpp
// 将时间戳从一个时间基转换到另一个时间基
// 公式:result = a × bq / cq
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq);
// 批量转换数据包的 pts、dts 和 duration 到新时间基
void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
关于时间基准(Time Base):
AV_TIME_BASE 是 FFmpeg 内部使用的时间基准常量,代表了 FFmpeg 内部计算时间的基本单位。AVRational AV_TIME_BASE_Q = {1, AV_TIME_BASE} 定义了时间戳(PTS/DTS)数值如何映射到真实时间,这是最核心的底层时间度量单位。
7.3 字典选项接口
cpp
// 设置整数字典选项
int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags);
// 设置字符串字典选项
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
// 释放字典对象资源
void av_dict_free(AVDictionary **m);
HLS 分片相关字典选项
| 选项 | 功能 |
|---|---|
hls_time |
指定每个 TS 分片的时长(单位:秒) |
hls_base_url |
设置 M3U8 文件中分片 URL 的基础路径 |
hls_list_size |
控制播放列表中保留的分片数量,0 表示不做限制 |
hls_playlist_type |
定义播放列表类型(vod:点播 / event:事件流) |
hls_flags |
启用高级功能,多选时用 + 分隔 |
hls_segment_filename |
自定义分片文件名格式(支持 %d 占位符) |
hls_key_info_file |
指定加密分片的密钥信息文件路径 |
hls_allow_cache |
控制客户端是否缓存分片(1 允许 / 0 禁用) |
hls_init_time |
设置初始分片的时长(用于快速起播) |
hls_flags 可选值:
| 标志 | 说明 |
|---|---|
split_by_time |
强制按 hls_time 分片,忽略关键帧间隔 |
independent_segments |
标记分片可独立解码,提升兼容性 |
delete_segments |
自动删除已被播放列表移除的旧分片 |
append_list |
在已有 M3U8 文件末尾追加新分片 |
点播配置示例:
cpp
av_dict_set(&options, "hls_time", "10", 0);
av_dict_set(&options, "hls_list_size", "0", 0);
av_dict_set(&options, "hls_playlist_type", "vod", 0);
av_dict_set(&options, "hls_flags", "independent_segments", 0);
av_dict_set(&options, "hls_segment_filename", "vod_segment_%03d.ts", 0);
av_dict_set(&options, "hls_base_url", "http://example.com/", 0);
8. 完整实现:HLS 视频分片
8.1 整体流程
- 打开输入文件,初始化输入格式化对象
- 查找输入视频参数
- 申请输出格式化对象
- 遍历输入媒体流:
- 为输出对象申请媒体流
- 从输入媒体流复制解码器参数到输出媒体流
- 设置分片字典选项
- 通过输出格式化对象写入媒体头部信息
- 遍历输入流中的数据帧:
- 将数据包中的时间戳从输入流时间基转换为输出流时间基
- 若数据帧的显示时间戳无效(
AV_NOPTS_VALUE),则默认从 0 开始 - 将数据帧写入输出格式化对象
- 释放帧结构资源
- 通过输出格式化对象写入媒体尾部信息
- 释放资源
8.2 示例代码
目录结构
.
├── hls.cc
├── makefile
└── movie.mp4
hls.cc
cpp
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/error.h>
}
#include <iostream>
const char* mavError(int err_code) {
static char errmsg[256];
av_strerror(err_code, errmsg, 255);
return errmsg;
}
int main(int argc, char *argv[])
{
if (argc != 3) {
std::cout << "Usage: ./hls input_file output_file\n";
return -1;
}
int ret;
// 1. 打开输入文件,创建输入格式化上下文对象
AVFormatContext *inputContext = nullptr, *outputContext = nullptr;
ret = avformat_open_input(&inputContext, argv[1], nullptr, nullptr);
if (ret < 0) {
std::cout << "打开输入文件失败:" << mavError(ret) << std::endl;
return -1;
}
// 2. 通过输入格式化上下文对象解析视频文件元信息
ret = avformat_find_stream_info(inputContext, nullptr);
if (ret < 0) {
std::cout << "解析媒体元信息失败:" << mavError(ret) << std::endl;
return -1;
}
// 3. 申请创建输出格式化上下文对象,设定输出格式为 hls
ret = avformat_alloc_output_context2(&outputContext, nullptr, "hls", argv[2]);
if (ret < 0) {
std::cout << "创建输出上下文对象失败:" << mavError(ret) << std::endl;
return -1;
}
// 4. 遍历输入媒体流,为输出创建流并复制编解码器参数
for (int i = 0; i < inputContext->nb_streams; i++) {
AVStream *inputStream = inputContext->streams[i];
AVStream *outputStream = avformat_new_stream(outputContext, nullptr);
avcodec_parameters_copy(outputStream->codecpar, inputStream->codecpar);
outputStream->avg_frame_rate = inputStream->avg_frame_rate;
outputStream->r_frame_rate = inputStream->r_frame_rate;
}
// 5. 设置 HLS 转码参数:点播模式,5 秒分片,路径前缀 /video/
AVDictionary *dict = nullptr;
av_dict_set_int(&dict, "hls_time", 5, 0);
av_dict_set(&dict, "hls_base_url", "/video/", 0);
av_dict_set(&dict, "hls_playlist_type", "vod", 0);
av_dict_set(&dict, "hls_flags", "independent_segments", 0);
// 6. 写入输出文件头部信息
ret = avformat_write_header(outputContext, &dict);
if (ret < 0) {
std::cout << "输出头部信息失败:" << mavError(ret) << std::endl;
return -1;
}
// 7. 遍历输入数据帧,进行时间基转换后写入输出文件
AVPacket pkt;
while (av_read_frame(inputContext, &pkt) >= 0) {
AVStream *inputStream = inputContext->streams[pkt.stream_index];
AVStream *outputStream = outputContext->streams[pkt.stream_index];
// 若显示时间戳无效,则从 0 开始
if (pkt.pts == AV_NOPTS_VALUE) {
pkt.pts = av_rescale_q(0, AV_TIME_BASE_Q, inputStream->time_base);
pkt.dts = pkt.pts;
}
av_packet_rescale_ts(&pkt, inputStream->time_base, outputStream->time_base);
// 写入输出文件
ret = av_interleaved_write_frame(outputContext, &pkt);
if (ret < 0) {
std::cout << "输出数据帧失败:" << mavError(ret) << std::endl;
return -1;
}
av_packet_unref(&pkt);
}
// 8. 写入文件尾部信息
ret = av_write_trailer(outputContext);
if (ret < 0) {
std::cout << "输出尾部信息失败:" << mavError(ret) << std::endl;
return -1;
}
// 9. 释放资源
av_dict_free(&dict);
avformat_free_context(outputContext);
avformat_close_input(&inputContext);
return 0;
}
Makefile
makefile
all: hls
hls: hls.cc
g++ -g -std=c++17 $^ -o $@ -lavcodec -lavformat -lavutil
clean:
rm -rf hls
8.3 运行演示
bash
$ ./hls ./movie.mp4 ./movie.m3u8
[mpegts @ 0x56544392ad40] Stream 2, codec bin_data, is muxed as a private data stream...
[hls @ 0x565443932180] Opening './movie0.ts' for writing
[hls @ 0x565443932180] Opening './movie1.ts' for writing
$ ls
hls hls.cc makefile movie.m3u8 movie.mp4 movie0.ts movie1.ts
9. 面向对象封装
在实际项目中,我们通常会将上述流程封装为可复用的类。这里封装两个核心类:
9.1 M3U8Info 类 --- M3U8 文件解析与生成
cpp
class M3U8Info {
public:
using ptr = std::shared_ptr<M3U8Info>;
using StrPair = std::pair<std::string, std::string>;
M3U8Info(const std::string &filename);
bool parse(); // 解析 M3U8 文件
bool write(); // 生成 M3U8 文件
std::vector<std::string>& headers(); // 获取头部字段列表
std::vector<StrPair>& pieces(); // 获取分片信息列表
private:
std::string _filename;
std::vector<std::string> _headers;
std::vector<StrPair> _pieces;
};
解析实现:
cpp
bool M3U8Info::parse() {
std::string body;
bool ret = biteutil::FUTIL::read(_filename, body);
if (ret == false) {
ERR("M3U8文件读取数据失败: {}", _filename);
return false;
}
std::vector<std::string> str_list;
int count = biteutil::STR::split(body, "\n", str_list);
for (int i = 0; i < count; i++) {
if (str_list[i].find(HLS_ENDLIST) != std::string::npos) {
break;
}
if (str_list[i].find(HLS_EXTINF) != std::string::npos) {
_pieces.push_back({str_list[i], str_list[i + 1]});
i++;
continue;
}
_headers.push_back(str_list[i]);
}
return true;
}
写入实现:
cpp
bool M3U8Info::write() {
std::stringstream ss;
for (auto &h : _headers) {
ss << h << "\n";
}
for (auto &p : _pieces) {
ss << p.first << "\n" << p.second << "\n";
}
ss << HLS_ENDLIST;
bool ret = biteutil::FUTIL::write(_filename, ss.str());
if (ret == false) {
ERR("M3U8文件写入数据失败: {}", _filename);
return false;
}
return true;
}
9.2 HLSTranscoder 类 --- HLS 视频转码
cpp
struct trans_settings {
int _hls_time = 10;
int _hls_list_size = 0;
std::string _hls_base_url;
std::string _hls_playlist_type = "vod";
std::string _hls_flags = "independent_segments";
AVDictionary* hls_dictionary();
};
class HLSTranscoder {
public:
static bool transcode(
const std::string &input_file,
const std::string &output_file,
const trans_settings &ts);
private:
static std::string avError(int error_code);
};
转码逻辑与前文的 main 函数流程一致,此处不再重复展开。核心步骤依旧是:打开输入 → 解析流信息 → 创建输出上下文 → 复制流参数 → 设置 HLS 选项 → 写入头 → 逐帧转换时间基并写入 → 写入尾 → 释放资源。
9.3 使用示例
cpp
#include "bite_scaffold/hls.h"
#include "bite_scaffold/log.h"
void hls_test() {
bitehls::hls_setttings settings = {
.hls_time = 10,
.playlist_type = "vod",
.base_url = "/video/"
};
bitehls::HLSTranscoder transcoder(settings);
bool ret = transcoder.transcode("../movie.mp4", "./dest.m3u8");
if (ret == false) {
ERR("转码失败!");
abort();
}
}
void m3u8_test() {
bitehls::M3U8Info info("./dest.m3u8");
bool ret = info.parse();
if (ret == false) {
ERR("m3u8文件解析失败!");
return;
}
// 打印头部信息
auto &headers = info.headers();
for (auto &h : headers) {
std::cout << "[" << h << "]" << std::endl;
}
// 打印并修改分片路径
auto &pieces = info.pieces();
for (int i = 0; i < pieces.size(); i++) {
std::cout << "[" << pieces[i].first << "]" << std::endl;
std::cout << "[" << pieces[i].second << "]" << std::endl;
pieces[i].second = "/hello" + pieces[i].second;
}
// 重新写入
ret = info.write();
if (ret == false) {
ERR("m3u8文件重写失败!");
return;
}
}
int main() {
bitelog::bitelog_init();
hls_test();
m3u8_test();
return 0;
}
CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.1.3)
project(main VERSION 1.0)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++17")
set(src_files ./test.cc)
add_executable(${PROJECT_NAME} ${src_files})
find_package(bite_scaffold REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE bite_scaffold::bite_scaffold)
9.4 运行结果
bash
$ make
$ ./main
[mpegts @ ...] Stream 2, codec bin_data, is muxed as a private data stream...
[hls @ ...] Opening './dest0.ts' for writing
[hls @ ...] Opening './dest1.ts' for writing
[#EXTM3U]
[#EXT-X-VERSION:6]
[#EXT-X-TARGETDURATION:10]
[#EXT-X-MEDIA-SEQUENCE:0]
[#EXT-X-PLAYLIST-TYPE:VOD]
[#EXT-X-INDEPENDENT-SEGMENTS]
==========================
[#EXTINF:10.143000,]
[/video/dest0.ts]
[#EXTINF:2.602000,]
[/video/dest1.ts]
运行后生成的 dest.m3u8 文件内容(分片路径已被 /hello 前缀修改):
m3u8
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-INDEPENDENT-SEGMENTS
#EXTINF:10.143000,
/hello/video/dest0.ts
#EXTINF:2.602000,
/hello/video/dest1.ts
#EXT-X-ENDLIST
生成的 TS 分片文件可以直接使用本地播放器(例如 Windows 11 自带的播放器)打开观看。
10. 总结
本文从 FFmpeg 的核心库体系出发,系统介绍了:
- libffmpeg 的八大核心库及其功能定位
- MP4 容器格式的 Box 结构和 Fast Start 优化
- HLS 协议的工作原理和 M3U8 文件格式
- 核心数据结构 :
AVFormatContext、AVStream、AVCodecParameters、AVPacket - 关键 API:从打开输入到写入输出的完整调用链
- 时间基准转换 的原理和
av_packet_rescale_ts的正确使用 - HLS 字典选项的配置方式
- 完整可运行的代码示例及面向对象的封装设计
掌握了这些知识,你就可以在自己的 C++ 项目中集成 FFmpeg,实现视频转码、HLS 分片、M3U8 解析与生成等常见的多媒体处理需求。