avformat_alloc_context详解

函数原型

复制代码
AVFormatContext *avformat_alloc_context(void);

功能描述

avformat_alloc_context是 FFmpeg 中用于分配和初始化一个 AVFormatContext 结构体 的函数。它创建了一个空的格式上下文,为后续的解封装(读取)或封装(写入)操作做好准备。

返回值

  • 成功 :返回指向新分配的 AVFormatContext的指针

  • 失败 :返回 NULL(通常是内存分配失败)

内部行为详解

1. 内存分配

复制代码
AVFormatContext *ctx = av_mallocz(sizeof(AVFormatContext));

函数内部使用 av_mallocz()分配内存,这个函数:

  • 分配指定大小的内存块

  • 将分配的内存全部初始化为0(zero-initialized)

  • 如果分配失败,返回 NULL

2. 结构体初始化

分配的内存会被部分初始化,包括:

复制代码
// 设置默认值
ctx->av_class = &av_format_context_class;  // 设置AVClass用于日志和配置
ctx->io_open  = io_open_default;           // 设置默认的I/O打开函数
ctx->io_close = io_close_default;          // 设置默认的I/O关闭函数

// 设置默认标志
ctx->flags = AVFMT_FLAG_GENPTS;            // 生成缺失的时间戳

3. 上下文状态

创建后的上下文处于未打开状态,需要进一步操作:

  • 对于输入:需要调用 avformat_open_input()打开文件

  • 对于输出:需要设置输出格式和流

使用场景

场景1:读取媒体文件(解封装)

复制代码
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (!fmt_ctx) {
    fprintf(stderr, "内存分配失败\n");
    return AVERROR(ENOMEM);
}

// 打开文件
if (avformat_open_input(&fmt_ctx, filename, NULL, NULL) < 0) {
    fprintf(stderr, "无法打开文件: %s\n", filename);
    avformat_free_context(fmt_ctx);
    return -1;
}

// 使用fmt_ctx...

场景2:写入媒体文件(封装)

复制代码
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (!fmt_ctx) {
    fprintf(stderr, "内存分配失败\n");
    return AVERROR(ENOMEM);
}

// 手动设置输出格式
fmt_ctx->oformat = av_guess_format("mp4", NULL, NULL);
if (!fmt_ctx->oformat) {
    fprintf(stderr, "不支持指定格式\n");
    avformat_free_context(fmt_ctx);
    return -1;
}

// 使用fmt_ctx...

场景3:自定义I/O(内存、网络等)

复制代码
// 自定义读写回调
int read_packet(void *opaque, uint8_t *buf, int buf_size) {
    // 从自定义源读取数据
    return read_from_custom_source(opaque, buf, buf_size);
}

AVFormatContext *fmt_ctx = avformat_alloc_context();
if (!fmt_ctx) {
    return AVERROR(ENOMEM);
}

// 创建自定义I/O上下文
AVIOContext *avio_ctx = avio_alloc_context(
    buffer,           // 内部缓冲区
    buffer_size,      // 缓冲区大小
    0,                // write_flag (0=只读)
    opaque_data,      // 用户自定义数据
    read_packet,      // 读回调
    NULL,             // 写回调(只读时NULL)
    NULL              // seek回调
);

if (!avio_ctx) {
    avformat_free_context(fmt_ctx);
    return AVERROR(ENOMEM);
}

// 设置I/O上下文
fmt_ctx->pb = avio_ctx;

完整示例代码

示例1:安全创建和清理

复制代码
#include <libavformat/avformat.h>

AVFormatContext* create_format_context_safe() {
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    if (!fmt_ctx) {
        fprintf(stderr, "错误: 无法分配格式上下文\n");
        return NULL;
    }
    
    // 设置一些默认选项
    AVDictionary *options = NULL;
    av_dict_set(&options, "rtsp_transport", "tcp", 0);
    av_dict_set(&options, "stimeout", "5000000", 0);  // 5秒超时
    
    return fmt_ctx;
}

void safe_cleanup(AVFormatContext **fmt_ctx, AVDictionary **options) {
    if (fmt_ctx && *fmt_ctx) {
        // 先关闭I/O上下文
        if ((*fmt_ctx)->pb) {
            avio_close((*fmt_ctx)->pb);
            (*fmt_ctx)->pb = NULL;
        }
        
        // 释放格式上下文
        avformat_close_input(fmt_ctx);
    }
    
    // 释放选项字典
    if (options && *options) {
        av_dict_free(options);
    }
}

示例2:网络流处理

复制代码
int open_network_stream(const char *url) {
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;
    
    // 分配格式上下文
    fmt_ctx = avformat_alloc_context();
    if (!fmt_ctx) {
        fprintf(stderr, "内存分配失败\n");
        return -1;
    }
    
    // 设置网络参数
    av_dict_set(&options, "rtsp_transport", "tcp", 0);
    av_dict_set(&options, "stimeout", "3000000", 0);  // 3秒超时
    av_dict_set(&options, "buffer_size", "1024000", 0);  // 1MB缓冲区
    
    // 设置无限缓冲区(避免丢包)
    fmt_ctx->flags |= AVFMT_FLAG_NOBUFFER;
    
    // 打开网络流
    int ret = avformat_open_input(&fmt_ctx, url, NULL, &options);
    if (ret < 0) {
        char error_buf[AV_ERROR_MAX_STRING_SIZE];
        av_strerror(ret, error_buf, sizeof(error_buf));
        fprintf(stderr, "无法打开流: %s, 错误: %s\n", url, error_buf);
        avformat_free_context(fmt_ctx);
        av_dict_free(&options);
        return ret;
    }
    
    // 获取流信息
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "无法获取流信息\n");
        avformat_close_input(&fmt_ctx);
        return ret;
    }
    
    printf("成功打开流: %s\n", url);
    printf("格式: %s\n", fmt_ctx->iformat->name);
    printf("时长: %.2f 秒\n", fmt_ctx->duration / 1000000.0);
    printf("流数量: %d\n", fmt_ctx->nb_streams);
    
    // 使用完成后清理
    avformat_close_input(&fmt_ctx);
    av_dict_free(&options);
    
    return 0;
}

内存管理最佳实践

1. 正确释放内存

复制代码
void process_media_file(const char *filename) {
    AVFormatContext *fmt_ctx = NULL;
    int ret = 0;
    
    // 1. 分配上下文
    fmt_ctx = avformat_alloc_context();
    if (!fmt_ctx) {
        fprintf(stderr, "内存分配失败\n");
        return;
    }
    
    // 2. 打开文件
    ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
    if (ret < 0) {
        char error[AV_ERROR_MAX_STRING_SIZE];
        av_strerror(ret, error, sizeof(error));
        fprintf(stderr, "打开文件失败: %s\n", error);
        
        // 注意:如果avformat_open_input失败,fmt_ctx可能被重新分配
        // 但内部会处理这种情况,我们直接调用avformat_free_context
        avformat_free_context(fmt_ctx);
        return;
    }
    
    // 3. 处理文件...
    
    // 4. 正确清理
    avformat_close_input(&fmt_ctx);
    // 注意:avformat_close_input 会将指针设为NULL
}

2. 错误处理模板

复制代码
AVFormatContext* open_media_file_with_retry(const char *filename, 
                                           int max_retries) {
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;
    int retry_count = 0;
    
    // 设置选项
    av_dict_set(&options, "reconnect", "1", 0);
    av_dict_set(&options, "reconnect_streamed", "1", 0);
    av_dict_set(&options, "reconnect_delay_max", "5", 0);
    
    while (retry_count < max_retries) {
        // 分配上下文
        fmt_ctx = avformat_alloc_context();
        if (!fmt_ctx) {
            fprintf(stderr, "内存分配失败\n");
            return NULL;
        }
        
        // 尝试打开
        int ret = avformat_open_input(&fmt_ctx, filename, NULL, &options);
        if (ret >= 0) {
            // 成功
            av_dict_free(&options);
            return fmt_ctx;
        }
        
        // 失败,清理并重试
        avformat_free_context(fmt_ctx);
        fmt_ctx = NULL;
        
        retry_count++;
        fprintf(stderr, "打开失败,第 %d 次重试...\n", retry_count);
        av_usleep(1000000);  // 等待1秒
    }
    
    av_dict_free(&options);
    return NULL;
}

常见问题与解决方案

问题1:内存泄漏

复制代码
// ❌ 错误的做法
void bad_example() {
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    // 如果后续操作失败,没有释放fmt_ctx
    if (some_error) {
        return;  // 内存泄漏!
    }
}

// ✅ 正确的做法
void good_example() {
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    if (!fmt_ctx) {
        return;
    }
    
    int ret = do_something(fmt_ctx);
    if (ret < 0) {
        avformat_free_context(fmt_ctx);  // 释放内存
        return;
    }
    
    // 成功完成...
    avformat_free_context(fmt_ctx);
}

问题2:双重释放

复制代码
// ❌ 错误的做法
void double_free() {
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    
    // 打开文件
    avformat_open_input(&fmt_ctx, filename, NULL, NULL);
    
    // 使用文件...
    
    // 错误:先释放上下文
    avformat_free_context(fmt_ctx);
    
    // 再关闭输入(可能导致双重释放)
    avformat_close_input(&fmt_ctx);
}

// ✅ 正确的做法
void correct_free() {
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    
    // 打开文件
    avformat_open_input(&fmt_ctx, filename, NULL, NULL);
    
    // 使用文件...
    
    // 正确:只需要调用avformat_close_input
    // 它会关闭文件并释放上下文
    avformat_close_input(&fmt_ctx);
}

高级用法

自定义内存分配器

复制代码
// 自定义内存分配回调
void *custom_malloc(void *opaque, size_t size) {
    printf("分配 %zu 字节\n", size);
    return malloc(size);
}

void custom_free(void *opaque, void *ptr) {
    printf("释放内存\n");
    free(ptr);
}

void *custom_realloc(void *opaque, void *ptr, size_t size) {
    printf("重新分配内存: %zu 字节\n", size);
    return realloc(ptr, size);
}

// 设置自定义分配器
void use_custom_allocator() {
    // 创建自定义内存分配器
    AVFormatContext *fmt_ctx = avformat_alloc_context();
    if (!fmt_ctx) {
        return;
    }
    
    // 注意:FFmpeg内部有复杂的缓存机制
    // 替换默认分配器需要谨慎处理
}

总结

avformat_alloc_context是 FFmpeg 媒体处理的起点函数。使用时要记住:

  1. 总是检查返回值:确保分配成功

  2. 正确管理生命周期

    • 通过 avformat_alloc_context创建

    • 通过 avformat_open_input打开输入

    • 通过 avformat_close_inputavformat_free_context释放

  3. 错误处理:每个FFmpeg函数调用后都应检查返回值

  4. 资源清理:确保所有分配的资源都被正确释放

相关推荐
大熊背2 小时前
根据单张图像检测动态范围大小
图像处理·人工智能·计算机视觉
啊阿狸不会拉杆3 小时前
《机器学习》完结篇-总结
人工智能·算法·机器学习·计算机视觉·ai·集成学习·ml
WJSKad12354 小时前
基于改进YOLO11的超市商品与电子设备多类别目标检测方法C3k2-ConvAttn
人工智能·目标检测·计算机视觉
飞Link6 小时前
Spatiotemporal Filtering(时空滤波)详解:从理论到实战
人工智能·深度学习·机器学习·计算机视觉
yang011110016 小时前
论文总结 HVI: A New Color Space for Low-light Image Enhancement
图像处理·人工智能·学习·计算机视觉
Piar1231sdafa6 小时前
野猪目标检测与识别_基于YOLO11-Attention模型的改进实现
人工智能·目标检测·计算机视觉
光羽隹衡6 小时前
计算机视觉——Opencv(基础操作二)
人工智能·opencv·计算机视觉
2501_941333106 小时前
乒乓球比赛场景目标检测与行为分析研究
人工智能·目标检测·计算机视觉
岑梓铭6 小时前
YOLO深度学习(计算机视觉)一很有用!!(进一步加快训练速度的操作)
人工智能·深度学习·神经网络·yolo·计算机视觉