read_thread解复用线程分析

news2024/11/16 19:44:10

read_thread() 线程的主要作用从 MP4 里面读取 AVPacket,然后丢进去 PacketQueue 队列。所以需要先学习一下 strcut PacketQueue 跟 struct MyAVPacketList 数据结构。如下:

typedef struct MyAVPacketList {
    AVPacket *pkt;
    int serial;
} MyAVPacketList;
typedef struct PacketQueue {
    AVFifoBuffer *pkt_list; //存储的是 MyAVPacketList
    int nb_packets;
    int size;
    int64_t duration;
    int abort_request;
    int serial;
    SDL_mutex *mutex;
    SDL_cond *cond;
} PacketQueue;

1,AVFifoBuffer *pkt_list ,AVFifoBuffer 是一个 circular buffer FIFO,一个环形的先进先出的缓存实现。里面存储的是 struct MyAVPacketList 结构的数据。

2,int nb_packets;,代表队列里面有多少个 AVPacket

3,int size; ,队列缓存的数据大小 ,算法是所有的 AVPacket 本身的大小加上 AVPacket->size 。

4,int64_t duration,队列的时长,通过累加 队列里所有的 AVPacket->duration 得到。

5,abort_request,代表队列终止请求,变成 1 会导致 audio_thread 跟 video_thread 退出。

6,int serial,队列的序号,每次跳转播放时间点 ,serial 就会 +1。另一个数据结构 MyAVPacketList 里面也有一个 serial 字段。

两个 serial 通过比较匹配来丢弃无效的缓存帧,什么情况会导致队列的缓存帧无效?跳转播放时间点的时候。

例如此时此刻,PacketQueue 队列里面缓存了 8 个帧,但是这 8 个帧都 第30分钟 才开始播放的,如果你通过 ➔ 按键前进到 第35分钟 的位置播放,那队列的 8 个缓存帧就无效了,需要丢弃。

由于每次跳转播放时间点, PacketQueue::serial 都会 +1 ,而 MyAVPacketList::serial 的值还是原来的,两个 serial 不一样,就会丢弃帧。

7,SDL_mutex *mutex ,SDL 互斥锁,主要用于修改队列的时候加锁。

8,SDL_cond *cond,SDL 条件变量,用于 read_thread() 线程 跟 audio_thread() ,video_thread() 线程 进行通信的。


在 ffplay -i juren-5s.mp4 的场景下,read_thread 线程的流程图如下:

 

 read_thread() 线程里面的逻辑相对比较复杂,重点也挺多。首先讲解一下 st_index[] 这个数组变量的含义,如下:

st_index[] 这个数组用的宏是 AVMEDIA_TYPE_NB,也就是这个数组涵盖了各种数据流,音频,视频,字幕,附件流等等。因为一个MP4里面可能会有多个视频流。

例如 第 5,第 6 个流都是视频流。这时候 st_index[AVMEDIA_TYPE_VIDEO] 保存的可能就是 5 或者 6 ,代表要播放哪个视频流,其他数据流类推。

默认 st_index[] 数组的值是通过 av_find_best_stream() 确定的,是通过 bit_rate 最大比特率,codec_info_nb_frames 等参数找出 最好的那个音频流 或者 视频流。


第二个重点是 interrupt_callback 这个操作,指定了中断回调函数。

 

decode_interrupt_cb() 函数实现如下:

static int decode_interrupt_cb(void *ctx)
{
    VideoState *is = ctx;
    return is->abort_request;
}

首先,is->abort_request 这个变量控制着整个播放器要不要停止播放,然后退出。

在播放本地文件的时候,interrupt_callback 回调函数的作用不是特别明显,因为本地读取MP4, av_read_frame() 会非常快返回。

但是如果在播放网络流的时候,网络卡顿,av_read_frame() 可能要 8 秒才能返回,这时候如果想关闭播放器,就需要 av_read_frame() 尽快地返回,不要再阻塞了。这时候,就需要 interrupt_callback 了,因为在 8 秒 内,av_read_frame() 内部也会定时执行 interrupt_callback(),只要 interrupt_callback() 返回 1,av_read_frame() 就会不再阻塞,立即返回。

提醒:播放网络流的时候,avformat_find_stream_info() 可能会跟 av_read_frame() 一样阻塞很久。


read_thread() 线程的第三个重点是 avformat_open_input() 函数的使用,在《FFmpeg打开输入文件》一文中,已经讲过这个函数的使用了,但是没有讲最后一个参数的用法。

 

最后的参数 format_opts 是一个 AVDictionary (字典)。注意,如果 avformat_open_input 函数内部使用了字典的某个选项,就会把这个选项从字典剔除

所以可以看到,后面判断了还有哪些 option 没使用,这些无法使用的 option (选项),通常是因为命令行参数写错了。

MP4,FLV,TS,等等容器格式,都有一些相同的 option,也有一些不同的 options。具体可以通过以下命令查看容器支持哪些 option ?

ffmpeg -h demuxer=mp4

提示:各种流媒体格式 也可以看成是 容器。


read_thread() 里面会处理 seek 操作,但是本文是讲解 ffplay -i juren-5s.mp4 简单场景下的逻辑的。

简单场景下,不会跑进去 seek 条件。 seek 操作可以后面再看这篇文章《FFplay跳转时间点播放》


read_thread() 线程的第四个重点是 AVRational sar 变量的应用,如下:

 

sar 这个值是不太容易理解的,我刚开始也被这个 sar 搞懵。我之前以为 sar 等于 width/height (宽高比) ,后来发现不是宽高比。

其实 sar 是以前的显示设备设计的历史遗留问题,不用过多关注,只需要知道,显示的时候用 sar 这个比例拉伸 width 跟 height 作为显示窗口,图像播放就不会扭曲了。sar 在大部分情况都是 1:1

推荐阅读,ffmpeg解析出的视频参数PAR,DAR,SAR的意义 跟 theory-videoaspectratios


接下来来到 read_thread() 线程里最重要的重点,stream_component_open() 函数的调用,audio_thread()video_thread() 等解码线程就是从 stream_component_open() 里 创建出来的。推荐阅读《stream_component_open函数分析》


上面所有代码干的活,主要是找出最好的音视频流,设置回调,各种初始化,打开容器实例。

现在到了 read_thread() 线程的主要任务,那就是进入 for (;;) {...} 死循环不断 从 容器实例 读取 AVPacket ,然后丢进去对应的 PacketQueue 队列

for 循环里面也有一些重点,如下:

对于播放本地文件,av_read_pause() 函数其实是没有作用的。av_read_pause() 只对网络流播放有效,有些流媒体协议支持暂停操作,暂停了,服务器就不会再往 ffplay 推送数据,如果想重新推数据,需要调用 av_read_play()


for 循环里面的第二个重点是 判断 队列缓存中的 AVPacket 是否够用,够用就会休眠 10ms。如下:

在播放本地文件的时候,infinite_buffer 总是 0,所以不用管它。

可以看到,判断 AVPacket 是否够用,就是根据 size 来判断,还有 stream_has_enough_packets() 函数,实现如下:

static int stream_has_enough_packets(AVStream *st, int stream_id, PacketQueue *queue) {
    return stream_id < 0 ||
           queue->abort_request ||
           (st->disposition & AV_DISPOSITION_ATTACHED_PIC) ||
           queue->nb_packets > MIN_FRAMES && (!queue->duration || av_q2d(st->time_base) * queue->duration > 1.0);
}

stream_has_enough_packets() 主要就是确认 队列至少有 MIN_FRAMES 个帧,而且所有帧的播放时长加起来大于 1 秒钟。


当 队列缓存中的 AVPacket 未满的时候,就会直接去读磁盘数据,把 AVPacket 读出来,但是也不是读出来就会立即丢进去 PacketQueue 队列,而是会判断一下AVPacket 是否在期待的播放时间范围内。如下: 

可以看到 定义了 一个 变量 pkt_in_play_range 来确定是否在播放时间范围内。播放时间范围这个概念是这样的。如果下面这样播放一个视频:

ffplay -i juren-5s.mp4

因为 juren-5s.mp4 是一个 5 秒的视频,而且命令行没有指定 -t,所以这时候 播放时间范围 就是 0 ~ 5 秒。只要读出来的 AVPacket 的 pts 在 0 ~ 5秒范围内,pkt_in_play_range 变量就为真。因此所有读出来的 AVPacket 都是符合播放时间范围的。

但是如果加了 -t 参数,如下:

ffplay -t 2 -i juren-5s.mp4

上面的的命令是 只播放 2秒视频,也就是 播放时间范围 变成了 0 ~ 2 秒,如果读出来的 AVPacket 的 pts 大于 2 秒,就会被丢弃。

这里就有一个有趣的事情,当视频播放到 第二秒的时候,虽然画面停止了,但是 read_thread() 还是会一直读数据,但由于不符合播放时间范围,会一直丢弃。直到读到文件结尾,返回 AVERROR_EOF 才会停下来休眠一小段时间。


读出来的 AVPacket 符合播放时间之后,就会 用 packet_queue_put() 丢进去 PacketQueue 队列。

可以看到,音频,视频流,是有各自的 PacketQueue 队列的,is->audioq 跟 is->videoq


FFplay 播放器的逻辑流转,目前就转到 for (;;) {...} 循环里面不断读取 AVPacket 数据。

read_thread() 线程函数最后的 fail: 标签代码,是播放器退出之后的清理逻辑,这个目前不需要理会,可以后续再看《FFplay退出处理》。


read_thread() 线程里面有几个逻辑,本文是 刻意忽略 或者 一笔带过 了的,这些也是可以后续再看的,分别是:

1,wanted_stream_spec[] 数组的作用,本文中,这个数组全是 -1,所以忽略了。推荐阅读 《FFplay指定数据流播放》

2, av_format_inject_global_side_data() ,推荐阅读《av_format_inject_global_side_data函数详解》

3,seek 操作,推荐阅读《FFplay跳转时间点播放》


推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:

Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/83106.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html文件里怎么引用vue组件?

这里我们使用 http-vue-loader 来实现&#xff1a;https://www.npmjs.com/package/http-vue-loader Load .vue files directly from your html/js. No node.js environment, no build step. 我做了个demo如下&#xff1a; html文件里面写下面的代码 <!DOCTYPE html> &l…

计算机研究生就业方向之当老师(中小学)

我一直跟学生们说你考计算机的研究生之前一定要想好你想干什么&#xff0c;如果你只是转码&#xff0c;那么你不一定要考研&#xff0c;至少以下几个职位研究生是没有啥优势的&#xff1a; 1&#xff0c;软件测试工程师&#xff08;培训一下就行&#xff09; 2&#xff0c;前…

股票购买接口系统怎么使用vn.py进行量化策略?

一般情况下&#xff0c;股票购买接口系统主要是可以运用在股票量化交易系统开发的一个大方向&#xff0c;也就是说&#xff0c;股票购买接口系统是根据这些量化的特点来开发的&#xff0c;就比如使用vn.py进行量化策略&#xff0c;在这方面&#xff0c;对交易者进行量化分析也起…

Web前端105天-day-41-JSCORE

JSCORE01 目录 前言 一、声明提升 二、宿主 window 三、断点功能 四、匿名函数解决全局污染 五、作用域链 六、闭包 七、私有 八、arguments 九、函数重载 十、方括号属性语法 十一、重载练习 十二、this 总结 前言 JSCORE01学习开始 一、声明提升 报错方案: 让…

走进SpringCloud微服务

微服务概述一、注册中心&#xff1a;Eureka ⭐⭐⭐1.1 原理1.2 代码二、负载均衡&#xff1a;Ribbon ⭐三、远程调用&#xff1a;Feigh ⭐⭐⭐3.1 原理3.2 代码四、熔断限流&#xff1a;Hystrix ⭐⭐⭐4.1线程池策略4.2 信号量隔离策略4.3 方法降级4.4 断路器、熔断器五、网关&…

MongoDB和MongoTemplate对于嵌套数据的判空查询

前言&#xff1a; 不知道有没有和小名一样&#xff0c;接触MongDB时间不长的小伙伴。由于MongoDB是以文档形式存储数据的&#xff0c;所以其中的数据类型相对MySql或者Oracle关系型数据库丰富一些&#xff08;MongoDB是NoSQL数据库这里比较不是很准确&#xff09; 我们在关系…

Dropout方法原理和使用方法

来源&#xff1a;投稿 作者&#xff1a;梦飞翔 编辑&#xff1a;学姐 为什么提出这种方法&#xff1f; 神经网络在训练过程中&#xff0c;由于正负样本不均衡、样本特征显著性不足、训练参数不合适等原因会导致发生过拟合现象&#xff0c;即模型参数陷入局部最优&#xff0c;仅…

QT6操作连接mysql数据库方法_增删改查

QT6操作mysql方法_增删改查 mysql数据库搭建相关方法&#xff1a; MySQL - 随笔分类 - txwtech - 博客园https://www.cnblogs.com/txwtech/category/1973054.htmlMySQL解压版配置方法 MySQL解压版配置方法 - txwtech - 博客园1.下载 https://downloads.mysql.com/archives/co…

【知识学习】C++QT配置opencv遇到的坑

最近要搞图像&#xff0c;老师说尽量用C&#xff0c;就开始研究配置opencv 当然&#xff0c;说在前面&#xff0c;C的比python的要麻烦特别多&#xff0c;所以如果不是必要的话&#xff0c;建议用python pip配opencv吧 C麻烦就在于要自己在本地编译一遍才能跑&#xff0c;直接…

如何将亚马逊Seller Central 用到极致~

不论是新手卖家还是有经验的老手&#xff0c;亚马逊卖家中心都是一个可以帮助卖家发展业务的好工具&#xff0c;对于许多新手小白来说&#xff0c;亚马逊这样巨大的平台仍有许多功能与服务等着挖掘。 什么是亚马逊卖家中心&#xff1f; 亚马逊卖家中心是第三方卖家用来管理和…

微信支付-全面详解(学习总结---从入门到深化)

微信支付_产品介绍 微信支付介绍 微信支付&#xff08;https://pay.weixin.qq.com&#xff09;是腾讯集团旗下中国领先 的第三方支付平台&#xff0c;一直致力于为用户和企业提供安全、便捷、专业的在线支付服务。 付款码支付 付款码支付是指用户展示微信钱包内的“付款码”给商…

指纹浏览器是什么?可以用来解决网络爬虫的什么问题?

在大数据时代的今天&#xff0c;各行各业的许多企业多多少少都会因为涉及到海外市场需要收集大量的市场信息&#xff0c;特别是对于跨境电商领域的商家来说&#xff0c;网络爬虫是必不可少的。因此&#xff0c;一定有很多从业者接触过网络爬虫&#xff0c;但对于刚打算进入这个…

ADI Blackfin DSP处理器-BF533的开发详解36:图像处理专题-RGB888 转 RGB565(含源代码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 功能介绍 作为一个最高600M主频&#xff0c;单核或双核的定点DSP&#xff0c;做图像处理还是挺行的&#xff0c;属于是老本行&#xff0c;那么我…

广州蓝景分享—HTML+CSS功能,让页面加载速度提高数倍

Hello&#xff0c;各位小伙伴&#xff0c;今天跟大家分享前端技术干货&#xff0c;页面加载速度问题。 首先我们都讨厌加载缓慢的页面&#xff01; 要知道加载时间每增加1秒&#xff08;0-5秒之间&#xff09;&#xff0c;网站转化率就会平均下降4.42%。页面加载时间的前五秒…

linux 虚拟机nat模式网络配置

文章目录1. linux 版本&#xff1a;2. 下载地址 Index of /centos-store/7.6.1810/isos/x86_64/ (liu.se)3. 账号密码&#xff1a;root root4.选择nat 模式&#xff0c;勾选 将主机虚拟适配器连接到此网络&#xff0c;勾选 使用本地DHCP服务将iP地址分配给虚拟机5.点击Nat 设置…

数据仓库分享

前言 数据仓库&#xff0c;是为企业所有级别的决策制定过程&#xff0c;提供所有类型数据支持的战略集合。它出于分析性报告和决策支持的目的而创建的。 数据仓库是一个数据集合 数据仓库是一个为业务决策提供数据支持的数据集合 数据仓库是通过监控业务流程的形式为业务决策提…

Css不常用的方法

flex布局换行之后&#xff0c;下面一行的布局会异常 .homeItemBox{ display: flex; flex-wrap: wrap; justify-content: flex-start;} .homeItem{ display: flex; width: calc((100% - 20rpx) / 4); flex-direction: column; align-items: center; flex-shrink: 0; …

盘点系列:一度大热的TWS耳机今年表现如何?

根据Canalys最新市场研究数据显示&#xff0c;全球智能个人耳机市场在2022年Q3已连续第二个季度出现下滑&#xff0c;出货量同比下降4%至1.136亿台。 而TWS是唯一出现增长的子类别&#xff01; Q3真无线耳机TWS销量达7690万部&#xff0c;同比增长6%。苹果&#xff08;含beats&…

软件测试基础理论体系学习6-黑盒测试方法白盒测试方法简述

13 白盒测试方法1 黑盒测试1.1 黑盒测试概述1.2 黑盒测试的使用场景1.3 “黑盒”的两种基本方法1.4 黑盒测试的优缺点1.4.1 优点1.4.2 缺点1.5 黑盒测试的测试用例设计方法2 白盒测试2.1 白盒测试概述2.2 逻辑覆盖2.3 语句覆盖2.3.1 基本思想是2.3.2 优点2.3.3 缺点2.4 判定覆盖…

Python及其在数据科学中的应用

前言 Python及其在数据科学中的应用 Python易学&#xff0c;语法也比较简单。它是一种流行的数据科学语言&#xff0c;因为它功能强大且易于使用。Python是一种出色的数据分析语言&#xff0c;因为它包含各种数据结构、模块和工具。 使用Python进行数据科学的原因有很多&…