乐趣区

关于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!!!

退出移动版