关于ffmpeg:ffplay整体框架

前言

虽说ffplay是一个简略的播放器,然而其实外部一点也不简略,其实笔者也不晓得说它简略的理由是什么,是因为它只有一个点c文件???

ffplay外部细节繁多,想要深入分析不单单要把握音视频的相干概念,还要把握多线程等相干常识,然而不得不说ffplay的确是学习的播放器开发的一个最佳例子。

倡议想要学习ffplay的童鞋们集成后边浏览边减少正文,多浏览几次,置信你每次浏览都会有不同的了解与播种…

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

ffplay代码大抵架构

对于fplay的架构很难喋喋不休说得分明,而且自己对它的了解也不是很深,加上行笔比拟啰嗦,可能就更加形容不清了,因而笔者制作了一张图,这张图形容了ffplay的三大子线程工作内容,当然如果加上字幕的话有四个子线程。

图导出的可能有点含糊,再加上上传图床后不晓得有没有更加含糊了,想要高清大图的能够后盾留言,加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显示过程中都进行以后的数据包、数据帧是否是属于最新的播放序列的内容的判断,如果不属于的话将被间接抛弃掉,
如果是属于以后播放序列的话就进行入队或解码或播放等相干操作。

举荐浏览

FFmpeg连载1-开发环境搭建
FFmpeg连载2-拆散视频和音频
FFmpeg连载3-视频解码
FFmpeg连载4-音频解码
FFmpeg连载5-音视频编码
FFmpeg连载6-音频重采样
FFmpeg连载8-视频合并以及替换视频背景音乐实战
ffplay调试环境搭建

关注我,一起提高,人生不止coding!!!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理