【以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輸出端的分析請見後續章節