ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Linux FFmpeg深度解析之av_seek_frame调用流程与实战(八)

Linux FFmpeg深度解析之av_seek_frame调用流程与实战(八)

简介:CSDN博客专家、《Android系统多媒体进阶实战》作者

博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课🚀
专题四:Android15快速自定义与集成音效实战课🚀
专题五:Android15音频策略实战课🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例)🚀

人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

更多原创,欢迎关注:Android系统攻城狮

点击获取二维码


🍉🍉🍉文章目录🍉🍉🍉

      • 🌻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()负责请求解复用器跳转到目标时间附近。它不负责定位到任意精确画面,也不负责清空解码器状态。

已打开的媒体上下文

目标流和目标时间

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提供的能力。

仅有read_seek2

其他情况

av_seek_frame请求

Demuxer寻址接口

转交avformat_seek_file

进入内部寻址

选择参考流并换算时间戳

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寻址能力改变解复用器读取位置,解码器状态仍由调用者单独维护。

返回列表