简介: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校验范围并选择Demuxer接口
- 3.2寻址成功后继续读取和解码
- 🌻4.实战案例
- 4.1准备媒体流和合法时间范围
- 4.2按范围寻址并读取视频包
- 4.3完整代码
- 🌻5.总结
🌻1.前言
本篇目的:理解
avformat_seek_file()如何依据时间戳范围请求解复用器定位,并通过读取定位后的数据包观察边界参数的作用。
播放器和媒体分析程序常常不仅需要一个目标时间,还需要限定允许定位的时间范围。avformat_seek_file()通过min_ts、ts和max_ts表达这个范围,并尝试选择落在范围内、且适合活动流呈现的位置。
该函数负责请求解复用器寻址。它不保证所有Demuxer都能严格满足范围,也不会刷新调用者持有的解码器状态。
🌻2.应用场景和用法
2.1为寻址操作设置可接受范围
avformat_seek_file()适合播放进度跳转、同步多路媒体流,以及希望控制目标时间附近可接受位置的场景。
| 场景 | 参数组织 | 作用 |
|---|---|---|
| 跳转到目标时间附近 | min_ts和max_ts设置为较宽范围 | 允许Demuxer在区间内选择可用位置 |
| 限制最大跳转误差 | 缩小min_ts和max_ts间距 | 超出范围的位置不符合请求 |
| 多流同步 | 指定参考流或使用-1 | 为解复用器提供时间戳单位参考 |
| 允许非关键帧寻址 | 设置AVSEEK_FLAG_ANY | 仅在Demuxer支持时生效 |
| 网络或特殊容器定位 | 由输入格式实现read_seek2 | 让Demuxer根据完整范围执行定位 |
指定stream_index时,三个时间戳都使用该流的time_base。stream_index=-1时使用AV_TIME_BASE单位。调用前必须保证min_ts <= ts <= max_ts,否则函数会直接返回错误。
2.2函数原型与范围语义
intavformat_seek_file(AVFormatContext*s,intstream_index,int64_tmin_ts,int64_tts,int64_tmax_ts,intflags);| 参数 | 含义 |
|---|---|
s | 已打开并分析流信息的AVFormatContext |
stream_index | 时间基参考流;-1表示使用AV_TIME_BASE |
min_ts | 可接受的最小时间戳 |
ts | 期望定位的目标时间戳 |
max_ts | 可接受的最大时间戳 |
flags | 寻址模式;AVSEEK_FLAG_BACKWARD在该接口中会被忽略 |
| 返回值 | 非负表示成功,负值表示失败 |
例如,指定视频流后要定位到起点后5秒、允许范围为前后1秒,三个值都必须换算到视频流时间基。AVSEEK_FLAG_BACKWARD不是该接口控制方向的方式;范围由min_ts和max_ts表达。
AVStream*stream=format_ctx->streams[video_stream_index];int64_tstart_ts=stream->start_time==AV_NOPTS_VALUE?0:stream->start_time;int64_tmin_ts=start_ts+av_rescale_q((int64_t)4*AV_TIME_BASE,AV_TIME_BASE_Q,stream->time_base);int64_ttarget_ts=start_ts+av_rescale_q((int64_t)5*AV_TIME_BASE,AV_TIME_BASE_Q,stream->time_base);int64_tmax_ts=start_ts+av_rescale_q((int64_t)6*AV_TIME_BASE,AV_TIME_BASE_Q,stream->time_base);ret=avformat_seek_file(format_ctx,video_stream_index,min_ts,target_ts,max_ts,0);avformat_seek_file()修改解复用器的位置和读取状态,不创建新的AVFormatContext或AVStream。如果程序已经把旧位置的数据包送入解码器,成功寻址后仍要由应用刷新解码器。
🌻3.调用流程剖析
3.1校验范围并选择Demuxer接口
函数首先检查范围是否满足min_ts <= ts <= max_ts,再检查stream_index是否有效。若上下文启用了允许任意帧寻址的策略,函数会相应设置AVSEEK_FLAG_ANY;随后清除AVSEEK_FLAG_BACKWARD,因为方向由上下界表示。
当输入格式实现read_seek2时,FFmpeg会先刷新解复用器内部读取状态,再把流索引、时间范围和标志传给Demuxer。若使用stream_index=-1且输入只有一条流,代码会把AV_TIME_BASE单位的时间范围换算到该流的时间基。
Demuxer寻址成功后,FFmpeg还会处理附带图片数据。若格式没有read_seek2,该版本源码会走旧式寻址兼容路径,并根据目标与范围尝试av_seek_frame()及边界重试。因此,不能把API参数范围误解成所有输入格式都能完全兑现的保证。
3.2寻址成功后继续读取和解码
成功返回后,解复用器内部包队列和解析器状态已经按寻址流程刷新。应用接下来仍需调用av_read_frame()获取新位置的数据包。
如果应用使用解码器,解码器可能保留跳转前的参考帧和延迟帧。应在成功寻址后调用avcodec_flush_buffers(),并妥善处理仍由应用持有的旧AVPacket。这两个清理动作面向不同对象:Demuxer状态由寻址路径处理,解码器状态由调用者处理。
🌻4.实战案例
4.1准备媒体流和合法时间范围
示例从命令行接收媒体文件,打开文件并分析流信息,然后选择最佳视频流。代码要求文件至少覆盖视频流起点后的6秒,以便构造4至6秒的寻址窗口。
4.2按范围寻址并读取视频包
程序把起点后4秒、5秒和6秒分别换算到视频流的time_base,依次作为最小时间、目标时间和最大时间传入avformat_seek_file()。调用成功后,程序读取数据包并筛选视频流,观察寻址后的数据包时间戳。
4.3完整代码
#include<libavformat/avformat.h>#include<libavutil/mathematics.h>#include<inttypes.h>#include<stdio.h>intmain(intargc,char**argv){AVFormatContext*format_ctx=NULL;AVPacket*packet=NULL;AVStream*video_stream;intvideo_stream_index;intret;intexit_code=1;int64_tstream_start;int64_tmin_ts;int64_ttarget_ts;int64_tmax_ts;if(argc!=2){fprintf(stderr,"Usage: %s input-file\n",argv[0]);return1;}/* 打开媒体并读取流信息 */ret=avformat_open_input(&format_ctx,argv[1],NULL,NULL);if(ret<0)gotodone;ret=avformat_find_stream_info(format_ctx,NULL);if(ret<0)gotodone;/* 选择视频流,时间范围以该流的time_base表示 */video_stream_index=av_find_best_stream(format_ctx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);if(video_stream_index<0)gotodone;video_stream=format_ctx->streams[video_stream_index];stream_start=video_stream->start_time;if(stream_start==AV_NOPTS_VALUE)stream_start=0;/* 构造起点后4到6秒的范围,目标时间为起点后5秒 */min_ts=stream_start+av_rescale_q((int64_t)4*AV_TIME_BASE,AV_TIME_BASE_Q,video_stream->time_base);target_ts=stream_start+av_rescale_q((int64_t)5*AV_TIME_BASE,AV_TIME_BASE_Q,video_stream->time_base);max_ts=stream_start+av_rescale_q((int64_t)6*AV_TIME_BASE,AV_TIME_BASE_Q,video_stream->time_base);/* 让Demuxer在给定范围内选择可用位置 */ret=avformat_seek_file(format_ctx,video_stream_index,min_ts,target_ts,max_ts,0);if(ret<0)gotodone;packet=av_packet_alloc();if(packet==NULL)gotodone;/* 读取寻址后的数据包,只观察目标视频流 */while((ret=av_read_frame(format_ctx,packet))>=0){if(packet->stream_index==video_stream_index){printf("video packet pts=%"PRId64", dts=%"PRId64"\n",packet->pts,packet->dts);av_packet_unref(packet);exit_code=0;break;}av_packet_unref(packet);}done:/* 释放应用持有的数据包和解复用上下文 */av_packet_free(&packet);avformat_close_input(&format_ctx);returnexit_code;}这段程序验证时间单位换算、范围参数传递和寻址后读取数据包的路径。能否在给定区间内找到合适位置,仍取决于容器格式和Demuxer能力。
🌻5.总结
avformat_seek_file()把最小时间、目标时间和最大时间交给Demuxer寻址,并由调用者继续处理解码器状态。