【C++脚手架】ffmpeg 库的介绍与使用

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 是一个领先的开源项目,主要处理音频、视频、字幕等多媒体流。其核心是命令行工具 ffmpegffplayffprobe
  • 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 工作流程

  1. 编码与分片:原始视频被转码为多码率版本,并切割为 TS 分片(通常 2~10 秒)
  2. 生成 M3U8 播放列表:每个码率对应一个 M3U8 文件,主播放列表(Master Playlist)描述所有可用码率
  3. 客户端拉取:播放器根据网络状况选择合适码率,按顺序下载分片并播放

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 整体流程

  1. 打开输入文件,初始化输入格式化对象
  2. 查找输入视频参数
  3. 申请输出格式化对象
  4. 遍历输入媒体流:
    • 为输出对象申请媒体流
    • 从输入媒体流复制解码器参数到输出媒体流
  5. 设置分片字典选项
  6. 通过输出格式化对象写入媒体头部信息
  7. 遍历输入流中的数据帧:
    • 将数据包中的时间戳从输入流时间基转换为输出流时间基
    • 若数据帧的显示时间戳无效(AV_NOPTS_VALUE),则默认从 0 开始
    • 将数据帧写入输出格式化对象
    • 释放帧结构资源
  8. 通过输出格式化对象写入媒体尾部信息
  9. 释放资源

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 的核心库体系出发,系统介绍了:

  1. libffmpeg 的八大核心库及其功能定位
  2. MP4 容器格式的 Box 结构和 Fast Start 优化
  3. HLS 协议的工作原理和 M3U8 文件格式
  4. 核心数据结构AVFormatContextAVStreamAVCodecParametersAVPacket
  5. 关键 API:从打开输入到写入输出的完整调用链
  6. 时间基准转换 的原理和 av_packet_rescale_ts 的正确使用
  7. HLS 字典选项的配置方式
  8. 完整可运行的代码示例及面向对象的封装设计

掌握了这些知识,你就可以在自己的 C++ 项目中集成 FFmpeg,实现视频转码、HLS 分片、M3U8 解析与生成等常见的多媒体处理需求。


相关推荐
会编程的土豆1 小时前
Go 里的 error 接口 + 假 nil(超级重点)
开发语言·后端·golang
WXDcsdn1 小时前
联想服务器使用RAID卡组建RAID(企业服务器解决方案)
运维·服务器
并不喜欢吃鱼1 小时前
从零开始 C++-----十一【C++ 数据结构】红黑树全解析:从定义到工程实现(一文搞定,十分详细)
开发语言·数据结构·c++
不会C语言的男孩1 小时前
C++ Primer Plus 第7章:函数——C++的编程模块
开发语言·c++
方也_arkling1 小时前
【Java-Day09】继承
java·开发语言
迈巴赫车主1 小时前
蓝桥杯21247弹跳鞋java
java·开发语言·数据结构·算法·职场和发展·蓝桥杯
jimy11 小时前
Linux动态加载器,loader,dynamic linker
linux·运维·服务器
Vick_Zhang1 小时前
ubuntu上rabbitmq
服务器·ubuntu·rabbitmq
kongba0071 小时前
ttyd Web终端安装指南(OpenCloudOS 9)
linux·前端