函数原型
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的核心函数,用于创建输出流。使用时需要注意:
-
必须设置时间基:这是最容易被忽略但最重要的参数
-
参数完整性:根据流类型设置所有必要的编解码器参数
-
内存管理:流由AVFormatContext管理,不要单独释放
-
错误处理:总是检查返回值,处理内存不足等错误
-
参数验证:写入文件头前确保所有参数有效
正确使用此函数是构建多媒体文件或流媒体的第一步,后续需要通过 avformat_write_header()写入文件头,然后写入数据包,最后用 av_write_trailer()完成文件。