【以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 )// videoadd_shortcut("ffmpeg")set_capability("video decoder", 70)set_callbacks(InitVideoDec, EndVideoDec)// audioadd_submodule()add_shortcut("ffmpeg")set_capability("audio decoder", 70)set_callbacks(InitAudioDec, EndAudioDec)// subtitleadd_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章节分析 TODOp_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_APPi_thread_count = __MIN( i_thread_count, 6 );
#elsei_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;
# endifdefault: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;} elsep_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为空时判断为falsecurrent_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 picturesame for if we are flushing (p_block==NULL) */if( !p_block || !(p_block->i_flags & BLOCK_FLAG_PREROLL) )b_need_output_picture = true;elseb_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 blockas 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位值全初始化为0memset( 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_PTSmtime_t i_pts = frame->pts;
#elsemtime_t i_pts = frame->pkt_pts;
#endifif (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;}elsepicture_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输出端的分析请见后续章节

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

  1. 【七】【vlc-android】vlc的decoder控制层传输数据与ffmpeg音频解码模块decoder层进行解码的数据交互流程源码分析

    [以mp4文件格式和AAC.H264编码的本地文件为例展开分析] 由第六章节可知音频解码器的加载方式与视频解码器也是类似的. 通过此前分析过的模块加载方式,可找到ffmpeg解码和编码模块的初始化加载 ...

  2. Activity启动流程源码分析(基于Android N)

    Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...

  3. Android端WebRTC本地音视频采集流程源码分析

    WebRTC源码版本为:org.webrtc:google-webrtc:1.0.32006 本文仅分析Java层源码,在分析之前,先说明一下一些重要类的基本概念. MediaSource:WebRT ...

  4. Android电话拨打流程源码分析

    前面分析了电话拨号界面及电话呼叫界面,由于Android的电话Phone设计的很复杂,因此先从UI层入手分析.想要了解Android的电话拨号UI,请查看Android电话拨号UI分析,电话拨号UI在 ...

  5. Android 电话博大流程源码分析

    前面分析了电话拨号界面及电话呼叫界面,由于Android的电话Phone设计的很复杂,因此先从UI层入手分析.想要了解Android的电话拨号UI,请查看Android电话拨号UI分析,电话拨号UI在 ...

  6. Android关机流程源码分析

    上一篇文章Android 开关机动画显示源码分析详细介绍了开关机动画的显示过程,Android系统开机时,在启动SurfaceFlinger服务过程中通过Android属性系统方式来启动bootani ...

  7. Android usb广播 ACTION_USB_DEVICE_ATTACHED流程源码分析

    整体流程图 大概意思就是UsbHostManager启动监控线程,monitorUsbHostBus会调用usb_host_run函数(使用inotify来监听USB设备的插拔)不停的读取bus总线, ...

  8. Android之View绘制流程源码分析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...

  9. Android平台类加载流程源码分析

    前言 其实大家都知道的Android是使用Java作为开发语言,但是他使用的虚拟机却并不是传统的JVM,在4.4以前Android使用Dalvik虚拟机,之后使用ART(Android Runtime ...

  10. Android系统启动流程源码分析

    原址 1.当系统引导程序启动Linux内核,内核会记载各种数据结构和驱动程序,加载完毕之后,Android系统开始启动并加载第一个用户级别的进程:Init.c(system\core\init) // ...

最新文章

  1. 给定一个介于0和1之间的实数,类型为double,打印它的二进制表示
  2. OVS DPDK--virtqueue(十七)
  3. 回文树(回文自动机) - URAL 1960 Palindromes and Super Abilities
  4. autossh端口映射
  5. jqGrid使用经验分享(一)——jqGrid简单使用、json格式和jsonReader介绍
  6. 关于set的自定义比较函数的使用及结构体的上下二分用法
  7. 常见HTTP状态(404,500等)
  8. java变量传递给asp,我应该如何将多个参数传递给ASP.Net Web API GET?
  9. 8个超棒的使用javascript开发的视觉特效网站
  10. python美化输出模块_python日志处理模块
  11. C学习笔记-makefile
  12. 2019 最烂密码排行榜大曝光!网友:已中招!
  13. 替换分隔符 ^p, 或者是回车
  14. java static 对象加锁_java安全编码指南之:lock和同步的正确使用
  15. wpf silverlight的Behavior
  16. Docker下部署oracle10g
  17. 量子退火算法入门(6):初识量子退火算法的发明过程
  18. 2022海亮SC游记
  19. 小程序推广换量经验分享
  20. oracle修改日历,oracle 日历

热门文章

  1. 【高等数学】微分方程
  2. python写微信小程序商城,oejia_weshop
  3. android 控件阴影
  4. 界门纲目科属种的英文——学生物的基础
  5. jquery使用 validate 插件进行验证是否通过
  6. F280049C 输出三相互补对称SPWM波
  7. C#中操作Word(7)—— 如何向Word中插入表格
  8. 图解|什么是蒙提霍尔问题(三门问题)
  9. 《葬花吟》笛箫简谱-红楼梦主题曲
  10. ios应用不通过应用商店下载