函数原型
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
功能描述
avcodec_parameters_copy是 FFmpeg 中用于深度复制 一个 AVCodecParameters结构体到另一个的函数。它会:
-
复制所有基本字段
-
深度复制
extradata(编解码器私有数据) -
确保目标结构体完全独立于源结构体
参数详解
/**
* 复制编解码器参数
*
* @param dst 目标AVCodecParameters,必须是已分配的结构体
* @param src 源AVCodecParameters
* @return 成功返回0,失败返回负的错误码
*/
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src);
函数行为
// 伪代码演示函数行为
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src) {
if (!dst || !src)
return AVERROR(EINVAL);
// 1. 保存目标原来的extradata指针
uint8_t *old_extradata = dst->extradata;
int old_extradata_size = dst->extradata_size;
// 2. 重置目标extradata,避免重复释放
dst->extradata = NULL;
dst->extradata_size = 0;
// 3. 复制所有基本字段
memcpy(dst, src, sizeof(*dst));
// 4. 深度复制extradata
if (src->extradata && src->extradata_size > 0) {
dst->extradata = av_malloc(src->extradata_size);
if (!dst->extradata) {
// 恢复原来的extradata
dst->extradata = old_extradata;
dst->extradata_size = old_extradata_size;
return AVERROR(ENOMEM);
}
memcpy(dst->extradata, src->extradata, src->extradata_size);
dst->extradata_size = src->extradata_size;
}
// 5. 释放原来的extradata
av_freep(&old_extradata);
return 0;
}
使用示例
基本复制
#include <libavcodec/avcodec.h>
int main() {
// 源参数(例如从流中获取)
AVCodecParameters *src_par = /* 从某个地方获取 */;
// 分配目标参数
AVCodecParameters *dst_par = avcodec_parameters_alloc();
if (!dst_par) {
return AVERROR(ENOMEM);
}
// 复制参数
int ret = avcodec_parameters_copy(dst_par, src_par);
if (ret < 0) {
fprintf(stderr, "复制参数失败: %s\n", av_err2str(ret));
avcodec_parameters_free(&dst_par);
return ret;
}
// 使用复制的参数...
// 清理
avcodec_parameters_free(&dst_par);
return 0;
}
完整示例:复制视频流参数
int copy_video_stream_params(AVFormatContext *in_fmt_ctx,
AVFormatContext *out_fmt_ctx,
int stream_index) {
// 获取输入流参数
AVCodecParameters *in_par = in_fmt_ctx->streams[stream_index]->codecpar;
// 创建输出流
AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL);
if (!out_stream) {
return AVERROR(ENOMEM);
}
// 复制参数到输出流
int ret = avcodec_parameters_copy(out_stream->codecpar, in_par);
if (ret < 0) {
fprintf(stderr, "无法复制编解码器参数: %s\n", av_err2str(ret));
return ret;
}
// 可能需要调整某些参数
out_stream->codecpar->codec_tag = 0; // 让FFmpeg自动选择codec_tag
return 0;
}
批量复制参数
int copy_all_streams_params(AVFormatContext *in_ctx, AVFormatContext *out_ctx) {
for (int i = 0; i < in_ctx->nb_streams; i++) {
// 创建输出流
AVStream *out_stream = avformat_new_stream(out_ctx, NULL);
if (!out_stream) {
return AVERROR(ENOMEM);
}
// 复制编解码器参数
int ret = avcodec_parameters_copy(out_stream->codecpar,
in_ctx->streams[i]->codecpar);
if (ret < 0) {
fprintf(stderr, "复制流 %d 的参数失败: %s\n", i, av_err2str(ret));
return ret;
}
// 复制其他流信息
out_stream->time_base = in_ctx->streams[i]->time_base;
}
return 0;
}
内存管理细节
深度复制 extradata
// 演示avcodec_parameters_copy如何处理extradata
void demonstrate_extradata_copy() {
// 创建源参数
AVCodecParameters *src = avcodec_parameters_alloc();
src->extradata_size = 100;
src->extradata = av_malloc(src->extradata_size);
memset(src->extradata, 0xAA, src->extradata_size);
// 创建目标参数
AVCodecParameters *dst = avcodec_parameters_alloc();
// 复制
avcodec_parameters_copy(dst, src);
// 验证
printf("源extradata地址: %p\n", src->extradata);
printf("目标extradata地址: %p\n", dst->extradata);
printf("是否相同地址: %s\n", src->extradata == dst->extradata ? "是" : "否");
// 修改源extradata
src->extradata[0] = 0xBB;
printf("目标受影响: %s\n", dst->extradata[0] == 0xBB ? "是" : "否");
// 输出:否,因为复制是深复制
// 清理
avcodec_parameters_free(&src);
avcodec_parameters_free(&dst);
}
重复复制时的内存管理
int safe_copy_with_reuse(AVCodecParameters *dst, AVCodecParameters *src1,
AVCodecParameters *src2) {
// 第一次复制
int ret = avcodec_parameters_copy(dst, src1);
if (ret < 0) {
return ret;
}
// 第二次复制到同一个目标
ret = avcodec_parameters_copy(dst, src2);
if (ret < 0) {
return ret;
}
// 注意:第二次复制会自动释放第一次复制的extradata
// 然后分配新的extradata
return 0;
}
实际应用场景
场景1:流映射
typedef struct {
int src_stream_index;
int dst_stream_index;
} StreamMapping;
int map_and_copy_streams(AVFormatContext *in_ctx, AVFormatContext *out_ctx,
StreamMapping *mappings, int nb_mappings) {
for (int i = 0; i < nb_mappings; i++) {
int src_idx = mappings[i].src_stream_index;
int dst_idx = mappings[i].dst_stream_index;
if (src_idx >= in_ctx->nb_streams) {
return AVERROR(EINVAL);
}
// 创建输出流
AVStream *out_stream = avformat_new_stream(out_ctx, NULL);
if (!out_stream) {
return AVERROR(ENOMEM);
}
// 复制参数
int ret = avcodec_parameters_copy(out_stream->codecpar,
in_ctx->streams[src_idx]->codecpar);
if (ret < 0) {
fprintf(stderr, "无法复制流 %d 的参数\n", src_idx);
return ret;
}
// 记录映射关系
mappings[i].dst_stream_index = out_ctx->nb_streams - 1;
}
return 0;
}
场景2:参数修改
AVCodecParameters* create_modified_params(const AVCodecParameters *src,
int64_t new_bitrate,
int new_width, int new_height) {
// 分配新参数
AVCodecParameters *dst = avcodec_parameters_alloc();
if (!dst) {
return NULL;
}
// 复制原始参数
int ret = avcodec_parameters_copy(dst, src);
if (ret < 0) {
avcodec_parameters_free(&dst);
return NULL;
}
// 修改参数
if (new_bitrate > 0) {
dst->bit_rate = new_bitrate;
}
if (new_width > 0 && new_height > 0) {
dst->width = new_width;
dst->height = new_height;
}
return dst;
}
错误处理
检查返回值
int process_codec_params(const AVCodecParameters *src) {
// 分配目标参数
AVCodecParameters *dst = avcodec_parameters_alloc();
if (!dst) {
fprintf(stderr, "内存不足\n");
return AVERROR(ENOMEM);
}
// 复制参数
int ret = avcodec_parameters_copy(dst, src);
if (ret < 0) {
// 具体错误处理
switch (ret) {
case AVERROR(ENOMEM):
fprintf(stderr, "复制extradata时内存不足\n");
break;
case AVERROR(EINVAL):
fprintf(stderr, "无效参数\n");
break;
default:
fprintf(stderr, "未知错误: %s\n", av_err2str(ret));
break;
}
avcodec_parameters_free(&dst);
return ret;
}
// 使用复制的参数...
avcodec_parameters_free(&dst);
return 0;
}
空指针检查
int safe_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src) {
if (!dst || !src) {
fprintf(stderr, "参数指针为空\n");
return AVERROR(EINVAL);
}
// 确保dst是已分配的结构体
if (dst->codec_type == 0 && dst->codec_id == 0) {
// 看起来是未初始化的结构体,但可能只是巧合
// 最好在调用前确保dst是通过avcodec_parameters_alloc分配的
}
return avcodec_parameters_copy(dst, src);
}
与相关函数的对比
| 函数 | 深度复制 extradata | 需要目标已分配 | 是否创建新结构体 | 使用场景 |
|---|---|---|---|---|
avcodec_parameters_copy() |
是 | 是 | 否 | 复制参数到已分配的结构体 |
avcodec_parameters_dup() |
是 | 否 | 是 | 创建参数的副本(FFmpeg 4.0+) |
memcpy() |
否 | 是 | 否 | 浅复制,危险!不推荐 |
| 手动复制字段 | 可选 | 是 | 否 | 复杂,容易出错 |
注意事项
1. 必须预先分配目标
// 错误:目标未分配
AVCodecParameters *dst; // 未初始化
avcodec_parameters_copy(dst, src); // 崩溃!
// 正确
AVCodecParameters *dst = avcodec_parameters_alloc();
avcodec_parameters_copy(dst, src);
2. 避免自复制
// 危险:自复制
avcodec_parameters_copy(par, par); // 可能导致内存问题
// 安全:检查是否是同一个指针
if (dst != src) {
avcodec_parameters_copy(dst, src);
}
3. 处理嵌套调用
int complex_operation(AVCodecParameters *dst, AVCodecParameters *src) {
// 先保存原始状态
AVCodecParameters *temp = avcodec_parameters_alloc();
if (!temp) {
return AVERROR(ENOMEM);
}
// 复制到临时变量
int ret = avcodec_parameters_copy(temp, dst);
if (ret < 0) {
avcodec_parameters_free(&temp);
return ret;
}
// 修改目标
ret = avcodec_parameters_copy(dst, src);
if (ret < 0) {
// 恢复原始状态
avcodec_parameters_copy(dst, temp);
avcodec_parameters_free(&temp);
return ret;
}
avcodec_parameters_free(&temp);
return 0;
}
4. 线程安全考虑
// 注意:avcodec_parameters_copy本身是线程安全的
// 但需要确保在复制过程中源参数不被修改
// 不安全:多线程同时修改和复制
// 线程1: 修改src->extradata
// 线程2: avcodec_parameters_copy(dst, src) // 可能导致不一致
// 安全:使用锁保护
pthread_mutex_lock(&mutex);
int ret = avcodec_parameters_copy(dst, src);
pthread_mutex_unlock(&mutex);
性能优化
避免不必要的复制
// 检查是否需要复制
int copy_if_needed(AVCodecParameters *dst, const AVCodecParameters *src) {
// 快速检查:如果已经相同,跳过复制
if (are_parameters_equal(dst, src)) {
return 0; // 无需复制
}
return avcodec_parameters_copy(dst, src);
}
int are_parameters_equal(const AVCodecParameters *a, const AVCodecParameters *b) {
// 比较关键字段
if (a->codec_id != b->codec_id ||
a->width != b->width ||
a->height != b->height ||
a->format != b->format) {
return 0;
}
// 比较extradata
if (a->extradata_size != b->extradata_size) {
return 0;
}
if (memcmp(a->extradata, b->extradata, a->extradata_size) != 0) {
return 0;
}
return 1;
}
总结
avcodec_parameters_copy是 FFmpeg 中关键且安全的参数复制函数:
-
完全深度复制:包括 extradata 的独立内存分配
-
内存安全:自动管理目标结构体的原有内存
-
线程安全:函数本身是线程安全的
-
错误处理:提供详细的错误码
使用时需注意:
-
目标参数必须已分配
-
检查返回值处理错误
-
避免自复制
-
在多线程环境中注意源参数的同步
正确使用此函数可以避免常见的指针和内存管理错误,是 FFmpeg 编程中的基础但重要的操作。