【六】【vlc-android】vlc的decoder控制层传输数据与ffmpeg视频解码模块decoder层的数据交互流程源码分析

【以mp4文件格式和H264编码的本地文件为例展开分析】

1、接着前面章节的p_dec->pf_decode( p_dec, p_block )实现分析:
由此前分析decoder初始化过程可知:decoder加载了具体的解码模块,因此该方法是在解码模块加载时模块初始化入口方法中进行赋值的,因此此处以分析h264格式来进行编码流程分析。
首先找到ffmpeg中对应的libavcodec模块有三个:video、audio、subtitle(字幕信息)。
通过此前分析过的模块加载方式,可找到ffmpeg解码和编码模块的初始化加载如下:

// 位于[vlc/modules/codec/avcodec/avcodec.c]

vlc_module_begin ()
    set_shortname( "FFmpeg")
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_VCODEC )
    /* decoder main module */
    set_description( N_("FFmpeg audio/video decoder") )
    set_help( MODULE_DESCRIPTION )
    set_section( N_("Decoding") , NULL )

    // video
    add_shortcut("ffmpeg")
    set_capability("video decoder", 70)
    set_callbacks(InitVideoDec, EndVideoDec)

    // audio
    add_submodule()
    add_shortcut("ffmpeg")
    set_capability("audio decoder", 70)
    set_callbacks(InitAudioDec, EndAudioDec)

    // subtitle
    add_submodule()
    add_shortcut("ffmpeg")
    set_capability("spu decoder", 70)
    set_callbacks(InitSubtitleDec, EndSubtitleDec)

// ...省略其他代码
vlc_module_end ()

有上面可知三个解码模块加载的初始化入口方法。
该章节分析video解码:
2、InitVideoDec的实现分析:[vlc/modules/codec/avcodec/video.c]

/*****************************************************************************
 * InitVideo: initialize the video decoder
 *****************************************************************************
 * the ffmpeg codec will be opened, some memory allocated. The vout is not yet
 * opened (done after the first decoded frame).
 *****************************************************************************/
int InitVideoDec( vlc_object_t *obj )
{
    
    
    decoder_t *p_dec = (decoder_t *)obj;
    // 具体的解码器对象
    const AVCodec *p_codec;
    // 初始化libavcocdec模块,根据track类型和avcodec的解码ID类型,
    // 来初始化对应解码器,并设置debug等级参数等,此处为分析video解码器
    AVCodecContext *p_context = ffmpeg_AllocContext( p_dec, &p_codec );
    if( p_context == NULL )
        return VLC_EGENERIC;

    int i_val;

    /* Allocate the memory needed to store the decoder's structure */
    decoder_sys_t *p_sys = calloc( 1, sizeof(*p_sys) );
    if( unlikely(p_sys == NULL) )
    {
    
    
        avcodec_free_context( &p_context );
        return VLC_ENOMEM;
    }

    p_dec->p_sys = p_sys;
    p_sys->p_context = p_context;
    p_sys->p_codec = p_codec;
    p_sys->p_va = NULL;
    // 初始化信号锁
    vlc_sem_init( &p_sys->sem_mt, 0 );

    /* ***** Fill p_context with init values ***** */
    p_context->codec_tag = ffmpeg_CodecTag( p_dec->fmt_in.i_original_fourcc ?
                                p_dec->fmt_in.i_original_fourcc : p_dec->fmt_in.i_codec );

    // 获取ffmpeg插件/组件配置信息
    /*  ***** Get configuration of ffmpeg plugin ***** */
    p_context->workaround_bugs =
        var_InheritInteger( p_dec, "avcodec-workaround-bugs" );
    p_context->err_recognition =
        var_InheritInteger( p_dec, "avcodec-error-resilience" );

    if( var_CreateGetBool( p_dec, "grayscale" ) )
        p_context->flags |= AV_CODEC_FLAG_GRAY;

    /* ***** Output always the frames ***** */
    p_context->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;

    // 对设置的帧类型【I、P、B帧】进行跳过过滤循环
    i_val = var_CreateGetInteger( p_dec, "avcodec-skiploopfilter" );
    if( i_val >= 4 ) p_context->skip_loop_filter = AVDISCARD_ALL;
    else if( i_val == 3 ) p_context->skip_loop_filter = AVDISCARD_NONKEY;
    else if( i_val == 2 ) p_context->skip_loop_filter = AVDISCARD_BIDIR;
    else if( i_val == 1 ) p_context->skip_loop_filter = AVDISCARD_NONREF;
    else p_context->skip_loop_filter = AVDISCARD_DEFAULT;

    if( var_CreateGetBool( p_dec, "avcodec-fast" ) )
        p_context->flags2 |= AV_CODEC_FLAG2_FAST;

    // 帧过滤方式算法
    /* ***** libavcodec frame skipping ***** */
    p_sys->b_hurry_up = var_CreateGetBool( p_dec, "avcodec-hurry-up" );
    p_sys->b_show_corrupted = var_CreateGetBool( p_dec, "avcodec-corrupted" );

    // 对设置的帧类型【I、P、B帧】进行跳过解码
    i_val = var_CreateGetInteger( p_dec, "avcodec-skip-frame" );
    if( i_val >= 4 ) p_sys->i_skip_frame = AVDISCARD_ALL;
    else if( i_val == 3 ) p_sys->i_skip_frame = AVDISCARD_NONKEY;
    else if( i_val == 2 ) p_sys->i_skip_frame = AVDISCARD_BIDIR;
    else if( i_val == 1 ) p_sys->i_skip_frame = AVDISCARD_NONREF;
    else if( i_val == -1 ) p_sys->i_skip_frame = AVDISCARD_NONE;
    else p_sys->i_skip_frame = AVDISCARD_DEFAULT;
    p_context->skip_frame = p_sys->i_skip_frame;

    // 对设置的帧类型【I、P、B帧】进行跳过离散余弦逆变换或反量化
    i_val = var_CreateGetInteger( p_dec, "avcodec-skip-idct" );
    if( i_val >= 4 ) p_context->skip_idct = AVDISCARD_ALL;
    else if( i_val == 3 ) p_context->skip_idct = AVDISCARD_NONKEY;
    else if( i_val == 2 ) p_context->skip_idct = AVDISCARD_BIDIR;
    else if( i_val == 1 ) p_context->skip_idct = AVDISCARD_NONREF;
    else if( i_val == -1 ) p_context->skip_idct = AVDISCARD_NONE;
    else p_context->skip_idct = AVDISCARD_DEFAULT;

    // libavcodec直接渲染参数设置
    /* ***** libavcodec direct rendering ***** */
    p_sys->b_direct_rendering = false;
    atomic_init(&p_sys->b_dr_failure, false);
    if( var_CreateGetBool( p_dec, "avcodec-dr" ) &&
       (p_codec->capabilities & AV_CODEC_CAP_DR1) &&
        /* No idea why ... but this fixes flickering on some TSCC streams */
        p_sys->p_codec->id != AV_CODEC_ID_TSCC &&
        p_sys->p_codec->id != AV_CODEC_ID_CSCD &&
        p_sys->p_codec->id != AV_CODEC_ID_CINEPAK )
    {
    
    
        /* Some codecs set pix_fmt only after the 1st frame has been decoded,
         * so we need to do another check in ffmpeg_GetFrameBuf() */
        p_sys->b_direct_rendering = true;
    }

    // 获取像素格式信息方法指针赋值
    // 注意:此为ffmpeg的buffer数据回调,见ffmpeg章节分析 TODO
    p_context->get_format = ffmpeg_GetFormat;
    // 总是使用我们的get_buffer该方法,那么我们就可以正确计算PTS时间值
    // 注意:此为ffmpeg的buffer数据回调,见ffmpeg章节分析 TODO
    /* Always use our get_buffer wrapper so we can calculate the
     * PTS correctly */
    p_context->get_buffer2 = lavc_GetFrame;
    p_context->opaque = p_dec;

    // 获取设置的线程池解码线程个数
    int i_thread_count = var_InheritInteger( p_dec, "avcodec-threads" );
    if( i_thread_count <= 0 )
    {
    
    
        i_thread_count = vlc_GetCPUCount();
        if( i_thread_count > 1 )
            i_thread_count++;

        //FIXME: take in count the decoding time
#if VLC_WINSTORE_APP
        i_thread_count = __MIN( i_thread_count, 6 );
#else
        i_thread_count = __MIN( i_thread_count, p_codec->id == AV_CODEC_ID_HEVC ? 10 : 6 );
#endif
    }
    // 最终的解码线程数
    i_thread_count = __MIN( i_thread_count, p_codec->id == AV_CODEC_ID_HEVC ? 32 : 16 );
    msg_Dbg( p_dec, "allowing %d thread(s) for decoding", i_thread_count );
    p_context->thread_count = i_thread_count;
    p_context->thread_safe_callbacks = true;

    // 此处是设置多线程解码的类型,如若是多线程同时解码多帧数据则会增加解码时间,
    // 若不提前预解码很多帧则慎用,目前高版本默认不开启
    switch( p_codec->id )
    {
    
    
        case AV_CODEC_ID_MPEG4:
        case AV_CODEC_ID_H263:
            p_context->thread_type = 0;
            break;
        case AV_CODEC_ID_MPEG1VIDEO:
        case AV_CODEC_ID_MPEG2VIDEO:
            p_context->thread_type &= ~FF_THREAD_SLICE;
            /* fall through */
# if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 1, 0))
        case AV_CODEC_ID_H264:
        case AV_CODEC_ID_VC1:
        case AV_CODEC_ID_WMV3:
            p_context->thread_type &= ~FF_THREAD_FRAME;
# endif
        default:
            break;
    }

    // 高版本默认不开启
    if( p_context->thread_type & FF_THREAD_FRAME )
        p_dec->i_extra_picture_buffers = 2 * p_context->thread_count;

    /* ***** misc init ***** */
    date_Init(&p_sys->pts, 1, 30001);
    date_Set(&p_sys->pts, VLC_TS_INVALID);
    p_sys->b_first_frame = true;
    p_sys->i_late_frames = 0;
    p_sys->b_from_preroll = false;
    p_sys->b_draining = false;

    // 设置输出属性即根据pixel format像素格式来获取输出的YUV和RGB转换格式
    /* Set output properties */
    if( GetVlcChroma( &p_dec->fmt_out.video, p_context->pix_fmt ) != VLC_SUCCESS )
    {
    
    
        /* we are doomed. but not really, because most codecs set their pix_fmt later on */
        p_dec->fmt_out.i_codec = VLC_CODEC_I420;
    }
    p_dec->fmt_out.i_codec = p_dec->fmt_out.video.i_chroma;

    p_dec->fmt_out.video.orientation = p_dec->fmt_in.video.orientation;

    if( p_dec->fmt_in.video.p_palette ) {
    
    
        p_sys->palette_sent = false;
        p_dec->fmt_out.video.p_palette = malloc( sizeof(video_palette_t) );
        if( p_dec->fmt_out.video.p_palette )
            *p_dec->fmt_out.video.p_palette = *p_dec->fmt_in.video.p_palette;
    } else
        p_sys->palette_sent = true;

    /* ***** init this codec with special data ***** */
    ffmpeg_InitCodec( p_dec );

    // 见3小节分析
    /* ***** Open the codec ***** */
    if( OpenVideoCodec( p_dec ) < 0 )
    {
    
    
        vlc_sem_destroy( &p_sys->sem_mt );
        free( p_sys );
        avcodec_free_context( &p_context );
        return VLC_EGENERIC;
    }

    // 链接解码和消费方法
    // 见4小节分析
    p_dec->pf_decode = DecodeVideo;
    // decoder解码器模块的[pf_flush]刷新buffer数据方法
    // 由前面章节分析可知该方法是被【DecoderProcessFlush】方法处理流程调用了,
    // 此处分析的是视频解码器的请求刷新清空解码器端buffer数据的处理流程
    // 见5小节分析
    p_dec->pf_flush  = Flush;

    // ffmpeg解码档次等级
    /* XXX: Writing input format makes little sense. */
    if( p_context->profile != FF_PROFILE_UNKNOWN )
        p_dec->fmt_in.i_profile = p_context->profile;
    if( p_context->level != FF_LEVEL_UNKNOWN )
        p_dec->fmt_in.i_level = p_context->level;
    return VLC_SUCCESS;
}

3、OpenVideoCodec实现分析:[vlc/modules/codec/avcodec/video.c]

static int OpenVideoCodec( decoder_t *p_dec )
{
    
    
    decoder_sys_t *p_sys = p_dec->p_sys;
    AVCodecContext *ctx = p_sys->p_context;
    const AVCodec *codec = p_sys->p_codec;
    int ret;

    if( ctx->extradata_size <= 0 )
    {
    
    
        if( codec->id == AV_CODEC_ID_VC1 ||
            codec->id == AV_CODEC_ID_THEORA )
        {
    
    
            msg_Warn( p_dec, "waiting for extra data for codec %s",
                      codec->name );
            return 1;
        }
    }

    // 视频图像显示区域宽高
    ctx->width  = p_dec->fmt_in.video.i_visible_width;
    ctx->height = p_dec->fmt_in.video.i_visible_height;

    // 编码的图像原始宽高
    ctx->coded_width = p_dec->fmt_in.video.i_width;
    ctx->coded_height = p_dec->fmt_in.video.i_height;

    // 每个像素所占位数 ===》量化精度【一个像素点/采样点用多少bit表示】
    ctx->bits_per_coded_sample = p_dec->fmt_in.video.i_bits_per_pixel;
    p_sys->pix_fmt = AV_PIX_FMT_NONE;
    p_sys->profile = -1;
    p_sys->level = -1;
    // 初始化字幕相关结构体数据
    cc_Init( &p_sys->cc );

    // 设置YUV色彩范围、色彩空间类型、色彩转换类型、基原色类型
    set_video_color_settings( &p_dec->fmt_in.video, ctx );
    if( p_dec->fmt_in.video.i_frame_rate_base &&
        p_dec->fmt_in.video.i_frame_rate &&
        (double) p_dec->fmt_in.video.i_frame_rate /
                 p_dec->fmt_in.video.i_frame_rate_base < 6 )
    {
    
    
        // 若帧率满足条件则强制采用低延迟特性
        ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
    }

    post_mt( p_sys );
    // 根据全局设置的播放器配置参数【"avcodec-options"】,打开并初始化ffmpeg的一些结构体数据,
    // 并且加锁只会初始化一次,设置解码器白名单、尺寸像素大小等等非常多的数据初始化
    ret = ffmpeg_OpenCodec( p_dec, ctx, codec );
    wait_mt( p_sys );
    if( ret < 0 )
        return ret;

    // 多线程编解码的支持情况
    switch( ctx->active_thread_type )
    {
    
    
        case FF_THREAD_FRAME:
            msg_Dbg( p_dec, "using frame thread mode with %d threads",
                     ctx->thread_count );
            break;
        case FF_THREAD_SLICE:
            msg_Dbg( p_dec, "using slice thread mode with %d threads",
                     ctx->thread_count );
            break;
        case 0:
            if( ctx->thread_count > 1 )
                msg_Warn( p_dec, "failed to enable threaded decoding" );
            break;
        default:
            msg_Warn( p_dec, "using unknown thread mode with %d threads",
                      ctx->thread_count );
            break;
    }
    return 0;
}

4、DecodeVideo实现分析:[vlc/modules/codec/avcodec/video.c]

static int DecodeVideo( decoder_t *p_dec, block_t *p_block )
{
    
    
    block_t **pp_block = p_block ? &p_block : NULL;
    picture_t *p_pic;
    bool error = false;
    // 解码当前一帧或多帧的block块数据
    // 见4.1小节分析
    while( ( p_pic = DecodeBlock( p_dec, pp_block, &error ) ) != NULL )
        // 将解码出来的单个视频图像数据入队【发送】给视频输出端队列中,输出端用于出队显示
        // 见4.2小节分析
        decoder_QueueVideo( p_dec, p_pic );
    return error ? VLCDEC_ECRITICAL : VLCDEC_SUCCESS;
}

4.1、DecodeBlock实现分析:[vlc/modules/codec/avcodec/video.c]

/*****************************************************************************
 * DecodeBlock: Called to decode one or more frames
 *****************************************************************************/
static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block, bool *error )
{
    
    
    decoder_sys_t *p_sys = p_dec->p_sys;
    AVCodecContext *p_context = p_sys->p_context;
    /* Boolean if we assume that we should get valid pic as result */
    bool b_need_output_picture = true;

    /* Boolean for END_OF_SEQUENCE */
    bool eos_spotted = false;

    block_t *p_block;
    mtime_t current_time;

    if( !p_context->extradata_size && p_dec->fmt_in.i_extra )
    {
    
    
        ffmpeg_InitCodec( p_dec );
        if( !avcodec_is_open( p_context ) )
            OpenVideoCodec( p_dec );
    }

    // 当block为空时,判断是否允许avcodec模块发送NULL空数据给编解码器
    p_block = pp_block ? *pp_block : NULL;
    if(!p_block && !(p_sys->p_codec->capabilities & AV_CODEC_CAP_DELAY) )
        return NULL;

    // 判断libavcodec模块是否已经初始化了
    if( !avcodec_is_open( p_context ) )
    {
    
    
        if( p_block )
            block_Release( p_block );
        return NULL;
    }

    // 判断当前block是否有效【注:该判断若block为NULL或为损坏时也是true】
    if( !check_block_validity( p_sys, p_block ) )
        return NULL;

    // 当允许丢帧时,判断当前block到达解码器时间与当前系统时间,比较到达时间是否有延迟帧
    // 注意:block为空时判断为false
    current_time = mdate();
    if( p_dec->b_frame_drop_allowed &&  check_block_being_late( p_sys, p_block, current_time) )
    {
    
    
        msg_Err( p_dec, "more than 5 seconds of late video -> "
                 "dropping frame (computer too slow ?)" );
        return NULL;
    }


    // 可能优先解码所有的I帧,再看其他帧
    /* A good idea could be to decode all I pictures and see for the other */

    // 【BLOCK_FLAG_PREROLL】表示必须解码但不显示
    // 注意:此次block空也处理为有效picture来处理
    /* Defaults that if we aren't in prerolling, we want output picture
       same for if we are flushing (p_block==NULL) */
    if( !p_block || !(p_block->i_flags & BLOCK_FLAG_PREROLL) )
        b_need_output_picture = true;
    else
        b_need_output_picture = false;

    // 【用于帧跳过算法设置】即对选择跳过解码的帧进行跳过解码设置
    /* Change skip_frame config only if hurry_up is enabled */
    if( p_sys->b_hurry_up )
    {
    
    
        p_context->skip_frame = p_sys->i_skip_frame;

        // 同时检查是否应该或可以丢弃当前block块数据,并移到下一块数据【新的I帧】,以便赶上播放速度
        /* Check also if we should/can drop the block and move to next block
            as trying to catchup the speed*/
        if( p_dec->b_frame_drop_allowed &&
            check_frame_should_be_dropped( p_sys, p_context, &b_need_output_picture ) )
        {
    
    // 超过11个延迟帧,丢弃这些帧
            if( p_block )
                block_Release( p_block );
            msg_Warn( p_dec, "More than 11 late frames, dropping frame" );
            return NULL;
        }
    }
    
    // 此处实现:【b_need_output_picture】即若不是有效视频图像数据,
    // 那么更新帧丢弃策略即被选中跳过级别帧【AVDiscard即为跳过级别类型枚举,即应该哪种帧可以被跳过不解码】
    if( !b_need_output_picture )
    {
    
    // 如果不是有效视频图像
        p_context->skip_frame = __MAX( p_context->skip_frame,
                                              AVDISCARD_NONREF );
    }

    /*
     * Do the actual decoding now */

    /* Don't forget that libavcodec requires a little more bytes
     * that the real frame size */
    if( p_block && p_block->i_buffer > 0 )
    {
    
    
        eos_spotted = ( p_block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE ) != 0;
        
        // FF_INPUT_BUFFER_PADDING_SIZE表示:
        // 在输入码流块block数据的末尾额外分配的用于解码的字节数。
        // 通常需要如此,因为一些优化的码流读取器一次读取32位或64位,并且可能读取超出结尾
        // 注意:如果前23位的额外字节不是0,那么损坏的MPEG位流可能会导致超读和分段错误。
        
        // 此处理:重分配内存使其大小符合字节对齐【32位或64位】
        p_block = block_Realloc( p_block, 0,
                            p_block->i_buffer + FF_INPUT_BUFFER_PADDING_SIZE );
        if( !p_block )
            return NULL;
        p_block->i_buffer -= FF_INPUT_BUFFER_PADDING_SIZE;
        *pp_block = p_block;
        // 此处实现重要:如上面的重分配存储后此处
        // 将最后的【FF_INPUT_BUFFER_PADDING_SIZE】32位值全初始化为0
        memset( p_block->p_buffer + p_block->i_buffer, 0,
                FF_INPUT_BUFFER_PADDING_SIZE );
    }

    do
    {
    
    
        int ret;
        int i_used = 0;
        const bool b_has_data = ( p_block && p_block->i_buffer > 0 );
        // true为需要执行输出解码器已解码数据的任务
        const bool b_start_drain = ((pp_block == NULL) || eos_spotted) && !p_sys->b_draining;

        post_mt( p_sys );

        if( b_has_data || b_start_drain )
        {
    
    
            // 已编码的数据包结构体信息
            // 解码时作为输入信息,编码时作为输出信息
            AVPacket pkt;
            av_init_packet( &pkt );
            if( b_has_data )
            {
    
    
                pkt.data = p_block->p_buffer;
                pkt.size = p_block->i_buffer;
                pkt.pts = p_block->i_pts > VLC_TS_INVALID ? p_block->i_pts : AV_NOPTS_VALUE;
                pkt.dts = p_block->i_dts > VLC_TS_INVALID ? p_block->i_dts : AV_NOPTS_VALUE;

                /* Make sure we don't reuse the same timestamps twice */
                p_block->i_pts =
                p_block->i_dts = VLC_TS_INVALID;
            }
            else /* start drain */
            {
    
    
                /* Return delayed frames if codec has CODEC_CAP_DELAY */
                pkt.data = NULL;
                pkt.size = 0;
                p_sys->b_draining = true;
            }

            // 调色板RGBA/YUVA设置
            if( !p_sys->palette_sent )
            {
    
    
                uint8_t *pal = av_packet_new_side_data(&pkt, AV_PKT_DATA_PALETTE, AVPALETTE_SIZE);
                if (pal) {
    
    
                    memcpy(pal, p_dec->fmt_in.video.p_palette->palette, AVPALETTE_SIZE);
                    p_sys->palette_sent = true;
                }
            }

            // 实现:初始化码流过滤器【BitStreamFilter】、
            // 发送已编码数据包【AVPacket】(复制pkt结构体信息)去解码
            // 见4.1.1小节分析
            ret = avcodec_send_packet(p_context, &pkt);
            if( ret != 0 && ret != AVERROR(EAGAIN) )
            {
    
    
                if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL))
                {
    
    
                    msg_Err(p_dec, "avcodec_send_packet critical error");
                    *error = true;
                }
                av_packet_unref( &pkt );
                break;
            }
            i_used = ret != AVERROR(EAGAIN) ? pkt.size : 0;
            av_packet_unref( &pkt );
        }

        // 初始化已解码的原始音视频数据结构体信息
        AVFrame *frame = av_frame_alloc();
        if (unlikely(frame == NULL))
        {
    
    
            *error = true;
            break;
        }

        // 从p_context中接收解码器端已解码的原始帧数据(音视频原始数据)存入frame,
        // 并剪辑成需要显示的视频尺寸
        // 见4.1.2小节分析
        ret = avcodec_receive_frame(p_context, frame);
        if( ret != 0 && ret != AVERROR(EAGAIN) )
        {
    
    
            if (ret == AVERROR(ENOMEM) || ret == AVERROR(EINVAL))
            {
    
    
                msg_Err(p_dec, "avcodec_receive_frame critical error");
                *error = true;
            }
            av_frame_free(&frame);
            /* After draining, we need to reset decoder with a flush */
            if( ret == AVERROR_EOF )
            {
    
    
                avcodec_flush_buffers( p_sys->p_context );
                p_sys->b_draining = false;
            }
            break;
        }
        // true为没接收到解码后的帧数据
        bool not_received_frame = ret;

        wait_mt( p_sys );

        if( eos_spotted )
            p_sys->b_first_frame = true;

        if( p_block )
        {
    
    
            if( p_block->i_buffer <= 0 )
                eos_spotted = false;

            // 重要处理注意:该处理为将当前已被拿去解码的数据大小从block中
            // 移位到未解码的数据负载开始和当前剩余数据负载长度
            /* Consumed bytes */
            p_block->p_buffer += i_used;
            p_block->i_buffer -= i_used;
        }

        // 若没有接收到帧数据并且本次用于解码的编码数据不为0,
        // 则继续解码剩余的编码数据
        /* Nothing to display */
        if( not_received_frame )
        {
    
    
            av_frame_free(&frame);
            if( i_used == 0 ) break;
            continue;
        }

        // 计算当前帧展示时间PTS,并更新到p_sys中
        /* Compute the PTS */
#ifdef FF_API_PKT_PTS
        mtime_t i_pts = frame->pts;
#else
        mtime_t i_pts = frame->pkt_pts;
#endif
        if (i_pts == AV_NOPTS_VALUE )
            i_pts = frame->pkt_dts;

        if( i_pts == AV_NOPTS_VALUE )
            i_pts = date_Get( &p_sys->pts );

        /* Interpolate the next PTS */
        if( i_pts > VLC_TS_INVALID )
            date_Set( &p_sys->pts, i_pts );

        // 对于一些编解码器,时基更接近于场速率而不是帧速率。
        // 最值得注意的是,H.264和MPEG-2指定time_base为帧持续时间的一半,如果没有电视电影使用的话
        // 设置为每帧的时间基数。默认1,例如H.264/MPEG-2设置为2。
        // 将四舍五入误差考虑在内,PTS时间递增并返回结果。
        const mtime_t i_next_pts = interpolate_next_pts(p_dec, frame);

        // 注译:更新帧延迟计数(执行preroll时除外)
        update_late_frame_count( p_dec, p_block, current_time, i_pts, i_next_pts);

        // 若不是有效的视频图像或某些数据为空或允许丢帧【丢弃有损帧且不显示有损帧】,
        // 则进行block的余下数据解码
        if( !b_need_output_picture ||
           ( !p_sys->p_va && !frame->linesize[0] ) ||
           ( p_dec->b_frame_drop_allowed && (frame->flags & AV_FRAME_FLAG_CORRUPT) &&
             !p_sys->b_show_corrupted ) )
        {
    
    
            av_frame_free(&frame);
            continue;
        }

        // 此处不讨论这种像素格式即老式电视制式,每秒25帧的帧率显示图像
        if( p_context->pix_fmt == AV_PIX_FMT_PAL8
         && !p_dec->fmt_out.video.p_palette )
        {
    
    
            /* See AV_PIX_FMT_PAL8 comment in avc_GetVideoFormat(): update the
             * fmt_out palette and change the fmt_out chroma to request a new
             * vout */
            assert( p_dec->fmt_out.video.i_chroma != VLC_CODEC_RGBP );

            video_palette_t *p_palette;
            p_palette = p_dec->fmt_out.video.p_palette
                      = malloc( sizeof(video_palette_t) );
            if( !p_palette )
            {
    
    
                *error = true;
                av_frame_free(&frame);
                break;
            }
            static_assert( sizeof(p_palette->palette) == AVPALETTE_SIZE,
                           "Palette size mismatch between vlc and libavutil" );
            assert( frame->data[1] != NULL );
            memcpy( p_palette->palette, frame->data[1], AVPALETTE_SIZE );
            p_palette->i_entries = AVPALETTE_COUNT;
            p_dec->fmt_out.video.i_chroma = VLC_CODEC_RGBP;
            if( decoder_UpdateVideoFormat( p_dec ) )
            {
    
    
                av_frame_free(&frame);
                continue;
            }
        }

        // 转换为vlc中定义的视频图像信息结构体,若没有初始化则进行创建和获取
        picture_t *p_pic = frame->opaque;
        if( p_pic == NULL )
        // 注意:此处说的直接(解码)渲染功能,根据该变量相关注释信息可大概知晓该功能是
        // 设置了某些特殊的硬件解码模块来解码并直接渲染的功能。
        // 因此分析其为空时更新video视频输出格式信息
        {
    
       /* When direct rendering is not used, get_format() and get_buffer()
             * might not be called. The output video format must be set here
             * then picture buffer can be allocated. */
            if (p_sys->p_va == NULL
             && lavc_UpdateVideoFormat(p_dec, p_context, p_context->pix_fmt,
                                       p_context->pix_fmt) == 0)
                // 调用p_dec的【p_dec->pf_vout_buffer_new( dec );】该方法
                // 来获取视频输出端vout的视频输出图像buffer缓冲对象(用于视频输出端输出视频的buffer)
                p_pic = decoder_NewPicture(p_dec);

            if( !p_pic )
            {
    
    
                av_frame_free(&frame);
                break;
            }

            // 将ffmpeg中的音视频原始帧数据(已解码数据)转换为vlc中视频输出图像buffer缓冲数据
            // 实现:对需要显示的尺寸像素位数据的拷贝处理
            /* Fill picture_t from AVFrame */
            if( lavc_CopyPicture( p_dec, p_pic, frame ) != VLC_SUCCESS )
            {
    
    
                av_frame_free(&frame);
                picture_Release( p_pic );
                break;
            }
        }
        else
        {
    
    
            // 注译:有些编解码器可能多次返回同一帧。当同样的帧再次返回时,
            // 此时要克隆底层图像信息就太晚了。所以主动克隆上层数据。
            /* Some codecs can return the same frame multiple times. By the
             * time that the same frame is returned a second time, it will be
             * too late to clone the underlying picture. So clone proactively.
             * A single picture CANNOT be queued multiple times.
             */
            p_pic = picture_Clone( p_pic );
            if( unlikely(p_pic == NULL) )
            {
    
    
                av_frame_free(&frame);
                break;
            }
        }

        // 视频像素宽高比检查
        if( !p_dec->fmt_in.video.i_sar_num || !p_dec->fmt_in.video.i_sar_den )
        {
    
    
            /* Fetch again the aspect ratio in case it changed */
            p_dec->fmt_out.video.i_sar_num
                = p_context->sample_aspect_ratio.num;
            p_dec->fmt_out.video.i_sar_den
                = p_context->sample_aspect_ratio.den;

            if( !p_dec->fmt_out.video.i_sar_num || !p_dec->fmt_out.video.i_sar_den )
            {
    
    
                p_dec->fmt_out.video.i_sar_num = 1;
                p_dec->fmt_out.video.i_sar_den = 1;
            }
        }

        // PTS图像显示时间
        p_pic->date = i_pts;
        /* Hack to force display of still pictures */
        p_pic->b_force = p_sys->b_first_frame;
        p_pic->i_nb_fields = 2 + frame->repeat_pict;
        // 是否为逐行扫描帧 [interlaced_frame为隔行扫描帧]
        p_pic->b_progressive = !frame->interlaced_frame;
        // 是否为隔行帧中的第一个【顶场】,是的话直接显示
        p_pic->b_top_field_first = frame->top_field_first;

        // 解析获取额外信息:视频显示的色度分量颜色值、基原色RGB分量值、亮度值等级,
        // 格式变化后更新视频输出格式信息【dec->pf_vout_format_update( dec )】,
        // 格式未变化则再解析获取并输出视频字幕信息给输出端【decoder_QueueCc--》
        // dec->pf_queue_cc( dec, p_cc, p_desc )】,最后刷新清空数据
        // TODO:该部分后续章节再考虑仔细分析流程
        if (DecodeSidedata(p_dec, frame, p_pic))
            i_pts = VLC_TS_INVALID;

        av_frame_free(&frame);

        /* Send decoded frame to vout */
        if (i_pts > VLC_TS_INVALID)
        {
    
    
            p_sys->b_first_frame = false;
            // 解码成功后返回当前已解码原始视频帧数据
            return p_pic;
        }
        else
            picture_Release( p_pic );
    } while( true );

    // 以下为解码失败后处理
    if( p_block )
        block_Release( p_block );
    if( p_sys->b_draining )
    {
    
    
        // 正在执行结束解码操作则刷新清空当前解码器数据并恢复原始值
        avcodec_flush_buffers( p_sys->p_context );
        p_sys->b_draining = false;
    }
    return NULL;
}

4.1.1、avcodec_send_packet实现分析:

见后续ffmpeg分析章节 TODO

4.1.2、avcodec_receive_frame实现分析:

见后续ffmpeg分析章节 TODO

4.2、decoder_QueueVideo实现分析:【将已解码原始视频数据给到输出端队列中用于显示】

//【vlc/include/vlc_codec.c】
/**
 * This function queues a single picture to the video output.
 *
 * \note
 * The caller doesn't own the picture anymore after this call (even in case of
 * error).
 * FIXME: input_DecoderFrameNext won't work if a module use this function.
 *
 * \return 0 if the picture is queued, -1 on error
 */
static inline int decoder_QueueVideo( decoder_t *dec, picture_t *p_pic )
{
    
    
    assert( p_pic->p_next == NULL );
    assert( dec->pf_queue_video != NULL );
    // 该方法赋值在【vlc/src/input/decoder.c】的CreateDecoder方法中
    // [p_dec->pf_queue_video = DecoderQueueVideo;]
    return dec->pf_queue_video( dec, p_pic );
}

// [pf_queue_video]赋值方法实现:【vlc/src/input/decoder.c】
static int DecoderQueueVideo( decoder_t *p_dec, picture_t *p_pic )
{
    
    
    assert( p_pic );
    unsigned i_lost = 0;
    decoder_owner_sys_t *p_owner = p_dec->p_owner;

    // 将已解码原始视频数据给到输出端队列中用于显示
    // 见4.2.1小节分析
    int ret = DecoderPlayVideo( p_dec, p_pic, &i_lost );

    // 该方法赋值在【vlc/src/input/decoder.c】的CreateDecoder方法中
    // [p_owner->pf_update_stat = DecoderUpdateStatVideo;]
    // 主要更新vlc输入输出端的相关数据状态
    // 见4.2.2小节分析
    p_owner->pf_update_stat( p_owner, 1, i_lost );
    return ret;
}

4.2.1、DecoderPlayVideo实现分析:【vlc/src/input/decoder.c】

static int DecoderPlayVideo( decoder_t *p_dec, picture_t *p_picture,
                             unsigned *restrict pi_lost_sum )
{
    
    
    decoder_owner_sys_t *p_owner = p_dec->p_owner;
    // vout模块为视频显示输出端线程描述结构信息
    vout_thread_t  *p_vout = p_owner->p_vout;
    bool prerolled;

    vlc_mutex_lock( &p_owner->lock );
   // 检查当前待显示图像的显示时间点PTS是否已过期或无效,则丢弃
   if( p_owner->i_preroll_end > p_picture->date )
   {
    
    
        vlc_mutex_unlock( &p_owner->lock );
        picture_Release( p_picture );
        return -1;
    }

    prerolled = p_owner->i_preroll_end > INT64_MIN;
    p_owner->i_preroll_end = INT64_MIN;
    vlc_mutex_unlock( &p_owner->lock );

    if( unlikely(prerolled) )
    {
    
    
        msg_Dbg( p_dec, "end of video preroll" );

        // 视频预解码完成则刷新视频输出端数据buffer队列,
        // 并可能等待其为空后才继续往下执行
        if( p_vout )
            vout_Flush( p_vout, VLC_TS_INVALID+1 );
    }

    if( p_picture->date <= VLC_TS_INVALID )
    {
    
    
        msg_Warn( p_dec, "non-dated video buffer received" );
        goto discard;
    }

    /* */
    vlc_mutex_lock( &p_owner->lock );

    if( p_owner->b_waiting && !p_owner->b_first )
    {
    
    
        // 该情况此前分析过,若demuxer线程当前已是暂停状态,则唤醒其继续解复用数据
        p_owner->b_has_data = true;
        vlc_cond_signal( &p_owner->wait_acknowledge );
    }
    // 是否为demuxer线程wait后的第一个视频图像
    bool b_first_after_wait = p_owner->b_waiting && p_owner->b_has_data;

    // 检查vlc的decoder控制层是否应该wait即暂停向解码模块传递编码数据,
    // 若是暂停了则会被上述demuxer层[wait_acknowledge]事件执行后的处理中进行唤醒
    DecoderWaitUnblock( p_dec );

    if( p_owner->b_waiting )
    {
    
    
        // 该case下,接收的是第一个视频图像数据
        assert( p_owner->b_first );
        msg_Dbg( p_dec, "Received first picture" );
        p_owner->b_first = false;
        p_picture->b_force = true;
    }

    const bool b_dated = p_picture->date > VLC_TS_INVALID;
    int i_rate = INPUT_RATE_DEFAULT;
    // 根据jitter buffer时钟策略,更新当前PTS时间,当前码率等
    DecoderFixTs( p_dec, &p_picture->date, NULL, NULL,
                  &i_rate, DECODER_BOGUS_VIDEO_DELAY );

    vlc_mutex_unlock( &p_owner->lock );

    /* FIXME: The *input* FIFO should not be locked here. This will not work
     * properly if/when pictures are queued asynchronously. */
    vlc_fifo_Lock( p_owner->p_fifo );
    if( unlikely(p_owner->paused) && likely(p_owner->frames_countdown > 0) )
        p_owner->frames_countdown--;
    vlc_fifo_Unlock( p_owner->p_fifo );

    /* */
    if( p_vout == NULL )
        goto discard;

    if( p_picture->b_force || p_picture->date > VLC_TS_INVALID )
        /* FIXME: VLC_TS_INVALID -- verify video_output */
    {
    
    
        if( i_rate != p_owner->i_last_rate || b_first_after_wait )
        {
    
    
            // 若码率发生变化或当前待显示图像是demuxer层wait后第一个图像,
            // 则先刷新清空此前vout视频输出端的图像队列旧数据,不显示旧图像
            // 见vout视频输出端章节分析 TODO
            /* Be sure to not display old picture after our own */
            vout_Flush( p_vout, p_picture->date );
            p_owner->i_last_rate = i_rate;
        }
        // 将当前视频图像给到vout视频输出端
        // 见4.2.1.1小节分析
        vout_PutPicture( p_vout, p_picture );
    }
    else
    {
    
    
        if( b_dated )
            // 跳过当前过早图像
            msg_Warn( p_dec, "early picture skipped" );
        else
            // 接收到了无PTS时间的图像
            msg_Warn( p_dec, "non-dated video buffer received" );
        goto discard;
    }

    return 0;
discard:
    *pi_lost_sum += 1;
    picture_Release( p_picture );
    return 0;
}

4.2.1.1、vout_PutPicture实现分析:

// [vlc/src/video_output/video_output.c]
/**
 * It gives to the vout a picture to be displayed.
 *
 * The given picture MUST comes from vout_GetPicture.
 *
 * Becareful, after vout_PutPicture is called, picture_t::p_next cannot be
 * read/used.
 */
void vout_PutPicture(vout_thread_t *vout, picture_t *picture)
{
    
    
    picture->p_next = NULL;
    // 判断当前已解码图像缓存池是否已改变
    if (picture_pool_OwnsPic(vout->p->decoder_pool, picture))
    {
    
    // 未改变
        // 将当前待显示原始图像push到图像解码输出端vout的解码缓冲队列中
        // 见后续分析
        picture_fifo_Push(vout->p->decoder_fifo, picture);

        // 控制vout视频输出端线程继续执行图像展示
        // 见后续分析
        vout_control_Wake(&vout->p->control);
    }
    else
    {
    
    // 已改变则drop丢弃当前视图图像
        /* FIXME: HACK: Drop this picture because the vout changed. The old
         * picture pool need to be kept by the new vout. This requires a major
         * "vout display" API change. */
        picture_Release(picture);
    }
}

// [vlc/src/misc/picture_fifo.c]
void picture_fifo_Push(picture_fifo_t *fifo, picture_t *picture)
{
    
    
    vlc_mutex_lock(&fifo->lock);
    PictureFifoPush(fifo, picture);
    vlc_mutex_unlock(&fifo->lock);
}
// [vlc/src/misc/picture_fifo.c]
static void PictureFifoPush(picture_fifo_t *fifo, picture_t *picture)
{
    
    
    assert(!picture->p_next);
    // 将当前待显示原始图像push到图像解码输出端vout的解码缓冲队列中
    *fifo->last_ptr = picture;
    fifo->last_ptr  = &picture->p_next;
}

// [vlc/src/video_output/control.c]
void vout_control_Wake(vout_control_t *ctrl)
{
    
    
    vlc_mutex_lock(&ctrl->lock);
    // 【若wait则唤醒】控制vout视频输出端线程继续执行图像展示,并设置线程标识不能sleep
    // vout视频输出端实现见后续另一章实现分析【TODO】
    ctrl->can_sleep = false;
    vlc_cond_signal(&ctrl->wait_request);
    vlc_mutex_unlock(&ctrl->lock);
}

4.2.2、pf_update_stat实现分析:

// 该方法赋值在【vlc/src/input/decoder.c】的CreateDecoder方法中
// [p_owner->pf_update_stat = DecoderUpdateStatVideo;]
static void DecoderUpdateStatVideo( decoder_owner_sys_t *p_owner,
                                    unsigned decoded, unsigned lost )
{
    
    
    input_thread_t *p_input = p_owner->p_input;
    unsigned displayed = 0;

    /* Update ugly stat */
    if( p_input == NULL )
        return;

    if( p_owner->p_vout != NULL )
    {
    
    
        unsigned vout_lost = 0;

        // 若vout输出端存在则读取vout视频输出端相关状态统计值【原子性】:是否显示成功、是否丢失
        vout_GetResetStatistic( p_owner->p_vout, &displayed, &vout_lost );
        lost += vout_lost;
    }

    // 加锁更新input输入端相关状态计数器统计值
    vlc_mutex_lock( &input_priv(p_input)->counters.counters_lock );
    stats_Update( input_priv(p_input)->counters.p_decoded_video, decoded, NULL );
    stats_Update( input_priv(p_input)->counters.p_lost_pictures, lost , NULL);
    stats_Update( input_priv(p_input)->counters.p_displayed_pictures, displayed, NULL);
    vlc_mutex_unlock( &input_priv(p_input)->counters.counters_lock );
}

// [stats_Update]实现分析: [vlc/src/input/stats.c]
/** Update a counter element with new values
 * \param p_counter the counter to update
 * \param val the vlc_value union containing the new value to aggregate. For
 * more information on how data is aggregated, \see stats_Create
 * \param val_new a pointer that will be filled with new data
 */
void stats_Update( counter_t *p_counter, uint64_t val, uint64_t *new_val )
{
    
    
    if( !p_counter )
        return;

    // 计数器类型:
    // 由此前分析过处理化过程中,知晓该类型初始化赋值在【vlc/src/input/input.c】的
    // [INIT_COUNTER]该宏定义实现。其中只有以下两个为【STATS_DERIVATIVE】类型:(输入码率和解复用码率)
    // INIT_COUNTER( input_bitrate, DERIVATIVE );INIT_COUNTER( demux_bitrate, DERIVATIVE );
    switch( p_counter->i_compute_type )
    {
    
    
    case STATS_DERIVATIVE:
    {
    
    
        counter_sample_t *p_new, *p_old;
        mtime_t now = mdate();
        if( now - p_counter->last_update < CLOCK_FREQ )
            return;
        // 该状态统计值计数器更新当前最新时间,并将新值插入到第一位置
        p_counter->last_update = now;
        /* Insert the new one at the beginning */
        p_new = (counter_sample_t*)malloc( sizeof( counter_sample_t ) );
        if (unlikely(p_new == NULL))
            return; /* NOTE: Losing sample here */

        p_new->value = val;
        p_new->date = p_counter->last_update;
        TAB_INSERT(p_counter->i_samples, p_counter->pp_samples, p_new, 0);

        if( p_counter->i_samples == 3 )
        {
    
    
            p_old = p_counter->pp_samples[2];
            TAB_ERASE(p_counter->i_samples, p_counter->pp_samples, 2);
            free( p_old );
        }
        break;
    }
    case STATS_COUNTER:
        if( p_counter->i_samples == 0 )
        {
    
    
            counter_sample_t *p_new = (counter_sample_t*)malloc(
                                               sizeof( counter_sample_t ) );
            if (unlikely(p_new == NULL))
                return; /* NOTE: Losing sample here */

            p_new->value = 0;

            TAB_APPEND(p_counter->i_samples, p_counter->pp_samples, p_new);
        }
        if( p_counter->i_samples == 1 )
        {
    
    
            // 该状态统计值计数器将统计值更新【即新值加上旧值】
            p_counter->pp_samples[0]->value += val;
            if( new_val )
                *new_val = p_counter->pp_samples[0]->value;
        }
        break;
    }
}

5、Flush实现分析:[vlc/modules/codec/avcodec/video.c]

// 视频解码器的请求刷新清空解码器端buffer数据的处理流程
static void Flush( decoder_t *p_dec )
{
    
    
    decoder_sys_t *p_sys = p_dec->p_sys;
    AVCodecContext *p_context = p_sys->p_context;

    date_Set(&p_sys->pts, VLC_TS_INVALID); /* To make sure we recover properly */
    p_sys->i_late_frames = 0;
    p_sys->b_draining = false;
    // 清空字幕流数据
    cc_Flush( &p_sys->cc );

    // 注译大概意思:中止/中断图片输出,防止工作线程和avcodec workers之间的死锁
    // 通知vout端图片输入中止事件
    // 该方法内部实现为此前分析过的【vout_control_Push】和【vout_control_WaitEmpty】实现流程
    // 即将当前暂停/播放状态指令加入到vout指令控制层并根据条件wait当前decoder线程,
    // 然后等待其接受执行指令后唤醒当前decoder线程继续执行
    // 后续流程分析见vout视频输出端分析  TODO  
    /* Abort pictures in order to unblock all avcodec workers threads waiting
     * for a picture. This will avoid a deadlock between avcodec_flush_buffers
     * and workers threads */
    decoder_AbortPictures( p_dec, true );

    // 唤醒可能存在的该信号量wait事件
    post_mt( p_sys );
    /* do not flush buffers if codec hasn't been opened (theora/vorbis/VC1) */
    if( avcodec_is_open( p_context ) )
        // 若已打开解码模块则刷新清空解码块模块的相关缓存数据
        // 关于ffmpeg端的输入输出处理,可见后续ffmpeg章节的分析
        avcodec_flush_buffers( p_context );
    wait_mt( p_sys );

    // 重新设置中止图片输出事件状态
    /* Reset cancel state to false */
    decoder_AbortPictures( p_dec, false );
}

TODO vout输出端的分析请见后续章节

猜你喜欢

转载自blog.csdn.net/u012430727/article/details/110590535