美文网首页
IJK核心代码剖析2019-06-08

IJK核心代码剖析2019-06-08

作者: laixh | 来源:发表于2019-06-08 11:02 被阅读0次

    一、总体说明

    1.打开ijkplayer,可看到其主要目录结构如下:

    tool - 初始化项目工程脚本

    config - 编译ffmpeg使用的配置文件

    extra - 存放编译ijkplayer所需的依赖源文件, 如ffmpeg、openssl等

    ijkmedia - 核心代码

    ijkplayer - 播放器数据下载及解码相关

    ijksdl - 音视频数据渲染相关

    ios - iOS平台上的上层接口封装以及平台相关方法

    android - android平台上的上层接口封装以及平台相关方法

    在功能的具体实现上,iOS和Android平台的差异主要表现在视频硬件解码以及音视频渲染方面,两者实现的载体区别如下表所示:

    Platform HardwareCodec VideoRender AudioOutput

    iOS VideoToolBox OpenGL ES AudioQueue

    Android MediaCodec OpenGL ES/MediaCodec OpenSL ES/AudioTrack

    2.IJK模块

    初始化模块:初始化完成的主要工作就是创建IJKMediaPlayer播放器对象,创建图像渲染对象SDL_Vout,创建平台相关的IJKFF_Pipeline对象

    核心模块:音视频数据读取、音视频解码、音视频渲染及同步

    事件处理:在播放过程中,某些行为的完成或者变化,如prepare完成,开始渲染等,需要以事件形式通知到外部,以便上层作出具体的业务处理

    二、初始化流程

    在该方法中主要完成了三个动作:

    1.创建IJKMediaPlayer对象;

    2.创建图像渲染对象SDL_Vout;

    3.创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分

    IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))//ijkmp_android_create(message_loop)

    //(21)通过ffp_create方法创建了FFPlayer对象,并设置消息处理函数

    ijkmp_create(msg_loop)

    IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));

    mp->ffplayer = ffp_create();

    mp->msg_loop = msg_loop;

    //(22)创建图像渲染对象SDL_Vout

    mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();

    return SDL_VoutAndroid_CreateForANativeWindow();

    SDL_Vout *vout = SDL_Vout_CreateInternal(sizeof(SDL_Vout_Opaque));

    //函数指针初始化

    vout->opaque_class = &g_nativewindow_class;

    vout->create_overlay= func_create_overlay;

    vout->free_l   = func_free_l;

    vout->display_overlay = func_display_overlay;

    //(23)创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分

    mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);

    IJKFF_Pipeline *pipeline = ffpipeline_alloc(&g_pipeline_class, sizeof(IJKFF_Pipeline_Opaque));

    //函数指针初始化

    pipeline->func_open_video_decoder = func_open_video_decoder;

    至此已经完成了ijkplayer播放器初始化的相关流程,简单来说,就是创建播放器对象,完成音视频解码、渲染的准备工作。

    static JNINativeMethod g_methods[] = {

      {

       "_setDataSource",

      "(LV",

       !!!(void *) IjkMediaPlayer_setDataSourceAndHeaders

      },

      { "_setDataSourceFd",   "(I)V",  (void *) IjkMediaPlayer_setDataSourceFd },

      { "_setDataSource",  "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },

      { "_setAndroidIOCallback","(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },

      { "_setVideoSurface",  "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },

      { "_prepareAsync",   "()V", (void *) IjkMediaPlayer_prepareAsync },//00

      { "_start",      "()V", (void *) IjkMediaPlayer_start },

      { "_stop",      "()V",  (void *) IjkMediaPlayer_stop },

      { "seekTo",      "(J)V", (void *) IjkMediaPlayer_seekTo },

      { "_pause",      "()V", (void *) IjkMediaPlayer_pause },

      { "isPlaying",    "()Z", (void *) IjkMediaPlayer_isPlaying },

      { "getCurrentPosition",  "()J", (void *) IjkMediaPlayer_getCurrentPosition },

      { "getDuration",    "()J",  (void *) IjkMediaPlayer_getDuration },

      { "_release",     "()V", (void *) IjkMediaPlayer_release },

      { "_reset",      "()V", (void *) IjkMediaPlayer_reset },

      { "setVolume",    "(FF)V",(void *) IjkMediaPlayer_setVolume },

      { "getAudioSessionId",  "()I",  (void *) IjkMediaPlayer_getAudioSessionId },

      { "native_init",    "()V",  (void *) IjkMediaPlayer_native_init },

      { "native_setup",    "(LV", (void *) IjkMediaPlayer_native_setup },

      { "native_finalize",  "()V", (void *) IjkMediaPlayer_native_finalize },

      { "_setOption",    "(IL IjkMediaPlayer_setOption },

      { "_setOption",    "(ILV",     (void *) IjkMediaPlayer_setOptionLong },

      { "_getColorFormatName", "(I)Ljava/lang/String;", (void *) IjkMediaPlayer_getColorFormatName },

      { "_getVideoCodecInfo",  "()Ljava/lang/String;",  (void *) IjkMediaPlayer_getVideoCodecInfo },

      { "_getAudioCodecInfo",  "()Ljava/lang/String;",  (void *) IjkMediaPlayer_getAudioCodecInfo },

      { "_getMediaMeta",   "()Landroid/os/Bundle;", (void *) IjkMediaPlayer_getMediaMeta },

      { "_setLoopCount",   "(I)V",       (void *) IjkMediaPlayer_setLoopCount },

      { "_getLoopCount",   "()I",      (void *) IjkMediaPlayer_getLoopCount },

      { "_getPropertyFloat",  "(IF)F",      (void *) ijkMediaPlayer_getPropertyFloat },

      { "_setPropertyFloat",  "(IF)V",      (void *) ijkMediaPlayer_setPropertyFloat },

      { "_getPropertyLong",   "(IJ)J",      (void *) ijkMediaPlayer_getPropertyLong },

      { "_setPropertyLong",   "(IJ)V",      (void *) ijkMediaPlayer_setPropertyLong },

      { "_setStreamSelected",  "(IZ)V",      (void *) ijkMediaPlayer_setStreamSelected },

      { "native_profileBegin", "(LV", (void *) IjkMediaPlayer_native_profileBegin },

      { "native_profileEnd",  "()V",       (void *) IjkMediaPlayer_native_profileEnd },

      { "native_setLogLevel",  "(I)V",       (void *) IjkMediaPlayer_native_setLogLevel },

      { "_setFrameAtTime",  "(L IjkMediaPlayer_setFrameAtTime },

    };

    三、核心代码剖析

    //ijkplayer实际上是基于ffplay.c实现的,

    //本章节将以该文件为主线,从数据接收(数据读取)、音视频解码、音视频渲染及同步这三大方面进行讲解,要求读者有基本的ffmpeg知识。

    IjkMediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)

    int ijkmp_prepare_async(IjkMediaPlayer *mp)

    static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)

    //开始播放时,启动消息线程

    mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");

    int ret = mp->msg_loop(arg);

    //该方法是启动播放器的入口函数,在此会设置player选项,打开audio output,最重要的是调用stream_open方法。

    int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)

    //音频输出:ijkplayer中Android平台使用OpenSL ES或AudioTrack输出音频,iOS平台使用AudioQueue输出音频。

    ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);

    return pipeline->func_open_audio_output(pipeline, ffp);

    if (ffp->opensles) {

    aout = SDL_AoutAndroid_CreateForOpenSLES();

    } else {

    //默认硬解,主要完成的是创建SDL_Aout对象

    //回到ffplay.c中,如果发现待播放的文件中含有音频,那么在调用stream_component_open打开解码器时,该方法里面也调用audio_open打开了audio output设备。

    aout = SDL_AoutAndroid_CreateForAudioTrack();

    }

    static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)

    从代码中可以看出,stream_open主要做了以下几件事情:

    (1)创建存放video/audio解码前数据的videoq/audioq

    (2)创建存放video/audio解码后数据的pictq/sampq

    (3)创建视频渲染线程video_refresh_thread

    (4)创建读数据线程read_thread

    if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)

    goto fail;

    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)

    goto fail;

    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)

    goto fail;

    //视频的处理流程:

    //1.在decoder_decode_frame 方法中从解码前的video queue中取出一帧数据,

    //2.送入decoder进行解码,解码后的数据在ffplay_video_thread中送入pictq,

    //3.解码后的数据被送到pictq后,video_image_display2函数会取出最新的解码后的视频数据

    //4.然后交给SDL通过openGL来进行渲染

    秒开,首先,我们知道在ijkplayer默认视频同步到音频,在video_refresh_thread对视频做了同步,

    我们把视频前两帧数据不做同步,即时刷新,这样能大大加快首屏时间,其次我们设置probesize大小,

    如果probesize不设置的话,avformat_find_stream_info会消耗很长时间,这里建议如果只是音频,设置1k,

    如果是音视频,设置为64k,更进一步的修改是自己设置相关解码属性,不用avformat_find_stream_info获取,

    最后我们还可以对前两帧的刷新时机进行进一步优化,现在通过sleep来控制,可以换成信号量,解码之后,

    立即通知开始执行渲染,改完这些之后基本上首屏能在500ms内。

    is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");

    //音视频同步

    参考时钟的选择也有多种方式:

    选取视频时间戳作为参考时钟源

    选取音频时间戳作为参考时钟源

    选取外部时间作为参考时钟源

    考虑人对视频、和音频的敏感度,在存在音频的情况下,优先选择音频作为主时钟源。

    video_refresh(ffp, &remaining_time);

    (312)

    //lastvp是上一帧,vp是当前帧,//循环队列,看不懂???

    //1.frame_queue_peek_last表示从循环队列帧里面取出当前需要显示的上一帧视频

    //2.frame_queue_peek表示从循环队列帧里面取出当前需要显示的一帧视频

    //3.frame_queue_peek_next表示从循环队列帧里面取出当前需要显示的下一帧视频

    //4.frame_queue_next从帧队列中取出帧之后的参数操作

    //5.Frame *frame_queue_peek_writable(FrameQueue *f)//返回要填充的frame_queue中的Frame。

    //6.frame_queue_push放帧到队列中,frame_queue_peek_writable之后的参数操作,windex++

    lastvp = frame_queue_peek_last(&is->pictq);

    vp = frame_queue_peek(&is->pictq);

    //last_duration则是根据当前帧和上一帧的pts,计算出来上一帧的显示时间,

    //经过compute_target_delay方法,计算出显示当前帧需要等待的时间

    在compute_target_delay方法中,如果发现当前主时钟源不是video,则计算当前视频时钟与主时钟的差值:

    如果当前视频帧落后于主时钟源,则需要减小下一帧画面的等待时间;

    如果视频帧超前,并且该帧的显示时间大于显示更新门槛,则显示下一帧的时间为超前的时间差加上上一帧的显示时间

    如果视频帧超前,并且上一帧的显示时间小于显示更新门槛,则采取加倍延时的策略。

    last_duration = vp_duration(is, lastvp, vp);

    delay = compute_target_delay(ffp, last_duration, is);

    //frame_timer实际上就是上一帧的播放时间,

    //而frame_timer + delay实际上就是当前这一帧的播放时间,

    //(312)如果系统时间还没有到当前这一帧的播放时间,直接跳转至display,

    //而此时is->force_refresh变量为0,不显示当前帧,

    //进入video_refresh_thread中下一次循环,并睡眠等待。

    time= av_gettime_relative()/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;

    }

    //(313)如果当前这一帧的播放时间已经过了,

    //并且其和当前系统时间的差值超过了AV_SYNC_THRESHOLD_MAX,

    //则将当前这一帧的播放时间改为系统时间,并在后续判断是否需要丢帧,

    //其目的是为后面帧的播放时间重新调整frame_timer,

    //如果缓冲区中有更多的数据,并且当前的时间已经大于当前帧的持续显示时间,

    //则丢弃当前帧,尝试显示下一帧。

    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);

    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;

    }

    }

    //(314)最后渲染图像的方法

    video_display2(ffp);

    video_image_display2(ffp);

    //数据传递:is->pictq 到 vp->bmp 到 overlay

    //(3131)从pictq中读取当前需要显示视频帧//f->rindex

    vp = frame_queue_peek_last(&is->pictq);

    //(3132)进行绘制////显示每帧图片

    SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp);

    return vout->display_overlay(vout, overlay);

    //在ijksdl_vout_android_nativewindow.c/SDL_VoutAndroid_CreateForANativeWindow()函数中

    vout->display_overlay = func_display_overlay;

    //调用OpengGL绘制图像

    int retval = func_display_overlay_l(vout, overlay);

    return IJK_EGL_display(opaque->egl, native_window, overlay);

    //(or)return SDL_Android_NativeWindow_display_l(native_window, overlay);

    //read_thread()调用了如下函数:

    1.avformat_open_input():打开媒体。

    2.avformat_find_stream_info():获得媒体信息。

    3.av_dump_format():输出媒体信息到控制台。

    4.stream_component_open():分别打开视频/音频/字幕解码线程。

    5.refresh_thread():视频刷新线程。

    6.av_read_frame():获取一帧压缩编码数据(即一个AVPacket)。

    7.packet_queue_put():根据压缩编码数据类型的不同(视频/音频/字幕),放到不同的PacketQueue中。

    is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");//创建数据读取线程

    ic = avformat_alloc_context();//(41)创建上下文结构体,这个结构体是最上层的结构体,表示输入上下文

    ic->interrupt_callback.callback = decode_interrupt_cb;//(42)设置中断函数,如果出错或者退出,就可以立刻退出

    ic->interrupt_callback.opaque = is;

    //(43)打开文件,主要是探测协议类型,如果是网络文件则创建网络链接等

    err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);

    //(44)探测媒体类型,可得到当前文件的封装格式,音视频编码参数等信息

    err = avformat_find_stream_info(ic, opts);

    //(45)打开ffmpeg视频、音频解码器。在此会打开相应解码器,并创建相应的解码线程。

    int stream_component_open(FFPlayer *ffp, int stream_index)

    //stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);

    //ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);

    //stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]);

    codec = avcodec_find_decoder(avctx->codec_id);//获取音视频编码格式

    if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) {//用一个编码格式打开一个编码文件

    goto fail;

    }

    switch (avctx->codec_type) {

    case AVMEDIA_TYPE_AUDIO:

    //(451)打开音频解码器,创建audio解码线程

    ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)

    //(4511)配置了音频输出的相关参数SDL_AudioSpec

    SDL_AudioSpec wanted_spec;

    wanted_spec.format = AUDIO_S16SYS;

    wanted_spec.silence = 0;

    wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AoutGetAudioPerSecondCallBacks(ffp->aout)));

    //(4513)AudioTrack(Android)/AudioQueue(IOS)模块在工作过程中,通过不断的callback来获取pcm数据进行播放

    wanted_spec.callback = sdl_audio_callback;

    //(4512)用此函数来打开音响设备

    while (SDL_AoutOpenAudio(ffp->aout, &wanted_spec, &spec) < 0) {}

    return aout->open_audio(aout, desired, obtained);

    ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")//audio的解码线程

    case AVMEDIA_TYPE_VIDEO:

    //(452)创建IJKFF_Pipenode

    ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);

    return pipeline->func_open_video_decoder(pipeline, ffp);//ffpipeline_anroid.c

    //iOS平台上硬解使用VideoToolbox,Android平台上使用MediaCodec。ijkplayer中的音频解码只支持软解,暂不支持硬解。

    // 根据是否设置mediacodec参数决定是否启用硬解码 //默认软解

    if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2){

    ALOGD("laixh1 ffpipenode_create_video_decoder_from_android_mediacodec()\n");

    node = ffpipenode_create_video_decoder_from_android_mediacodec(ffp, pipeline, opaque->weak_vout);

    }

    if (!node) {//如果ffpipenode_create_video_decoder_from_android_mediacodec返回的node为NULL 那么就会走软解

    ALOGD("laixh2 ffpipenode_create_video_decoder_from_ffplay()\n");

    node = ffpipenode_create_video_decoder_from_ffplay(ffp);

    }

    //(453)video的解码线程

    //不管视频解码还是音频解码,其基本流程都是从解码前的数据缓冲区中取出一帧数据进行解码,完成后放入相应的解码后的数据缓冲区

    ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")

    if (ffp->node_vdec)

    {

    ret = ffpipenode_run_sync(ffp->node_vdec);

    //func_run_sync取决于播放前配置的软硬解。

    //软解:调用ffpipenode_ffplay_vdec调用ffpipenode_ffplay_vdec.c中的函数定义;//默认软解

    //硬解:调用ffpipenode_ffplay_vdec调用ffpipenode_ffplay_mediacodec.c中的函数定义;

    return node->func_run_sync(node);

    return ffp_video_thread(opaque->ffp);

    return ffplay_video_thread(ffp);//ff_ffplay.c

    for (;;) {

    //(4531)该方法中从解码前的video queue中取出一帧数据,送入decoder进行解码,

    ret = get_video_frame(ffp, frame);

    got_picture = decoder_decode_frame(ffp, &is->viddec, frame, NULL)

    AVPacket pkt;

    for (;;) {

    //(45311)从解码前的video queue中取出一帧数据

    do {

    packet_queue_get_or_buffering(ffp, d->queue, &pkt, &d->pkt_serial, &d->finished) < 0)

    while(1){

    //获取压缩编码数据(一个AVPacket)

    int new_packet = packet_queue_get(q, pkt, 0, serial);

    }

    }while (d->queue->serial != d->pkt_serial);

    //(45312)将流数据输出给解码器进行解码

    ffmpeg中解码的API之前的是avcodec_decode_video2()和avcodec_decode_audio4(),

    现在使用avcodec_send_packet()/ avcodec_receive_frame()来代替原有的接口

    // 1)对于解码,请调用avcodec_send_packet()以在AVPacket中给出解码器原始的压缩数据。

    // 2)对于编码,请调用avcodec_send_frame()为编码器提供包含未压缩音频或视频的AVFrame

    avcodec_send_packet和avcodec_receive_frame调用关系并不一定是一对一的,

    比如一些音频数据一个AVPacket中包含了1秒钟的音频,调用一次avcodec_send_packet之后,

    可能需要调用25次 avcodec_receive_frame才能获取全部的解码音频数据

    do {

    ret = avcodec_receive_frame(d->avctx, frame);

    }while (ret != AVERROR(EAGAIN));

    avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)

    decode_receive_frame_internal(avctx, avci->buffer_frame);

    av_packet_unref(&pkt);

    }

    //(4532)默认MAX_RETRY_CONVERT_IMAGE=3//laixh秒开优化改成1,验证效果???没有效果!!

    while (retry_convert_image <= MAX_RETRY_CONVERT_IMAGE) {

    ret = convert_image(ffp, frame, (int64_t)pts, frame->width, frame->height);

    //FFmpeg里面的sws_scale库可以在一个函数里面同时实现:

    //1.图像色彩空间转换;2.分辨率缩放;3.前后图像滤波处理。

    //其核心函数主要有三个:sws_getContext():初始化一个SwsContext;sws_scale():处理图像数据;sws_freeContext():释放一个SwsContext。

    //初始化一个SwsContext //src_frame->format转AV_PIX_FMT_RGB24

    img_info->frame_img_convert_ctx = sws_getContext(width,height,src_frame->format,dst_width,dst_height,AV_PIX_FMT_RGB24,SWS_BICUBIC,NULL, NULL,NULL)

    //处理图像数据。

    //参数struct SwsContext *c,为上面sws_getContext函数返回值;

    //参数const uint8_t *const srcSlice[], const int srcStride[]定义输入图像信息(当前处理区域的每个通道数据指针,每个通道行字节数)

    //参数uint8_t *const dst[], const int dstStride[]定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)

    ret = sws_scale(img_info->frame_img_convert_ctx,(const uint8_t * const *) src_frame->data,src_frame->linesize,0,src_frame->height,dst_frame->data, dst_frame->linesize);

    //avctx:编码器的AVCodecContext;avpkt:编码输出的AVPacket;frame:编码输入的AVFrame;got_packet_ptr:成功编码一个AVPacket的时候设置为1。

    //函数返回0代表编码成功。//不是解码吗?为什么还要进行编码???

    ret = avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,const AVFrame *frame, int *got_packet_ptr);

    if (ret >= 0 && got_packet > 0) {

    fd = open(file_path, O_RDWR | O_TRUNC | O_CREAT, 0600);

    write(fd, avpkt.data, avpkt.size);

    }

    //释放一个SwsContext。

    sws_freeContext()

    }

    //(4533)解码后的数据在ffplay_video_thread中送入pictq

    ret = queue_picture(ffp, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);

    //返回要填充的frame_queue中的Frame

    //Frame *frame_queue_peek_writable(FrameQueue *f)

    vp = frame_queue_peek_writable(&is->pictq)

    //放帧到队列中//frame_queue_peek_writable之后的参数操作,windex++

    frame_queue_push(&is->pictq);

    }

    }

    //重复(46)、(47)步,即可不断获取待播放的数据。

    //(46)读取媒体数据,得到的是音视频分离的解码前数据

    ret = av_read_frame(ic, pkt);

    //(47)将音视频数据分别送入相应的queue中

    if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {

    packet_queue_put(&is->audioq, pkt);

    } else if (pkt->stream_index == is->video_stream && pkt_in_play_range

     && !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) {

    packet_queue_put(&is->videoq, pkt);

    } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {

    packet_queue_put(&is->subtitleq, pkt);

    } else {

    av_packet_unref(pkt);

    }

    四、事件处理

    //在播放过程中,某些行为的完成或者变化,如prepare完成,开始渲染等,需要以事件形式通知到外部,以便上层作出具体的业务处理。

    //ijkplayer支持的事件比较多,具体定义在ijkplayer/ijkmedia/ijkplayer/ff_ffmsg.h中

    #define FFP_MSG_FLUSH        0

    #define FFP_MSG_ERROR        100 

    #define FFP_MSG_PREPARED      200

    #define FFP_MSG_COMPLETED      300

    #define FFP_MSG_VIDEO_SIZE_CHANGED  400 

    #define FFP_MSG_SAR_CHANGED     401 

    #define FFP_MSG_VIDEO_RENDERING_START  402

    #define FFP_MSG_AUDIO_RENDERING_START  403

    #define FFP_MSG_VIDEO_ROTATION_CHANGED 404

    #define FFP_MSG_BUFFERING_START    500

    #define FFP_MSG_BUFFERING_END     501

    #define FFP_MSG_BUFFERING_UPDATE   502

    #define FFP_MSG_BUFFERING_BYTES_UPDATE 503

    #define FFP_MSG_BUFFERING_TIME_UPDATE  504

    #define FFP_MSG_SEEK_COMPLETE     600 

    #define FFP_MSG_PLAYBACK_STATE_CHANGED 700

    #define FFP_MSG_TIMED_TEXT     800

    #define FFP_MSG_VIDEO_DECODER_OPEN  10001

    #define MEDIA_INFO_UNKNOWN = 1,

    #define MEDIA_INFO_VIDEO_RENDERING_START = 3, // 第一帧视频数据渲染时间

    #define MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001,

    #define MEDIA_INFO_AUDIO_RENDERING_START= 10002,

    #define MEDIA_INFO_AUDIO_DECODED_START= 10003,

    #define MEDIA_INFO_VIDEO_DECODED_START= 10004, //第一帧视频解码完成时间

    #define MEDIA_INFO_OPEN_INPUT    = 10005, //avformat_open_input执行完成时间

    #define MEDIA_INFO_FIND_STREAM_INFO  = 10006, //avformat_find_stream_info执行完成时间

    #define MEDIA_INFO_COMPONENT_OPEN   = 10007, //IO设备操作完成时间

    #define MEDIA_INFO_VIDEO_FIRSTPKT_GOT //首帧获取时间

    //ijkmp_android_create(message_loop)

    IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))

    ijkmp_create(msg_loop)

    //(41)消息上报初始化

    mp->msg_loop = msg_loop;

    mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();

    return SDL_VoutAndroid_CreateForANativeWindow();

    //函数指针初始化

    vout->opaque_class = &g_nativewindow_class;

    vout->create_overlay= func_create_overlay;

    vout->free_l   = func_free_l;

    vout->display_overlay = func_display_overlay;

    IjkMediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)

    int ijkmp_prepare_async(IjkMediaPlayer *mp)

    static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)

    //(42)开始播放时,启动消息线程

    mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");

    int ret = mp->msg_loop(arg);

    message_loop

    message_loop_n

    while (1) {

    //(44)最后是在该方法中读取消息,并采用notification通知到APP上层

    int retval = ijkmp_get_msg(mp, &msg, 1);

    switch (msg.what) {

    case FFP_MSG_VIDEO_RENDERING_START:

    MPTRACE("FFP_MSG_VIDEO_RENDERING_START:\n");

    post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_VIDEO_RENDERING_START, 0);

    break;

    case FFP_MSG_OPEN_INPUT:

    MPTRACE("FFP_MSG_OPEN_INPUT:\n");

    post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_OPEN_INPUT, 0);

    break;

    case FFP_MSG_FIND_STREAM_INFO:

    MPTRACE("FFP_MSG_FIND_STREAM_INFO:\n");

    post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_FIND_STREAM_INFO, 0);

    break;

    }

    }

    //(43)ffplay.c中上报PREPARED完成

    ffp_notify_msg1(ffp, FFP_MSG_PREPARED);

    ffp_notify_msg1(ffp, FFP_MSG_VIDEO_RENDERING_START);

    //将事件及其参数封装成了AVMessge对象

    msg_queue_put_simple3(&ffp->msg_queue, what, 0, 0);

    msg_init_msg(&msg);

    msg_queue_put(q, &msg);

    //消息对象放入消息队列

    ret = msg_queue_put_private(q, msg);

    预加载接口

      public void doPreload(String url, int tsindex){

       //stop

       _doPreload(url, tsindex);

      }

    //预缓存清除接口

      public void deleteCache() {

       _deleteCache();

      }

    相关文章

      网友评论

          本文标题:IJK核心代码剖析2019-06-08

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