avformat_new_stream 详细解析

函数原型

复制代码
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

功能概述

avformat_new_stream用于在 AVFormatContext 中创建一个新的媒体流。这是复用(muxing)过程中的关键函数,用于构建输出文件的流结构。

参数详解

1. *AVFormatContext s

  • 指向格式上下文的指针

  • 必须是通过 avformat_alloc_output_context2()创建的输出上下文

  • 不能是输入上下文

2. *const AVCodec c

  • 指向编解码器的指针

  • 可以为 NULL

  • 如果提供,会用编解码器的默认参数初始化流

  • 通常设置为 NULL,后续手动设置参数

返回值

  • 成功:返回指向新创建的 AVStream 的指针

  • 失败:返回 NULL(通常是内存不足)

内部工作原理

1. 内存分配

复制代码
// 简化版内部实现
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c) {
    // 分配AVStream结构
    AVStream *st = av_mallocz(sizeof(AVStream));
    if (!st) return NULL;
    
    // 设置流索引
    st->index = s->nb_streams;
    st->id = s->nb_streams;  // 可以后续修改
    
    // 分配编解码器参数
    st->codecpar = avcodec_parameters_alloc();
    if (!st->codecpar) {
        av_free(st);
        return NULL;
    }
    
    // 如果提供了编解码器,初始化参数
    if (c) {
        st->codecpar->codec_type = c->type;
        st->codecpar->codec_id = c->id;
    }
    
    // 添加到格式上下文的流数组
    AVStream **streams = av_realloc_array(s->streams, s->nb_streams + 1, sizeof(*s->streams));
    if (!streams) {
        avcodec_parameters_free(&st->codecpar);
        av_free(st);
        return NULL;
    }
    s->streams = streams;
    s->streams[s->nb_streams++] = st;
    
    return st;
}

2. 初始化的默认值

复制代码
// 创建后的默认状态
AVStream *st = avformat_new_stream(fmt_ctx, NULL);
// 以下字段会自动初始化:

st->index = 0;                    // 第一个流索引为0
st->id = 0;                      // 初始ID
st->time_base = {0, 1};          // 无效时间基,需要手动设置
st->avg_frame_rate = {0, 0};     // 初始帧率
st->r_frame_rate = {0, 0};       // 初始帧率
st->start_time = AV_NOPTS_VALUE; // 无起始时间
st->duration = AV_NOPTS_VALUE;   // 无持续时间
st->nb_frames = 0;               // 帧数为0
st->disposition = 0;             // 无特殊标志
st->discard = AVDISCARD_NONE;    // 不丢弃
st->metadata = NULL;             // 无元数据

使用示例

示例1:基本使用 - 创建输出流

复制代码
AVFormatContext *output_ctx = NULL;
AVStream *video_stream = NULL;
AVStream *audio_stream = NULL;

// 创建输出格式上下文
avformat_alloc_output_context2(&output_ctx, NULL, "mp4", "output.mp4");

// 创建视频流
video_stream = avformat_new_stream(output_ctx, NULL);
if (!video_stream) {
    fprintf(stderr, "创建视频流失败\n");
    return AVERROR(ENOMEM);
}

// 设置视频流参数
video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
video_stream->codecpar->codec_id = AV_CODEC_ID_H264;
video_stream->codecpar->width = 1920;
video_stream->codecpar->height = 1080;
video_stream->codecpar->format = AV_PIX_FMT_YUV420P;
video_stream->codecpar->bit_rate = 4000000;  // 4Mbps
video_stream->time_base = (AVRational){1, 25};  // 25fps
video_stream->avg_frame_rate = (AVRational){25, 1};
video_stream->r_frame_rate = (AVRational){25, 1};
video_stream->id = 1;  // 设置流ID

// 创建音频流
audio_stream = avformat_new_stream(output_ctx, NULL);
if (!audio_stream) {
    fprintf(stderr, "创建音频流失败\n");
    return AVERROR(ENOMEM);
}

// 设置音频流参数
audio_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
audio_stream->codecpar->codec_id = AV_CODEC_ID_AAC;
audio_stream->codecpar->sample_rate = 44100;
audio_stream->codecpar->channels = 2;
audio_stream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
audio_stream->codecpar->format = AV_SAMPLE_FMT_FLTP;
audio_stream->codecpar->bit_rate = 128000;  // 128kbps
audio_stream->time_base = (AVRational){1, 44100};
audio_stream->id = 2;

示例2:转封装 - 复制输入流

复制代码
int remux_streams(const char *input_file, const char *output_file) {
    AVFormatContext *input_ctx = NULL, *output_ctx = NULL;
    int ret;
    
    // 打开输入文件
    if ((ret = avformat_open_input(&input_ctx, input_file, NULL, NULL)) < 0) {
        return ret;
    }
    
    if ((ret = avformat_find_stream_info(input_ctx, NULL)) < 0) {
        avformat_close_input(&input_ctx);
        return ret;
    }
    
    // 创建输出上下文
    avformat_alloc_output_context2(&output_ctx, NULL, NULL, output_file);
    if (!output_ctx) {
        avformat_close_input(&input_ctx);
        return AVERROR(ENOMEM);
    }
    
    // 遍历所有输入流
    for (int i = 0; i < input_ctx->nb_streams; i++) {
        AVStream *in_stream = input_ctx->streams[i];
        
        // 只处理视频和音频流
        if (in_stream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
            in_stream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
            continue;
        }
        
        // 创建输出流
        AVStream *out_stream = avformat_new_stream(output_ctx, NULL);
        if (!out_stream) {
            ret = AVERROR(ENOMEM);
            goto cleanup;
        }
        
        // 复制编解码器参数
        ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
        if (ret < 0) {
            goto cleanup;
        }
        
        // 复制时间基
        out_stream->time_base = in_stream->time_base;
        
        // 复制显示宽高比
        out_stream->sample_aspect_ratio = in_stream->sample_aspect_ratio;
        
        // 复制元数据
        if (in_stream->metadata) {
            out_stream->metadata = av_dict_copy(in_stream->metadata, NULL, 0);
        }
        
        // 如果输入流有编码器上下文,复制一些额外参数
        if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            // 确保宽高比有效
            if (out_stream->codecpar->sample_aspect_ratio.num == 0) {
                out_stream->codecpar->sample_aspect_ratio = (AVRational){1, 1};
            }
        }
        
        printf("复制流 #%d: %s\n", i, 
               av_get_media_type_string(out_stream->codecpar->codec_type));
    }
    
    // ... 写入文件头和数据包
    
cleanup:
    if (input_ctx) avformat_close_input(&input_ctx);
    if (output_ctx) avformat_free_context(output_ctx);
    return ret;
}

示例3:创建字幕流

复制代码
AVStream *create_subtitle_stream(AVFormatContext *fmt_ctx, 
                                 const char *language) {
    AVStream *sub_stream = avformat_new_stream(fmt_ctx, NULL);
    if (!sub_stream) {
        return NULL;
    }
    
    // 设置字幕流参数
    sub_stream->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
    sub_stream->codecpar->codec_id = AV_CODEC_ID_SUBRIP;  // SRT格式
    
    // 设置时间基
    sub_stream->time_base = (AVRational){1, 1000};  // 毫秒精度
    
    // 设置语言元数据
    if (language) {
        av_dict_set(&sub_stream->metadata, "language", language, 0);
    }
    
    // 设置为默认字幕
    sub_stream->disposition |= AV_DISPOSITION_DEFAULT;
    
    return sub_stream;
}

高级用法

1. 使用编解码器初始化

复制代码
// 使用编解码器自动初始化部分参数
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
    fprintf(stderr, "找不到H.264编码器\n");
    return AVERROR_ENCODER_NOT_FOUND;
}

AVStream *stream = avformat_new_stream(fmt_ctx, codec);
if (!stream) {
    return AVERROR(ENOMEM);
}

// 此时流已自动设置codec_type和codec_id
printf("流类型: %s\n", av_get_media_type_string(stream->codecpar->codec_type));
printf("编解码器ID: %s\n", avcodec_get_name(stream->codecpar->codec_id));

// 仍需设置其他参数
stream->codecpar->width = 1920;
stream->codecpar->height = 1080;
// ...

2. 创建数据流

复制代码
AVStream *create_data_stream(AVFormatContext *fmt_ctx, 
                            const char *mime_type) {
    AVStream *data_stream = avformat_new_stream(fmt_ctx, NULL);
    if (!data_stream) {
        return NULL;
    }
    
    data_stream->codecpar->codec_type = AVMEDIA_TYPE_DATA;
    data_stream->codecpar->codec_id = AV_CODEC_ID_NONE;
    
    // 设置MIME类型
    if (mime_type) {
        av_dict_set(&data_stream->metadata, "mimetype", mime_type, 0);
    }
    
    // 设置时间基
    data_stream->time_base = (AVRational){1, 1000};
    
    return data_stream;
}

3. 批量创建多个流

复制代码
typedef struct {
    enum AVMediaType type;
    int width;
    int height;
    int bitrate;
    AVRational frame_rate;
} StreamConfig;

int create_multiple_streams(AVFormatContext *fmt_ctx, 
                            StreamConfig *configs, 
                            int num_configs) {
    AVStream **streams = av_malloc_array(num_configs, sizeof(AVStream*));
    if (!streams) {
        return AVERROR(ENOMEM);
    }
    
    for (int i = 0; i < num_configs; i++) {
        StreamConfig *cfg = &configs[i];
        AVStream *st = avformat_new_stream(fmt_ctx, NULL);
        if (!st) {
            // 清理已创建的流
            for (int j = 0; j < i; j++) {
                // 注意:通常不需要单独释放,会随fmt_ctx一起释放
            }
            av_free(streams);
            return AVERROR(ENOMEM);
        }
        
        streams[i] = st;
        
        // 设置基本参数
        st->codecpar->codec_type = cfg->type;
        st->id = i + 1;  // 设置唯一ID
        
        if (cfg->type == AVMEDIA_TYPE_VIDEO) {
            st->codecpar->codec_id = AV_CODEC_ID_H264;
            st->codecpar->width = cfg->width;
            st->codecpar->height = cfg->height;
            st->codecpar->bit_rate = cfg->bitrate;
            st->codecpar->format = AV_PIX_FMT_YUV420P;
            st->time_base = (AVRational){cfg->frame_rate.den, cfg->frame_rate.num};
            st->avg_frame_rate = cfg->frame_rate;
        } else if (cfg->type == AVMEDIA_TYPE_AUDIO) {
            st->codecpar->codec_id = AV_CODEC_ID_AAC;
            st->codecpar->sample_rate = 44100;
            st->codecpar->channels = 2;
            st->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
            st->codecpar->bit_rate = cfg->bitrate;
            st->time_base = (AVRational){1, 44100};
        }
    }
    
    av_free(streams);
    return 0;
}

重要注意事项

1. 必须设置时间基

复制代码
// 错误:不设置时间基会导致写入失败
AVStream *stream = avformat_new_stream(fmt_ctx, NULL);
// 忘记设置 time_base

// 正确:必须设置时间基
stream->time_base = (AVRational){1, 25};  // 视频通常用帧率倒数
// 或
stream->time_base = (AVRational){1, 44100};  // 音频通常用采样率

2. 流索引和ID

复制代码
// 流索引自动分配,从0开始递增
for (int i = 0; i < 3; i++) {
    AVStream *stream = avformat_new_stream(fmt_ctx, NULL);
    printf("流索引: %d, ID: %d\n", stream->index, stream->id);
    // 输出: 0,0 / 1,1 / 2,2
}

// 可以修改ID
stream->id = 0x100;  // 自定义流ID

3. 编解码器参数必须完整

复制代码
// 视频流必须设置的基本参数
video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
video_stream->codecpar->codec_id = AV_CODEC_ID_H264;
video_stream->codecpar->width = 1920;
video_stream->codecpar->height = 1080;
video_stream->codecpar->format = AV_PIX_FMT_YUV420P;
video_stream->time_base = (AVRational){1, 25};

// 音频流必须设置的基本参数
audio_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
audio_stream->codecpar->codec_id = AV_CODEC_ID_AAC;
audio_stream->codecpar->sample_rate = 44100;
audio_stream->codecpar->channels = 2;
audio_stream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
audio_stream->codecpar->format = AV_SAMPLE_FMT_FLTP;
audio_stream->time_base = (AVRational){1, 44100};

4. 内存管理

复制代码
// 错误:手动释放AVStream
AVStream *stream = avformat_new_stream(fmt_ctx, NULL);
// ... 使用stream
av_free(stream);  // 错误!应该由fmt_ctx管理

// 正确:通过释放AVFormatContext来释放所有流
avformat_free_context(fmt_ctx);  // 自动释放所有流

常见错误处理

复制代码
AVStream *create_stream_safe(AVFormatContext *fmt_ctx) {
    AVStream *stream = avformat_new_stream(fmt_ctx, NULL);
    if (!stream) {
        fprintf(stderr, "无法创建流: 内存不足\n");
        return NULL;
    }
    
    // 检查编解码器参数是否分配成功
    if (!stream->codecpar) {
        fprintf(stderr, "编解码器参数分配失败\n");
        // 注意:这里不能直接释放stream,因为它已添加到fmt_ctx
        return NULL;
    }
    
    return stream;
}

int add_stream_with_fallback(AVFormatContext *fmt_ctx, 
                            enum AVCodecID codec_id) {
    const AVCodec *codec = avcodec_find_encoder(codec_id);
    AVStream *stream = NULL;
    
    if (codec) {
        // 尝试用编解码器创建
        stream = avformat_new_stream(fmt_ctx, codec);
    }
    
    if (!stream) {
        // 回退:创建空流
        stream = avformat_new_stream(fmt_ctx, NULL);
        if (!stream) {
            return AVERROR(ENOMEM);
        }
        stream->codecpar->codec_id = codec_id;
    }
    
    return 0;
}

实际应用场景

1. 视频录制应用

复制代码
typedef struct {
    AVFormatContext *fmt_ctx;
    AVStream *video_stream;
    AVStream *audio_stream;
    int64_t video_frame_count;
    int64_t audio_sample_count;
} RecorderContext;

int init_recorder(RecorderContext *ctx, const char *filename) {
    int ret;
    
    // 创建输出格式
    ret = avformat_alloc_output_context2(&ctx->fmt_ctx, NULL, NULL, filename);
    if (ret < 0) return ret;
    
    // 创建视频流
    ctx->video_stream = avformat_new_stream(ctx->fmt_ctx, NULL);
    if (!ctx->video_stream) return AVERROR(ENOMEM);
    
    ctx->video_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    ctx->video_stream->codecpar->codec_id = AV_CODEC_ID_H264;
    ctx->video_stream->codecpar->width = 1280;
    ctx->video_stream->codecpar->height = 720;
    ctx->video_stream->codecpar->format = AV_PIX_FMT_YUV420P;
    ctx->video_stream->time_base = (AVRational){1, 30};
    ctx->video_stream->avg_frame_rate = (AVRational){30, 1};
    
    // 创建音频流
    ctx->audio_stream = avformat_new_stream(ctx->fmt_ctx, NULL);
    if (!ctx->audio_stream) return AVERROR(ENOMEM);
    
    ctx->audio_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
    ctx->audio_stream->codecpar->codec_id = AV_CODEC_ID_AAC;
    ctx->audio_stream->codecpar->sample_rate = 48000;
    ctx->audio_stream->codecpar->channels = 2;
    ctx->audio_stream->codecpar->channel_layout = AV_CH_LAYOUT_STEREO;
    ctx->audio_stream->time_base = (AVRational){1, 48000};
    
    return 0;
}

2. 流媒体服务器

复制代码
AVStream *create_hls_stream(AVFormatContext *fmt_ctx, 
                           enum AVCodecID codec_id,
                           int bitrate) {
    AVStream *stream = avformat_new_stream(fmt_ctx, NULL);
    if (!stream) return NULL;
    
    // 设置HLS特定参数
    av_dict_set(&stream->metadata, "hls_group_id", "main", 0);
    av_dict_set(&stream->metadata, "hls_name", "EN", 0);
    
    // 设置编解码器参数
    stream->codecpar->codec_id = codec_id;
    
    if (codec_id == AV_CODEC_ID_H264) {
        stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
        stream->codecpar->width = 1280;
        stream->codecpar->height = 720;
        stream->codecpar->bit_rate = bitrate;
        stream->time_base = (AVRational){1, 90000};  // MPEG-TS时间基
    } else if (codec_id == AV_CODEC_ID_AAC) {
        stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
        stream->codecpar->sample_rate = 44100;
        stream->codecpar->channels = 2;
        stream->codecpar->bit_rate = 128000;
        stream->time_base = (AVRational){1, 44100};
    }
    
    return stream;
}

总结

avformat_new_stream是FFmpeg复用(muxing)API的核心函数,用于创建输出流。使用时需要注意:

  1. 必须设置时间基:这是最容易被忽略但最重要的参数

  2. 参数完整性:根据流类型设置所有必要的编解码器参数

  3. 内存管理:流由AVFormatContext管理,不要单独释放

  4. 错误处理:总是检查返回值,处理内存不足等错误

  5. 参数验证:写入文件头前确保所有参数有效

正确使用此函数是构建多媒体文件或流媒体的第一步,后续需要通过 avformat_write_header()写入文件头,然后写入数据包,最后用 av_write_trailer()完成文件。

相关推荐
fie88893 小时前
基于Matlab实现的指纹识别系统流程
opencv·计算机视觉·matlab
sali-tec18 小时前
C# 基于OpenCv的视觉工作流-章26-图像拼接
图像处理·人工智能·opencv·算法·计算机视觉
xinxiangwangzhi_18 小时前
立体匹配--Cross-Scale Cost Aggregation for Stereo Matching
图像处理·计算机视觉
沃达德软件19 小时前
模糊图像复原技术解析
图像处理·人工智能·深度学习·目标检测·机器学习·计算机视觉·目标跟踪
CoovallyAIHub19 小时前
模糊、噪声、压缩……让检测器学会主动评估画质
深度学习·算法·计算机视觉
智驱力人工智能20 小时前
地铁隧道轨道障碍物实时检测方案 守护城市地下动脉的工程实践 轨道障碍物检测 高铁站区轨道障碍物AI预警 铁路轨道异物识别系统价格
人工智能·算法·yolo·目标检测·计算机视觉·边缘计算
仙女修炼史1 天前
Making Convolutional Networks Shift-Invariant Again
人工智能·深度学习·计算机视觉
Σίσυφος19001 天前
OpenCV 之双线性插值
人工智能·opencv·计算机视觉
咚咚王者1 天前
人工智能之视觉领域 计算机视觉 第十五章 简单物体识别
人工智能·计算机视觉