首页 / 资讯中心 / 文章详情

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

Linux FFmpeg深度解析之av_seek_frame调用流程与实战(八) ★ FEATURED ARTICLE
简介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已打开输入文件并完成流信息分析的AVFormatContextstream_index时间戳参考流索引传入-1时由FFmpeg选择默认流timestamp目标时间戳指定流时使用该流的time_base未指定流时使用AV_TIME_BASEflags寻址方向和寻址模式标志返回值非负表示成功负值表示寻址失败指定视频流时应先把目标秒数换算到对应AVStream.time_base。如果使用stream_index-1FFmpeg会选择默认流并在内部把AV_TIME_BASE单位的时间戳换算到该流的时间基。AVStream*streamformat_ctx-streams[video_stream_index];int64_ttarget_tsav_rescale_q((int64_t)5*AV_TIME_BASE,AV_TIME_BASE_Q,stream-time_base);retav_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完整代码#includelibavformat/avformat.h#includelibavutil/mathematics.h#includeinttypes.h#includestdio.hintmain(intargc,char**argv){AVFormatContext*format_ctxNULL;AVPacket*packetNULL;AVStream*video_stream;intvideo_stream_index;intret;intexit_code1;int64_tstream_start;int64_ttarget_ts;if(argc!2){fprintf(stderr,Usage: %s input-file\n,argv[0]);return1;}/* 打开媒体并读取流信息 */retavformat_open_input(format_ctx,argv[1],NULL,NULL);if(ret0)gotodone;retavformat_find_stream_info(format_ctx,NULL);if(ret0)gotodone;/* 选择最佳视频流后续时间戳以该流为参考 */video_stream_indexav_find_best_stream(format_ctx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);if(video_stream_index0)gotodone;video_streamformat_ctx-streams[video_stream_index];/* 以视频流起始时间为基准计算起点后5秒的目标时间戳 */stream_startvideo_stream-start_time;if(stream_startAV_NOPTS_VALUE)stream_start0;target_tsstream_startav_rescale_q((int64_t)5*AV_TIME_BASE,AV_TIME_BASE_Q,video_stream-time_base);/* 请求定位到目标时间之前或附近的可用位置 */retav_seek_frame(format_ctx,video_stream_index,target_ts,AVSEEK_FLAG_BACKWARD);if(ret0)gotodone;packetav_packet_alloc();if(packetNULL)gotodone;/* 读取寻址后的数据包筛选目标视频流 */while((retav_read_frame(format_ctx,packet))0){if(packet-stream_indexvideo_stream_index){printf(video packet pts%PRId64, dts%PRId64\n,packet-pts,packet-dts);av_packet_unref(packet);exit_code0;break;}av_packet_unref(packet);}done:/* 释放数据包和输入上下文 */av_packet_free(packet);avformat_close_input(format_ctx);returnexit_code;}这段程序验证寻址是否成功以及后续读取是否能得到目标视频流的数据包。它不包含解码如果接入解码流程应在成功寻址后刷新对应的AVCodecContext。5.总结av_seek_frame()根据指定流的时间基和Demuxer寻址能力改变解复用器读取位置解码器状态仍由调用者单独维护。
阅读完成 · 觉得有帮助?
咨询建站