简介: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筛选候选流并按优先级比较
- 3.2流选择与解码器创建的边界
- 🌻4.实战案例
- 4.1打开输入并准备流信息
- 4.2检查选择结果并读取对应包
- 4.3完整代码
- 🌻5.总结
🌻1.前言
本篇目的:理解
av_find_best_stream()如何按照媒体类型和流信息选择候选流,说明返回索引与可选解码器之间的关系,并用音频输入示例检查选择结果。
一个媒体文件可以包含多条视频、音频或字幕流。应用需要先选定目标流,才能创建对应的解码器上下文并处理该流的包。av_find_best_stream()负责选择流索引,并可选地返回匹配的解码器;它不打开解码器,也不读取后续媒体包。
🌻2.应用场景和用法
2.1按媒体类型选择目标流
应用可在打开输入并读取流信息后,按媒体类型选择一路默认音频、视频或字幕流。选出的流索引可用于定位AVStream,再读取其中的codecpar配置解码器。
| 使用场景 | 调用方式 | 结果 |
|---|---|---|
| 选择默认视频流 | type设为AVMEDIA_TYPE_VIDEO | 返回选中视频流的索引 |
| 选择默认音频流 | type设为AVMEDIA_TYPE_AUDIO | 返回选中音频流的索引 |
| 指定流索引 | wanted_stream_nb传入非负索引 | 只尝试该索引对应的流 |
| 优先选择同节目流 | 设置related_stream,并令wanted_stream_nb为-1 | 若关联节目存在,先在该节目范围内搜索 |
| 同时获取解码器 | decoder_ret传入有效指针 | 成功时一并返回可用的AVCodec |
2.2函数原型与返回语义
函数原型如下:
intav_find_best_stream(AVFormatContext*ic,enumAVMediaTypetype,intwanted_stream_nb,intrelated_stream,constAVCodec**decoder_ret,intflags);| 参数 | 含义 |
|---|---|
ic | 已打开并包含流信息的格式上下文 |
type | 要选择的媒体类型 |
wanted_stream_nb | 指定流索引;传入-1表示自动选择 |
related_stream | 用于优先搜索同一节目中相关流的索引;不指定时传入-1 |
decoder_ret | 可选的解码器输出指针;不需要函数检查解码器时可传入NULL |
flags | 当前没有定义可用标志,通常传入0 |
函数成功时返回非负流索引。没有匹配类型的流时返回AVERROR_STREAM_NOT_FOUND;找到流但要求解码器、且没有可用解码器时返回AVERROR_DECODER_NOT_FOUND。
decoder_ret为非空时,函数成功返回就保证该指针指向有效的AVCodec。它只返回解码器描述对象,不会分配AVCodecContext,也不会调用avcodec_open2()。
🌻3.调用流程剖析
3.1筛选候选流并按优先级比较
在FFmpeg6.1实现中,函数遍历上下文中的流;当指定related_stream且未指定固定流索引时,如果能找到对应节目,就先在该节目包含的流索引范围内搜索。
每个候选流先按codecpar->codec_type与请求类型匹配。指定了wanted_stream_nb时,其他索引会被跳过。音频候选还要求声道布局和采样率有效。
若decoder_ret非空,函数会查找候选流的默认解码器;找不到解码器的候选流不会被选中。其余候选流按流Disposition、已读取的编解码帧数和码率等信息比较,最终返回优先级最高的流索引。
3.2流选择与解码器创建的边界
典型调用顺序是先使用avformat_open_input()打开输入,再调用avformat_find_stream_info()读取流信息,然后使用av_find_best_stream()选流。
av_find_best_stream()本身不会调用av_read_frame()读取媒体包,也不会创建解码器上下文。应用需要用返回索引访问ic->streams[index],再将该流的codecpar复制到AVCodecContext并打开解码器。
如果decoder_ret传入NULL,函数不会因为缺少解码器而跳过候选流;如果传入非空指针,则解码器是否可用会参与候选筛选。指定固定流索引时,选择也不会自动改用其他流。
🌻4.实战案例
4.1打开输入并准备流信息
程序接收一个媒体文件路径,打开输入并读取流信息。它请求选择默认音频流,并让函数同时返回该流的默认解码器。
4.2检查选择结果并读取对应包
程序通过返回索引取得AVStream,打印流索引和解码器名称。随后调用av_read_frame()遍历输入,只统计stream_index等于选中索引的包,以确认流选择结果与实际解封装包相对应。
4.3完整代码
#include<libavcodec/avcodec.h>#include<libavformat/avformat.h>#include<libavutil/error.h>#include<stdio.h>intmain(intargc,char**argv){AVFormatContext*fmt_ctx=NULL;AVPacket*pkt=NULL;constAVCodec*decoder=NULL;AVStream*audio_stream;intaudio_index;intret;intread_ret;int64_taudio_packet_count=0;if(argc<2){fprintf(stderr,"Usage: %s input\n",argv[0]);return1;}/* 打开输入并读取流信息,供后续候选筛选使用 */ret=avformat_open_input(&fmt_ctx,argv[1],NULL,NULL);if(ret<0)gotoend;ret=avformat_find_stream_info(fmt_ctx,NULL);if(ret<0)gotoend;/* 自动选择音频流,并同时要求返回可用解码器 */audio_index=av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_AUDIO,-1,-1,&decoder,0);if(audio_index<0){ret=audio_index;gotoend;}/* 返回值是流索引,解码器仍未打开 */audio_stream=fmt_ctx->streams[audio_index];printf("audio stream index=%d codec=%s\n",audio_index,decoder->name);pkt=av_packet_alloc();if(pkt==NULL){ret=AVERROR(ENOMEM);gotoend;}/* 通过包上的stream_index验证被选中的流 */while((read_ret=av_read_frame(fmt_ctx,pkt))>=0){if(pkt->stream_index==audio_stream->index)audio_packet_count++;av_packet_unref(pkt);}if(read_ret==AVERROR_EOF){printf("selected audio packet count=%lld\n",(longlong)audio_packet_count);ret=0;}else{ret=read_ret;}end:av_packet_free(&pkt);avformat_close_input(&fmt_ctx);returnret<0?1:0;}🌻5.总结
av_find_best_stream()根据媒体类型和流信息选择流索引,并可选返回默认解码器,但不创建解码器上下文或读取媒体包。