ffmpeg介绍(一)——解封装
解封装
常用函数
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:将解析到的流信息填充到AVFormatContext的streams数组中。 - 准备解码:为后续的解码操作分配必要的缓冲区和数据结构。
典型用法
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_VIDEO、AVMEDIA_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_ctx:AVFormatContext指针,表示媒体文件的上下文。pkt:AVPacket指针,用于存储读取到的数据包。
-
返回值:
- 成功时返回
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. 常见问题
-
为什么seek后无法读取到数据?
- 缓冲区未刷新:调用
av_flush_packets(fmt_ctx)清空输入缓冲区。 - 流未seekable:某些流(如直播流)不支持随机访问。
- 缓冲区未刷新:调用
-
如何实现逐帧播放?
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++; } -
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]; // 文件名或URLint64_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_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 通常在以下场景中使用:
-
解复用(Demuxing):
- 当从容器(如 MP4、MKV)中读取音视频流时,
AVFormatContext会为每个流分配一个AVStream,而AVStream中的codecpar字段就是AVCodecParameters。 - 通过
AVCodecParameters,可以获取流的编解码信息,而不需要初始化编解码器。
- 当从容器(如 MP4、MKV)中读取音视频流时,
-
编码/解码前的准备:
- 在初始化编解码器(
AVCodecContext)之前,可以使用AVCodecParameters中的信息来配置编解码器。
- 在初始化编解码器(
-
流的复制或转封装:
- 在转封装(Remuxing)时,可以直接将
AVCodecParameters从一个流复制到另一个流,而不需要重新解析编解码信息。
- 在转封装(Remuxing)时,可以直接将
3. AVCodecParameters 与 AVCodecContext 的区别
-
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 = 90000且time_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() | 强制释放包 | 直接释放内存(不依赖引用计数) |
三者的协作流程
-
初始化容器:
- 通过
AVFormatContext打开输入文件,获取全局信息。 - 遍历
AVFormatContext->streams获取各个AVStream。
- 通过
-
处理数据包:
- 使用
av_read_frame读取AVPacket。 - 根据
AVPacket->stream_index找到对应的AVStream。 - 将
AVPacket送入解码器(需结合AVCodecContext)。
- 使用
-
时间戳转换:
- 将
AVPacket的pts和dts转换为实际时间:double timestamp_sec = pkt.pts * av_q2d(stream->time_base);
- 将
-
资源释放:
- 使用
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)),其buf的refcount会减到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_INC和AV_ATOMIC_DEC)确保增减操作的原子性,但用户代码仍需遵守规则。
关键规则
- 每个线程必须独立管理引用:
- 如果线程 A 持有缓冲区的引用,线程 B 不能直接释放它。
- 克隆后需显式增加引用:
- 即使缓冲区由 FFmpeg 内部管理(
owner=1),多线程环境下仍需调用av_buffer_ref(),避免其他线程误释放。
- 即使缓冲区由 FFmpeg 内部管理(
- 使用
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=0、pts=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); // 释放
相关文章:
ffmpeg介绍(一)——解封装
解封装 常用函数 1. avformat_open_input() 作用 打开媒体文件或网络资源:解析文件路径或 URL,识别媒体格式(如 MP4、AVI、RTSP 等)。初始化 AVFormatContext:分配并初始化 AVFormatContext 结构体,…...
版本控制GIT的使用
在 GitCode 上进行代码提交的步骤与在 GitHub 或其他 Git 托管平台上提交代码的步骤类似。以下是一个基本的流程: 1. 安装 Git 如果你还没有安装 Git,首先需要在你的计算机上安装 Git。你可以从 Git 官方网站 下载并安装适合你操作系统的版本。 2. 配…...
本周安全速报(2025.3.18~3.24)
合规速递 01 2025欧洲网络安全报告:DDoS攻击同比增长137%,企业应如何应对? 原文: https://hackread.com/european-cyber-report-2025-137-more-ddos-attacks/ 最新的Link11《欧洲网络安全报告》揭示了一个令人担忧的趋势:DDo…...
CMS网站模板设计与用户定制化实战评测
内容概要 在数字化转型背景下,CMS平台作为企业内容管理的核心载体,其模板架构的灵活性与用户定制能力直接影响运营效率。通过对WordPress、Baklib等主流系统的技术解构发现,模块化设计理念已成为行业基准——WordPress依托超过6万款主题库实…...
【后端开发面试题】每日 3 题(二十)
✍个人博客:Pandaconda-CSDN博客 📣专栏地址:https://blog.csdn.net/newin2020/category_12903849.html 📚专栏简介:在这个专栏中,我将会分享后端开发面试中常见的面试题给大家,每天的题目都是独…...
搭建个人博客教程(Hexo)
如何快速搭建一套本地的博客系统呢?这里有一套gitNode.jsHexo的部署方案来进行解决。 安装git Git 是一款免费开源的分布式版本控制系统,由 Linus Torvalds 于 2005 年为 Linux 内核开发设计。它通过本地仓库和远程仓库实现代码管理,支持分支…...
Docker 可视化工具 Portainer
Docker 可视化工具 Portainer安装 官方安装地址:https://docs.portainer.io/start/install-ce/server/docker/wsl 一,首先,创建 Portainer Server 用来存储数据库的卷: docker volume create portainer_data二,然后…...
数据库基础知识点(系列二)
1.关系数据模型由哪三个要素组成。 答:关系数据模型由关系数据结构、关系操作集合和关系完整性约束三部分组成。 2.简述关系的性质。(关系就是一张二维表格,但不是任何二维表都叫关系) 答:(1…...
Docker-Compose部署 EasySearch 异常问题排查
近期将原本运行在 macOS 上的 EasySearch、Console 和 Coco-server 等服务迁移至群晖 NAS 平台。在迁移过程中遇到了EasySearch容器无法正常启动或运行中意外终止的问题。本文记录了这些问题的具体表现及解决方案,旨在为后续类似部署提供参考。 基础部署配置 以下…...
如何进行灌区闸门自动化改造-闸门远程控制系统建设
改造背景 操作效率低:人工启闭耗时耗力,单次操作需2-3人配合,耗时长。 水资源浪费:依赖经验估算放水量,易导致漫灌或供水不足。 管理滞后:无法实时监控水位、流量,故障响应延迟。 …...
深入解析 Vue3 响应式系统:原理、性能优化与应用场景
文章目录 1. Vue3 响应式系统的基本原理:Proxy 与 Reflect1.1 Proxy 和 Reflect 概述1.1.1 Proxy1.1.2 Reflect1.1.3 Proxy 和 Reflect 的协作 1.2 Vue3 响应式系统:如何通过 Proxy 实现数据代理1.3 Vue3 中 Proxy 的核心概念:响应式数据的创…...
C++11QT复习(二)
文章目录 Day4-4 New 与 delete 表达式(2025.03.20)1. new 表达式的三个步骤2. delete 表达式的两个步骤3. new[] 与 delete[] Day5 类的定义和关键字再探(2025.03.24)1. C 关键字 const、static、extern2. 类的定义:C…...
【数据挖掘】数据预处理——以鸢尾花数据集为例
数据预处理——以鸢尾花数据集为例 一、实验手册(一)实验目的(二)实验原理(三)实验环境(四)实验步骤(五)实验报告要求 二、案例代码(以鸢尾花数据…...
【算法笔记】图论基础(二):最短路、判环、二分图
目录 最短路松弛操作Dijkstra朴素Dijkstra时间复杂度算法过程例题 堆优化Dijkstra时间按复杂度算法过程例题 bellman-ford时间复杂度为什么dijkstra不能处理负权边?dijkstra的三个步骤:反例失效的原因 算法过程例题 spfa时间复杂度算法过程例题spfa求最短…...
HTTP/HTTPS 中 GET 请求和 POST 请求的区别与联系
一、基础概念 HTTP (HyperText Transfer Protocol, 超文本传输协议) 是一种用于浏览器与服务器之间进行数据交互的协议。HTTPS (加密的 HTTP) 则通过 SSL/TLS 协议实现通信加密与数据安全性。 二、GET 和 POST 概述 GET 请求: 用于从服务器获取资源。 POST 请求: 用于将数据…...
Spring、Spring Boot与Spring Cloud深度解析:核心关系与实战应用指南
1. 技术定位 Spring Framework:企业级Java开发的基础框架Spring Boot:快速构建独立运行的Spring应用Spring Cloud:分布式系统开发的微服务全家桶 二、Spring Framework核心解析 1. 关键特性 // 典型Spring MVC控制器示例 Controller Reque…...
EMS小车技术特点与优势:高效灵活的自动化输送解决方案
北成新控伺服技术丨EMS小车调试视频 EMS小车是一种基于单轨运行的电动输送系统,通过电力驱动实现物料的高效搬运和输送,具有高效灵活、节能环保、多功能集成、行业适配性强等特性,广泛应用于汽车制造、工程机械、家电生产、仓储物流等行业自动…...
uniapp运行到支付宝开发者工具
使用uniapp编写专有钉钉和浙政钉出现的样式问题 在支付宝开发者工具中启用2.0构建的时候,在开发工具中页面样式正常 但是在真机调试和线上的时候不正常 页面没问题,所有组件样式丢失 解决 在manifest.json mp-alipay中加入 "styleIsolation&qu…...
C++ 性能优化隐藏陷阱:从系统调用到并发开销的深度反思
作为一名C++技术专家,我深知性能优化不仅是代码层面的艺术,更是理解硬件与语言交互的科学。在现代计算中,C++的抽象为开发者提供了便利,却也隐藏了硬件的复杂性。如何揭开这些“谎言”,让代码与硬件协同工作?本文将以小案例为载体,通过优化前后的对比,深入剖析每个章节…...
Unity 使用 Protobuf(Pb2)二进制数据全流程工具详解
前言 在Unity游戏开发中,高效、快速、安全地读取配置数据是一项重要需求。本文介绍一种完整的解决方案——使用Protobuf二进制格式(Pb2)存储和读取游戏数据,并详细分享实现全流程的Unity工具。 一、技术流程概览 实现Unity读取…...
基于QT(C++)实现绘图程序
绘图程序 1 核心算法 1.1 图元生成 1.1.1 直线 画直线的算法采用了课上讲到的 Bresenhan 算法,采用整数增量运算,精确而有效的光栅设备生成算法。 基本思想是:当直线斜率的绝对值小于 1 时,从左端点开始作为起点&#…...
深入剖析ReLU激活函数:特性、优势与梯度消失问题的解决之道,以及Leaky ReLU 和 Parametric ReLU
深入剖析ReLU激活函数:特性、优势与梯度消失问题的解决之道 在深度学习领域,激活函数的选择直接影响神经网络的训练效果和性能。整流线性单元(Rectified Linear Unit,简称ReLU)因其简单性、高效性以及对梯度消失问题的…...
vscode设置console.log的快捷输出方式
vscode设置console.log的快捷输出方式 编辑器中输入clg回车,可以直接输出console.log,并且同步输出变量的字符串和值 1、打开vscode点击左上角的文件 2、找到首选项 3、点击用户代码配置 4、在顶部输入框种输入javas,选择JavaScript选项 5、…...
服务注册/服务发现-Eureka
目录 1.引言:如果一个父项目中有多个子项目,但是这些子项目如何如何相互调用彼此的业务呢? 2.什么是注册中心 3.CAP理论 4.EureKa 5.服务注册 6.服务发现 7.负载均衡 1.引言:如果一个父项目中有多个子项目,但是…...
【机器学习】什么是随机森林?
什么是随机森林? 随机森林(Random Forest)是一种集成学习方法,它通过组合多个决策树来提高预测的准确性和鲁棒性。可以把随机森林看作是“森林”,而森林中的每棵树就是一个决策树。每棵树独立地做出预测,最…...
【Rust】一文掌握 Rust 的详细用法(Rust 备忘清单)
文章目录 入门配置 vscode 调试Hello_World.rs原始类型格式化打印风格变量注释函数声明宏元变量结构体元组结构体单元结构体 语句与表达式语句表达式 区间表达式 Rust 类型类型别名整数浮点数布尔值字符字符串字面量数组切片元组 Rust 字符串字符串字面量字符串对象.capacity()…...
为什么后端接口返回数字类型1.00前端会取到1?
这得从axios中得默认值说起: Axios 的 transformResponse axios 在接收到服务器的响应后,会通过一系列的转换函数(transformResponse)来处理响应数据,使其适合在应用程序中使用。默认情况下,axios 的 tran…...
单片机串口打印调试信息②
在STM32开发中,使用串口(UART)打印调试信息是调试嵌入式程序的核心手段。以下是基于STM32 HAL库的详细实现步骤和调试策略: 一、硬件准备 硬件连接: STM32开发板:以STM32F4系列为例,选择任意UAR…...
Windows下安装常用软件--MySQL篇
Windows下安装常用软件--MySQL篇 文章说明安装指导安装MySQL脚本 资料下载 文章说明 记录一下Windows下安装zip版的MySQL,采用简洁的方式安装,便于学习使用;作为对该篇文章的修正与完善(MySQL 关于 zip安装) 安装指导 …...
Qt 高效读写JSON文件,玩转QJsonDocument与QJsonObject
一、前言 JSON作为轻量级的数据交换格式,已成为开发者必备技能。Qt框架为JSON处理提供了完整的解决方案,通过QJsonDocument、QJsonObject和QJsonArray三大核心类,轻松实现数据的序列化与反序列化。 JSON vs INI 特性JSONINI数据结构支持嵌…...
