美文网首页iOS Developer
ijkplayer 源码解析1(重要结构体介绍)

ijkplayer 源码解析1(重要结构体介绍)

作者: pengxiaochao | 来源:发表于2023-02-06 22:58 被阅读0次

ijkplayer作为b站开源的播放器,在业界享誉盛名,深受开发者喜爱,因为底层采用ffmpeg解码,支持主流的流媒体协议,再软件兼容度上非常高;今天我们就针对ijkplayer做一些源码分析,帮助那些喜爱ijkplayer但是苦于2w多行代码无从下手的同学们

系列文章讲解将按照以下顺序进行分析,以方便读者理解;

1.重要结构体分析
2.读数据线程解析
3.音频包解析和音频播放解析
4.视频包解析和视频渲染解析
5.音视频同步解析
6.其它功能解析

播放流程图.jpg

ijkplayer 中使用到的重要数据结构体 VideoState 、Clock、MyAVPacketList 、PacketQueue、FrameQueue、AudioParams 、Decoder、Frame、 AVPacket、AVFrame;
为什么第一篇文章要先介绍一下这些重要的结构体呢,因为ijkplayer内部的流程比较复杂,如果对这些常用的结构体没有一个清晰的了解,很容易在读源码的时候,迷失方向不知所云;

struct VideoState

VideoState 是一个很重要的结构体,在整个播放过程中,存储了播放器需要的很多参数及状态;

typedef struct VideoState {
    SDL_Thread *read_tid;      //读线程句柄
    SDL_Thread _read_tid;  
    AVInputFormat *iformat;    //demuxer
    int abort_request;         // ==1 时退出播放
    int force_refresh;         //==1 时需要立即刷新画面
    int paused;                // ==1 表示暂停,==0 表示播放
    int last_paused;           //暂存 “播放/暂停”状态
    int queue_attachments_req;
    int seek_req;              // 标识一次seek 操作
    int seek_flags;            //seek标识,例如AVSEEK_FLAG_BYTE
    int64_t seek_pos;          //seek的目标位置(位置+增量)
    int64_t seek_rel;          //本次seek的位置增量
    AVFormatContext *ic;       //输入媒体的上下文
    int realtime;              // ==1 表示是否是实时流

    Clock audclk;             //音频时钟
    Clock vidclk;             //视频时钟
    Clock extclk;             //外部时钟

    FrameQueue pictq;         //视频Frame 队列
    FrameQueue subpq;         //字母Frame 队列
    FrameQueue sampq;         //音频Frame 队列

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

    int audio_stream;         //音频流索引

    int av_sync_type;         //音视频同步类型
    void *handle;
    double audio_clock;
    int audio_clock_serial;
    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;        //音频AVPacket队列
    int audio_hw_buf_size;
    uint8_t *audio_buf;        //指向需要重采样的数据
    uint8_t *audio_buf1;       //指向重采样后的数据
    short *audio_new_buf;  /* for soundtouch buf */
    unsigned int audio_buf_size;     //待播放的一帧音频数据
    unsigned int audio_buf1_size;    //申请到的音频缓冲区的大小
    unsigned int audio_new_buf_size;
    int audio_buf_index; /* in bytes */
    int audio_write_buf_size;
    int audio_volume;              //音量
    int muted;                     // 静音表示 ,==1 表示静音,==0 表示非静音
    struct AudioParams audio_src;  //输入媒体的音频参数
    struct AudioParams audio_tgt;  //SDL 支持的输出音频参数
    struct SwrContext *swr_ctx;    //音频重采样上下文
    int frame_drops_early;
    int frame_drops_late;
    int continuous_frame_drops_early;

    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;
    double last_vis_time;

    int subtitle_stream;
    AVStream *subtitle_st;      //字幕流
    PacketQueue subtitleq;      //字幕流AVPacket队列

    double frame_timer;         //记录最后一帧播放的时刻
    double frame_last_returned_time;
    double frame_last_filter_delay;
    int video_stream;            //视频流索引
    AVStream *video_st;          //视频流Stream
    PacketQueue videoq;          //视频AVPacket队列
    double max_frame_duration;   //一帧最大间隔
    struct SwsContext *img_convert_ctx;  //视频尺寸转换上下文

    int eof;                     //是否读取结束标识

    char *filename;              //媒体文件名
    int width, height, xleft, ytop;//宽、高、x起始坐标,y起始坐标
    int step;                    //==1 步进播放模式、 ==0 其他模式

    int last_video_stream, last_audio_stream, last_subtitle_stream;

    SDL_cond *continue_read_thread;  //当队列数据满后休眠,可通过condition 唤起读线程

    /* extra fields */
    SDL_mutex  *play_mutex; // only guard state, do not block any long operation
    SDL_Thread *video_refresh_tid;
    SDL_Thread _video_refresh_tid;

    int buffering_on;
    int pause_req;

    int dropping_frame;
    int is_video_high_fps; // above 30fps
    int is_video_high_res; // above 1080p

    PacketQueue *buffer_indicator_queue;

    volatile int latest_video_seek_load_serial;
    volatile int latest_audio_seek_load_serial;
    volatile int64_t latest_seek_load_start_at;

    int drop_aframe_count;
    int drop_vframe_count;
    int64_t accurate_seek_start_time;
    volatile int64_t accurate_seek_vframe_pts;
    volatile int64_t accurate_seek_aframe_pts;
    int audio_accurate_seek_req;
    int video_accurate_seek_req;
    SDL_mutex *accurate_seek_mutex;
    SDL_cond  *video_accurate_seek_cond;
    SDL_cond  *audio_accurate_seek_cond;
    volatile int initialized_decoder;
    int seek_buffering;
} VideoState;

struct Clock

时钟结构体 Clock,用于音视频同步

typedef struct Clock {
    double pts;           //当前帧(待播放)显示时间戳
    double pts_drift;     //当前时钟与当前系统时钟的差值
    double last_updated;  //最后一根刺更新的系统时钟
    double speed;         //时钟速度(控制播放速度)
    int serial;           //时钟序列号
    int paused;           // 是否暂停  (==1 表示暂停)
    int *queue_serial;    /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;

struct MyAVPacketList

MyAVPacketList 可以理解为队列的一个节点,通过next 字段访问下一个节点;
在整个播放器中PacketQueue 队列中存储的就是MyAVPacketList数据,而MyAVPacketList数据中存放的就是未解码的AVPacket结构体(音频/视频/字幕帧);

typedef struct MyAVPacketList {
    AVPacket pkt;                   //解封装后的数据
    struct MyAVPacketList *next;    //下一个节点
    int serial;                     //播放序列号
} MyAVPacketList;

struct PacketQueue

存储未解码数据AVPacketPacket 队列;(在整个播放过程中充当生产者的角色)
在 ffplay.c 函数中,定义了一些PacketQueue的方法

  • packet_queue_init (初始化队列)
  • packet_queue_destroy (销毁队列)
  • packet_queue_abort (中断队列)
  • packet_queue_start (启用队列)
  • packet_queue_flush (清空队列内的packet)
  • packet_queue_get (获取第一个节点)
  • packet_queue_get_or_buffering (去缓冲等待水位后获取第一个节点)
  • packet_queue_put (入队列一个节点)
  • packet_queue_put_nullpacket (入队列一个空包)
  • packet_queue_put_private (存入一个节点,唤醒packet_queue_get 等待锁)
typedef struct PacketQueue {
    MyAVPacketList *first_pkt, *last_pkt; //队列头、队列尾
    int nb_packets;                       //当前队列元素数量
    int size;                             //当前队列所有元素的大小总和
    int64_t duration;                     //队列内的数据可播放时间
    int abort_request;                    //用户请求退出标识
    int serial;                           //序列号
    SDL_mutex *mutex;                     //用于维护PacketQueue的多线程安全
    SDL_cond *cond;                       //用于读、写线程的相互通知
    MyAVPacketList *recycle_pkt;
    int recycle_count;
    int alloc_count;

    int is_buffer_indicator;
} PacketQueue;

struct FrameQueue

解码后的音视频数据结构体是 AVFrame,字幕是AVSubtitle,解码后的数据存放在FrameQueue该结构体内部;FrameQueue的设计是一个环形缓冲区,数组的大小在初始化的时候设置,每一个FrameQueue有一个写端和一个读端,写端位于解码线程,读端位于播放线程;
在 ffplay.c 函数中,定义了一些FrameQueue的方法

  • frame_queue_init (初始化)
  • frame_queue_destory (销毁)
  • frame_queue_signal (发送唤起信号)
  • frame_queue_peek (获取当前Frame)
  • frame_queue_peek_next (获取当前Frame 的下一个Frame)
  • frame_queue_peek_last (获取上一个Frame)
  • frame_queue_peek_writable (获取一个可写Frame)
  • frame_queue_peek_readable (获取一个可读Frame)
  • frame_queue_push (更新写索引)
  • frame_queue_next (更新读索引)
  • frame_queue_nb_remaining (获取队列Frame节点个数)
  • frame_queue_last_pos (获取最近播放Frame 在媒体文件的位置)
typedef struct FrameQueue {
    Frame queue[FRAME_QUEUE_SIZE];   //Frame队列数组
    int rindex;                      //读索引
    int windex;                      //写索引
    int size;                        //当前队列总帧数
    int max_size;                    //可存储的最大帧数
    int keep_last;                   //队列保持最后一针的数据不释放(==1)
    int rindex_shown;                //初始化为0 ,配合 keep_last =1 使用
    SDL_mutex *mutex;                //互斥量
    SDL_cond *cond;                  //条件变量
    PacketQueue *pktq;               //数据包缓冲队列
} FrameQueue;

AudioParams

音频参数,用于SDL 播放或者 音频重采样的配置结构体

typedef struct AudioParams {
    int freq;                    //采样率
    int channels;                //通道数
    int64_t channel_layout;      //通道布局
    enum AVSampleFormat fmt;     //音频采样格式(采样深度 + 排列模式)
    int frame_size;              //一个单元占用的字节数
    int bytes_per_sec;           //一秒时间的字节数
} AudioParams;

Decoder

  • decoder_init (初始化解码器)
  • decoder_destroy (销毁解码器)
  • decoder_decode_frame (解码)
  • decoder_abort (中止解码器)
typedef struct Decoder {
    AVPacket pkt;                  //当前数据包
    AVPacket pkt_temp;             //
    PacketQueue *queue;            //数据包队列
    AVCodecContext *avctx;         //解码上下文
    int pkt_serial;                //包序列号
    int finished;                  //解码器工作是否完成(==0表示工作中,==1 表示空闲)
    int packet_pending;            // 是否异常 (==0表示异常,==1 表示正常)
    int bfsc_ret;  
    uint8_t *bfsc_data;

    SDL_cond *empty_queue_cond;  
    int64_t start_pts;              //初始化stream的 开始时间
    AVRational start_pts_tb;        //初始化stream 的time_base
    int64_t next_pts;               //最近一次解码后的frame 的pts(有的frame没有pts,用于推算 pts)
    AVRational next_pts_tb;  /next_pts 的time_base
    SDL_Thread *decoder_tid;
    SDL_Thread _decoder_tid;

    SDL_Profiler decode_profiler;
    Uint64 first_frame_decoded_time;
    int    first_frame_decoded;
} Decoder;

struct Frame

typedef struct Frame {
    AVFrame *frame;          //媒体数据帧(音频/视频)
    AVSubtitle sub;          //用于字幕
    int serial;              //播放序列号
    double pts;              //展示时间戳,单位s
    double duration;         //该帧持续时间,单位s
    int64_t pos;             //该帧再文件中的字节位置
#ifdef FFP_MERGE
    SDL_Texture *bmp;
#else
    SDL_VoutOverlay *bmp;
#endif
    int allocated;
    int width;                //媒体帧宽
    int height;               //媒体帧高
    int format;               //媒体类型
    AVRational sar;           //宽高比(未知情况则为0/1)
    int uploaded;             //用于记录该帧是否显示过
} Frame;

相关文章

网友评论

    本文标题:ijkplayer 源码解析1(重要结构体介绍)

    本文链接:https://www.haomeiwen.com/subject/jpstkdtx.html