当前位置: 首页 > article >正文

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) },

http://www.kler.cn/news/17415.html

相关文章:

  • LeetCode 1003. Check If Word Is Valid After Substitutions【栈,字符串】中等
  • 【GAMES101】03 Transformation
  • 回忆我的爷爷
  • 什么是图数据库Neo4j
  • 力扣---LeetCode141/142. 环形链表 (I)和(II) (代码详解+流程图+数学逻辑拓展)
  • 自动驾驶技术:前景、优势与挑战
  • kubernetes安装
  • Docker 架构
  • Vue生命周期
  • 第二十四回:如何屏蔽事件
  • SpringMVC(后)SSM整合
  • [创新工具和方法论]-01- DOE课程基础知识
  • K8s 安全是云安全的未来
  • AI仿写软件-仿写文章生成器
  • 计算机组成原理4.2.3提高存储器访问速度的措施
  • 送了老弟一台 Linux 服务器,它又懵了!
  • Ae:橡皮擦工具
  • Redis缓存穿透和雪崩
  • 3 文件和目录
  • 归纳截图小结
  • innodb_flush_log_at_trx_commit 和 sync_binlog 参数解析
  • 数字中国建设峰会|大模型带来产业智能化新机遇
  • 【Linux0.11代码分析】03 之 setup.s 启动流程
  • C++——类和对象(3)
  • 初识 OPC
  • 05_Uboot源码目录分析
  • Java 版 spring cloud 工程系统管理 工程项目管理系统源码 工程项目各模块及其功能点清单
  • 2.压力测试+优化(Jmeter)
  • ChatGPT提示词工程(四):Inferring推断
  • MySQL基础