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

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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容