ijkplayer
作为b站开源的播放器,在业界享誉盛名,深受开发者喜爱,因为底层采用ffmpeg解码,支持主流的流媒体协议,再软件兼容度上非常高;今天我们就针对ijkplayer
做一些源码分析,帮助那些喜爱ijkplayer
但是苦于2w多行代码无从下手的同学们
系列文章讲解将按照以下顺序进行分析,以方便读者理解;
1.重要结构体分析
2.读数据线程解析
3.音频包解析和音频播放解析
4.视频包解析和视频渲染解析
5.音视频同步解析
6.其它功能解析
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
存储未解码数据AVPacket
的Packet
队列;(在整个播放过程中充当生产者的角色)
在 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;