ffmpeg介绍(一)——解封装

news2025/3/25 21:26:38

解封装

常用函数

1. avformat_open_input()

作用
  • 打开媒体文件或网络资源:解析文件路径或 URL,识别媒体格式(如 MP4、AVI、RTSP 等)。
  • 初始化 AVFormatContext:分配并初始化 AVFormatContext 结构体,用于存储媒体文件的元数据和流信息。
  • 准备后续操作:为后续的解封装(demuxing)和解码操作做好准备。
典型用法
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 数组中。
  • 准备解码:为后续的解码操作分配必要的缓冲区和数据结构。
典型用法
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 中查找最佳匹配的流。
  • 简化流选择:避免手动遍历所有流,自动选择最合适的流。
典型用法
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. 函数原型
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
AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);
avformat_find_stream_info(fmt_ctx, NULL);
2. 准备 AVPacket
AVPacket pkt;
av_init_packet(&pkt);
3. 循环读取数据包
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. 释放资源
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. 函数原型
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:跳转到指定时间(秒)
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:跳转到指定帧
// 跳转到第 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 表示微秒)。
  • 转换公式
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. 高级场景
多流同步
// 同步视频和音频流到同一时间戳
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);
动态调整播放速度
// 加速播放(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. 如何实现逐帧播放?

    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)操作。
关键字段
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;

典型用法
// 打开输入文件
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_typeAVMediaType媒体类型(视频、音频、字幕等)。例如:AVMEDIA_TYPE_VIDEO 表示视频。
codec_idAVCodecID编解码器 ID(如 AV_CODEC_ID_H264 表示 H.264 编码)。
formatint像素格式(视频)或采样格式(音频)。例如:AV_PIX_FMT_YUV420P
width / heightint视频的宽度和高度(以像素为单位)。
sample_rateint音频的采样率(如 44100 Hz)。
channelsint音频的声道数(如 2 表示立体声)。
channel_layoutuint64_t音频的声道布局(如 AV_CH_LAYOUT_STEREO 表示立体声)。
bit_rateint64_t流的比特率(单位:比特/秒)。
extradatauint8_t*编解码器特定的额外数据(如 H.264 的 SPS/PPS)。
extradata_sizeint额外数据的大小。

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)等。
关键字段
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 流)。
    那么该帧的实际时间可以通过公式计算:

    double seconds = timestamp * av_q2d(time_base);
    

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

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

    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)。
关键字段
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 转换为实际时间:
      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 数组和元数据。

函数原型
AVPacket *av_packet_alloc(int buf_count);
参数

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

返回值

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

示例
// 分配一个支持 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 引用、时间戳、流索引等所有字段。

函数原型
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))。

示例
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 的引用计数,确保缓冲区不会被意外释放。

函数原型
void av_buffer_ref(AVBufferRef *buf);
参数

buf: 需要增加引用的 AVBufferRef

使用场景

克隆后保留数据:克隆 AVPacket 后,若需长期使用其缓冲区,需手动调用 av_buffer_ref()
多线程共享:在多线程环境中,确保每个线程对缓冲区的引用合法。

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

引用计数的作用

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

克隆后的风险

  • 示例场景
    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]) 显式增加引用计数,确保缓冲区不会被意外释放:
    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,由其自动处理引用计数递减。

示例场景

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

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

解决方案

  • 线程内独立引用
    每个线程在克隆后必须自行增加引用,并在结束时释放:
    // 线程 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,自动递减引用计数。

函数原型
void av_packet_free(AVPacket *pkt);
注意事项

引用计数规则
• 若 buf 由 FFmpeg 内部管理(buf->owner=1),调用 av_packet_free() 会自动释放。
• 若 buf 由用户管理(如硬件解码器返回的 GPU 缓冲区),需手动释放。
替代函数:推荐使用 av_packet_unref(),它会自动处理引用计数。

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

5. av_init_packet()

作用

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

函数原型
void av_init_packet(AVPacket *pkt);
av_packet_alloc() 的区别

无需分配内存:仅初始化现有结构体的字段。
典型用法:在复用已分配的 AVPacket 时调用(如循环处理数据包)。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2321548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CMS网站模板设计与用户定制化实战评测

内容概要 在数字化转型背景下&#xff0c;CMS平台作为企业内容管理的核心载体&#xff0c;其模板架构的灵活性与用户定制能力直接影响运营效率。通过对WordPress、Baklib等主流系统的技术解构发现&#xff0c;模块化设计理念已成为行业基准——WordPress依托超过6万款主题库实…

搭建个人博客教程(Hexo)

如何快速搭建一套本地的博客系统呢&#xff1f;这里有一套gitNode.jsHexo的部署方案来进行解决。 安装git Git 是一款免费开源的分布式版本控制系统&#xff0c;由 Linus Torvalds 于 2005 年为 Linux 内核开发设计。它通过本地仓库和远程仓库实现代码管理&#xff0c;支持分支…

Docker 可视化工具 Portainer

Docker 可视化工具 Portainer安装 官方安装地址&#xff1a;https://docs.portainer.io/start/install-ce/server/docker/wsl 一&#xff0c;首先&#xff0c;创建 Portainer Server 用来存储数据库的卷&#xff1a; docker volume create portainer_data二&#xff0c;然后…

数据库基础知识点(系列二)

1&#xff0e;关系数据模型由哪三个要素组成。 答&#xff1a;关系数据模型由关系数据结构、关系操作集合和关系完整性约束三部分组成。 2&#xff0e;简述关系的性质。&#xff08;关系就是一张二维表格&#xff0c;但不是任何二维表都叫关系&#xff09; 答&#xff1a;(1…

如何进行灌区闸门自动化改造-闸门远程控制系统建设

改造背景 操作效率低‌&#xff1a;人工启闭耗时耗力&#xff0c;单次操作需2-3人配合&#xff0c;耗时长。 ‌水资源浪费‌&#xff1a;依赖经验估算放水量&#xff0c;易导致漫灌或供水不足。 ‌管理滞后‌&#xff1a;无法实时监控水位、流量&#xff0c;故障响应延迟。 …

【算法笔记】图论基础(二):最短路、判环、二分图

目录 最短路松弛操作Dijkstra朴素Dijkstra时间复杂度算法过程例题 堆优化Dijkstra时间按复杂度算法过程例题 bellman-ford时间复杂度为什么dijkstra不能处理负权边&#xff1f;dijkstra的三个步骤&#xff1a;反例失效的原因 算法过程例题 spfa时间复杂度算法过程例题spfa求最短…

EMS小车技术特点与优势:高效灵活的自动化输送解决方案

北成新控伺服技术丨EMS小车调试视频 EMS小车是一种基于单轨运行的电动输送系统&#xff0c;通过电力驱动实现物料的高效搬运和输送&#xff0c;具有高效灵活、节能环保、多功能集成、行业适配性强等特性&#xff0c;广泛应用于汽车制造、工程机械、家电生产、仓储物流等行业自动…

uniapp运行到支付宝开发者工具

使用uniapp编写专有钉钉和浙政钉出现的样式问题 在支付宝开发者工具中启用2.0构建的时候&#xff0c;在开发工具中页面样式正常 但是在真机调试和线上的时候不正常 页面没问题&#xff0c;所有组件样式丢失 解决 在manifest.json mp-alipay中加入 "styleIsolation&qu…

C++ 性能优化隐藏陷阱:从系统调用到并发开销的深度反思

作为一名C++技术专家,我深知性能优化不仅是代码层面的艺术,更是理解硬件与语言交互的科学。在现代计算中,C++的抽象为开发者提供了便利,却也隐藏了硬件的复杂性。如何揭开这些“谎言”,让代码与硬件协同工作?本文将以小案例为载体,通过优化前后的对比,深入剖析每个章节…

Unity 使用 Protobuf(Pb2)二进制数据全流程工具详解

前言 在Unity游戏开发中&#xff0c;高效、快速、安全地读取配置数据是一项重要需求。本文介绍一种完整的解决方案——使用Protobuf二进制格式&#xff08;Pb2&#xff09;存储和读取游戏数据&#xff0c;并详细分享实现全流程的Unity工具。 一、技术流程概览 实现Unity读取…

基于QT(C++)实现绘图程序

绘图程序 1 核心算法 1.1 图元生成 1.1.1 直线 画直线的算法采用了课上讲到的 Bresenhan 算法&#xff0c;采用整数增量运算&#xff0c;精确而有效的光栅设备生成算法。 基本思想是&#xff1a;当直线斜率的绝对值小于 1 时&#xff0c;从左端点开始作为起点&#…

深入剖析ReLU激活函数:特性、优势与梯度消失问题的解决之道,以及Leaky ReLU 和 Parametric ReLU

深入剖析ReLU激活函数&#xff1a;特性、优势与梯度消失问题的解决之道 在深度学习领域&#xff0c;激活函数的选择直接影响神经网络的训练效果和性能。整流线性单元&#xff08;Rectified Linear Unit&#xff0c;简称ReLU&#xff09;因其简单性、高效性以及对梯度消失问题的…

服务注册/服务发现-Eureka

目录 1.引言&#xff1a;如果一个父项目中有多个子项目&#xff0c;但是这些子项目如何如何相互调用彼此的业务呢&#xff1f; 2.什么是注册中心 3.CAP理论 4.EureKa 5.服务注册 6.服务发现 7.负载均衡 1.引言&#xff1a;如果一个父项目中有多个子项目&#xff0c;但是…

计算机网络——数据链路层的功能

目录 物理链路 逻辑链路 封装成帧&#xff08;组帧&#xff09; 帧定界 透明传输 SDU 差错控制 可靠传输 流量控制 介质访问控制 主机需要实现第一层到第五层的功能&#xff0c;而路由器这种节点只需要实现第一层到第三层的这些功能 假设左边用户需要给右边用户发送…

第60天:Web攻防-XSS跨站文件类型功能逻辑SVGPDFSWFPMessageLocalStorage

#知识点 1、Web攻防-XSS跨站-文件类型-html&pdf&swf&svg 2、Web攻防-XSS跨站-功能逻辑-postMessage&localStorage 术语&#xff1a;上传xss->其实就是将有恶意js代码的各类文件&#xff08;swf,pdf,svg,html.xml等&#xff09;上传->访问该文件->让浏…

C/C++都有哪些开源的Web框架?

CppCMS CppCMS是一个采用C语言开发的高性能Web框架&#xff0c;通过模版元编程方式实现了在编译期检查RESTful路由系统&#xff0c;支持传统的MVC模式和多种语言混合开发模式。 CppCMS最厉害的功能是WebSocket&#xff0c;10万连接在内存中长期保存占用的大小不超过600MB&…

RISC-V AIA学习2---IMSIC

我在学习文档这章时&#xff0c;对技术术语不太理解&#xff0c;所以用比较恰当的比喻来让自己更好的理解。 比较通俗的理解&#xff1a; 将 RISC-V 系统比作一个工厂&#xff1a; hart → 工厂的一条独立生产线IMSIC → 每条生产线配备的「订单接收员」MSI 中断 → 客户通过…

2024年MathorCup数学建模B题甲骨文智能识别中原始拓片单字自动分割与识别研究解题全过程文档加程序

2024年第十四届MathorCup高校数学建模挑战赛 B题 甲骨文智能识别中原始拓片单字自动分割与识别研究 原题再现&#xff1a; 甲骨文是我国目前已知的最早成熟的文字系统&#xff0c;它是一种刻在龟甲或兽骨上的古老文字。甲骨文具有极其重要的研究价值&#xff0c;不仅对中国文…

Python----计算机视觉处理(Opencv:霍夫变换)

一、霍夫变换 霍夫变换是图像处理中的一种技术&#xff0c;主要用于检测图像中的直线、圆或其他形状。其基本思想就是将图像空间中的点映射到参数空间中&#xff0c;通过在参数空间中寻找累计最大值来实现对特定形状的检测。 二、 霍夫直线变换 那么对于一个二值化后的图形来说…

多语言生成语言模型的少样本学习

摘要 大规模生成语言模型&#xff0c;如GPT-3&#xff0c;是极具竞争力的少样本学习模型。尽管这些模型能够共同表示多种语言&#xff0c;但其训练数据以英语为主&#xff0c;这可能限制了它们的跨语言泛化能力。在本研究中&#xff0c;我们在一个涵盖多种语言的语料库上训练了…