简介:CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课🚀
专题四:Android15快速自定义与集成音效实战课🚀
专题五:Android15音频策略实战课🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例)🚀
人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
点击获取二维码
🍉🍉🍉文章目录🍉🍉🍉
- 🌻1.前言
- 🌻2.应用场景和用法
- 2.1创建输出轨道
- 2.2函数原型与所有权
- 🌻3.调用流程剖析
- 3.1扩展Stream数组并分配轨道对象
- 3.2调用者补充编码参数并交给Muxer
- 🌻4.实战案例
- 4.1准备Matroska输出上下文
- 4.2创建Stream并填充轨道信息
- 4.3完整代码
- 🌻5.总结
🌻1.前言
本篇目的:理解
avformat_new_stream()如何在AVFormatContext中创建并登记一个AVStream,并通过创建音视频输出流的示例检查流索引和参数对象。
复用或封装媒体时,输出容器需要知道文件中包含哪些音视频轨道。AVFormatContext负责管理整个媒体文件的格式和多个Stream;每条AVStream保存轨道级信息,codecpar保存该轨道的编解码参数。
avformat_new_stream()负责创建并登记AVStream,不会自动完成编码器配置,也不会写入容器文件。
🌻2.应用场景和用法
2.1创建输出轨道
创建输出文件时,应用先选择Muxer并获得AVFormatContext,再为每条音视频轨道调用avformat_new_stream()。
| 场景 | 调用方式 | 后续操作 |
|---|---|---|
| 封装音视频文件 | 每条输出轨道创建一个AVStream | 分别填充视频和音频的codecpar |
| 转码输出 | 为每个Encoder创建输出Stream | 将Encoder参数复制到对应的codecpar |
| Remux | 为输入的每条Stream创建输出Stream | 复制参数并建立输入输出Stream映射 |
| 解复用 | 由Demuxer内部创建Stream | 应用通过AVFormatContext->streams[]读取结果 |
解复用时,Demuxer会在读取文件头过程中创建Stream;某些无文件头格式也可能在读取Packet时创建。应用创建输出流时,通常在调用avformat_write_header()之前完成所有Stream登记。
2.2函数原型与所有权
函数原型如下:
AVStream*avformat_new_stream(AVFormatContext*s,constAVCodec*c);| 参数或返回值 | 含义 |
|---|---|
s | 已分配并选择输出格式的AVFormatContext |
c | 在FFmpeg6.1中未使用,不会自动绑定Encoder或填充编码参数 |
| 返回值 | 新建并登记的AVStream,失败时返回NULL |
函数创建的Stream由AVFormatContext管理。调用者不应单独释放AVStream;最终通过avformat_free_context()释放整个上下文及其Stream资源。
最小调用方式如下:
AVStream*stream;stream=avformat_new_stream(format_context,NULL);if(stream==NULL){/* 处理分配失败 */}创建成功后,调用者需要填充stream->codecpar中的媒体类型、Codec ID、图像或音频参数,并设置需要的stream->time_base。avformat_new_stream()不负责打开输出IO,也不写容器头。
🌻3.调用流程剖析
3.1扩展Stream数组并分配轨道对象
函数先检查AVFormatContext当前Stream数量是否达到max_streams。通过检查后,它扩展streams指针数组,再分配内部FFStream对象。
接着,函数分配公开的AVCodecParameters以及内部使用的AVCodecContext,并初始化Stream索引、时间戳状态和重排缓存等默认字段。全部初始化成功后,函数将新Stream写入AVFormatContext->streams[]并增加nb_streams。
任一步骤分配失败,函数会清理未完成的Stream并返回NULL。参数c在该版本中没有参与这些步骤。
3.2调用者补充编码参数并交给Muxer
新Stream创建后,codecpar已经分配,但具体Codec参数仍需调用者填写。转码场景通常从Encoder的AVCodecContext复制参数;Remux场景则从输入Stream的codecpar复制参数。
所有输出Stream准备完成后,调用者打开输出IO并调用avformat_write_header()。Muxer可能在写头阶段调整AVStream.time_base,因此Packet时间戳应按照写头之后实际的Stream时间基进行换算。
AVStream表示一条轨道的封装参数和时间信息,不是保存连续音视频帧的队列。实际媒体数据由AVPacket承载,之后通过写Packet接口提交给Muxer。
🌻4.实战案例
4.1准备Matroska输出上下文
案例创建一个Matroska输出上下文,然后添加一条视频Stream和一条音频Stream。示例只验证Stream对象创建和参数归属,不打开文件,也不写入媒体数据。
4.2创建Stream并填充轨道信息
每次成功调用都会返回一个由AVFormatContext管理的AVStream。第一条Stream的索引为0,第二条为1。视频尺寸和音频采样率分别写入各自的codecpar。
4.3完整代码
#include<libavformat/avformat.h>#include<libavutil/channel_layout.h>#include<stdio.h>intmain(void){AVFormatContext*format_context=NULL;AVStream*video_stream;AVStream*audio_stream;intret=1;/* 创建Matroska输出上下文 */if(avformat_alloc_output_context2(&format_context,NULL,"matroska","streams.mkv")<0||format_context==NULL){fprintf(stderr,"Could not allocate output context\n");gotodone;}/* 新建视频Stream,c参数在FFmpeg6.1中不负责配置Codec */video_stream=avformat_new_stream(format_context,NULL);if(video_stream==NULL){fprintf(stderr,"Could not create video stream\n");gotodone;}video_stream->id=0;video_stream->time_base=(AVRational){1,25};video_stream->codecpar->codec_type=AVMEDIA_TYPE_VIDEO;video_stream->codecpar->codec_id=AV_CODEC_ID_H264;video_stream->codecpar->width=1280;video_stream->codecpar->height=720;/* 再新建音频Stream,两个Stream由同一上下文统一管理 */audio_stream=avformat_new_stream(format_context,NULL);if(audio_stream==NULL){fprintf(stderr,"Could not create audio stream\n");gotodone;}audio_stream->id=1;audio_stream->time_base=(AVRational){1,48000};audio_stream->codecpar->codec_type=AVMEDIA_TYPE_AUDIO;audio_stream->codecpar->codec_id=AV_CODEC_ID_AAC;audio_stream->codecpar->sample_rate=48000;av_channel_layout_default(&audio_stream->codecpar->ch_layout,2);/* 验证返回对象已登记到上下文并获得连续索引 */if(format_context->nb_streams!=2||video_stream->index!=0||audio_stream->index!=1){fprintf(stderr,"Unexpected stream registration\n");gotodone;}ret=0;done:/* Stream由FormatContext持有,通过释放上下文统一清理 */avformat_free_context(format_context);returnret;}🌻5.总结
avformat_new_stream()就是在AVFormatContext中分配并登记一条AVStream,后续轨道参数仍由调用者填充。