【FFmpeg实战】ffplay整体框架

news2024/11/25 13:19:25

原文地址:https://segmentfault.com/a/1190000042611796

本文使用的ffplay.c的版本是搭配ffmpeg5.0的版本。

ffplay代码大致架构

关于fplay的架构很难三言两语说得清楚,而且本人对它的理解也不是很深,加上行笔比较啰嗦,可能就更加描述不清了,因此笔者制作了一张图,这张图描述了ffplay的三大子线程工作内容,当然如果加上字幕的话有四个子线程。

img

图导出的可能有点模糊,再加上上传图床后不知道有没有更加模糊了,想要高清大图的可以后台留言,加v信索取。

上图只是简单地画出了ffplay的大体架构,不同版本的代码API名称可能有点不同,但是大致思路肯定是一致的。这个图也忽略了一些细节,比如解码线程与读取线程的同步处理等。

ffplay关键数据结构

1、VideoState

VideoState可以说是ffplay的内部管家了,几乎能拿到ffplay内部的所有变量以及相关状态。下面是笔者加了注释的的VideoState源码:

/**
 * 统领全局的结构体,几乎能拿到ffplay内部所有的变量
 */
typedef struct VideoState {
    SDL_Thread *read_tid;  // 资源读取线程
    const AVInputFormat *iformat; // 解封装输入格式
    int abort_request; // 是否退出
    int force_refresh;  // =1时需要刷新画面,请求立即刷新画面的意思
    int paused;  // =1时暂停,=0时播放
    int last_paused; // 存取了paused状态,是因为多线程???
    int queue_attachments_req;  // 队列附件请求,比如一些mp3是带有专辑封面的
    int seek_req;  // 表示是否进行了seek请求
    int seek_flags;  // seek类型,有些封装格式是按照字节seek,有些是按照播放时间seek
    int64_t seek_pos; // seek位置,当前位置+增量
    int64_t seek_rel; // seek增量
    int read_pause_return; // rtsp等流协议控制???
    AVFormatContext *ic; // 解封装上下文
    int realtime; // =1为实时流  直播还是点播?

    Clock audclk; // 音频时钟 每个流都需要有自己独立的时钟,然后在同步时拿自己的时钟去与别人的比较
    Clock vidclk; // 视频时钟
    Clock extclk; // 外部时钟

    FrameQueue pictq; // 视频帧队列
    FrameQueue subpq; // 字幕
    FrameQueue sampq; // 音频帧队列

    Decoder auddec; // 音频解码器
    Decoder viddec; // 视频解码器
    Decoder subdec; // 字幕解码器

    int audio_stream; // 音频流索引

    int av_sync_type; // 音视频同步的类型,以音频为基准?是视频为基准?以外部时钟为基准?

    double audio_clock; // 当前音频帧的PTS+当前帧Duration
    int audio_clock_serial; // 当前音频帧的播放序列,seek可改变此值
    double audio_diff_cum; /* used for AV difference average computation */
    double audio_diff_avg_coef;
    double audio_diff_threshold;
    int audio_diff_avg_count;
    AVStream *audio_st; // 音频流
    PacketQueue audioq; // 音频包队列
    int audio_hw_buf_size; // 下面个是SDL播放相关
    uint8_t *audio_buf;
    uint8_t *audio_buf1;
    unsigned int audio_buf_size; /* in bytes */
    unsigned int audio_buf1_size;
    int audio_buf_index; /* in bytes */
    int audio_write_buf_size;
    int audio_volume;
    int muted;
    struct AudioParams audio_src;
#if CONFIG_AVFILTER
    struct AudioParams audio_filter_src;
#endif
    struct AudioParams audio_tgt;
    struct SwrContext *swr_ctx;
    int frame_drops_early;
    int frame_drops_late;

    enum ShowMode {
        SHOW_MODE_NONE = -1, SHOW_MODE_VIDEO = 0, SHOW_MODE_WAVES, SHOW_MODE_RDFT, SHOW_MODE_NB
    } show_mode;
    int16_t sample_array[SAMPLE_ARRAY_SIZE];
    int sample_array_index;
    int last_i_start;
    RDFTContext *rdft;
    int rdft_bits;
    FFTSample *rdft_data;
    int xpos;
    double last_vis_time;
    SDL_Texture *vis_texture;
    SDL_Texture *sub_texture;
    SDL_Texture *vid_texture;

    // 下面几个字幕相关
    int subtitle_stream;
    AVStream *subtitle_st;
    PacketQueue subtitleq;

    double frame_timer; // 最后一帧播放的时刻
    double frame_last_returned_time; //上一次返回时间
    double frame_last_filter_delay;
    int video_stream; // 视频流索引
    AVStream *video_st; // 视频流
    PacketQueue videoq; // 视频包队列
    double max_frame_duration;    // 一帧最大的间隔,音视频同步时使用  // maximum duration of a frame - above this, we consider the jump a timestamp discontinuity
    struct SwsContext *img_convert_ctx; // 视频尺寸变化上下文
    struct SwsContext *sub_convert_ctx;
    int eof; // 文件是否读取结束

    char *filename;
    int width, height, xleft, ytop;
    int step; // =1 步进播放模式,暂停状态下seek需要更新seek成功的哪一帧 =0 其他模式

#if CONFIG_AVFILTER
    int vfilter_idx;
    AVFilterContext *in_video_filter;   // the first filter in the video chain
    AVFilterContext *out_video_filter;  // the last filter in the video chain
    AVFilterContext *in_audio_filter;   // the first filter in the audio chain
    AVFilterContext *out_audio_filter;  // the last filter in the audio chain
    AVFilterGraph *agraph;              // audio filter graph
#endif

    int last_video_stream, last_audio_stream, last_subtitle_stream;

    SDL_cond *continue_read_thread;
} VideoState;

关于SDL相关的,以及一些filter相关的笔者忽略了,因为从本质上讲它们不属于ffplay的核心内容。

2、 MyAVPacketList

MyAVPacketList是表示待解码数据包的数据内容。

// 未解码的包队列节点数据、增加了一个播放序列 serial 字段而已
typedef struct MyAVPacketList {
    AVPacket *pkt;
    int serial;
} MyAVPacketList;

在新版本中笔者感觉这个结构体有些鸡肋,因为它是搭配PacketQueue使用的,但是PacketQueue又没有直接使用它,而是拷贝了它的数据…

3、PacketQueue

PacketQueue表示待解码包的队列,就是从流文件中读取到的数据包,然后将它们放入到这个容器中去,然后等待解码线程获取进行消费,涉及到线程安全、互斥量、条件变量等。

// 未解码数据包队列
typedef struct PacketQueue {
    AVFifoBuffer *pkt_list; // 包内容,先进先出队列
    int nb_packets; // 队列长度,队列有几个包
    int size; // 队列所有元素大小之和
    int64_t duration; // 队列所有包所持续播放的时间之和
    int abort_request; // 是否退出了
    int serial; // 播放序列
    SDL_mutex *mutex; // 同步互斥量
    SDL_cond *cond; // 条件变量
} PacketQueue;

4、Clock

时钟是ffplay中一个很重要的概念,没了它音视频同步就没法做。音频和视频都有各自的时钟,在同步的时候以参考系看自己到底是快了还是慢了及时作出校正。

这个看不懂不要紧,后面在音视频同步中我们再详细介绍下这个结构体。

// 时钟/同步时钟
typedef struct Clock {
    double pts;       // 当前正在播放的帧的pts    /* clock base */
    double pts_drift;   // 当前的pts与系统时间的差值  保持设置pts时候的差值,后面就可以利用这个差值推算下一个pts播放的时间点
    double last_updated; // 最后一次更新时钟的时间,应该是一个系统时间吧?
    double speed;  // 播放速度控制
    int serial;     // 播放序列      /* clock is based on a packet with this serial */
    int paused;  // 是否暂停
    int *queue_serial;   // 队列的播放序列 PacketQueue中的 serial /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;

5、Frame

Frame表示解码后的数据帧,它内部封装了ffmpeg的结构体AVFrame。

/**
 * 解码后的数据帧
 */
typedef struct Frame {
    AVFrame *frame;
    AVSubtitle sub; // 字幕
    int serial;   // 播放序列
    double pts;    // 当前帧指向的pts 时间戳,单位为秒       /* presentation timestamp for the frame */
    double duration;  // 当前帧所持续的时间    /* estimated duration of the frame */
    int64_t pos;      // 该帧在文件中的字节位置    /* byte position of the frame in the input file */
    int width;     // 宽度,感觉和*frame里面的冗余了....
    int height;
    int format;   // 图像或声音格式
    AVRational sar; // 图像宽高比 如果未知或未指定则为0/1
    int uploaded;  // 用来记录该帧是否已经显示过?
    int flip_v;   // =1则旋转180, = 0则正常播放
} Frame;

6、FrameQueue

FrameQueue解释解码后的待播放的数据帧队列,它是环形的队列,通过索引进行操作内部元素,涉及到线程安全、互斥量、条件变量等。

/**
 * 帧队列,环形队列,播放时并不会删除,移动索引指针
 */
typedef struct FrameQueue {
    Frame queue[FRAME_QUEUE_SIZE];
    int rindex; // 读索引
    int windex; // 写索引
    int size; // 队列中总帧数量
    int max_size; // 队列最大的容量
    int keep_last; // 是否保持最后一帧不释放,= 1说明要在队列里面保持最后一帧的数据不释放,只在销毁队列的时候才将其真正释放
    int rindex_shown; // 初始化为0,配合keep_last=1使用,要么为0要么为1
    SDL_mutex *mutex;
    SDL_cond *cond;
    PacketQueue *pktq; // 包队列,为了获取包里面的播放序列???
} FrameQueue;

7、Decoder

Decoder是ffplay封装的解码器结构体,其实内部是调用了ffmpeg的API进行解码音频、视频、字幕等,内部封装了子线程。

/**
 *视频、音频、字幕解码器
 */
typedef struct Decoder {
    AVPacket *pkt; // 缓冲包
    PacketQueue *queue; // 待解码队列
    AVCodecContext *avctx; // 解码器上下文
    int pkt_serial; // 解码序列
    int finished;
    int packet_pending; // 是否有缓冲包,就是是否有缓冲的*pkt变量需要处理
    SDL_cond *empty_queue_cond;
    int64_t start_pts;  // 初始化时是stream的start time
    AVRational start_pts_tb; // 初始化时是stream的time_base
    int64_t next_pts; //  记录最近一次解码后的frame的pts,当解出来的部分帧没有有效的pts时则使用next_pts进行推算
    AVRational next_pts_tb; // next_pts的单位
    SDL_Thread *decoder_tid; // 解码线程
} Decoder;

关于ffpaly的整体架构今天就先介绍到这里,关于ffplay内部的其他细节问题,我们将在后面继续介绍,敬请关注。

播放序列

在上面的数据结构的介绍中我们看到几乎每个结构体都含有一个类似于serial这样的播放序列的字段,那么这个播放序列的字段是干啥用的呢?

所谓的播放序列是指一个连续的播放过程,比如播放了一个媒体流一直没有进行操作就是一个播放序列,一旦操作了播放位置就是另外一个播放序列了,比如说开始播放的时候播放序列是0,进行了seek

操作之后播放序列就不是0了,会变成1,每次seek之后播放序列都会加1,然后在读取线程、解码线程、SDL显示过程中都进行当前的数据包、数据帧是否是属于最新的播放序列的内容的判断,如果不属于的话将被直接丢弃掉,

如果是属于当前播放序列的话就进行入队或解码或播放等相关操作。

  >>> 音视频开发 视频教程: https://ke.qq.com/course/3202131?flowToken=1031864 
  >>> 音视频开发学习资料、教学视频,免费分享有需要的可以自行添加学习交流群 739729163 领取

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

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

相关文章

springboot配置多个mongo数据源

yaml配置文件: spring:data:mongodb:uri: mongodb://admin:密码ip:27017/paasoo?authSourceadminother:uri: mongodb://admin:密码ip:27017/conversation?authSourceadmin java config文件: package com.paasoo.quartz.config.mongo;import org.spr…

VR数字乡村激活乡土文化生命力,助力乡村振兴

民俗节庆、传统技艺等蕴含着中华五千年以来的传统文化,乡村文化建设在为文化留住血脉的同时,也为高质量发展创造更多可能。找准乡村文化与产业的结合点,有利于激发产业发展的潜力,激活乡土文化的生命力,为乡村振兴注入…

baichuan-7B模型

文章目录 baichuan-7B介绍baichuan-7B 推理baichuan-7B 微调 baichuan-7B介绍 2023年6月15日,搜狗创始人王小川创立的百川智能公司,发布了70 亿参数量的中英文预训练大模型——baichuan-7B。 baichuan-7B 基于 Transformer 结构,在大约 1.2…

【Ubuntu学习MySQL——安装MySQL】

首先得su,然后输入密码,进入到root模式下,以下命令均在root用户模式下进行 1.在这里我们使用RPM包来安装Mysql,所以首先安装RPM包 apt install rpm2.安装完RPM包之后,检测系统是否自带安装MySQL,如果没有…

最小年龄仅5岁!盘点全球最“天才”少年黑客 TOP 10

你还能想起自己8岁的时候,每天都在玩什么吗?可能是在楼下和小朋友一起捉迷藏?在家追一本连载的漫画书?又或者在电脑上玩种菜偷菜的小游戏? 当同龄人还在沉迷于这些比较“基础”的小游戏时,有这样一批和互联…

ARM_uart_发送接收字符 and 发送接收字符串

include/uart4.h #ifndef __UART4_H__ #define __UART4_H__#include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" #include "stm32mp1xx_uart.h"//初始化相关操作 void hal_uart4_init();//发送一个字符 void hal_put_char(const char st…

逆波兰表达式

思路 变量 String[] arr Stack 代码 public class Test1 {public static void main(String[] args) {String s "3 40 5 * 6 -";Stack numArr new Stack(10);int num1 0;int num2 0;int res 0;int index 0;String[] arr s.split(" ");for(String…

Flink 读写Kafka总结

前言 总结Flink读写Kafka Flink 版本 1.15.4 Table API 本文主要总结Table API的使用(SQL),官方文档:https://nightlies.apache.org/flink/flink-docs-release-1.17/zh/docs/connectors/table/kafka/ kerberos认证相关配置 …

spring生命周期配置

初始化方法,可读化方法: 初始化方法定义在java接口文件: init-method:指定类中的初始化方法名称 destroy-method :指定类中销毁方法名称 这里要在配置文件中配置一份: 如果想要destroy文件关闭后还能运行&…

Spring源码整体脉络介绍及源码编译

需完成的任务 类------------------------------------------BeanFactory----------------------------------------->Bean【BeanFactory调用getBean()生产出来的】 BeanFactory Spring顶层核心接口,使用了简单工厂模式【根据名字,生产出不同的Bean…

【MR】搭建 Vision Pro App 开发环境

2023 年 6 月 21 日,Apple 发布 Xcode 15 Beta 2,包含了初代版本的 visionOS 1 beta,标志着苹果正式发布 Vsion Pro 的 SDK。 一、安装 Xcode 15 Beta 2 官网下载,需要 MacOS > 13.4 可以不选 visionOS 1 beta,直接…

ubuntu系统linux下安装指定版本的gcc方法

1:查看当前linux系统的gcc版本 gcc -v2:查看和cuda版本对应的gcc版本 3:安装和cuda对应版本的gcc MAX_GCC_VERSION8 sudo apt install gcc-$MAX_GCC_VERSION g-$MAX_GCC_VERSION4:建立gcc和cuda的软链接 sudo ln -s /usr/bin/gcc-$MAX_GC…

分布式监控系统zabbix应用

文章目录 一、zabbix简介1.1 什么是zabbix1.2 zabbix 监控原理:1.3 Zabbix 6.0 新特性:1.4 Zabbix 6.0 功能组件: 二、部署 zabbix 服务端1.3 部署数据库(要求 MySQL 5.7 或 Mariadb 10.5 及以上版本)1.4 编译安装 zab…

PHPExcel7.4数据导入很容易跳的坑

数据导入插入数据表很容易跳的坑 1:数据表有id并且是自增的,唯一的,这时候excel表格里面也有id,所以导致添加报错, 解决方法,把excel表格的字段名id换成名称_id,或者修改数据表 2.excel有年月…

飞书接入机器人NODE开发自动回复

一、创建飞书应用 1.登录飞书开放平台 进入开发者后台 创建自建应用 2.添加应用能力 选择机器人添加 3.添加事件订阅并根据权限开通权限 此处只添加获取消息事件 4.配置应用服务端地址(当事件触发 会触发设置的地址 并发送事件数据)开启Encrypt Key 实…

简要介绍 | 元学习:学会学习的新途径

注1:本文系“简要介绍”系列之一,仅从概念上对元学习(Meta-Learning)进行非常简要的介绍,不适合用于深入和详细的了解。 元学习:学会学习的新途径 BLOG | Samsung Research 1 背景介绍 元学习(…

Linux基础工具|C/C++编译器:gcc/g++的使用

1.基础使用 gcc是专门用来编译C语言的编译器,而g是编译C的编译器(也可以编译C语言,毕竟C语言兼容C)。Linux下gcc默认使用8字节的指针。 下载安装:gcc的安装指令“sudo yum install gcc”(在Linux里大部分…

【C++】详解set和map

目录 一、什么是关联式容器及树形结构的关联式容器二、键值对三、set1、什么是set2、set的使用1)set的模版参数列表2)set的构造3)set的迭代器4)set的容量5)set的修改操作 3、multiset的介绍及使用 四、map1、什么是map…

vue3怎么把路由的 # 去掉 ? 为什么用mode: ‘history‘没有生效?

代码完成后 请重启项目 一定要重启! 重启!重启! (没重启项目不生效) 1.找到项目中的路由文件 2.引用vueRouter // vue3 import { createRouter, createWebHistory } from "vue-router";var router crea…

C# 交错数组学习

C# 交错数组是元素为数组的数组。 一个示例; using System;class Program {static void Main(string[] args) {string[][] weeks new string[3][];weeks[0] new string[] { "星期日", "星期一", "星期二", "星期三", &…