ffmpeg介绍(一)——解封装

解封装

常用函数

1. avformat_open_input()

作用
  • 打开媒体文件或网络资源:解析文件路径或 URL,识别媒体格式(如 MP4、AVI、RTSP 等)。
  • 初始化 AVFormatContext :分配并初始化 AVFormatContext 结构体,用于存储媒体文件的元数据和流信息。
  • 准备后续操作:为后续的解封装(demuxing)和解码操作做好准备。
典型用法
cpp 复制代码
AVFormatContext* fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, input_file, NULL, NULL) < 0) {
    fprintf(stderr, "Could not open input file: %s\n", input_file);
    return -1;
}
关键点
  • 输入参数
    • AVFormatContext** fmt_ctx:指向 AVFormatContext 指针的指针,用于存储媒体文件的上下文。
    • const char* url:文件路径或 URL。
    • AVInputFormat* fmt:指定输入格式(通常为 NULL,自动检测)。
    • AVDictionary** options:额外的选项(如超时、缓冲区大小等)。
  • 输出
    • 成功时返回 0,失败时返回负值。
    • 初始化后的 AVFormatContext 包含媒体文件的元数据和流信息。

2. avformat_find_stream_info()

作用
  • 解析流信息:分析媒体文件中的视频、音频流,提取编码器类型、帧率、时长、分辨率等关键信息。
  • 填充 AVFormatContext :将解析到的流信息填充到 AVFormatContextstreams 数组中。
  • 准备解码:为后续的解码操作分配必要的缓冲区和数据结构。
典型用法
cpp 复制代码
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
    fprintf(stderr, "Could not find stream information\n");
    avformat_close_input(&fmt_ctx);
    return -1;
}
关键点
  • 输入参数
    • AVFormatContext* fmt_ctx:已打开的 AVFormatContext
    • AVDictionary** options:额外的选项(如最大读取时长、最大帧数等)。
  • 输出
    • 成功时返回 0,失败时返回负值。
    • fmt_ctx->streams 数组包含所有流的信息(如视频、音频、字幕等)。

3. av_find_best_stream()

作用
  • 查找最佳流 :根据指定的流类型(如视频、音频)在 AVFormatContext 中查找最佳匹配的流。
  • 简化流选择:避免手动遍历所有流,自动选择最合适的流。
典型用法
cpp 复制代码
int video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_stream_idx < 0) {
    fprintf(stderr, "Could not find video stream\n");
    avformat_close_input(&fmt_ctx);
    return -1;
}
关键点
  • 输入参数
    • AVFormatContext* fmt_ctx:已打开的 AVFormatContext
    • enum AVMediaType type:流类型(如 AVMEDIA_TYPE_VIDEOAVMEDIA_TYPE_AUDIO)。
    • int wanted_stream_nb:期望的流索引(通常为 -1,自动选择)。
    • int related_stream:相关流索引(通常为 -1)。
    • AVCodec** decoder_ret:返回解码器(通常为 NULL)。
    • int flags:标志位(通常为 0)。
  • 输出
    • 成功时返回流索引,失败时返回负值。

4. av_read_frame()

av_read_frame 是 FFmpeg 中一个非常重要的函数,用于从媒体文件(如 MP4、MKV 等)中读取一帧数据(可以是视频帧、音频帧或其他类型的包)。它的作用是从 AVFormatContext 中读取下一个数据包(AVPacket),并将其存储到指定的 AVPacket 结构中。


1. 函数原型
c 复制代码
int av_read_frame(AVFormatContext *fmt_ctx, AVPacket *pkt);
  • 参数

    • fmt_ctxAVFormatContext 指针,表示媒体文件的上下文。
    • pktAVPacket 指针,用于存储读取到的数据包。
  • 返回值

    • 成功时返回 0
    • 如果到达文件末尾,返回 AVERROR_EOF
    • 如果发生错误,返回负的错误代码。

2. 功能说明

av_read_frame 的作用是从媒体文件中读取下一个数据包(AVPacket),并将其存储到 pkt 中。数据包可以是:

  • 视频帧(如 H.264 帧)。
  • 音频帧(如 AAC 帧)。
  • 其他类型的包(如字幕或元数据)。

每次调用 av_read_frame 时,它会从文件中读取一个完整的数据包,并将其填充到 pkt 中。读取的数据包需要后续通过解码器(AVCodecContext)进行解码。


3. 使用步骤

以下是使用 av_read_frame 的典型步骤:

1. 打开媒体文件并初始化 AVFormatContext
c 复制代码
AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);
avformat_find_stream_info(fmt_ctx, NULL);
2. 准备 AVPacket
c 复制代码
AVPacket pkt;
av_init_packet(&pkt);
3. 循环读取数据包
c 复制代码
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
    // 检查数据包属于哪个流
    if (pkt.stream_index == video_stream_index) {
        // 处理视频帧
    } else if (pkt.stream_index == audio_stream_index) {
        // 处理音频帧
    }

    // 释放数据包
    av_packet_unref(&pkt);
}
4. 释放资源
c 复制代码
avformat_close_input(&fmt_ctx);

4. 关键点
1. AVPacket 的生命周期
  • av_read_frame 会为 pkt 分配内存并填充数据。
  • 使用完 pkt 后,必须调用 av_packet_unref 释放其内存,否则会导致内存泄漏。
2. 流索引(stream_index
  • 每个数据包都属于某个流(视频流、音频流等),通过 pkt.stream_index 可以确定数据包所属的流。
  • 流的索引可以通过 AVFormatContext 中的 streams 数组获取。
3. 数据包的时间戳
  • 数据包中包含时间戳(PTS 和 DTS),用于同步音视频。
  • 时间戳的单位是流的时间基(AVStream->time_base),需要通过 av_q2d 转换为秒。

5. av_seek_frame()

在 FFmpeg 中,av_seek_frame() 是用于在输入流中定位到指定时间戳或帧索引的核心函数。它允许你在处理音视频流时跳转到特定位置,广泛应用于播放器、编辑器等场景。以下是详细解析:


1. 函数原型
c 复制代码
int av_seek_frame(AVFormatContext *fmt_ctx, int stream_idx, int64_t timestamp, int flags);
参数
  • fmt_ctx : 输入流的上下文(AVFormatContext*),表示要操作的媒体文件或流。
  • stream_idx : 需要操作的流的索引(如视频流为 0,音频流为 1)。若为 -1,表示操作所有流。
  • timestamp : 目标时间戳(单位由流的 time_base 定义,如微秒)。
  • flags : 控制 seek 行为的标志位,例如:
    • AVSEEK_FLAG_BACKWARD: 向后搜索(最近的匹配位置)。
    • AVSEEK_FLAG_FORWARD: 向前搜索(第一个匹配位置)。
    • AVSEEK_FLAG_FRAME精确: 精确匹配帧边界。
    • AVSEEK_FLAG_ANY: 允许任何近似值。
返回值

≥0 : 成功,返回新的时间戳位置。

<0 : 失败,返回错误码(如 AVERROR_EOF)。


2. 核心功能
  • 时间戳定位 :将播放头移动到指定的时间戳(如 10秒)。
  • 帧索引定位 :直接跳转到指定帧(如第 100 帧)。
  • 流同步:确保多个流(视频+音频)同步到同一时间点。

3. 使用示例
场景 1:跳转到指定时间(秒)
c 复制代码
AVFormatContext *fmt_ctx = ...; // 初始化的输入流上下文
int video_stream_idx = ...;     // 视频流索引

// 跳转到第 5 秒(需转换为时间戳)
 AVRational time_base = fmt_ctx->streams[video_stream_idx]->time_base;
 int64_t target_ts = 5 * av_q2d(time_base); // 5秒 = 5 / 1 (假设 time_base=1/1)

int ret = av_seek_frame(fmt_ctx, video_stream_idx, target_ts, AVSEEK_FLAG_BACKWARD);
if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "Seek failed\n");
} else {
    av_log(NULL, AV_LOG_INFO, "Seeked to %lld microseconds\n", ret);
}
场景 2:跳转到指定帧
c 复制代码
// 跳转到第 100 帧(仅视频流支持)
int frame = 100;
ret = av_seek_frame(fmt_ctx, video_stream_idx, frame, AVSEEK_FLAG_FRAME精确);

4. 关键注意事项
1. 时间基转换
  • 时间戳单位timestamp 的单位由流的 time_base 决定(如 AV_TIME_BASE 表示微秒)。
  • 转换公式
c 复制代码
int64_t timestamp = seconds * av_q2d(time_base); 
// 或 av_rescale_q(seconds, AV_TIME_BASE, time_base)
2. 流索引处理
  • 单一流操作 :明确指定 stream_idx(如视频或音频流)。
  • 多流同步 :若需同步多个流,需分别对每个流调用 av_seek_frame()
3. 错误处理
  • 检查返回值 :失败时可能返回 AVERROR_EOF(未找到位置)或 AVERROR_IO(I/O 错误)。
  • 流状态 :确保流未被关闭,且处于可seek状态(如 AVFS_SEEKABLE)。
4. 性能优化
  • 批量 seek :避免频繁调用,可结合 av_read_frame()AVFRAME_FLAG Sebastian 标志读取多帧。
  • 硬件加速:某些解码器(如 NVIDIA NVDEC)可能不支持随机访问,需特殊处理。

5. 高级场景
多流同步
c 复制代码
// 同步视频和音频流到同一时间戳
int videoStream = ...;
int audioStream = ...;
int64_t target_ts = ...;

av_seek_frame(fmt_ctx, videoStream, target_ts, 0);
av_seek_frame(fmt_ctx, audioStream, target_ts, 0);
动态调整播放速度
c 复制代码
// 加速播放(2倍速)
int64_t new_ts = av_rescale_q(current_ts, fmt_ctx->streams[0]->time_base, 
                              av_make_q(1, 2)); // 时间缩放因子为 0.5
av_seek_frame(fmt_ctx, 0, new_ts, 0);

6. 常见问题
  1. 为什么seek后无法读取到数据?

    • 缓冲区未刷新 :调用 av_flush_packets(fmt_ctx) 清空输入缓冲区。
    • 流未seekable:某些流(如直播流)不支持随机访问。
  2. 如何实现逐帧播放?

    c 复制代码
    int frame = 0;
    while (frame < total_frames) {
        av_seek_frame(fmt_ctx, videoStream, frame, AVSEEK_FLAG_FRAME精确);
        AVPacket pkt;
        av_init_packet(&pkt);
        avcodec_decode_video2(...); // 读取当前帧
        frame++;
    }
  3. seek到帧边界的问题

    • 使用 AVSEEK_FLAG_FRAME精确 确保定位到帧起始位置。

常用数据结构

AVFormatContext:媒体格式的全局管理者

作用
  • 管理容器格式:存储媒体文件的容器信息(如MP4、MKV、FLV等)。
  • 封装流信息 :包含文件中所有流(AVStream)的元数据。
  • 控制输入/输出:用于解封装(demuxing)或封装(muxing)操作。
关键字段
c 复制代码
typedef struct AVFormatContext {
    const AVClass *av_class;          // 类信息(用于日志和回调)
    AVInputFormat *iformat;           // 输入格式(解封装时使用)
    AVOutputFormat *oformat;          // 输出格式(封装时使用)
    AVIOContext *pb;                  // I/O上下文(文件或网络读写)
    unsigned int nb_streams;          // 流的数量
    AVStream **streams;               // 流数组(每个元素对应一个AVStream)
    char filename[1024];              // 文件名或URL
    int64_t duration;                 // 文件总时长(微秒)
    int64_t bit_rate;                 // 全局比特率(bps)
    AVDictionary *metadata;           // 元数据(标题、作者等)
} AVFormatContext;

典型用法
c 复制代码
// 打开输入文件
AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);

// 读取流信息
avformat_find_stream_info(fmt_ctx, NULL);

// 遍历所有流,找到视频流索引
int video_stream_idx = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
    if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
        video_stream_idx = i;
        break;
    }
}

// 关闭并释放资源
avformat_close_input(&fmt_ctx);

AVCodecParameters

AVCodecParameters 是 FFmpeg 中用于存储编解码器参数的核心结构体,存储了流的编解码器参数。 ,包括视频、音频的分辨率、帧率、编码格式、比特率等关键参数。 它的主要目的是在解复用(Demuxing)时提取流的编解码信息,而不需要初始化完整的编解码器上下文(AVCodecContext)。


1. AVCodecParameters 的主要字段

以下是 AVCodecParameters 中一些重要的字段:

字段名 类型 描述
codec_type AVMediaType 媒体类型(视频、音频、字幕等)。例如:AVMEDIA_TYPE_VIDEO 表示视频。
codec_id AVCodecID 编解码器 ID(如 AV_CODEC_ID_H264 表示 H.264 编码)。
format int 像素格式(视频)或采样格式(音频)。例如:AV_PIX_FMT_YUV420P
width / height int 视频的宽度和高度(以像素为单位)。
sample_rate int 音频的采样率(如 44100 Hz)。
channels int 音频的声道数(如 2 表示立体声)。
channel_layout uint64_t 音频的声道布局(如 AV_CH_LAYOUT_STEREO 表示立体声)。
bit_rate int64_t 流的比特率(单位:比特/秒)。
extradata uint8_t* 编解码器特定的额外数据(如 H.264 的 SPS/PPS)。
extradata_size int 额外数据的大小。

2. AVCodecParameters 的使用场景

AVCodecParameters 通常在以下场景中使用:

  1. 解复用(Demuxing)

    • 当从容器(如 MP4、MKV)中读取音视频流时,AVFormatContext 会为每个流分配一个 AVStream,而 AVStream 中的 codecpar 字段就是 AVCodecParameters
    • 通过 AVCodecParameters,可以获取流的编解码信息,而不需要初始化编解码器。
  2. 编码/解码前的准备

    • 在初始化编解码器(AVCodecContext)之前,可以使用 AVCodecParameters 中的信息来配置编解码器。
  3. 流的复制或转封装

    • 在转封装(Remuxing)时,可以直接将 AVCodecParameters 从一个流复制到另一个流,而不需要重新解析编解码信息。

3. AVCodecParametersAVCodecContext 的区别
  • AVCodecParameters

    • 仅存储编解码器的参数(如格式、分辨率、采样率等)。
    • 不包含编解码器的状态或运行时数据。
    • 更轻量级,适合在解复用或转封装时使用。
  • AVCodecContext

    • 存储编解码器的参数和状态。
    • 包含编解码器的运行时数据(如帧缓冲区、编码延迟等)。
    • 需要在编解码时使用。

AVStream:媒体流的详细信息

作用
  • 描述单个流 :每个 AVStream 对应一个媒体流(如视频、音频、字幕)。
  • 存储流参数 :包含流的编解码参数(如分辨率、采样率)、时间基(time_base)等。
关键字段
c 复制代码
typedef struct AVStream {
    int index;                        // 流索引(唯一标识)
    AVCodecParameters *codecpar;      // 编解码参数(已过时)
    AVRational time_base;             // 时间基(理解成分数就行了)
    int64_t duration;                 // 流的总时长(单位:time_base)
    AVRational avg_frame_rate;        // 平均帧率(视频流)
} AVStream;

AVRational time_base 是 FFmpeg 中用于表示时间基的结构体。时间基是一个分数,形式为 num/den,其中 num 是分子,den 是分母。它定义了时间的基本单位,用于将时间值转换为秒或其他时间单位。

具体解释:
时间基的定义

时间基是一个分数,形式为 AVRational {num, den},表示每个时间戳的单位是 num/den 秒。

例如:

  • 如果 time_base = {1, 1000},那么每个时间戳的单位是 1/1000 秒(即 1 毫秒)。

  • 如果 time_base = {1, 90000},那么每个时间戳的单位是 1/90000 秒(常见于 MPEG-TS 流)。

    那么该帧的实际时间可以通过公式计算:

    c 复制代码
    double seconds = timestamp * av_q2d(time_base);

    其中 av_q2d 是 FFmpeg 提供的函数,用于将 AVRational 转换为浮点数。

  • 示例

    假设 time_base = {1, 1000},即 1/1000,表示时间单位是毫秒。如果某个帧的时间戳是 5000,那么该帧的实际时间是:

    c 复制代码
    double seconds = 5000 * (1.0 / 1000) = 5.0 秒
  • AVStream 中的意义

    • time_base 是流的时间基准,用于解释该流中的时间戳。
    • 例如,视频流的时间基可能是 1/90000(常见于 MPEG-TS 流),而音频流的时间基可能是 1/44100(CD 音质)。
  • 与其他字段的关系

    • duration 字段表示流的总时长,单位是 time_base。例如,如果 duration = 90000time_base = {1, 1000},那么流的总时长是 90 秒。
    • avg_frame_rate 是视频流的平均帧率,也是一个 AVRational,表示每秒的帧数。

AVPacket:编码后的数据包

作用
  • 存储压缩数据:保存从媒体文件读取的编码后的数据(如一个视频帧或音频帧)。
  • 携带时间信息:包含解码时间戳(DTS)和显示时间戳(PTS)。
关键字段
c 复制代码
typedef struct AVPacket {
	AVBufferRef *buf;
    uint8_t *data;                    // 数据指针(压缩数据)
    int size;                         // 数据大小
    int64_t pts;                      // 表示数据应被显示的时间点 (num/den)
    int64_t dts;                      // 表示数据应被解码的时间点(num/den)
    int stream_index;                 // 所属流的索引
    int flags;                        // 标志位(关键帧等)
} AVPacket;
av_packet 的生命周期管理
函数 作用 内存操作
av_packet_alloc() 分配新包 分配内存,引用计数初始化为 0
av_packet_clone() 克隆包(共享缓冲区) 引用计数不变
av_buffer_ref() 增加缓冲区引用 引用计数 +1
av_packet_unref() 释放包 引用计数 -1,释放内存
av_packet_free() 强制释放包 直接释放内存(不依赖引用计数)

三者的协作流程
  1. 初始化容器

    • 通过 AVFormatContext 打开输入文件,获取全局信息。
    • 遍历 AVFormatContext->streams 获取各个 AVStream
  2. 处理数据包

    • 使用 av_read_frame 读取 AVPacket
    • 根据 AVPacket->stream_index 找到对应的 AVStream
    • AVPacket 送入解码器(需结合 AVCodecContext)。
  3. 时间戳转换

    • AVPacketptsdts 转换为实际时间:

      c 复制代码
      double timestamp_sec = pkt.pts * av_q2d(stream->time_base);
  4. 资源释放

    • 使用 avformat_close_input 释放 AVFormatContext
    • 使用 av_packet_unref 释放 AVPacket

总结
  • AVFormatContext:媒体文件的全局管理器,负责解封装和流信息存储。
  • AVStream:单个流的详细信息,包含编解码参数和时间基。
  • AVPacket:编码后的数据包,携带压缩数据和时间戳。

三者协作实现媒体文件的读取、处理和写入,是FFmpeg处理流程的核心结构体。

AVPacket的关键函数

1. av_packet_alloc()

作用

动态分配一个空的 AVPacket,初始化 buf 数组和元数据。

函数原型
c 复制代码
AVPacket *av_packet_alloc(int buf_count);
参数

buf_count: 预分配的 buf 数组长度(需 ≥ 数据平面数)。

返回值

• 成功返回指向新分配的 AVPacket,失败返回 NULL

示例
c 复制代码
// 分配一个支持 3 数据平面的包(如视频 YUV420P)
AVPacket *pkt = av_packet_alloc(3);
if (!pkt) {
    av_log(NULL, AV_LOG_ERROR, "Allocation failed\n");
    exit(1);
}

// 使用后释放
av_packet_unref(pkt); // 自动释放内存

2. av_packet_clone()

作用

深度克隆现有 AVPacket,包括 buf 引用、时间戳、流索引等所有字段。

函数原型
c 复制代码
int av_packet_clone(AVPacket *src, AVPacket *dst, int buf_count);
参数

src: 源数据包。

dst: 目标数据包(需已通过 av_packet_alloc() 分配)。

buf_count: 目标 buf 数组容量(需 ≥ src->buf_count)。

返回值

• 成功返回 0,失败返回错误码(如 AVERROR(ENOMEM))。

示例
c 复制代码
AVPacket *src_pkt, *dst_pkt;
av_packet_alloc(&dst_pkt, src_pkt->buf_count); // 预分配缓冲区

int ret = av_packet_clone(src_pkt, dst_pkt, src_pkt->buf_count);
if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "Clone failed\n");
}

// 增加引用计数(若需长期保留)
for (int i = 0; i < dst_pkt->buf_count; i++) {
    av_buffer_ref(dst_pkt->buf[i]);
}

av_packet_unref(dst_pkt); // 自动释放

3. av_packet_ref()

作用

增加 AVBufferRef 的引用计数,确保缓冲区不会被意外释放。

函数原型
c 复制代码
void av_buffer_ref(AVBufferRef *buf);
参数

buf: 需要增加引用的 AVBufferRef

使用场景

克隆后保留数据 :克隆 AVPacket 后,若需长期使用其缓冲区,需手动调用 av_buffer_ref()

多线程共享:在多线程环境中,确保每个线程对缓冲区的引用合法。

1. 为什么克隆后需要手动调用 av_buffer_ref()

引用计数的作用

  • 共享缓冲区AVPacket 克隆后,缓冲区引用是共享的 (即克隆后的 buf 数组直接指向源包的 AVBufferRef)。
  • 引用计数规则
    • 引用计数 (refcount) :表示当前有多少个 AVBufferRef 指向同一块内存。
    • 释放条件 :当 refcount 降为 0 时,FFmpeg 会自动释放缓冲区内存。

克隆后的风险

  • 示例场景

    c 复制代码
    AVPacket *src_pkt = ...; // 原始数据包,buf->refcount=1
    AVPacket *clone_pkt = av_packet_clone(src_pkt, ...); // 克隆后,clone_pkt->buf 的 refcount=1
  • 问题
    如果此时源包 src_pkt 被释放(av_packet_unref(src_pkt)),其 bufrefcount 会减到 0,导致缓冲区被销毁。此时 clone_pkt 仍然指向已释放的内存,引发未定义行为(如崩溃或数据错误)。

解决方案

  • 手动增加引用
    通过 av_buffer_ref(clone_pkt->buf[i]) 显式增加引用计数,确保缓冲区不会被意外释放:

    c 复制代码
    for (int i = 0; i < clone_pkt->buf_count; i++) {
        av_buffer_ref(clone_pkt->buf[i]); // refcount +=1
    }
  • 引用计数变化
    • 克隆后:buf->refcount=1(共享)。
    • 增加引用后:buf->refcount=2,即使源包被释放,缓冲区仍保留。


2. 为什么多线程环境需要确保引用合法?

线程安全问题

  • 竞态条件
    多个线程可能同时操作同一缓冲区的引用计数(如一个线程释放内存,另一个线程正在读取数据)。
  • 原子操作
    FFmpeg 的 refcount 使用原子操作(如 AV_ATOMIC_INCAV_ATOMIC_DEC)确保增减操作的原子性,但用户代码仍需遵守规则

关键规则

  1. 每个线程必须独立管理引用
    • 如果线程 A 持有缓冲区的引用,线程 B 不能直接释放它。
  2. 克隆后需显式增加引用
    • 即使缓冲区由 FFmpeg 内部管理(owner=1),多线程环境下仍需调用 av_buffer_ref(),避免其他线程误释放。
  3. 使用 av_packet_unref() 而非直接 free
    • 始终通过 av_packet_unref() 释放 AVPacket,由其自动处理引用计数递减。

示例场景

c 复制代码
// 线程 1:克隆数据包并处理
AVPacket *clone_pkt = av_packet_clone(src_pkt, ...);
av_buffer_ref(clone_pkt->buf[0]); // 增加引用

// 线程 2:释放源包(可能导致问题!)
av_packet_unref(src_pkt); // 如果 clone_pkt 未增加引用,此处会释放缓冲区

解决方案

  • 线程内独立引用
    每个线程在克隆后必须自行增加引用,并在结束时释放:

    c 复制代码
    // 线程 1
    AVPacket *clone_pkt = av_packet_clone(src_pkt, ...);
    av_buffer_ref(clone_pkt->buf[0]); // 线程 1 的引用
    process(clone_pkt);
    av_buffer_unref(clone_pkt->buf[0]); // 线程 1 释放引用
    av_packet_unref(clone_pkt);
    
    // 线程 2
    av_packet_unref(src_pkt); // 安全释放(假设 src_pkt 无其他引用)

3. 深层原理:FFmpeg 的内存管理策略

AVBufferRef 的设计

  • 引用计数 (refcount)
    • 初始值为 1(由分配者持有)。
    • 每次 av_buffer_ref() 调用,refcount 增加;每次 av_buffer_unref() 调用,refcount 减少。
  • 所有者标志 (owner)
    • owner=1:缓冲区由 FFmpeg 管理,av_buffer_unref() 会释放内存。
    • owner=0:用户管理内存,av_buffer_unref() 仅减少引用计数,不释放内存。

克隆操作的副作用

  • 浅拷贝av_packet_clone() 是浅拷贝,buf 数组直接引用源包的 AVBufferRef
  • 引用计数共享 :克隆后的 buf 引用计数与源包一致,不自动增加

4. 最佳实践总结
场景 正确操作 错误操作 结果
克隆后长期使用 av_buffer_ref(clone_pkt->buf[i]) 直接使用,不增加引用 缓冲区被源包释放,导致崩溃或数据错误
多线程共享数据包 每个线程独立调用 av_buffer_ref()av_buffer_unref() 所有线程共享同一个引用 竞态条件,内存泄漏或崩溃
释放数据包 av_packet_unref(pkt) 直接 free(pkt)av_free(pkt) 引用计数未正确递减,内存泄漏

4. av_packet_free()

作用

释放 AVPacket 及其关联的 AVBufferRef,自动递减引用计数。

函数原型
c 复制代码
void av_packet_free(AVPacket *pkt);
注意事项

引用计数规则

• 若 buf 由 FFmpeg 内部管理(buf->owner=1),调用 av_packet_free() 会自动释放。

• 若 buf 由用户管理(如硬件解码器返回的 GPU 缓冲区),需手动释放。

替代函数 :推荐使用 av_packet_unref(),它会自动处理引用计数。

示例
c 复制代码
AVPacket *pkt = av_packet_alloc(3);
// ... 使用 pkt ...
av_packet_free(pkt); // 释放内存

5. av_init_packet()

作用

初始化 AVPacket 结构体,设置默认值(如 size=0pts=dts=0)。

函数原型
c 复制代码
void av_init_packet(AVPacket *pkt);
av_packet_alloc() 的区别

无需分配内存 :仅初始化现有结构体的字段。

典型用法 :在复用已分配的 AVPacket 时调用(如循环处理数据包)。

示例
c 复制代码
AVPacket pkt;
av_init_packet(&pkt); // 初始化
pkt.buf_count = 3;    // 设置 buf 数组长度
// ... 填充数据 ...
av_packet_unref(&pkt); // 释放

相关推荐
ZouZou老师3 小时前
FFmpeg性能优化经典案例
性能优化·ffmpeg
aqi006 小时前
FFmpeg开发笔记(九十)采用FFmpeg套壳的音视频转码百宝箱FFBox
ffmpeg·音视频·直播·流媒体
齐齐大魔王7 小时前
FFmpeg
ffmpeg
你好音视频9 小时前
FFmpeg RTSP拉流流程深度解析
ffmpeg
IFTICing20 小时前
【环境配置】ffmpeg下载、安装、配置(Windows环境)
windows·ffmpeg
haiy201120 小时前
FFmpeg 编译
ffmpeg
aqi001 天前
FFmpeg开发笔记(八十九)基于FFmpeg的直播视频录制工具StreamCap
ffmpeg·音视频·直播·流媒体
八月的雨季 最後的冰吻1 天前
FFmepg--28- 滤镜处理 YUV 视频帧:实现上下镜像效果
ffmpeg·音视频
ganqiuye1 天前
向ffmpeg官方源码仓库提交patch
大数据·ffmpeg·video-codec
草明1 天前
ffmpeg 把 ts 转换成 mp3
ffmpeg