【以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输出端的分析请见后续章节