ffplay数据读取线程

news2024/10/6 14:25:01

从这张图开始,主要介绍ffplay的读取线程部分。

 

ffplay总体架构

从图中可以看出,解码线程的主要工作内容是将资源包从待解码列队中取出,然后送进解码器,最后将解码出的数据帧放入帧队列中,等待SDL获取播放。

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~
  

解码过程

解码线程是在打开流的时候创建的,也就是在函数stream_component_open创建的,视频解码线程的工作函数是video_thread。

以下是函数video_thread的内容,可以看到去掉filter的相关处理后,这个函数是非常精简的,就是在for循环中通过函数get_video_frame获取到一帧图像数据后,调整数据帧的pts,然后将数据帧放入帧队列中:

/**
 * 视频解码
 * @param arg
 * @return
 */
static int video_thread(void *arg)
{
    VideoState *is = arg;
    // 分配frame
    AVFrame *frame = av_frame_alloc();
    double pts;
    double duration;
    int ret;
    AVRational tb = is->video_st->time_base;
    AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
​
#if CONFIG_AVFILTER
    AVFilterGraph *graph = NULL;
    AVFilterContext *filt_out = NULL, *filt_in = NULL;
    int last_w = 0;
    int last_h = 0;
    enum AVPixelFormat last_format = -2;
    int last_serial = -1;
    int last_vfilter_idx = 0;
#endif
​
    if (!frame)
        return AVERROR(ENOMEM);
​
    for (;;) {
        // 获取一帧视频图像,返回负值会退出线程,什么时候会返回负值?问管家VideoState
        ret = get_video_frame(is, frame);
        if (ret < 0)
            goto the_end;
        if (!ret)
            continue;
​
#if CONFIG_AVFILTER
        if (   last_w != frame->width
            || last_h != frame->height
            || last_format != frame->format
            || last_serial != is->viddec.pkt_serial
            || last_vfilter_idx != is->vfilter_idx) {
            av_log(NULL, AV_LOG_DEBUG,
                   "Video frame changed from size:%dx%d format:%s serial:%d to size:%dx%d format:%s serial:%d\n",
                   last_w, last_h,
                   (const char *)av_x_if_null(av_get_pix_fmt_name(last_format), "none"), last_serial,
                   frame->width, frame->height,
                   (const char *)av_x_if_null(av_get_pix_fmt_name(frame->format), "none"), is->viddec.pkt_serial);
            avfilter_graph_free(&graph);
            graph = avfilter_graph_alloc();
            if (!graph) {
                ret = AVERROR(ENOMEM);
                goto the_end;
            }
            graph->nb_threads = filter_nbthreads;
            if ((ret = configure_video_filters(graph, is, vfilters_list ? vfilters_list[is->vfilter_idx] : NULL, frame)) < 0) {
                SDL_Event event;
                event.type = FF_QUIT_EVENT;
                event.user.data1 = is;
                SDL_PushEvent(&event);
                goto the_end;
            }
            filt_in  = is->in_video_filter;
            filt_out = is->out_video_filter;
            last_w = frame->width;
            last_h = frame->height;
            last_format = frame->format;
            last_serial = is->viddec.pkt_serial;
            last_vfilter_idx = is->vfilter_idx;
            frame_rate = av_buffersink_get_frame_rate(filt_out);
        }
​
        ret = av_buffersrc_add_frame(filt_in, frame);
        if (ret < 0)
            goto the_end;
​
        while (ret >= 0) {
            is->frame_last_returned_time = av_gettime_relative() / 1000000.0;
​
            ret = av_buffersink_get_frame_flags(filt_out, frame, 0);
            if (ret < 0) {
                if (ret == AVERROR_EOF)
                    is->viddec.finished = is->viddec.pkt_serial;
                ret = 0;
                break;
            }
​
            is->frame_last_filter_delay = av_gettime_relative() / 1000000.0 - is->frame_last_returned_time;
            if (fabs(is->frame_last_filter_delay) > AV_NOSYNC_THRESHOLD / 10.0)
                is->frame_last_filter_delay = 0;
            tb = av_buffersink_get_time_base(filt_out);
#endif
            // 计算持续播放时间
            duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
            pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
            // 将解码得到的帧放进队列
            ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
            av_frame_unref(frame);
#if CONFIG_AVFILTER
            if (is->videoq.serial != is->viddec.pkt_serial)
                break;
        }
#endif
​
        if (ret < 0)
            goto the_end;
    }
 the_end:
#if CONFIG_AVFILTER
    avfilter_graph_free(&graph);
#endif
    av_frame_free(&frame);
    return 0;
}

我们来看看函数get_video_frame做了什么:

/**
 * 获取一帧图像
 * @param is
 * @param frame
 * @return
 */
static int get_video_frame(VideoState *is, AVFrame *frame)
{
    int got_picture;
    // 获取解码数据
    if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0)
        return -1;
​
    // 分析是否需要丢帧,例如解码出来的图片已经比真正播放的音频都慢了,那就要丢帧了
    if (got_picture) {
        double dpts = NAN;
​
        if (frame->pts != AV_NOPTS_VALUE)
            // 计算pts
            dpts = av_q2d(is->video_st->time_base) * frame->pts;
​
        frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
​
        // 同步时钟不以视频为基准时
        if (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
            if (frame->pts != AV_NOPTS_VALUE) {
                // 理论上如果需要连续接上播放的话  dpts + diff = get_master_clock(is)
                // 所以可以算出diff  注意绝对值
                double diff = dpts - get_master_clock(is);
                if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&
                    diff - is->frame_last_filter_delay < 0 &&
                    is->viddec.pkt_serial == is->vidclk.serial &&
                    is->videoq.nb_packets) {
                    is->frame_drops_early++;
                    av_frame_unref(frame);
                    got_picture = 0;
                }
            }
        }
    }
​
    return got_picture;
}

这个函数内部又调用了函数decoder_decode_frame获取解码帧,然后根据同步时钟判断是否需要进行丢帧处理。那么我们再看看函数decoder_decode_frame

static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {
    int ret = AVERROR(EAGAIN);
​
    for (;;) {
        // 解码器的序列需要和解码包队列的序列一致
        if (d->queue->serial == d->pkt_serial) {
            do {
                // 请求退出了则返回-1
                if (d->queue->abort_request)
                    return -1;
​
                switch (d->avctx->codec_type) {
                    case AVMEDIA_TYPE_VIDEO:
                        // 获取1帧解码数据
                        ret = avcodec_receive_frame(d->avctx, frame);
                        if (ret >= 0) {
                            // 更新pts为AVPacket的pts
                            if (decoder_reorder_pts == -1) {
                                frame->pts = frame->best_effort_timestamp;
                            } else if (!decoder_reorder_pts) {
                                frame->pts = frame->pkt_dts;
                            }
                        }
                        break;
                    case AVMEDIA_TYPE_AUDIO:
                        ret = avcodec_receive_frame(d->avctx, frame);
                        if (ret >= 0) {
                            AVRational tb = (AVRational){1, frame->sample_rate};
                            if (frame->pts != AV_NOPTS_VALUE)
                                frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
                            else if (d->next_pts != AV_NOPTS_VALUE)
                                frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
                            if (frame->pts != AV_NOPTS_VALUE) {
                                d->next_pts = frame->pts + frame->nb_samples;
                                d->next_pts_tb = tb;
                            }
                        }
                        break;
                }
                if (ret == AVERROR_EOF) {
                    d->finished = d->pkt_serial;
                    // 刷新解码器
                    avcodec_flush_buffers(d->avctx);
                    return 0;
                }
                if (ret >= 0)
                    return 1;
            } while (ret != AVERROR(EAGAIN));
        }
​
        do {
            if (d->queue->nb_packets == 0)
                // 队列空了,唤醒读取线程,赶紧读取数据
                SDL_CondSignal(d->empty_queue_cond);
            if (d->packet_pending) {
                // 处理有缓冲的数据
                d->packet_pending = 0;
            } else {
                int old_serial = d->pkt_serial;
                if (packet_queue_get(d->queue, d->pkt, 1, &d->pkt_serial) < 0)
                    return -1;
                if (old_serial != d->pkt_serial) {
                    // 刷新解码器
                    avcodec_flush_buffers(d->avctx);
                    d->finished = 0;
                    d->next_pts = d->start_pts;
                    d->next_pts_tb = d->start_pts_tb;
                }
            }
            if (d->queue->serial == d->pkt_serial)
                break;
            av_packet_unref(d->pkt);
        } while (1);
​
        if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {
            int got_frame = 0;
            ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, d->pkt);
            if (ret < 0) {
                ret = AVERROR(EAGAIN);
            } else {
                if (got_frame && !d->pkt->data) {
                    d->packet_pending = 1;
                }
                ret = got_frame ? 0 : (d->pkt->data ? AVERROR(EAGAIN) : AVERROR_EOF);
            }
            av_packet_unref(d->pkt);
        } else {
            // 数据送进解码器失败,遇到EAGAIN怎么办?
            if (avcodec_send_packet(d->avctx, d->pkt) == AVERROR(EAGAIN)) {
                av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
                d->packet_pending = 1;
            } else {
                av_packet_unref(d->pkt);
            }
        }
    }
}
原来函数decoder_decode_frame才是真正的解码核心,可以看到视频、音频和字幕都是通过调用这个函数进行解码的。这个函数的主要工作内容就是在for循环中不断取出数据包然后调用FFmpeg的API进行解码。
​
1、首先判断待解码队列的播放序列和解码器的播放序列是否一致:
​
```apache
if (d->queue->serial == d->pkt_serial) 
```
​
如果一致则通过FFmpeg函数avcodec_receive_frame获取数据帧,然后更新数据帧pts。
​
在这里有个疑问,我们之前不是说解码过程是先avcodec_send_packet然后是n个avcodec_receive_frame获取数据帧吗?这里怎么直接就先avcodec_receive_frame获取数据帧了呢?
​
这是因为函数decoder_decode_frame是一个不断被调用的for循环,优先调用avcodec_receive_frame可以防止解码器内部缓冲过多数据而导致解码包无法送入解码器的现象出现。
​
2、待解码队列中是否有数据,如果没有则唤醒读取线程
​
```apache
 if (d->queue->nb_packets == 0)
                // 队列空了,唤醒读取线程,赶紧读取数据
                SDL_CondSignal(d->empty_queue_cond);
​
```
​
3、判断是否有送入解码器失败的包,如果有则优先处理
​
```apache
 if (d->packet_pending) {
                // 处理有缓冲的数据
                d->packet_pending = 0;
            }
​
```
​
​
​
这包是在调用`avcodec_send_packet`返回了`AVERROR(EAGAIN)`时所缓存下来的。
​
4、如果没有缓存的包则从队列中获取packet_queue_get
​
```apache
  int old_serial = d->pkt_serial;
                if (packet_queue_get(d->queue, d->pkt, 1, &d->pkt_serial) < 0)
                    return -1;
                if (old_serial != d->pkt_serial) {
                    // 刷新解码器
                    avcodec_flush_buffers(d->avctx);
                    d->finished = 0;
                    d->next_pts = d->start_pts;
                    d->next_pts_tb = d->start_pts_tb;
                }
​
```
​
​
​
5、将包送进解码器,如果返回的值是`AVERROR(EAGAIN)`,则将包缓存起来,下次再优先送入解码器处理
​
同时这里也可以解析了在第一步中为什么优先调用`avcodec_receive_frame`的原因,就是为了数据可以顺利地送进解码器。
​
```apache
 // 数据送进解码器失败,遇到EAGAIN怎么办?
            if (avcodec_send_packet(d->avctx, d->pkt) == AVERROR(EAGAIN)) {
                av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
                d->packet_pending = 1;
            } else {
                av_packet_unref(d->pkt);
            }
​
```

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

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

相关文章

【开源电路】ST-LINK/V2、ST-LINK/V2-1、DAP-LINK烧录器(已验证)

【开源电路】ST-LINK/V2、ST-LINK/V2-1、DAP-LINK烧录器&#xff08;已验证&#xff09;PCBA实物图 最终的的PCB 3D效果图 效果图和PCBA实物图差异说明 由于设计之初只考虑当ST-LINK V2来用&#xff0c;主要是用来给STM8和stm32烧录程序。没有考虑到会将固件升级到ST-LINK…

可变电阻元件封装

实验目的 掌握原理图封装的绘制操作掌握原理图封装和PCB封装的联系掌握PCB封装的绘制探索逻辑元件的选择 实验原理 采用EDA软件进行电路原理图设计 实验仪器 电脑、Altium Designer软件、相关元器件 实验内容 制作可变电阻元件 1. 创建工作环境2. 管理元件库在左侧面板中打开…

2023年,pmp还值得去考试吗?

为什么不值得呢&#xff1f;PMP的中文翻译过来就是项目管理专业人士&#xff0c;专业人士才考的证难道会差吗&#xff1f;有了它&#xff0c;即使是项目管理小白也能变成PMP专业人士&#xff01; 张嘴就冒那种别人听了就一脸懵但依然觉得你牛逼坏了的专业术语&#xff08;笑&a…

站稳前沿消费趋势,IU酒店持续领跑轻中端品牌

站稳前沿消费趋势&#xff0c;IU凸显品牌影响魅力 在疫情点状散发的背景下&#xff0c;身处一线的酒店行业深受影响&#xff0c;在现今错综复杂的市场环境中&#xff0c;投资者如何谋求机遇?酒店业为何破局重生?另一方面&#xff0c;随着消费升级以及年轻一代消费群体的崛起…

【SpringCloud】02 搭建springcloud微服务项目

文章目录搭建springcloud微服务项目1. 微服务父工程2. 创建子模块-shop-common3. 创建子模块--shop-product4. 创建子模块--shop-order搭建springcloud微服务项目 技术栈: springcloud-alibabamybatis-plus 持久性框架mysql数据库5.7以上springboot来搭建每个微服务。 1. 微服…

艾美捷Immunochemistry MitoPT JC-1试剂盒

艾美捷Immunochemistry MitoPT JC-1测定利用荧光染料JC-1检测线粒体膜去极化。当积聚在带负电的极化线粒体中时&#xff0c;JC-1发出橙色荧光。当线粒体膜电位在凋亡或代谢应激的细胞中崩溃时&#xff0c;JC-1试剂分散在细胞中并发出绿色荧光。使用流式细胞仪、荧光平板读取器或…

【场景化解决方案】慧致造ERP,为企业提供生产全流程数字化管理

方案简介 慧致造ERP以钉钉为基座&#xff0c;借助钉钉待办、工作通知、OA审批、工作台组件、酷应用、定制工作台等开放能力&#xff0c;围绕制造业生产管理场景与钉钉深度融合&#xff0c;为中小制造业打造业财一体化的生产制造解决方案&#xff0c;企业用户只需一个平台&…

Linux企业运维之git的使用

文章目录前言一、git简介以及基础操作二、github或者在gitee上创建项目并且上传本地项目自动化创建&#xff08;触发jenkins&#xff09;前言 一、git简介以及基础操作 git 简单来说就是版本控制系统 但是相对于其他版本控制系统来说&#xff0c;它又具有一些优点&#xff1a;…

集群渲染和渲染农场是什么意思?跟云渲染有什么关系?

嗨咯&#xff0c;大家好&#xff0c;今天后台有同学问集群渲染什么意思&#xff1f;集群渲染怎么做&#xff1f; 集群渲染&#xff08;cluster rendering&#xff09;指的是一组计算机通过通信协议连接在一起的计算机群&#xff0c;它们能够将工作负载从一个超载的计算机迁移到…

20款免费项目管理系统推荐

通过本篇文章您将了解&#xff1a;1、国内外20款最佳项目管理软件&#xff1b;2、使用免费项目管理工具可能面临的风险。一、项目管理软件的重要性 根据 Capterra 的数据研究&#xff0c;项目管理软件即将成为人们最需要的软件。一个项目无论大小&#xff0c;都需要一款高效且…

基于PHP+MySQL游戏视频网站的设计与实现

游戏是茶前饭后一个很好的娱乐方式,但是由于当下网络的高速发展,游戏的模式和种类也丰富多彩,这就导致很多时候人们不能够很快的对游戏上手。 为了改变这一情况很多视频娱乐类网站都出现了游戏视频,但是大多数时候这种网站并不是一个专业的游戏视频网站,跟多的时候是各类电影和…

SpringMVC学习

SpringMVCSpring MVC概述&#xff1a;**什么是Spring MVC &#xff1f;****什么是MVC?**第一个SpringMVC程序具体步骤&#xff1a;具体实现&#xff1a;第一个SpringMVC小程序的完善Spring MVC概述&#xff1a; 什么是Spring MVC &#xff1f; 他是基于MVC开发模式的框架&am…

数学建模英文论文的写作方法和步骤

目录 一、语言技巧 二、论文结构 1.标题写作 2.摘要写作时态​编辑 2.2摘要写作语态 2.3摘要写作人称 2.4摘要写作注意事项 3.问题重述 4. 符号说明​编辑 三线表 ​5.模型假设(以三到七个合理假设为宜 ) 6.模型分析及建立模型 7.模型求解 8.模型检验 9.模型优…

电视剧里的代码真能运行吗?

大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 前几天&#xff0c;后台老有小伙伴留言“爱心代码”。这不是Crossin很早之前发过的内容嘛&#xff0c;怎么最近突然又被人翻出来了&#xff1f;后来才知道&#xff0c;原来是一部有关程序员的青春偶像剧《点燃我,温…

022_SSS_Novel View Synthesis with Diffusion Models

Novel View Synthesis with Diffusion Models 1. Introduction 本文利用diffusion模型&#xff0c;在给定参考图的条件下&#xff0c;生成指定pose的图像&#xff0c;作者称为3DiM。并且可以在给定一张特定视角的图的条件下&#xff0c;生成其他所有视角的图。 本文的主要贡…

Treap 原理详解和实战

一 点睛 Treap 指 Tree heap&#xff0c;又叫作树堆&#xff0c;同时满足二叉搜索树和堆两种性质。二叉搜索树满足中序有序性&#xff0c;输入序列不同&#xff0c;创建的二叉搜索树也不同&#xff0c;在最坏的情况下&#xff08;只有左子树或只有右子树&#xff09;会退化为…

CMCT-FA修饰阿霉素纳米脂质体/ADR-HAS-MS单抗Hab18偶联阿霉素人血清白蛋白微球的制备方法

瑞禧生物这里整理的内容是CMCT-FA修饰阿霉素纳米脂质体/ADR-HAS-MS单抗Hab18偶联阿霉素人血清白蛋白微球的相关制备方法&#xff0c;来学习&#xff01; MCT-FA修饰阿霉素纳米脂质体的研究&#xff1a; 利用1-乙基-3-(3-二甲基丙基)-碳二亚胺(EDC)介导 反应合成了叶酸偶联的羧甲…

LeetCode刷题---160. 相交链表(双指针-对撞指针)

文章目录一、编程题&#xff1a;160. 相交链表&#xff08;双指针-对撞指针&#xff09;1.题目描述2.示例1&#xff1a;3.示例2&#xff1a;4.示例3&#xff1a;5.提示&#xff1a;6.提示&#xff1a;二、解题思路1.思路2.复杂度分析&#xff1a;3.算法图解三、代码实现总结一、…

CSS——图标字体

为什么需要图标字体&#xff1f; 在网页中经常会有需要使用一些图标的地方&#xff0c;比如&#x1f6d2; &#xff0c; &#x1f464;&#xff0c;⏫等等&#xff0c;虽然我们可以通过图片来引入图标&#xff0c;但是图片本身比较大&#xff0c;页面刷新加载图片耗时不说&…

[附源码]java毕业设计小区疫情防控系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…