简介: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.前言
本篇目的:理解
av_seek_frame()如何把目标时间交给FFmpeg解复用器定位,并通过读取定位后的数据包观察寻址结果。
读取媒体文件时,av_read_frame()会从当前解复用位置继续返回数据包。播放进度跳转、定位关键帧或从指定位置重新分析媒体,都需要改变这个读取位置。
av_seek_frame()负责请求解复用器跳转到目标时间附近。它不负责定位到任意精确画面,也不负责清空解码器状态。
🌻2.应用场景和用法
2.1按目标时间改变读取位置
av_seek_frame()常用于播放器拖动进度条、视频分析跳转到目标时间,以及解码器需要从关键帧重新开始的场景。
| 场景 | 调用方式 | 注意事项 |
|---|---|---|
| 跳转到视频时间点 | 选择视频流并传入流时间基的时间戳 | 返回位置可能落在目标时间之前的关键帧 |
| 跳转到默认流时间点 | stream_index传入-1 | 时间戳单位使用AV_TIME_BASE |
| 从目标位置向前找关键帧 | 设置AVSEEK_FLAG_BACKWARD | 具体位置受容器索引和Demuxer实现影响 |
| 按字节位置定位 | 设置AVSEEK_FLAG_BYTE | 格式必须支持字节寻址 |
| 定位到非关键帧 | 设置AVSEEK_FLAG_ANY | 是否支持取决于Demuxer |
成功寻址只表示解复用器接受了定位操作。视频解码器可能仍保留跳转前的参考帧和重排序状态,因此解码应用还要在成功跳转后调用avcodec_flush_buffers()。
2.2函数原型与时间戳单位
intav_seek_frame(AVFormatContext*s,intstream_index,int64_ttimestamp,intflags);| 参数 | 含义 |
|---|---|
s | 已打开输入文件并完成流信息分析的AVFormatContext |
stream_index | 时间戳参考流索引;传入-1时由FFmpeg选择默认流 |
timestamp | 目标时间戳;指定流时使用该流的time_base,未指定流时使用AV_TIME_BASE |
flags | 寻址方向和寻址模式标志 |
| 返回值 | 非负表示成功,负值表示寻址失败 |
指定视频流时,应先把目标秒数换算到对应AVStream.time_base。如果使用stream_index=-1,FFmpeg会选择默认流,并在内部把AV_TIME_BASE单位的时间戳换算到该流的时间基。
AVStream*stream=format_ctx->streams[video_stream_index];int64_ttarget_ts=av_rescale_q((int64_t)5*AV_TIME_BASE,AV_TIME_BASE_Q,stream->time_base);ret=av_seek_frame(format_ctx,video_stream_index,target_ts,AVSEEK_FLAG_BACKWARD);av_seek_frame()只改变解复用器读取位置。后续仍由av_read_frame()取出数据包;如果应用继续送包给解码器,还要清理待处理数据并刷新解码器状态。
🌻3.调用流程剖析
3.1目标时间进入Demuxer寻址分支
av_seek_frame()会根据当前输入格式是否实现read_seek2或旧式read_seek选择路径。
当输入格式实现read_seek2、但没有实现read_seek时,函数会转交avformat_seek_file(),并按AVSEEK_FLAG_BACKWARD构造寻址范围。其他情况进入seek_frame_internal()。
如果调用时stream_index为负数,内部会先选择默认流,再把AV_TIME_BASE单位的时间戳换算为该流的时间基。随后依次尝试格式专用的read_seek、基于时间戳读取的二分寻址,以及通用索引寻址。具体能走到哪一步,取决于Demuxer提供的能力。
寻址内部会清理解复用器的包队列和解析器状态。av_seek_frame()成功返回后,应用从新的位置继续调用av_read_frame()。如果下游存在解码器,解复用状态的刷新不等于解码器状态已经刷新。
3.2寻址结果与解码器状态分开处理
寻址成功后,FFmpeg可能还会把附带图片加入解复用队列。实际返回的数据包位置受索引、关键帧分布和容器寻址实现影响,因此调用成功不表示首个读取到的视频包时间戳必定等于目标值。
播放器常见处理顺序是:先请求解复用器寻址,再清理解码器状态,之后重新读取并解码数据包。若应用在跳转前还持有未消费的AVPacket,也要按自身的数据生命周期释放或复用它,避免把旧位置的数据继续送入解码器。
🌻4.实战案例
4.1准备输入文件和目标视频流
示例从命令行接收一个媒体文件,打开输入并分析流信息,再查找最佳视频流。目标设置在该流起点之后5秒,程序会把时间换算到视频流的time_base。
4.2调用接口并读取寻址后的数据包
程序调用av_seek_frame()并设置AVSEEK_FLAG_BACKWARD,请求定位到目标时间之前或附近的可用位置。寻址成功后,程序循环读取数据包,直到拿到目标视频流的数据包。
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_ttarget_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;/* 选择最佳视频流,后续时间戳以该流为参考 */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];/* 以视频流起始时间为基准,计算起点后5秒的目标时间戳 */stream_start=video_stream->start_time;if(stream_start==AV_NOPTS_VALUE)stream_start=0;target_ts=stream_start+av_rescale_q((int64_t)5*AV_TIME_BASE,AV_TIME_BASE_Q,video_stream->time_base);/* 请求定位到目标时间之前或附近的可用位置 */ret=av_seek_frame(format_ctx,video_stream_index,target_ts,AVSEEK_FLAG_BACKWARD);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;}这段程序验证寻址是否成功,以及后续读取是否能得到目标视频流的数据包。它不包含解码;如果接入解码流程,应在成功寻址后刷新对应的AVCodecContext。
🌻5.总结
av_seek_frame()根据指定流的时间基和Demuxer寻址能力改变解复用器读取位置,解码器状态仍由调用者单独维护。