av_packet_alloc是FFmpeg中创建AVPacket的推荐方法,它会分配一个AVPacket并正确初始化所有字段。
一、函数原型
AVPacket *av_packet_alloc(void);
返回值
-
成功: 返回指向新分配AVPacket的指针
-
失败: 返回NULL
二、基本用法
2.1 创建AVPacket
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <stdio.h>
void basic_allocation_example() {
AVPacket *pkt = NULL;
// 分配AVPacket
pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "无法分配AVPacket\n");
return;
}
// 此时pkt已经被正确初始化
printf("成功分配AVPacket\n");
printf(" 数据指针: %p\n", pkt->data);
printf(" 数据大小: %d\n", pkt->size);
printf(" PTS: %ld\n", pkt->pts);
printf(" DTS: %ld\n", pkt->dts);
printf(" 流索引: %d\n", pkt->stream_index);
printf(" 标志位: 0x%x\n", pkt->flags);
// 使用packet...
// 释放AVPacket
av_packet_free(&pkt);
printf("已释放AVPacket\n");
}
2.2 完整的创建-使用-释放流程
#include <libavcodec/avcodec.h>
#include <libavutil/mem.h>
int full_packet_lifecycle() {
AVPacket *pkt = NULL;
int ret = 0;
// 1. 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "错误: 无法分配AVPacket\n");
return AVERROR(ENOMEM);
}
printf("步骤1: 已分配AVPacket\n");
// 2. 分配数据缓冲区
int data_size = 4096; // 4KB数据
ret = av_new_packet(pkt, data_size);
if (ret < 0) {
fprintf(stderr, "错误: 无法分配数据缓冲区: %s\n", av_err2str(ret));
av_packet_free(&pkt);
return ret;
}
printf("步骤2: 已分配%字节的数据缓冲区\n", data_size);
// 3. 填充数据
for (int i = 0; i < pkt->size; i++) {
pkt->data[i] = i % 256; // 填充一些测试数据
}
// 4. 设置packet属性
pkt->pts = 1000; // 演示时间戳
pkt->dts = 900; // 解码时间戳
pkt->stream_index = 0; // 第一个流
pkt->flags |= AV_PKT_FLAG_KEY; // 标记为关键帧
printf("步骤3-4: 已填充数据并设置属性\n");
printf(" PTS: %ld\n", pkt->pts);
printf(" DTS: %ld\n", pkt->dts);
printf(" 关键帧: %s\n", (pkt->flags & AV_PKT_FLAG_KEY) ? "是" : "否");
// 5. 使用packet (这里只是演示,实际可能进行编码、解码、写文件等操作)
printf("步骤5: 使用packet\n");
printf(" 实际数据大小: %d\n", pkt->size);
printf(" 数据缓冲区地址: %p\n", pkt->data);
// 6. 清理
av_packet_free(&pkt);
printf("步骤6: 已清理所有资源\n");
return 0;
}
三、实际应用示例
3.1 从文件读取packet
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
int read_and_process_file(const char *filename) {
AVFormatContext *fmt_ctx = NULL;
AVPacket *pkt = NULL;
int ret = 0;
int packet_count = 0;
// 1. 打开输入文件
if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {
fprintf(stderr, "无法打开文件 '%s': %s\n",
filename, av_err2str(ret));
return ret;
}
// 2. 获取流信息
if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret));
avformat_close_input(&fmt_ctx);
return ret;
}
printf("文件信息:\n");
printf(" 格式: %s\n", fmt_ctx->iformat->name);
printf(" 时长: %.2f秒\n",
(double)fmt_ctx->duration / AV_TIME_BASE);
printf(" 流数量: %d\n", fmt_ctx->nb_streams);
// 3. 创建packet
pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "无法分配packet\n");
avformat_close_input(&fmt_ctx);
return AVERROR(ENOMEM);
}
// 4. 读取并处理每个packet
while (1) {
// 清空packet以便重用
av_packet_unref(pkt);
ret = av_read_frame(fmt_ctx, pkt);
if (ret < 0) {
if (ret == AVERROR_EOF) {
printf("文件结束,共读取 %d 个packet\n", packet_count);
ret = 0; // EOF不是错误
} else {
fprintf(stderr, "读取错误: %s\n", av_err2str(ret));
}
break;
}
packet_count++;
// 获取流信息
AVStream *stream = fmt_ctx->streams[pkt->stream_index];
AVMediaType type = stream->codecpar->codec_type;
// 转换时间戳
double pts_time = (pkt->pts == AV_NOPTS_VALUE) ?
NAN : pkt->pts * av_q2d(stream->time_base);
// 打印packet信息
const char *type_str = "未知";
switch (type) {
case AVMEDIA_TYPE_VIDEO: type_str = "视频"; break;
case AVMEDIA_TYPE_AUDIO: type_str = "音频"; break;
case AVMEDIA_TYPE_SUBTITLE: type_str = "字幕"; break;
default: break;
}
printf("Packet #%d: 类型=%s, 大小=%6d, 时间戳=%.3fs, 关键帧=%s\n",
packet_count, type_str, pkt->size, pts_time,
(pkt->flags & AV_PKT_FLAG_KEY) ? "是" : "否");
// 这里可以进行解码、处理等操作
}
// 5. 清理
av_packet_free(&pkt);
avformat_close_input(&fmt_ctx);
return ret;
}
3.2 编码生成packet
int encode_and_create_packets(AVCodecContext *enc_ctx, AVFrame *frame) {
AVPacket *pkt = NULL;
int ret = 0;
int packet_count = 0;
if (!enc_ctx) {
return AVERROR(EINVAL);
}
// 1. 创建packet
pkt = av_packet_alloc();
if (!pkt) {
return AVERROR(ENOMEM);
}
// 2. 如果有输入帧,发送到编码器
if (frame) {
ret = avcodec_send_frame(enc_ctx, frame);
if (ret < 0) {
fprintf(stderr, "发送帧到编码器失败: %s\n", av_err2str(ret));
av_packet_free(&pkt);
return ret;
}
} else {
// 发送NULL帧表示编码结束
ret = avcodec_send_frame(enc_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "发送结束帧失败: %s\n", av_err2str(ret));
av_packet_free(&pkt);
return ret;
}
}
// 3. 接收编码后的packet
while (1) {
av_packet_unref(pkt); // 清空以便接收新数据
ret = avcodec_receive_packet(enc_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// 需要更多输入或编码已完成
break;
} else if (ret < 0) {
fprintf(stderr, "接收packet失败: %s\n", av_err2str(ret));
break;
}
packet_count++;
// 处理编码后的packet
printf("编码packet #%d: 大小=%d, PTS=%ld, DTS=%ld\n",
packet_count, pkt->size, pkt->pts, pkt->dts);
// 这里可以写入文件、传输等
// write_packet_to_file(pkt);
// 注意:这里不清空pkt,让循环自动清空
}
printf("共编码 %d 个packet\n", packet_count);
// 4. 清理
av_packet_free(&pkt);
return 0;
}
四、高级用法
4.1 批量处理多个packet
#define MAX_PACKETS 10
int batch_process_packets(AVFormatContext *fmt_ctx) {
AVPacket *packets[MAX_PACKETS];
int packet_count = 0;
int ret = 0;
// 1. 分配多个packet
for (int i = 0; i < MAX_PACKETS; i++) {
packets[i] = av_packet_alloc();
if (!packets[i]) {
fprintf(stderr, "无法分配packet %d\n", i);
// 清理已分配的
for (int j = 0; j < i; j++) {
av_packet_free(&packets[j]);
}
return AVERROR(ENOMEM);
}
}
printf("已分配 %d 个packet\n", MAX_PACKETS);
// 2. 批量读取
for (int i = 0; i < MAX_PACKETS; i++) {
ret = av_read_frame(fmt_ctx, packets[i]);
if (ret < 0) {
if (ret == AVERROR_EOF) {
printf("文件结束,只读取了 %d 个packet\n", i);
} else {
fprintf(stderr, "读取错误: %s\n", av_err2str(ret));
}
packet_count = i;
break;
}
packet_count++;
}
// 3. 批量处理
if (packet_count > 0) {
printf("开始批量处理 %d 个packet\n", packet_count);
for (int i = 0; i < packet_count; i++) {
AVPacket *pkt = packets[i];
AVStream *stream = fmt_ctx->streams[pkt->stream_index];
double pts_time = pkt->pts * av_q2d(stream->time_base);
printf(" 处理packet[%d]: 大小=%d, 时间=%.3fs\n",
i, pkt->size, pts_time);
// 这里可以进行批量解码、分析等操作
}
}
// 4. 批量释放
printf("清理资源...\n");
for (int i = 0; i < MAX_PACKETS; i++) {
av_packet_free(&packets[i]);
}
return 0;
}
4.2 使用引用计数共享数据
void reference_counting_demo() {
printf("=== 引用计数演示 ===\n\n");
// 1. 创建原始packet
AVPacket *original = av_packet_alloc();
if (!original) {
fprintf(stderr, "无法分配原始packet\n");
return;
}
// 分配数据
int data_size = 100;
if (av_new_packet(original, data_size) < 0) {
fprintf(stderr, "无法分配数据\n");
av_packet_free(&original);
return;
}
// 填充测试数据
for (int i = 0; i < data_size; i++) {
original->data[i] = 'A' + (i % 26);
}
original->data[data_size - 1] = '\0';
printf("1. 创建原始packet:\n");
printf(" 数据地址: %p\n", original->data);
printf(" 数据内容: %s\n", original->data);
// 2. 创建引用
AVPacket *reference = av_packet_alloc();
if (!reference) {
fprintf(stderr, "无法分配引用packet\n");
av_packet_free(&original);
return;
}
// 增加引用计数
if (av_packet_ref(reference, original) < 0) {
fprintf(stderr, "无法创建引用\n");
av_packet_free(&original);
av_packet_free(&reference);
return;
}
printf("\n2. 创建引用packet:\n");
printf(" 引用数据地址: %p (与原始相同)\n", reference->data);
printf(" 引用数据内容: %s\n", reference->data);
// 3. 修改原始packet的数据
original->data[0] = 'Z';
printf("\n3. 修改原始packet第一个字符为 'Z'\n");
printf(" 原始数据: %s\n", original->data);
printf(" 引用数据: %s (也被修改了,因为共享数据)\n", reference->data);
// 4. 释放引用
av_packet_unref(reference);
av_packet_free(&reference);
printf("\n4. 释放引用packet:\n");
printf(" 原始数据仍然有效: %s\n", original->data);
// 5. 释放原始packet
av_packet_unref(original);
av_packet_free(&original);
printf("\n5. 释放原始packet:\n");
printf(" 数据被释放\n");
}
五、错误处理
5.1 完整的错误处理流程
AVPacket *create_packet_with_error_handling(int data_size, int64_t pts, int64_t dts) {
AVPacket *pkt = NULL;
int ret = 0;
// 1. 分配packet结构
pkt = av_packet_alloc();
if (!pkt) {
fprintf(stderr, "错误: 无法分配AVPacket结构\n");
goto error;
}
// 2. 分配数据缓冲区
if (data_size > 0) {
ret = av_new_packet(pkt, data_size);
if (ret < 0) {
fprintf(stderr, "错误: 无法分配数据缓冲区: %s\n",
av_err2str(ret));
goto error;
}
// 3. 填充数据
for (int i = 0; i < data_size; i++) {
pkt->data[i] = rand() % 256; // 随机数据
}
}
// 4. 设置属性
pkt->pts = pts;
pkt->dts = dts;
pkt->stream_index = 0;
// 5. 成功返回
printf("成功创建packet: 大小=%d, PTS=%ld, DTS=%ld\n",
pkt->size, pkt->pts, pkt->dts);
return pkt;
error:
// 清理资源
if (pkt) {
av_packet_free(&pkt);
}
return NULL;
}
5.2 内存泄漏检查
void memory_leak_check_example() {
printf("开始内存泄漏检查演示\n");
// 1. 创建多个packet但不释放
AVPacket *packets[5];
for (int i = 0; i < 5; i++) {
packets[i] = av_packet_alloc();
if (packets[i]) {
av_new_packet(packets[i], 1024);
printf("创建packet %d, 地址: %p\n", i, (void*)packets[i]);
}
}
// 2. 故意不释放一些packet
printf("\n故意不释放packet 1和3\n");
av_packet_free(&packets[0]); // 正确释放
// packets[1] 不释放 - 内存泄漏!
av_packet_free(&packets[2]); // 正确释放
// packets[3] 不释放 - 内存泄漏!
av_packet_free(&packets[4]); // 正确释放
// 3. 使用工具如Valgrind检查内存泄漏
printf("\n使用Valgrind检查内存泄漏:\n");
printf("valgrind --leak-check=full ./your_program\n");
// 注意:实际代码中应该总是释放所有资源
// 这里只是演示
}
六、性能优化
6.1 重用AVPacket
int process_with_reused_packet(AVFormatContext *fmt_ctx, int max_packets) {
AVPacket *pkt = NULL;
int ret = 0;
int processed = 0;
// 1. 只分配一次packet
pkt = av_packet_alloc();
if (!pkt) {
return AVERROR(ENOMEM);
}
// 2. 循环处理
for (int i = 0; i < max_packets; i++) {
// 清空packet以便重用
av_packet_unref(pkt);
ret = av_read_frame(fmt_ctx, pkt);
if (ret < 0) {
if (ret == AVERROR_EOF) {
printf("文件结束\n");
ret = 0;
}
break;
}
processed++;
// 处理packet
AVStream *stream = fmt_ctx->streams[pkt->stream_index];
double pts_time = pkt->pts * av_q2d(stream->time_base);
printf("处理packet %d: 大小=%d, 时间=%.3fs\n",
i, pkt->size, pts_time);
// 这里可以进行解码、分析等操作
// 注意:不需要每次分配新packet
}
printf("共处理 %d 个packet\n", processed);
// 3. 最后清理
av_packet_free(&pkt);
return ret;
}
七、注意事项
-
必须配对使用 :
av_packet_alloc()必须与av_packet_free()配对使用 -
避免混合使用 : 不要对通过
av_packet_alloc()创建的packet调用av_init_packet() -
清空数据 : 重用packet前必须调用
av_packet_unref() -
检查返回值 : 总是检查
av_packet_alloc()的返回值是否为NULL -
引用计数: 了解引用计数机制,避免重复释放
-
线程安全: AVPacket不是线程安全的
-
数据有效性: 在释放packet后不要访问其数据
八、常见问题
8.1 忘记调用av_packet_unref
void memory_leak_example() {
AVPacket *pkt = av_packet_alloc();
for (int i = 0; i < 10; i++) {
// 错误:没有调用av_packet_unref
// 每次调用av_read_frame前应该清空packet
// 正确做法:
// av_packet_unref(pkt);
// 模拟读取
// av_read_frame(fmt_ctx, pkt);
}
av_packet_free(&pkt);
}
8.2 错误的内存访问
void invalid_access_example() {
AVPacket *pkt = av_packet_alloc();
// 错误:访问未分配的数据
// printf("数据大小: %d\n", pkt->size); // 大小为0
// printf("第一个字节: %d\n", pkt->data[0]); // 访问无效内存
// 正确做法:先分配数据
av_new_packet(pkt, 1024);
printf("数据大小: %d\n", pkt->size); // 现在可以访问
printf("第一个字节: %d\n", pkt->data[0]); // 现在可以访问
av_packet_free(&pkt);
}
九、最佳实践总结
-
使用新API : 总是使用
av_packet_alloc()而不是av_init_packet() -
检查返回值: 检查所有FFmpeg函数的返回值
-
及时清理: 使用完后立即释放资源
-
重用packet: 在循环中重用packet以提高性能
-
了解生命周期: 理解AVPacket的生命周期和所有权
-
避免野指针: 释放后不要访问packet
-
正确处理引用 : 使用
av_packet_ref()和av_packet_unref()管理共享数据// 最佳实践示例
void best_practice_example() {
AVPacket *pkt = av_packet_alloc();
if (!pkt) {
return; // 检查分配失败
}// 使用packet int ret = av_new_packet(pkt, 1024); if (ret < 0) { av_packet_free(&pkt); // 及时清理 return; } // ... 使用pkt ... av_packet_free(&pkt); // 使用后立即释放}