FFmpeg的HEVC解码器源码简单分析:解码器主干部分

=====================================================

HEVC源码分析文章列表:

【解码 -libavcodec HEVC 解码器】

FFmpeg的HEVC解码器源码简单分析:概述

FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分

FFmpeg的HEVC解码器源码简单分析:解码器主干部分

FFmpeg的HEVC解码器源码简单分析:CTU解码(CTU Decode)部分-PU

FFmpeg的HEVC解码器源码简单分析:CTU解码(CTU Decode)部分-TU

FFmpeg的HEVC解码器源码简单分析:环路滤波(LoopFilter)

=====================================================


本文分析FFmpeg的libavcodec中的HEVC解码器的主干部分。“主干部分”是相对于“CTU解码”、 “环路滤波”这些细节部分而言的。它包括了HEVC解码器直到hls_decode_entry()前面的函数调用关系(hls_decode_entry()后面就是HEVC解码器的细节部分,主要包括了“CTU解码”、 “环路滤波”2个部分)。


函数调用关系图

FFmpeg HEVC解码器主干部分在整个HEVC解码器中的位置例如以下图所看到的。


单击查看更清晰的大图

HEVC解码器主干部分的源码的调用关系例如以下图所看到的。


单击查看更清晰的大图

从图中能够看出,HEVC解码器初始化函数是hevc_decode_init(),解码函数是hevc_decode_frame(),关闭函数是hevc_decode_free()。当中hevc_decode_frame()调用了decode_nal_units()进行一帧NALU的解码,decode_nal_units()又调用了decode_nal_unit()进行一个NALU的解码。
decode_nal_unit()一方面调用解析函数ff_hevc_decode_nal_vps(),ff_hevc_decode_nal_sps(),ff_hevc_decode_nal_pps()等对VPS、SPS、PPS进行解析;还有一方面调用了hls_slice_header()和hls_slice_data()对Slice数据进行解码。
hls_slice_data()中调用了hls_decode_entry()。在当中完毕了Slice Data解码的流程。该流程包括了CU、PU、TU解码,环路滤波、SAO滤波等环节。

ff_hevc_decoder

ff_hevc_decoder是HEVC解码器相应的AVCodec结构体。该结构体的定义位于libavcodec\hevc.c,例如以下所看到的。


AVCodec ff_hevc_decoder = {.name                  = "hevc",.long_name             = NULL_IF_CONFIG_SMALL("HEVC (High Efficiency Video Coding)"),.type                  = AVMEDIA_TYPE_VIDEO,.id                    = AV_CODEC_ID_HEVC,.priv_data_size        = sizeof(HEVCContext),.priv_class            = &hevc_decoder_class,.init                  = hevc_decode_init,.close                 = hevc_decode_free,.decode                = hevc_decode_frame,.flush                 = hevc_decode_flush,.update_thread_context = hevc_update_thread_context,.init_thread_copy      = hevc_init_thread_copy,.capabilities          = CODEC_CAP_DR1 | CODEC_CAP_DELAY |CODEC_CAP_SLICE_THREADS | CODEC_CAP_FRAME_THREADS,.profiles              = NULL_IF_CONFIG_SMALL(profiles),
};
从源码能够看出。HEVC解码器初始化函数是hevc_decode_init()。解码函数是hevc_decode_frame(),关闭函数是hevc_decode_free()。

hevc_decode_init()

hevc_decode_init()用于初始化HEVC解码器。

该函数的定义例如以下。

//初始化HEVC解码器
static av_cold int hevc_decode_init(AVCodecContext *avctx)
{HEVCContext *s = avctx->priv_data;int ret;//初始化CABACff_init_cabac_states();avctx->internal->allocate_progress = 1;//为HEVCContext中的变量分配内存空间ret = hevc_init_context(avctx);if (ret < 0)return ret;s->enable_parallel_tiles = 0;s->picture_struct = 0;if(avctx->active_thread_type & FF_THREAD_SLICE)s->threads_number = avctx->thread_count;elses->threads_number = 1;//假设AVCodecContext中包括extradata。则解码之if (avctx->extradata_size > 0 && avctx->extradata) {ret = hevc_decode_extradata(s);if (ret < 0) {hevc_decode_free(avctx);return ret;}}if((avctx->active_thread_type & FF_THREAD_FRAME) && avctx->thread_count > 1)s->threads_type = FF_THREAD_FRAME;elses->threads_type = FF_THREAD_SLICE;return 0;
}

从源码中能够看出,hevc_decode_init()对HEVCContext中的变量做了一些初始化工作。当中调用了一个函数hevc_init_context()用于给HEVCContext中的变量分配内存空间。

hevc_init_context()

hevc_init_context()用于给HEVCContext中的变量分配内存空间。该函数的定义例如以下所看到的。
//为HEVCContext中的变量分配内存空间
static av_cold int hevc_init_context(AVCodecContext *avctx)
{HEVCContext *s = avctx->priv_data;int i;s->avctx = avctx;s->HEVClc = av_mallocz(sizeof(HEVCLocalContext));if (!s->HEVClc)goto fail;s->HEVClcList[0] = s->HEVClc;s->sList[0] = s;s->cabac_state = av_malloc(HEVC_CONTEXTS);if (!s->cabac_state)goto fail;s->tmp_frame = av_frame_alloc();if (!s->tmp_frame)goto fail;s->output_frame = av_frame_alloc();if (!s->output_frame)goto fail;for (i = 0; i < FF_ARRAY_ELEMS(s->DPB); i++) {s->DPB[i].frame = av_frame_alloc();if (!s->DPB[i].frame)goto fail;s->DPB[i].tf.f = s->DPB[i].frame;}s->max_ra = INT_MAX;s->md5_ctx = av_md5_alloc();if (!s->md5_ctx)goto fail;ff_bswapdsp_init(&s->bdsp);s->context_initialized = 1;s->eos = 0;return 0;fail:hevc_decode_free(avctx);return AVERROR(ENOMEM);
}

hevc_decode_free()

hevc_decode_free()用于关闭HEVC解码器。该函数的定义例如以下所看到的。
//关闭HEVC解码器
static av_cold int hevc_decode_free(AVCodecContext *avctx)
{HEVCContext       *s = avctx->priv_data;int i;pic_arrays_free(s);av_freep(&s->md5_ctx);for(i=0; i < s->nals_allocated; i++) {av_freep(&s->skipped_bytes_pos_nal[i]);}av_freep(&s->skipped_bytes_pos_size_nal);av_freep(&s->skipped_bytes_nal);av_freep(&s->skipped_bytes_pos_nal);av_freep(&s->cabac_state);av_frame_free(&s->tmp_frame);av_frame_free(&s->output_frame);for (i = 0; i < FF_ARRAY_ELEMS(s->DPB); i++) {ff_hevc_unref_frame(s, &s->DPB[i], ~0);av_frame_free(&s->DPB[i].frame);}for (i = 0; i < FF_ARRAY_ELEMS(s->vps_list); i++)av_buffer_unref(&s->vps_list[i]);for (i = 0; i < FF_ARRAY_ELEMS(s->sps_list); i++)av_buffer_unref(&s->sps_list[i]);for (i = 0; i < FF_ARRAY_ELEMS(s->pps_list); i++)av_buffer_unref(&s->pps_list[i]);s->sps = NULL;s->pps = NULL;s->vps = NULL;av_buffer_unref(&s->current_sps);av_freep(&s->sh.entry_point_offset);av_freep(&s->sh.offset);av_freep(&s->sh.size);for (i = 1; i < s->threads_number; i++) {HEVCLocalContext *lc = s->HEVClcList[i];if (lc) {av_freep(&s->HEVClcList[i]);av_freep(&s->sList[i]);}}if (s->HEVClc == s->HEVClcList[0])s->HEVClc = NULL;av_freep(&s->HEVClcList[0]);for (i = 0; i < s->nals_allocated; i++)av_freep(&s->nals[i].rbsp_buffer);av_freep(&s->nals);s->nals_allocated = 0;return 0;
}

从源码能够看出,hevc_decode_free()释放了HEVCContext中的内存。

hevc_decode_frame()

hevc_decode_frame()是HEVC解码器中最关键的函数。用于解码一帧数据。

该函数的定义例如以下所看到的。

/** 解码一帧数据** 凝视:雷霄骅* leixiaohua1020@126.com* http://blog.csdn.net/leixiaohua1020**/
static int hevc_decode_frame(AVCodecContext *avctx, void *data, int *got_output,AVPacket *avpkt)
{int ret;HEVCContext *s = avctx->priv_data;//没有输入码流的时候。输出解码器中剩余数据//相应“Flush Decoder”功能if (!avpkt->size) {//第3个參数flush取值为1ret = ff_hevc_output_frame(s, data, 1);if (ret < 0)return ret;*got_output = ret;return 0;}s->ref = NULL;//解码一帧数据ret    = decode_nal_units(s, avpkt->data, avpkt->size);if (ret < 0)return ret;/* verify the SEI checksum */if (avctx->err_recognition & AV_EF_CRCCHECK && s->is_decoded &&s->is_md5) {ret = verify_md5(s, s->ref->frame);if (ret < 0 && avctx->err_recognition & AV_EF_EXPLODE) {ff_hevc_unref_frame(s, s->ref, ~0);return ret;}}s->is_md5 = 0;if (s->is_decoded) {av_log(avctx, AV_LOG_DEBUG, "Decoded frame with POC %d.\n", s->poc);s->is_decoded = 0;}if (s->output_frame->buf[0]) {//输出解码后数据av_frame_move_ref(data, s->output_frame);*got_output = 1;}return avpkt->size;
}

从源码能够看出。hevc_decode_frame()依据输入的AVPacket的data是否为NULL分成两个情况:
(1)AVPacket的data为NULL的时候。代表没有输入码流。这时候直接调用ff_hevc_output_frame()输出解码器中缓存的帧。


(2)AVPacket的data不为NULL的时候。调用decode_nal_units()解码输入的一帧数据的NALU。

以下看一下一帧NALU的解码函数decode_nal_units()。

decode_nal_units()

decode_nal_units()用于解码一帧NALU。该函数的定义例如以下所看到的。
//解码一帧数据
static int decode_nal_units(HEVCContext *s, const uint8_t *buf, int length)
{int i, consumed, ret = 0;s->ref = NULL;s->last_eos = s->eos;s->eos = 0;/* split the input packet into NAL units, so we know the upper bound on the* number of slices in the frame */s->nb_nals = 0;while (length >= 4) {HEVCNAL *nal;int extract_length = 0;if (s->is_nalff) {int i;for (i = 0; i < s->nal_length_size; i++)extract_length = (extract_length << 8) | buf[i];buf    += s->nal_length_size;length -= s->nal_length_size;if (extract_length > length) {av_log(s->avctx, AV_LOG_ERROR, "Invalid NAL unit size.\n");ret = AVERROR_INVALIDDATA;goto fail;}} else {/* search start code *///查找起始码0x000001while (buf[0] != 0 || buf[1] != 0 || buf[2] != 1) {++buf;--length;if (length < 4) {av_log(s->avctx, AV_LOG_ERROR, "No start code is found.\n");ret = AVERROR_INVALIDDATA;goto fail;}}//找到后,跳过起始码(3Byte)buf           += 3;length        -= 3;}if (!s->is_nalff)extract_length = length;if (s->nals_allocated < s->nb_nals + 1) {int new_size = s->nals_allocated + 1;HEVCNAL *tmp = av_realloc_array(s->nals, new_size, sizeof(*tmp));if (!tmp) {ret = AVERROR(ENOMEM);goto fail;}s->nals = tmp;memset(s->nals + s->nals_allocated, 0,(new_size - s->nals_allocated) * sizeof(*tmp));av_reallocp_array(&s->skipped_bytes_nal, new_size, sizeof(*s->skipped_bytes_nal));av_reallocp_array(&s->skipped_bytes_pos_size_nal, new_size, sizeof(*s->skipped_bytes_pos_size_nal));av_reallocp_array(&s->skipped_bytes_pos_nal, new_size, sizeof(*s->skipped_bytes_pos_nal));s->skipped_bytes_pos_size_nal[s->nals_allocated] = 1024; // initial buffer sizes->skipped_bytes_pos_nal[s->nals_allocated] = av_malloc_array(s->skipped_bytes_pos_size_nal[s->nals_allocated], sizeof(*s->skipped_bytes_pos));s->nals_allocated = new_size;}s->skipped_bytes_pos_size = s->skipped_bytes_pos_size_nal[s->nb_nals];s->skipped_bytes_pos = s->skipped_bytes_pos_nal[s->nb_nals];nal = &s->nals[s->nb_nals];consumed = ff_hevc_extract_rbsp(s, buf, extract_length, nal);s->skipped_bytes_nal[s->nb_nals] = s->skipped_bytes;s->skipped_bytes_pos_size_nal[s->nb_nals] = s->skipped_bytes_pos_size;s->skipped_bytes_pos_nal[s->nb_nals++] = s->skipped_bytes_pos;if (consumed < 0) {ret = consumed;goto fail;}ret = init_get_bits8(&s->HEVClc->gb, nal->data, nal->size);if (ret < 0)goto fail;hls_nal_unit(s);if (s->nal_unit_type == NAL_EOB_NUT ||s->nal_unit_type == NAL_EOS_NUT)s->eos = 1;buf    += consumed;length -= consumed;}/* parse the NAL units */for (i = 0; i < s->nb_nals; i++) {int ret;s->skipped_bytes = s->skipped_bytes_nal[i];s->skipped_bytes_pos = s->skipped_bytes_pos_nal[i];//解码NALUret = decode_nal_unit(s, s->nals[i].data, s->nals[i].size);if (ret < 0) {av_log(s->avctx, AV_LOG_WARNING,"Error parsing NAL unit #%d.\n", i);goto fail;}}fail:if (s->ref && s->threads_type == FF_THREAD_FRAME)ff_thread_report_progress(&s->ref->tf, INT_MAX, 0);return ret;
}

从源码能够看出。decode_nal_units()中又调用了还有一个函数decode_nal_unit(),两者的名字仅仅相差一个“s”。

由此能够看出decode_nal_unit()作用是解码一个NALU。

decode_nal_unit()

decode_nal_unit()用于解码一个NALU。该函数的定义例如以下所看到的。
//解码一个NALU
static int decode_nal_unit(HEVCContext *s, const uint8_t *nal, int length)
{HEVCLocalContext *lc = s->HEVClc;GetBitContext *gb    = &lc->gb;int ctb_addr_ts, ret;ret = init_get_bits8(gb, nal, length);if (ret < 0)return ret;ret = hls_nal_unit(s);if (ret < 0) {av_log(s->avctx, AV_LOG_ERROR, "Invalid NAL unit %d, skipping.\n",s->nal_unit_type);goto fail;} else if (!ret)return 0;switch (s->nal_unit_type) {case NAL_VPS://解析VPSret = ff_hevc_decode_nal_vps(s);if (ret < 0)goto fail;break;case NAL_SPS://解析SPSret = ff_hevc_decode_nal_sps(s);if (ret < 0)goto fail;break;case NAL_PPS://解析PPSret = ff_hevc_decode_nal_pps(s);if (ret < 0)goto fail;break;case NAL_SEI_PREFIX:case NAL_SEI_SUFFIX://解析SEIret = ff_hevc_decode_nal_sei(s);if (ret < 0)goto fail;break;case NAL_TRAIL_R:case NAL_TRAIL_N:case NAL_TSA_N:case NAL_TSA_R:case NAL_STSA_N:case NAL_STSA_R:case NAL_BLA_W_LP:case NAL_BLA_W_RADL:case NAL_BLA_N_LP:case NAL_IDR_W_RADL:case NAL_IDR_N_LP:case NAL_CRA_NUT:case NAL_RADL_N:case NAL_RADL_R:case NAL_RASL_N:case NAL_RASL_R://解析Slice//解析Slice Headerret = hls_slice_header(s);if (ret < 0)return ret;if (s->max_ra == INT_MAX) {if (s->nal_unit_type == NAL_CRA_NUT || IS_BLA(s)) {s->max_ra = s->poc;} else {if (IS_IDR(s))s->max_ra = INT_MIN;}}if ((s->nal_unit_type == NAL_RASL_R || s->nal_unit_type == NAL_RASL_N) &&s->poc <= s->max_ra) {s->is_decoded = 0;break;} else {if (s->nal_unit_type == NAL_RASL_R && s->poc > s->max_ra)s->max_ra = INT_MIN;}if (s->sh.first_slice_in_pic_flag) {ret = hevc_frame_start(s);if (ret < 0)return ret;} else if (!s->ref) {av_log(s->avctx, AV_LOG_ERROR, "First slice in a frame missing.\n");goto fail;}if (s->nal_unit_type != s->first_nal_type) {av_log(s->avctx, AV_LOG_ERROR,"Non-matching NAL types of the VCL NALUs: %d %d\n",s->first_nal_type, s->nal_unit_type);return AVERROR_INVALIDDATA;}if (!s->sh.dependent_slice_segment_flag &&s->sh.slice_type != I_SLICE) {ret = ff_hevc_slice_rpl(s);if (ret < 0) {av_log(s->avctx, AV_LOG_WARNING,"Error constructing the reference lists for the current slice.\n");goto fail;}}//解码 Slice Dataif (s->threads_number > 1 && s->sh.num_entry_point_offsets > 0)ctb_addr_ts = hls_slice_data_wpp(s, nal, length);elsectb_addr_ts = hls_slice_data(s);if (ctb_addr_ts >= (s->sps->ctb_width * s->sps->ctb_height)) {s->is_decoded = 1;}if (ctb_addr_ts < 0) {ret = ctb_addr_ts;goto fail;}break;case NAL_EOS_NUT:case NAL_EOB_NUT:s->seq_decode = (s->seq_decode + 1) & 0xff;s->max_ra     = INT_MAX;break;case NAL_AUD:case NAL_FD_NUT:break;default:av_log(s->avctx, AV_LOG_INFO,"Skipping NAL unit %d\n", s->nal_unit_type);}return 0;
fail:if (s->avctx->err_recognition & AV_EF_EXPLODE)return ret;return 0;
}

从源码能够看出。decode_nal_unit()依据不同的NALU类型调用了不同的处理函数。这些处理函数能够分为两类——解析函数和解码函数,例如以下所看到的。
(1)解析函数(获取信息):
ff_hevc_decode_nal_vps():解析VPS。
ff_hevc_decode_nal_sps():解析SPS。

ff_hevc_decode_nal_pps():解析PPS。
ff_hevc_decode_nal_sei():解析SEI。
hls_slice_header():解析Slice Header。
(2)解码函数(解码得到图像):
hls_slice_data():解码Slice Data。
当中解析函数在文章《FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分》已经有过介绍,就不再反复叙述了。解码函数hls_slice_data()完毕了解码Slice的工作,以下看一下该函数的定义。



hls_slice_data()

hls_slice_data()用于解码Slice Data。该函数的定义例如以下所看到的。
//解码Slice Data
static int hls_slice_data(HEVCContext *s)
{int arg[2];int ret[2];arg[0] = 0;arg[1] = 1;//解码入口函数s->avctx->execute(s->avctx, hls_decode_entry, arg, ret , 1, sizeof(int));return ret[0];
}

能够看出该函数的源码非常easy,调用了还有一个函数hls_decode_entry()。

hls_decode_entry()

hls_decode_entry()是Slice Data解码的入口函数。该函数的定义例如以下所看到的。
/** 解码入口函数** 凝视:雷霄骅* leixiaohua1020@126.com* http://blog.csdn.net/leixiaohua1020**/
static int hls_decode_entry(AVCodecContext *avctxt, void *isFilterThread)
{HEVCContext *s  = avctxt->priv_data;//CTB尺寸int ctb_size    = 1 << s->sps->log2_ctb_size;int more_data   = 1;int x_ctb       = 0;int y_ctb       = 0;int ctb_addr_ts = s->pps->ctb_addr_rs_to_ts[s->sh.slice_ctb_addr_rs];if (!ctb_addr_ts && s->sh.dependent_slice_segment_flag) {av_log(s->avctx, AV_LOG_ERROR, "Impossible initial tile.\n");return AVERROR_INVALIDDATA;}if (s->sh.dependent_slice_segment_flag) {int prev_rs = s->pps->ctb_addr_ts_to_rs[ctb_addr_ts - 1];if (s->tab_slice_address[prev_rs] != s->sh.slice_addr) {av_log(s->avctx, AV_LOG_ERROR, "Previous slice segment missing\n");return AVERROR_INVALIDDATA;}}while (more_data && ctb_addr_ts < s->sps->ctb_size) {int ctb_addr_rs = s->pps->ctb_addr_ts_to_rs[ctb_addr_ts];//CTB的位置x和yx_ctb = (ctb_addr_rs % ((s->sps->width + ctb_size - 1) >> s->sps->log2_ctb_size)) << s->sps->log2_ctb_size;y_ctb = (ctb_addr_rs / ((s->sps->width + ctb_size - 1) >> s->sps->log2_ctb_size)) << s->sps->log2_ctb_size;//初始化周围的參数hls_decode_neighbour(s, x_ctb, y_ctb, ctb_addr_ts);//初始化CABACff_hevc_cabac_init(s, ctb_addr_ts);//样点自适应补偿參数hls_sao_param(s, x_ctb >> s->sps->log2_ctb_size, y_ctb >> s->sps->log2_ctb_size);s->deblock[ctb_addr_rs].beta_offset = s->sh.beta_offset;s->deblock[ctb_addr_rs].tc_offset   = s->sh.tc_offset;s->filter_slice_edges[ctb_addr_rs]  = s->sh.slice_loop_filter_across_slices_enabled_flag;/** CU示意图** 64x64块** 深度d=0* split_flag=1时候划分为4个32x32** +--------+--------+--------+--------+--------+--------+--------+--------+* |                                                                       |* |                                   |                                   |* |                                                                       |* +                                   |                                   +* |                                                                       |* |                                   |                                   |* |                                                                       |* +                                   |                                   +* |                                                                       |* |                                   |                                   |* |                                                                       |* +                                   |                                   +* |                                                                       |* |                                   |                                   |* |                                                                       |* + --  --  --  --  --  --  --  --  --+ --  --  --  --  --  --  --  --  --+* |                                   |                                   |* |                                                                       |* |                                   |                                   |* +                                                                       +* |                                   |                                   |* |                                                                       |* |                                   |                                   |* +                                                                       +* |                                   |                                   |* |                                                                       |* |                                   |                                   |* +                                                                       +* |                                   |                                   |* |                                                                       |* |                                   |                                   |* +--------+--------+--------+--------+--------+--------+--------+--------+*** 32x32 块* 深度d=1* split_flag=1时候划分为4个16x16** +--------+--------+--------+--------+* |                                   |* |                 |                 |* |                                   |* +                 |                 +* |                                   |* |                 |                 |* |                                   |* + --  --  --  --  + --  --  --  --  +* |                                   |* |                 |                 |* |                                   |* +                 |                 +* |                                   |* |                 |                 |* |                                   |* +--------+--------+--------+--------+*** 16x16 块* 深度d=2* split_flag=1时候划分为4个8x8** +--------+--------+* |                 |* |        |        |* |                 |* +  --  --+ --  -- +* |                 |* |        |        |* |                 |* +--------+--------+*** 8x8块* 深度d=3* split_flag=1时候划分为4个4x4** +----+----+* |    |    |* + -- + -- +* |    |    |* +----+----+**//** 解析四叉树结构。而且解码** hls_coding_quadtree(HEVCContext *s, int x0, int y0, int log2_cb_size, int cb_depth)中:* s:HEVCContext上下文结构体* x_ctb:CB位置的x坐标* y_ctb:CB位置的y坐标* log2_cb_size:CB大小取log2之后的值* cb_depth:深度**/more_data = hls_coding_quadtree(s, x_ctb, y_ctb, s->sps->log2_ctb_size, 0);if (more_data < 0) {s->tab_slice_address[ctb_addr_rs] = -1;return more_data;}ctb_addr_ts++;//保存解码信息以供下次使用ff_hevc_save_states(s, ctb_addr_ts);//去块效应滤波ff_hevc_hls_filters(s, x_ctb, y_ctb, ctb_size);}if (x_ctb + ctb_size >= s->sps->width &&y_ctb + ctb_size >= s->sps->height)ff_hevc_hls_filter(s, x_ctb, y_ctb, ctb_size);return ctb_addr_ts;
}

从源码能够看出。hls_decode_entry()以CTB为单位处理输入的视频流。每一个CTB的压缩数据经过以下两个基本步骤进行处理:
(1)调用hls_coding_quadtree()对CTB解码。当中包括了CU、PU、TU的解码。
(2)调用ff_hevc_hls_filters()进行滤波。当中包括去块效应滤波和SAO滤波。
hls_decode_entry()的函数调用关系例如以下图所看到的。

兴许的几篇文章将会对其调用的函数进行分析。


至此。FFmpeg HEVC解码器的主干部分的源码就分析完毕了。




雷霄骅
leixiaohua1020@126.com
http://blog.csdn.net/leixiaohua1020




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/290062.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET 产品组问卷调查|和我们分享你的 .NET 使用情况

作为一名 .NET 开发者&#xff0c;是什么让你开始学习 .NET&#xff1f;在你看来 .NET 在哪些场景下最有效&#xff1f;在平时的工作或学习中&#xff0c;你都在哪里学习 .NET 资源&#xff1f;你更希望在哪里看到更多 .NET 本地化内容&#xff1f;你觉得 .NET 的社区推动力如何…

C#+SQL Server数据库系统操作日志的实现完整案例

在开发数据库系统时,通常需要添加系统日志功能。系统日志是用来记录用户、管理员等对系统的操作记录,系统操作日志的实现方式有很多,本文基于C#和SQL Server数据库,通过设计日志记录表、编写操作记录存储过程、前端调用与展示结果等过程,实现操作日志功能完整程序设计流程…

使用机器学习算法在 .NET Core 中运行的 100% C# 开源 AI 聊天机器人平台构建器...

简介BotSharp是一个用于 AI Bot 平台构建器的开源机器学习框架。该项目涉及自然语言理解、计算机视觉和音频处理技术&#xff0c;旨在推动智能机器人助手在信息系统中的开发和应用。开箱即用的机器学习算法让普通程序员可以更快、更轻松地开发人工智能应用程序。地址https://gi…

win下nginx+php+mysql服务器套装_WNMP(Windows+Nginx+PHP+MySQL)安装

这篇文章介绍的内容是关于WNMP(Windows Nginx PHP MySQL) 安装&#xff0c;有着一定的参考价值&#xff0c;现在分享给大家&#xff0c;有需要的朋友可以参考一下最近在开发一个新的项目&#xff0c;环境用的是&#xff1a;Nginx1.10.3 下载地址&#xff1a; http://nginx.o…

本地开发时连接后台数据库时出现的错误,附自救方法

2019独角兽企业重金招聘Python工程师标准>>> 一、跨域问题 现状&#xff1a;后端跨域权限无法打开&#xff0c;现在的浏览器出于安全策略的限制&#xff0c;都是不允许跨域的&#xff0c;但是开发的时候经常需要一些别的域的接口&#xff0c;特别是一些接口不是自己…

Extjs 中的cookie设置

2019独角兽企业重金招聘Python工程师标准>>> 发现Extjs中有两个cookie 其一&#xff1a;设置cookie如下 saveacctisForm.getForm().findField(itemselector).getValue();Ext.util.Cookies.set(saveacct,saveacct); 取cookie中数据如下 var validStatus Ext.util.Co…

Java设计模式----策略模式(Strategy)

1. 策略模式&#xff1a; 策略模式&#xff0c;也称为政策模式,定义如下&#xff1a; 定义一组算法&#xff0c;将每个算法都封装起来&#xff0c;使他们可以相互转化 2. 策略模式的原理是面向对象的继承和多态。策略模式的3个角色 a. Strategy 抽象策略角色 定义每个策略或算…

遥控器原理的分页

索引&#xff1a;前几天看电视&#xff0c;使用遥控器的时候突然想到&#xff0c;我们的数据分页也可以用这种模式。于是敲出来一个类似于遥控器控制电视原理的分页。 现在详细介绍下上图中按钮的作用&#xff1a; 清除按钮&#xff1a;当按下0-9这个几个按钮时&#xff0c;如果…

.NET Core剪裁器升级瘦身引擎,并支持剪裁计划的录制和回放

上周&#xff0c;我发布了对.NET Core程序进行瘦身的开源软件Zack.DotNetTrimmer&#xff0c;与.NET Core内置的剪裁器相比&#xff0c;Zack.DotNetTrimmer不仅对程序的剪裁效果更好&#xff0c;而且还支持WPF、WinForm程序。下面是Zack.DotNetTrimmer与.NET内置的剪裁器的对比…

python 查看当前目录_Python的武器库11:os模块

说到编程语言python&#xff0c;有一个著名的格言"余生太短&#xff0c;只用python"。如果要分析为什么会存在这么一句格言&#xff1f;python的语法并不简单&#xff0c;有复杂难懂的部分&#xff0c;之所以又这样一句格言&#xff0c;是因为python中有很多强大的模…

ASP.NET Core 实现自定义认证

前言在 ASP.NET Core 中&#xff0c;我们常使用基于 JWT 的认证&#xff1a;services.AddAuthentication(option > {option.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme;option.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme;})…

图像处理工具类

为什么80%的码农都做不了架构师&#xff1f;>>> package net.kitbox.util;import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rende…

点击按钮,图片和按钮的文字发生改变

点击“隐藏”按钮&#xff0c;下方的图片隐藏&#xff0c;并且按钮上的文字由“隐藏”变为“显示”。再次点击&#xff0c;图片显示并且位子再次由“显示”变为“隐藏” 直接上代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta c…

mysql安装10045_mysql数据库5.6.45安装后的配置(离线安装包版)

二、windows10下的配置(1) 环境变量配置打开控制面板>系统和安全>系统>高级系统设置,选择环境变量,在系统变量中找到path,编辑该选项。第一行是oracle数据库的环境变量path配置&#xff0c;上图中最后一行是jdk的安装路径path配置。我们要添加mysql安装路径path配置。…

mysql 未找到命令_MySQL主从复制配置说明,一文教你搞懂数据库主从复制

一&#xff0c;MySQL主从配置原理1. mysql支持的复制格式基于语句复制(STATEMENT)&#xff08;优点&#xff09;基于statement复制的优点很明显&#xff0c;简单的记录执行语句同步到从库执行同样的语句&#xff0c;占用磁盘空间小&#xff0c;网络传输快&#xff0c;并且通过m…

.NET 5.0即将不再提供服务更新,请升级到.NET 6.0

5 月 8 日更新之后&#xff0c;微软将不再为 .NET 5.0 提供服务更新&#xff0c;包括安全修复或技术支持&#xff0c;用户需要将 .NET 版本更新到受支持的版本 (.NET 6.0 ) 才能继续接收更新。.NET 5.0 不是 LTS 版本&#xff0c;因此将在发布 18 个月或下一个版本发布后的 6 个…

关于PHP默认Expires: Thu, 19 Nov 1981...的故事

为何PHP不设置Expires头的时候, 默认输出如下的缓存头呢&#xff1f;: Expires: Thu, 19 Nov 1981 08:52:00 GMT 答案来自stackoverflow : Its an attempt to disable caching. 这是用于尝试禁用浏览器缓存PHP请求的 The date is the birthday of the developer Sascha Schuman…

认识与入门:Markdown

原文&#xff1a;http://www.jianshu.com/p/22ba695a7ce3 Markdown 是一种轻量级的「标记语言」&#xff0c;它的优点很多&#xff0c;目前也被越来越多的写作爱好者&#xff0c;撰稿者广泛使用。看到这里请不要被「标记」、「语言」所迷惑&#xff0c;Markdown 的语法十分简单…

游戏开发Camera之Cinematic Camera-深度

人的视觉系统是二维的&#xff0c;它通过生理和心理的暗示来感知图像的深度&#xff0c;在现实世界中视觉系统会自动用深度线索depth cue来确定对象之间的距离游戏画面也是二维的&#xff0c;用x&#xff0c;y轴来定义&#xff0c;画面深度用z轴来定义&#xff0c;可以通过创造…

hibernate注解方式来处理映射关系

在hibernate中&#xff0c;通常配置对象关系映射关系有两种&#xff0c;一种是基于xml的方式&#xff0c;另一种是基于annotation的注解方式&#xff0c;熟话说&#xff0c;萝卜青菜&#xff0c;可有所爱&#xff0c;每个人都有自己喜欢的配置方式&#xff0c;我在试了这两种方…