av_packet_alloc详解

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;
}

七、注意事项

  1. 必须配对使用 : av_packet_alloc()必须与 av_packet_free()配对使用

  2. 避免混合使用 : 不要对通过 av_packet_alloc()创建的packet调用 av_init_packet()

  3. 清空数据 : 重用packet前必须调用 av_packet_unref()

  4. 检查返回值 : 总是检查 av_packet_alloc()的返回值是否为NULL

  5. 引用计数: 了解引用计数机制,避免重复释放

  6. 线程安全: AVPacket不是线程安全的

  7. 数据有效性: 在释放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);
}

九、最佳实践总结

  1. 使用新API : 总是使用 av_packet_alloc()而不是 av_init_packet()

  2. 检查返回值: 检查所有FFmpeg函数的返回值

  3. 及时清理: 使用完后立即释放资源

  4. 重用packet: 在循环中重用packet以提高性能

  5. 了解生命周期: 理解AVPacket的生命周期和所有权

  6. 避免野指针: 释放后不要访问packet

  7. 正确处理引用 : 使用 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);  // 使用后立即释放

    }

相关推荐
Echo_NGC22371 天前
【FFmpeg 使用指南】Part 3:码率控制策略与质量评估体系
人工智能·ffmpeg·视频·码率
xmRao1 天前
Qt+FFmpeg 实现 PCM 音频转 AAC 编码
qt·ffmpeg·pcm
xmRao1 天前
Qt+FFmpeg 实现录音程序(pcm转wav)
qt·ffmpeg
阿里巴啦2 天前
python+yt-dlp开源项目,支持 YouTube, Bilibili, TikTok/抖音,快手 等多个平台的视频/音频/字幕下载/ai摘要等功能
python·ffmpeg·whisper·音视频·视频处理·ai摘要·音视频转录
来鸟 鸣间3 天前
linux下ffmpeg源码编译
linux·运维·ffmpeg
Echo_NGC22373 天前
【FFmpeg使用指南】Part 2:滤镜图架构与信号处理
架构·ffmpeg·音视频·信号处理
Echo_NGC22373 天前
【FFmpeg使用指南】Part 1:核心架构与媒体流处理
ffmpeg·音视频·媒体·视频
ssxueyi3 天前
用 Claude Code 从零开发自己的Direct3D 硬件加速播放器
ffmpeg·ai编程·directx·视频播放器·从零开始·claude code·csdn征文活动
Yan_uuu3 天前
ubuntu18.04 安装 x264、ffmpeg、nv-codec-hearers 支持GPU硬件加速
c++·图像处理·ubuntu·ffmpeg