函数原型
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 媒体处理的起点函数。使用时要记住:
-
总是检查返回值:确保分配成功
-
正确管理生命周期:
-
通过
avformat_alloc_context创建 -
通过
avformat_open_input打开输入 -
通过
avformat_close_input或avformat_free_context释放
-
-
错误处理:每个FFmpeg函数调用后都应检查返回值
-
资源清理:确保所有分配的资源都被正确释放