IJKPLAYER源码分析-重要字段
前言
本文来介绍下IJKPLAYER的几个重要字段,对这些个字段的理解,将更有助于理解IJKPLAYER播放器内核。
几个字段
AVFormatContext之start_time
在ffmpeg的AVFormatContext结构体的定义:
AVFormatContext {
......
/**
* Position of the first frame of the component, in
* AV_TIME_BASE fractional seconds. NEVER set this value directly:
* It is deduced from the AVStream values.
*
* Demuxing only, set by libavformat.
*/
int64_t start_time;
......
};
换言之,start_time表示的是首帧的pts时间,因为首帧pts不一定是0,所以用此字段来表示首帧pts时间。在IJKPLAYER播放器里,主要用在seek和获取当前播放位置ffp_get_current_position_l时。
这里是在seek的时候,计算实际seek位置时的用法:
int ffp_seek_to_l(FFPlayer *ffp, long msec)
{
assert(ffp);
VideoState *is = ffp->is;
int64_t start_time = 0;
int64_t seek_pos = milliseconds_to_fftime(msec);
int64_t duration = milliseconds_to_fftime(ffp_get_duration_l(ffp));
if (!is)
return EIJK_NULL_IS_PTR;
if (duration > 0 && seek_pos >= duration && ffp->enable_accurate_seek) {
is->drag_to_end = true;
} else {
is->drag_to_end = false;
}
if (!is->ic) {
return EIJK_NULL_IS_PTR;
}
// 取得start_time值
start_time = is->ic->start_time;
// 计算实际seek的位置
if (start_time > 0 && start_time != AV_NOPTS_VALUE)
seek_pos += start_time;
// FIXME: 9 seek by bytes
// FIXME: 9 seek out of range
// FIXME: 9 seekable
av_log(ffp, AV_LOG_DEBUG, "stream_seek %"PRId64"(%d) + %"PRId64", \n", seek_pos, (int)msec, start_time);
stream_seek(is, seek_pos, 0, 0);
return 0;
}
获取当前播放位置:
long ffp_get_current_position_l(FFPlayer *ffp)
{
assert(ffp);
VideoState *is = ffp->is;
if (!is || !is->ic)
return 0;
// 获取start_time值
int64_t start_time = is->ic->start_time;
int64_t start_diff = 0;
// 将start_time转为毫秒
if (start_time > 0 && start_time != AV_NOPTS_VALUE)
start_diff = fftime_to_milliseconds(start_time);
int64_t pos = 0;
double pos_clock = get_master_clock(is);
if (isnan(pos_clock)) {
pos = fftime_to_milliseconds(is->seek_pos);
} else {
pos = pos_clock * 1000;
}
// If using REAL time and not ajusted, then return the real pos as calculated from the stream
// the use case for this is primarily when using a custom non-seekable data source that starts
// with a buffer that is NOT the start of the stream. We want the get_current_position to
// return the time in the stream, and not the player's internal clock.
if (ffp->no_time_adjust) {
return (long)pos;
}
if (pos < 0 || pos < start_diff)
return 0;
// 时间播放位置=当前主时钟的PTS - start_time
int64_t adjust_pos = pos - start_diff;
return (long)adjust_pos;
}
auto_resume
toggle_pause_l此方法控制播放或暂停,若播放,则auto_resume为1,相反则为0:
static void toggle_pause_l(FFPlayer *ffp, int pause_on)
{
VideoState *is = ffp->is;
if (is->pause_req && !pause_on) {
set_clock(&is->vidclk, get_clock(&is->vidclk), is->vidclk.serial);
set_clock(&is->audclk, get_clock(&is->audclk), is->audclk.serial);
}
is->pause_req = pause_on;
// 非pause_on即为auto_resume
ffp->auto_resume = !pause_on;
stream_update_pause_l(ffp);
is->step = 0;
}
此字段用处有2,其一是在read_thread线程进入主loop之前:
/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg) {
......
// 若在播放器初始化时已将该字段置为1,则启动播放
if (ffp->auto_resume) {
ffp_notify_msg1(ffp, FFP_REQ_START);
ffp->auto_resume = 0;
}
......
// 进入线程方法
for (;;) {
}
}
其二,从指定pos开始播放时:
// 从指定pos开始播放时
int ffp_start_from_l(FFPlayer *ffp, long msec)
{
assert(ffp);
VideoState *is = ffp->is;
if (!is)
return EIJK_NULL_IS_PTR;
// 将此字段置为1
ffp->auto_resume = 1;
ffp_toggle_buffering(ffp, 1);
ffp_seek_to_l(ffp, msec);
return 0;
}
何时使用呢?当前仅在点播场景下使用,并且在触发了seek_req请求之后;
static void read_thread(void* arg) {
......
SDL_LockMutex(ffp->is->play_mutex);
if (ffp->auto_resume) {
is->pause_req = 0;
// 若使能了缓冲packet
if (ffp->packet_buffering)
is->buffering_on = 1;
ffp->auto_resume = 0;
// 进入自动播放
stream_update_pause_l(ffp);
}
if (is->pause_req)
step_to_next_frame_l(ffp);
SDL_UnlockMutex(ffp->is->play_mutex);
}
......
}
使用完毕,则将auto_resume置为0,恢复状态。
start_on_prepared
该字段缺省为1,表示使能该选项;
{ "start-on-prepared", "automatically start playing on prepared",
OPTION_OFFSET(start_on_prepared), OPTION_INT(1, 0, 1) },
而后,在read_thread线程里,赋值给is->pause_req字段,来表示自动播放或暂停:
is->pause_req = !ffp->start_on_prepared;
is->pause_req字段含义,此处不再赘述,请参考文章 IJKPLAYER点播原理。
packet_buffering
该字段缺省为1,表示使能播放器缓冲功能:
{ "packet-buffering", "pause output until enough packets have been read after stalling",
OPTION_OFFSET(packet_buffering), OPTION_INT(1, 0, 1) },
换言之,若使能了该选项,音视频缓冲数据若用完,则开始缓冲到队列满,再开始解码播放。
find_stream_info
用于查找音视频流包括字幕流元数据,缺省为1:
{ "find_stream_info", "read and decode the streams to fill missing information with heuristics" ,
OPTION_OFFSET(find_stream_info), OPTION_INT(1, 0, 1) },
然后,会调用avformat_find_stream_info查找音视频流信息;
if (ffp->find_stream_info) {
......
// 调用ffmpeg此方法来查找流信息
err = avformat_find_stream_info(ic, opts);
......
}
值得一提的是,音视频流信息,以video为例,是指分辨率、帧率、码率、视频编码参数是H264还是其他等等。
cover_after_prepared
该字段若为1,则表示将首帧作为封面显示,缺省为0:
{ "cover-after-prepared", "display the first video frame as cover, then toggle pause",
OPTION_OFFSET(cover_after_prepared), OPTION_INT(0, 0, 1) },
infinite_buffer
若使能为1,表示不限制缓冲队列的大小,一般直播不限制缓冲区大小、点播限制,缺省为0:
{ "infbuf", "don't limit the input buffer size (useful with realtime streams)",
OPTION_OFFSET(infinite_buffer), OPTION_INT(0, 0, 1) },
// 若是直播,则使能该选项,不限制缓冲区大小
if (ffp->infinite_buffer < 0 && is->realtime)
ffp->infinite_buffer = 1;
若为点播,则限制缓冲区大小:
/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg) {
/* if the queue are full, no need to read more */
// 点播则限制缓冲区大小
if (ffp->infinite_buffer < 1 && !is->seek_req &&
#ifdef FFP_MERGE
(is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
#else
(is->audioq.size + is->videoq.size + is->subtitleq.size > ffp->dcc.max_buffer_size
#endif
|| ( stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq, MIN_FRAMES)
&& stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq, MIN_FRAMES)
&& stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq, MIN_FRAMES)))) {
if (!is->eof) {
ffp_toggle_buffering(ffp, 0);
}
/* wait 10 ms */
SDL_LockMutex(wait_mutex);
SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
SDL_UnlockMutex(wait_mutex);
continue;
}
}
framedrop
若该值配置>0,表示在必要时丢弃帧,缺省为0,不丢帧:
{ "framedrop", "drop frames when cpu is too slow",
OPTION_OFFSET(framedrop), OPTION_INT(0, -1, 120) },
那么,丢弃的是哪种帧呢?其实是video帧。那丢帧时机呢?其实是在解码后render前。丢帧主要有2处,其一是在ffmpeg解码后:
static int get_video_frame(FFPlayer *ffp, AVFrame *frame)
{
VideoState *is = ffp->is;
int got_picture;
ffp_video_statistic_l(ffp);
// 调用此方法用ffmpeg解码video帧
if ((got_picture = decoder_decode_frame(ffp, &is->viddec, frame, NULL)) < 0)
return -1;
// 解码成功,拿到了画面
if (got_picture) {
double dpts = NAN;
if (frame->pts != AV_NOPTS_VALUE)
dpts = av_q2d(is->video_st->time_base) * frame->pts;
frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
// 若配置了framedrop > 0,会丢弃video帧
if (ffp->framedrop>0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
ffp->stat.decode_frame_count++;
if (frame->pts != AV_NOPTS_VALUE) {
double diff = dpts - get_master_clock(is);
if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&
diff - is->frame_last_filter_delay < 0 &&
is->viddec.pkt_serial == is->vidclk.serial &&
is->videoq.nb_packets) {
is->frame_drops_early++;
is->continuous_frame_drops_early++;
// 最多丢弃framedrop个video帧
if (is->continuous_frame_drops_early > ffp->framedrop) {
is->continuous_frame_drops_early = 0;
} else {
// 统计丢帧数及丢帧率
ffp->stat.drop_frame_count++;
ffp->stat.drop_frame_rate = (float)(ffp->stat.drop_frame_count) / (float)(ffp->stat.decode_frame_count);
av_frame_unref(frame);
got_picture = 0;
}
}
}
}
}
return got_picture;
}
其二,在get_video_frame解码成功之后render之前:
/* called to display each frame */
static void video_refresh(FFPlayer *opaque, double *remaining_time)
{
FFPlayer *ffp = opaque;
VideoState *is = ffp->is;
double time;
Frame *sp, *sp2;
......
if (is->video_st) {
retry:
if (frame_queue_nb_remaining(&is->pictq) == 0) {
// nothing to do, no picture to display in the queue
} else {
double last_duration, duration, delay;
Frame *vp, *lastvp;
/* dequeue the picture */
lastvp = frame_queue_peek_last(&is->pictq);
vp = frame_queue_peek(&is->pictq);
if (vp->serial != is->videoq.serial) {
frame_queue_next(&is->pictq);
goto retry;
}
if (lastvp->serial != vp->serial)
is->frame_timer = av_gettime_relative() / 1000000.0;
if (is->paused && (ffp->first_video_frame_rendered || !ffp->cover_after_prepared))
goto display;
/* compute nominal last_duration */
last_duration = vp_duration(is, lastvp, vp);
delay = compute_target_delay(ffp, last_duration, is);
int64_t time_us = av_gettime_relative();
if (!is->audio_st)
ffp_clock_msg_notify_cycle(ffp, time_us / 1000);
time = time_us / 1000000.0;
if (isnan(is->frame_timer) || time < is->frame_timer)
is->frame_timer = time;
if (time < is->frame_timer + delay) {
*remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
goto display;
}
is->frame_timer += delay;
if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)
is->frame_timer = time;
SDL_LockMutex(is->pictq.mutex);
if (!isnan(vp->pts))
update_video_pts(is, vp->pts, vp->pos, vp->serial);
SDL_UnlockMutex(is->pictq.mutex);
if (frame_queue_nb_remaining(&is->pictq) > 1) {
Frame *nextvp = frame_queue_peek_next(&is->pictq);
duration = vp_duration(is, vp, nextvp);
// 若不是逐帧播放模式,并且framedrop配置了丢帧数&&video播放间隔 > duration,会丢video帧
if(!is->step && (ffp->framedrop > 0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration) {
frame_queue_next(&is->pictq);
goto retry;
}
}
......
}
......
}
那么,为何不能在解码前丢弃video帧呢?那是因为,video帧并不所有帧都是IDR帧,若在解码前丢弃video帧,会造成解码失败、画面异常。
an
若使能了该选项,表示禁用audio,缺省为0:
{ "an", "disable audio",
OPTION_OFFSET(audio_disable), OPTION_INT(0, 0, 1) },
/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg) {
......
// 若audio未禁用,才会取得audio的流索引,从而才会对audio初始化并创建解码线程
if (!ffp->audio_disable)
st_index[AVMEDIA_TYPE_AUDIO] =
av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
st_index[AVMEDIA_TYPE_AUDIO],
st_index[AVMEDIA_TYPE_VIDEO],
NULL, 0);
......
/* open the streams */
if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
// 若成功取到了audio的流索引,才会对audio进行初始化并创建解码线程
stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);
} else {
// 若无audio或禁用,则音视频同步以video为主时钟
ffp->av_sync_type = AV_SYNC_VIDEO_MASTER;
is->av_sync_type = ffp->av_sync_type;
}
......
}
vn
若使能了该选项,则禁用video,缺省为0:
{ "vn", "disable video",
OPTION_OFFSET(video_disable), OPTION_INT(0, 0, 1) },
/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg) {
......
// 若video未禁用,则取得video的流索引
if (!ffp->video_disable)
st_index[AVMEDIA_TYPE_VIDEO] =
av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
......
if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
// 若成功取到video的流索引,则初始化video并创建解码线程
ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);
}
.....
}
nodip
若使能了该选项,则video只解码但不render,缺省为0:
{ "nodisp", "disable graphical display",
OPTION_OFFSET(display_disable), OPTION_INT(0, 0, 1) },
/* called to display each frame */
static void video_refresh(FFPlayer *opaque, double *remaining_time) {
......
display:
/* display picture */
// 若使能了display_disable选项,则不显示video帧
if (!ffp->display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)
video_display2(ffp);
......
}
enable-accurate-seek
若点播使能了该选项,则会做精准SEEK,缺省为0:
{ "enable-accurate-seek", "enable accurate seek",
OPTION_OFFSET(enable_accurate_seek), OPTION_INT(0, 0, 1) },
accurate-seek-timeout
若配置了该选项,表示精准SEEK的等待时长,与enable-accurate-seek配合使用,缺省5s:
{ "accurate-seek-timeout", "accurate seek timeout",
OPTION_OFFSET(accurate_seek_timeout), OPTION_INT(MAX_ACCURATE_SEEK_TIMEOUT, 0, MAX_ACCURATE_SEEK_TIMEOUT) },