精华内容
下载资源
问答
  • 学习HEVC的必备资料,详细讲解HEVC解码器的硬件架构框架和具体细节
  • 针对HEVC解码的主观质量优化复杂度控制
  • 最新HEVC解码插件。

    2014-04-10 14:38:17
    2014年4月份发布的最新的Elecard的HEVC解码插件。
  • HEVC解码相关代码

    2018-01-11 16:12:40
    ffmpeg中hevc解码相关代码

    ff_hevc_decoder
    hevc_decode_frame–>decode_nal_units–>decode_nal_unit–>hls_slice_data–>hls_decode_entry–>hls_coding_quadtree(CTU)–>hls_coding_unit(解码CU)

    hls_coding_unit–>hls_prediction_unit(解码PU)–>hevc_luma_mv_mvp_mode(得到pu的mvp)–>ff_hevc_hls_mvd_coding(得到pu的mvd)–>abs_mvd_greater0_flag_decode

    hls_coding_unit–>hls_transform_tree(以当前CU为TU的根节点,判断是否划分)–>hls_transform_unit(解码PU)–>ff_hevc_hls_residual_coding

    static int hevc_decode_frame(AVCodecContext *avctx, void *data, int *got_output,
                                 AVPacket *avpkt)
    {//输入AVPacket *avpkt,输出为data;当有输出帧时got_output=1。
    //解码时,解码帧放在AVFrame *output_frame中
        int ret;
        int new_extradata_size;
        uint8_t *new_extradata;
        HEVCContext *s = avctx->priv_data;
    
        if (!avpkt->size) {
            ret = ff_hevc_output_frame(s, data, 1);
            if (ret < 0)
                return ret;
    
            *got_output = ret;
            return 0;
        }
    
        new_extradata = av_packet_get_side_data(avpkt, AV_PKT_DATA_NEW_EXTRADATA,
                                                &new_extradata_size);
        if (new_extradata && new_extradata_size > 0) {
            ret = hevc_decode_extradata(s, new_extradata, new_extradata_size, 0);
            if (ret < 0)
                return ret;
        }
    
        s->ref = NULL;
        ret    = decode_nal_units(s, avpkt->data, avpkt->size);//解码NAL,对每个nal会调用decode_nal_unit
        if (ret < 0)
            return ret;
    
        if (avctx->hwaccel) {
            if (s->ref && (ret = avctx->hwaccel->end_frame(avctx)) < 0) {
                av_log(avctx, AV_LOG_ERROR,
                       "hardware accelerator failed to decode picture\n");
                ff_hevc_unref_frame(s, s->ref, ~0);
                return ret;
            }
        } else {
            /* 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) {//解码完一帧后is_decoded=1
            av_log(avctx, AV_LOG_DEBUG, "Decoded frame with POC %d.\n", s->poc);
            s->is_decoded = 0;
        }
    
        if (s->output_frame->buf[0]) {//s->output_frame存放的是解码重构图像
            av_frame_move_ref(data, s->output_frame);
            *got_output = 1;
        }
    
        return avpkt->size;
    }
    
    static int hls_coding_quadtree(HEVCContext *s, int x0, int y0,
                                   int log2_cb_size, int cb_depth)
    {//(x0,y0)是当前cu的地址,log2_cb_size为当前cu的大小,cb_depth为当前cu的深度(64*64时为0)
        HEVCLocalContext *lc = s->HEVClc;
        const int cb_size    = 1 << log2_cb_size;
        int ret;
        int split_cu;
    
        lc->ct_depth = cb_depth;
        if (x0 + cb_size <= s->ps.sps->width  &&
            y0 + cb_size <= s->ps.sps->height &&
            log2_cb_size > s->ps.sps->log2_min_cb_size) {
            split_cu = ff_hevc_split_coding_unit_flag_decode(s, cb_depth, x0, y0);
        } else {
            split_cu = (log2_cb_size > s->ps.sps->log2_min_cb_size);
        }
        if (s->ps.pps->cu_qp_delta_enabled_flag &&
            log2_cb_size >= s->ps.sps->log2_ctb_size - s->ps.pps->diff_cu_qp_delta_depth) {
            lc->tu.is_cu_qp_delta_coded = 0;
            lc->tu.cu_qp_delta          = 0;
        }
    
        if (s->sh.cu_chroma_qp_offset_enabled_flag &&
            log2_cb_size >= s->ps.sps->log2_ctb_size - s->ps.pps->diff_cu_chroma_qp_offset_depth) {
            lc->tu.is_cu_chroma_qp_offset_coded = 0;
        }
    
        if (split_cu) {//划分为子块,分别处理4个子块
            int qp_block_mask = (1<<(s->ps.sps->log2_ctb_size - s->ps.pps->diff_cu_qp_delta_depth)) - 1;
            const int cb_size_split = cb_size >> 1;
            const int x1 = x0 + cb_size_split;
            const int y1 = y0 + cb_size_split;
    
            int more_data = 0;
    
            more_data = hls_coding_quadtree(s, x0, y0, log2_cb_size - 1, cb_depth + 1);//递归
            if (more_data < 0)
                return more_data;
    
            if (more_data && x1 < s->ps.sps->width) {
                more_data = hls_coding_quadtree(s, x1, y0, log2_cb_size - 1, cb_depth + 1);
                if (more_data < 0)
                    return more_data;
            }
            if (more_data && y1 < s->ps.sps->height) {
                more_data = hls_coding_quadtree(s, x0, y1, log2_cb_size - 1, cb_depth + 1);
                if (more_data < 0)
                    return more_data;
            }
            if (more_data && x1 < s->ps.sps->width &&
                y1 < s->ps.sps->height) {
                more_data = hls_coding_quadtree(s, x1, y1, log2_cb_size - 1, cb_depth + 1);
                if (more_data < 0)
                    return more_data;
            }
    
            if(((x0 + (1<<log2_cb_size)) & qp_block_mask) == 0 &&
                ((y0 + (1<<log2_cb_size)) & qp_block_mask) == 0)
                lc->qPy_pred = lc->qp_y;
    
            if (more_data)
                return ((x1 + cb_size_split) < s->ps.sps->width ||
                        (y1 + cb_size_split) < s->ps.sps->height);
            else
                return 0;
        } else {//不划分,解码当前块
            ret = hls_coding_unit(s, x0, y0, log2_cb_size);//x0和y0分别为当前块的起始地址
            if (ret < 0)
                return ret;
            if ((!((x0 + cb_size) %
                   (1 << (s->ps.sps->log2_ctb_size))) ||
                 (x0 + cb_size >= s->ps.sps->width)) &&
                (!((y0 + cb_size) %
                   (1 << (s->ps.sps->log2_ctb_size))) ||
                 (y0 + cb_size >= s->ps.sps->height))) {
                int end_of_slice_flag = ff_hevc_end_of_slice_flag_decode(s);
                return !end_of_slice_flag;
            } else {
                return 1;
            }
        }
    
        return 0;
    }
    static int hls_coding_unit(HEVCContext *s, int x0, int y0, int log2_cb_size)
    {//(x0,y0)是当前cu的地址,log2_cb_size为当前cu的大小
        int cb_size          = 1 << log2_cb_size;//当前块的大小
        HEVCLocalContext *lc = s->HEVClc;
        int log2_min_cb_size = s->ps.sps->log2_min_cb_size;//值为4 。。编码器指定,可能为3,可能为4
        int length           = cb_size >> log2_min_cb_size;//可以将最小划分块看成1,则为横向有多少个最小划分块。
        int min_cb_width     = s->ps.sps->min_cb_width;//120  为图像宽/最小划分块          1920/16=120
        int x_cb             = x0 >> log2_min_cb_size;
        int y_cb             = y0 >> log2_min_cb_size;//(x_cb,y_cb)是基于最小划分块的地址,比如(x0,y0)=(32,0),最小划分块为16*16,则(x_cb,y_cb)=(2,0)
        int idx              = log2_cb_size - 2;
        int qp_block_mask    = (1<<(s->ps.sps->log2_ctb_size - s->ps.pps->diff_cu_qp_delta_depth)) - 1;
        int x, y, ret;
    
        lc->cu.x                = x0;
        lc->cu.y                = y0;
        lc->cu.pred_mode        = MODE_INTRA;
        lc->cu.part_mode        = PART_2Nx2N;
        lc->cu.intra_split_flag = 0;
    
        SAMPLE_CTB(s->skip_flag, x_cb, y_cb) = 0;
        for (x = 0; x < 4; x++)
            lc->pu.intra_pred_mode[x] = 1;
        if (s->ps.pps->transquant_bypass_enable_flag) {
            lc->cu.cu_transquant_bypass_flag = ff_hevc_cu_transquant_bypass_flag_decode(s);
            if (lc->cu.cu_transquant_bypass_flag)
                set_deblocking_bypass(s, x0, y0, log2_cb_size);
        } else
            lc->cu.cu_transquant_bypass_flag = 0;
    
        if (s->sh.slice_type != HEVC_SLICE_I) {
            uint8_t skip_flag = ff_hevc_skip_flag_decode(s, x0, y0, x_cb, y_cb);//解析出skip_flag
    
            x = y_cb * min_cb_width + x_cb;
            for (y = 0; y < length; y++) {
                memset(&s->skip_flag[x], skip_flag, length);
                x += min_cb_width;
            }
            lc->cu.pred_mode = skip_flag ? MODE_SKIP : MODE_INTER;
        } else {
            x = y_cb * min_cb_width + x_cb;
            for (y = 0; y < length; y++) {
                memset(&s->skip_flag[x], 0, length);
                x += min_cb_width;
            }
        }
    
        if (SAMPLE_CTB(s->skip_flag, x_cb, y_cb)) {//skip模式,是merge中的一种,不需要传输残差和mv信息
            //av_log(NULL, AV_LOG_INFO, "XXXXXXXXXXXXXXXXskip_flag\n");
            hls_prediction_unit(s, x0, y0, cb_size, cb_size, log2_cb_size, 0, idx);
            intra_prediction_unit_default_value(s, x0, y0, log2_cb_size);
    
            if (!s->sh.disable_deblocking_filter_flag)
                ff_hevc_deblocking_boundary_strengths(s, x0, y0, log2_cb_size);
        } else {//av_log(NULL, AV_LOG_INFO, "XXXXXXXXXXXXXXXX2\n");
            int pcm_flag = 0;
    
            if (s->sh.slice_type != HEVC_SLICE_I)
                lc->cu.pred_mode = ff_hevc_pred_mode_decode(s);
            if (lc->cu.pred_mode != MODE_INTRA ||
                log2_cb_size == s->ps.sps->log2_min_cb_size) {
                lc->cu.part_mode        = ff_hevc_part_mode_decode(s, log2_cb_size);//cu的预测模式,帧间或帧内
                lc->cu.intra_split_flag = lc->cu.part_mode == PART_NxN &&
                                          lc->cu.pred_mode == MODE_INTRA;//pu的划分
            }
    
            if (lc->cu.pred_mode == MODE_INTRA) {//intra cu
                if (lc->cu.part_mode == PART_2Nx2N && s->ps.sps->pcm_enabled_flag &&
                    log2_cb_size >= s->ps.sps->pcm.log2_min_pcm_cb_size &&
                    log2_cb_size <= s->ps.sps->pcm.log2_max_pcm_cb_size) {
                    pcm_flag = ff_hevc_pcm_flag_decode(s);
                }
                if (pcm_flag) {//PCM模式
                    intra_prediction_unit_default_value(s, x0, y0, log2_cb_size);
                    ret = hls_pcm_sample(s, x0, y0, log2_cb_size);
                    if (s->ps.sps->pcm.loop_filter_disable_flag)
                        set_deblocking_bypass(s, x0, y0, log2_cb_size);
    
                    if (ret < 0)
                        return ret;
                } else {
                    intra_prediction_unit(s, x0, y0, log2_cb_size);//帧内预测CU得到预测值
                }
            } else {//inter cu
                intra_prediction_unit_default_value(s, x0, y0, log2_cb_size);
                switch (lc->cu.part_mode) {
                case PART_2Nx2N:
                    hls_prediction_unit(s, x0, y0, cb_size, cb_size, log2_cb_size, 0, idx);//帧间预测CU得到预测值
                    break;
                case PART_2NxN:
                    hls_prediction_unit(s, x0, y0,               cb_size, cb_size / 2, log2_cb_size, 0, idx);
                    hls_prediction_unit(s, x0, y0 + cb_size / 2, cb_size, cb_size / 2, log2_cb_size, 1, idx);
                    break;
                case PART_Nx2N:
                    hls_prediction_unit(s, x0,               y0, cb_size / 2, cb_size, log2_cb_size, 0, idx - 1);
                    hls_prediction_unit(s, x0 + cb_size / 2, y0, cb_size / 2, cb_size, log2_cb_size, 1, idx - 1);
                    break;
                case PART_2NxnU:
                    hls_prediction_unit(s, x0, y0,               cb_size, cb_size     / 4, log2_cb_size, 0, idx);
                    hls_prediction_unit(s, x0, y0 + cb_size / 4, cb_size, cb_size * 3 / 4, log2_cb_size, 1, idx);
                    break;
                case PART_2NxnD:
                    hls_prediction_unit(s, x0, y0,                   cb_size, cb_size * 3 / 4, log2_cb_size, 0, idx);
                    hls_prediction_unit(s, x0, y0 + cb_size * 3 / 4, cb_size, cb_size     / 4, log2_cb_size, 1, idx);
                    break;
                case PART_nLx2N:
                    hls_prediction_unit(s, x0,               y0, cb_size     / 4, cb_size, log2_cb_size, 0, idx - 2);
                    hls_prediction_unit(s, x0 + cb_size / 4, y0, cb_size * 3 / 4, cb_size, log2_cb_size, 1, idx - 2);
                    break;
                case PART_nRx2N:
                    hls_prediction_unit(s, x0,                   y0, cb_size * 3 / 4, cb_size, log2_cb_size, 0, idx - 2);
                    hls_prediction_unit(s, x0 + cb_size * 3 / 4, y0, cb_size     / 4, cb_size, log2_cb_size, 1, idx - 2);
                    break;
                case PART_NxN:
                    hls_prediction_unit(s, x0,               y0,               cb_size / 2, cb_size / 2, log2_cb_size, 0, idx - 1);
                    hls_prediction_unit(s, x0 + cb_size / 2, y0,               cb_size / 2, cb_size / 2, log2_cb_size, 1, idx - 1);
                    hls_prediction_unit(s, x0,               y0 + cb_size / 2, cb_size / 2, cb_size / 2, log2_cb_size, 2, idx - 1);
                    hls_prediction_unit(s, x0 + cb_size / 2, y0 + cb_size / 2, cb_size / 2, cb_size / 2, log2_cb_size, 3, idx - 1);
                    break;
                }
            }
    
            if (!pcm_flag) {
                int rqt_root_cbf = 1;
    
                if (lc->cu.pred_mode != MODE_INTRA &&
                    !(lc->cu.part_mode == PART_2Nx2N && lc->pu.merge_flag)) {
                    rqt_root_cbf = ff_hevc_no_residual_syntax_flag_decode(s);
                }
    
                if (rqt_root_cbf) {
                    const static int cbf[2] = { 0 };
                    lc->cu.max_trafo_depth = lc->cu.pred_mode == MODE_INTRA ?
                                             s->ps.sps->max_transform_hierarchy_depth_intra + lc->cu.intra_split_flag :
                                             s->ps.sps->max_transform_hierarchy_depth_inter;
    
                    ret = hls_transform_tree(s, x0, y0, x0, y0, x0, y0,
                                             log2_cb_size,
                                             log2_cb_size, 0, 0, cbf, cbf);//处理TU 
                    if (ret < 0)
                        return ret;
                } else {
                    if (!s->sh.disable_deblocking_filter_flag)
                        ff_hevc_deblocking_boundary_strengths(s, x0, y0, log2_cb_size);
                }
            }
        }
    
        if (s->ps.pps->cu_qp_delta_enabled_flag && lc->tu.is_cu_qp_delta_coded == 0)
            ff_hevc_set_qPy(s, x0, y0, log2_cb_size);
    
        x = y_cb * min_cb_width + x_cb;
        for (y = 0; y < length; y++) {
            memset(&s->qp_y_tab[x], lc->qp_y, length);
            x += min_cb_width;
        }
        //av_log(NULL, AV_LOG_INFO,"###########qp_y_tab=%d %d %d\n",s->qp_y_tab[x],s->qp_y_tab[x+1],s->qp_y_tab[x+1]);
    
        if(((x0 + (1<<log2_cb_size)) & qp_block_mask) == 0 &&
           ((y0 + (1<<log2_cb_size)) & qp_block_mask) == 0) {
            lc->qPy_pred = lc->qp_y;
        }
    
        set_ct_depth(s, x0, y0, log2_cb_size, lc->ct_depth);
    
        return 0;
    }
    static int hls_transform_tree(HEVCContext *s, int x0, int y0,
                                  int xBase, int yBase, int cb_xBase, int cb_yBase,
                                  int log2_cb_size, int log2_trafo_size,
                                  int trafo_depth, int blk_idx,
                                  const int *base_cbf_cb, const int *base_cbf_cr)
    {//log2_cb_size为cu的大小;log2_trafo_size为tu的大小(log后)。。 (x0,y0)为tu起始像素地址;(xBase,yase)为cu起始像素地址
    //blk_idx为块的索引(若划分为四个子块,则为0~3)
        HEVCLocalContext *lc = s->HEVClc;
        uint8_t split_transform_flag;
        int cbf_cb[2];
        int cbf_cr[2];
        int ret;
    
        cbf_cb[0] = base_cbf_cb[0];
        cbf_cb[1] = base_cbf_cb[1];
        cbf_cr[0] = base_cbf_cr[0];
        cbf_cr[1] = base_cbf_cr[1];
    
        if (lc->cu.intra_split_flag) {
            if (trafo_depth == 1) {
                lc->tu.intra_pred_mode   = lc->pu.intra_pred_mode[blk_idx];
                if (s->ps.sps->chroma_format_idc == 3) {
                    lc->tu.intra_pred_mode_c = lc->pu.intra_pred_mode_c[blk_idx];
                    lc->tu.chroma_mode_c     = lc->pu.chroma_mode_c[blk_idx];
                } else {
                    lc->tu.intra_pred_mode_c = lc->pu.intra_pred_mode_c[0];
                    lc->tu.chroma_mode_c     = lc->pu.chroma_mode_c[0];
                }
            }
        } else {
            lc->tu.intra_pred_mode   = lc->pu.intra_pred_mode[0];
            lc->tu.intra_pred_mode_c = lc->pu.intra_pred_mode_c[0];
            lc->tu.chroma_mode_c     = lc->pu.chroma_mode_c[0];
        }
    
        if (log2_trafo_size <= s->ps.sps->log2_max_trafo_size &&
            log2_trafo_size >  s->ps.sps->log2_min_tb_size    &&
            trafo_depth     < lc->cu.max_trafo_depth       &&
            !(lc->cu.intra_split_flag && trafo_depth == 0)) {
            split_transform_flag = ff_hevc_split_transform_flag_decode(s, log2_trafo_size);
        } else {
            int inter_split = s->ps.sps->max_transform_hierarchy_depth_inter == 0 &&
                              lc->cu.pred_mode == MODE_INTER &&
                              lc->cu.part_mode != PART_2Nx2N &&
                              trafo_depth == 0;
    
            split_transform_flag = log2_trafo_size > s->ps.sps->log2_max_trafo_size ||
                                   (lc->cu.intra_split_flag && trafo_depth == 0) ||
                                   inter_split;//是否划分
        }
    
        if (s->ps.sps->chroma_format_idc && (log2_trafo_size > 2 || s->ps.sps->chroma_format_idc == 3)) {
            if (trafo_depth == 0 || cbf_cb[0]) {
                cbf_cb[0] = ff_hevc_cbf_cb_cr_decode(s, trafo_depth);
                if (s->ps.sps->chroma_format_idc == 2 && (!split_transform_flag || log2_trafo_size == 3)) {
                    cbf_cb[1] = ff_hevc_cbf_cb_cr_decode(s, trafo_depth);
                }
            }
    
            if (trafo_depth == 0 || cbf_cr[0]) {
                cbf_cr[0] = ff_hevc_cbf_cb_cr_decode(s, trafo_depth);
                if (s->ps.sps->chroma_format_idc == 2 && (!split_transform_flag || log2_trafo_size == 3)) {
                    cbf_cr[1] = ff_hevc_cbf_cb_cr_decode(s, trafo_depth);
                }
            }
        }
    
        if (split_transform_flag) {//划分,递归调用
            const int trafo_size_split = 1 << (log2_trafo_size - 1);
            const int x1 = x0 + trafo_size_split;
            const int y1 = y0 + trafo_size_split;
    
    #define SUBDIVIDE(x, y, idx)                                                    \
    do {                                                                            \
        ret = hls_transform_tree(s, x, y, x0, y0, cb_xBase, cb_yBase, log2_cb_size, \
                                 log2_trafo_size - 1, trafo_depth + 1, idx,         \
                                 cbf_cb, cbf_cr);                                   \
        if (ret < 0)                                                                \
            return ret;                                                             \
    } while (0)
    
            SUBDIVIDE(x0, y0, 0);//blk_idx在变,划分四个子块,每个块递归处理
            SUBDIVIDE(x1, y0, 1);
            SUBDIVIDE(x0, y1, 2);
            SUBDIVIDE(x1, y1, 3);
    
    #undef SUBDIVIDE
        } else {//处理当前tu
            int min_tu_size      = 1 << s->ps.sps->log2_min_tb_size;
            int log2_min_tu_size = s->ps.sps->log2_min_tb_size;
            int min_tu_width     = s->ps.sps->min_tb_width;
            int cbf_luma         = 1;
    
            if (lc->cu.pred_mode == MODE_INTRA || trafo_depth != 0 ||
                cbf_cb[0] || cbf_cr[0] ||
                (s->ps.sps->chroma_format_idc == 2 && (cbf_cb[1] || cbf_cr[1]))) {
                cbf_luma = ff_hevc_cbf_luma_decode(s, trafo_depth);
            }
                //av_log(NULL, AV_LOG_INFO, "$$$$$$$$$$$tu=%d %d\n",log2_trafo_size , s->ps.sps->log2_max_trafo_size);
    
            ret = hls_transform_unit(s, x0, y0, xBase, yBase, cb_xBase, cb_yBase,
                                     log2_cb_size, log2_trafo_size,
                                     blk_idx, cbf_luma, cbf_cb, cbf_cr);//处理TU-反量化DCT反变换  
            if (ret < 0)
                return ret;
            // TODO: store cbf_luma somewhere else
            if (cbf_luma) {
                int i, j;
                for (i = 0; i < (1 << log2_trafo_size); i += min_tu_size)
                    for (j = 0; j < (1 << log2_trafo_size); j += min_tu_size) {
                        int x_tu = (x0 + j) >> log2_min_tu_size;
                        int y_tu = (y0 + i) >> log2_min_tu_size;
                        s->cbf_luma[y_tu * min_tu_width + x_tu] = 1;
                    }
            }
            if (!s->sh.disable_deblocking_filter_flag) {
                ff_hevc_deblocking_boundary_strengths(s, x0, y0, log2_trafo_size);
                if (s->ps.pps->transquant_bypass_enable_flag &&
                    lc->cu.cu_transquant_bypass_flag)
                    set_deblocking_bypass(s, x0, y0, log2_trafo_size);
            }
        }
        return 0;
    }
    static int hls_transform_unit(HEVCContext *s, int x0, int y0,
                                  int xBase, int yBase, int cb_xBase, int cb_yBase,
                                  int log2_cb_size, int log2_trafo_size,
                                  int blk_idx, int cbf_luma, int *cbf_cb, int *cbf_cr)
    {//(x0,y0)为tu起始像素地址;(xBase,yase)为cu起始像素地址
    //blk_idx为当前块在上一层的索引(0123HEVCLocalContext *lc = s->HEVClc;
        const int log2_trafo_size_c = log2_trafo_size - s->ps.sps->hshift[1];
        int i;
    
        if (lc->cu.pred_mode == MODE_INTRA) {
            int trafo_size = 1 << log2_trafo_size;
            ff_hevc_set_neighbour_available(s, x0, y0, trafo_size, trafo_size);
    
            s->hpc.intra_pred[log2_trafo_size - 2](s, x0, y0, 0);
        }
    
        if (cbf_luma || cbf_cb[0] || cbf_cr[0] ||
            (s->ps.sps->chroma_format_idc == 2 && (cbf_cb[1] || cbf_cr[1]))) {
            int scan_idx   = SCAN_DIAG;
            int scan_idx_c = SCAN_DIAG;
            int cbf_chroma = cbf_cb[0] || cbf_cr[0] ||
                             (s->ps.sps->chroma_format_idc == 2 &&
                             (cbf_cb[1] || cbf_cr[1]));
    
            if (s->ps.pps->cu_qp_delta_enabled_flag && !lc->tu.is_cu_qp_delta_coded) {
                lc->tu.cu_qp_delta = ff_hevc_cu_qp_delta_abs(s);//得到tu的亮度qp_delta
                if (lc->tu.cu_qp_delta != 0)
                    if (ff_hevc_cu_qp_delta_sign_flag(s) == 1)
                        lc->tu.cu_qp_delta = -lc->tu.cu_qp_delta;
                lc->tu.is_cu_qp_delta_coded = 1;
    
                if (lc->tu.cu_qp_delta < -(26 + s->ps.sps->qp_bd_offset / 2) ||
                    lc->tu.cu_qp_delta >  (25 + s->ps.sps->qp_bd_offset / 2)) {
                    av_log(s->avctx, AV_LOG_ERROR,
                           "The cu_qp_delta %d is outside the valid range "
                           "[%d, %d].\n",
                           lc->tu.cu_qp_delta,
                           -(26 + s->ps.sps->qp_bd_offset / 2),
                            (25 + s->ps.sps->qp_bd_offset / 2));
                    return AVERROR_INVALIDDATA;
                }
    
                ff_hevc_set_qPy(s, cb_xBase, cb_yBase, log2_cb_size);//得到tu的亮度qp,存放在s->HEVClc->qp_y中
            }
    
            if (s->sh.cu_chroma_qp_offset_enabled_flag && cbf_chroma &&
                !lc->cu.cu_transquant_bypass_flag  &&  !lc->tu.is_cu_chroma_qp_offset_coded) {
                int cu_chroma_qp_offset_flag = ff_hevc_cu_chroma_qp_offset_flag(s);
                if (cu_chroma_qp_offset_flag) {
                    int cu_chroma_qp_offset_idx  = 0;
                    if (s->ps.pps->chroma_qp_offset_list_len_minus1 > 0) {
                        cu_chroma_qp_offset_idx = ff_hevc_cu_chroma_qp_offset_idx(s);
                        av_log(s->avctx, AV_LOG_ERROR,
                            "cu_chroma_qp_offset_idx not yet tested.\n");
                    }
                    lc->tu.cu_qp_offset_cb = s->ps.pps->cb_qp_offset_list[cu_chroma_qp_offset_idx];//得到tu的色度qp_offset
                    lc->tu.cu_qp_offset_cr = s->ps.pps->cr_qp_offset_list[cu_chroma_qp_offset_idx];
                } else {
                    lc->tu.cu_qp_offset_cb = 0;
                    lc->tu.cu_qp_offset_cr = 0;
                }
                lc->tu.is_cu_chroma_qp_offset_coded = 1;
            }
    
            if (lc->cu.pred_mode == MODE_INTRA && log2_trafo_size < 4) {
                if (lc->tu.intra_pred_mode >= 6 &&
                    lc->tu.intra_pred_mode <= 14) {
                    scan_idx = SCAN_VERT;
                } else if (lc->tu.intra_pred_mode >= 22 &&
                           lc->tu.intra_pred_mode <= 30) {
                    scan_idx = SCAN_HORIZ;
                }
    
                if (lc->tu.intra_pred_mode_c >=  6 &&
                    lc->tu.intra_pred_mode_c <= 14) {
                    scan_idx_c = SCAN_VERT;
                } else if (lc->tu.intra_pred_mode_c >= 22 &&
                           lc->tu.intra_pred_mode_c <= 30) {
                    scan_idx_c = SCAN_HORIZ;
                }
            }
    
            lc->tu.cross_pf = 0;
            //av_log(NULL, AV_LOG_INFO, "%d",s->ps.sps->chroma_format_idc);//1
            //av_log(NULL, AV_LOG_INFO, "%d",cbf_cb[0]);
    
            if (cbf_luma)
                ff_hevc_hls_residual_coding(s, x0, y0, log2_trafo_size, scan_idx, 0);//读取残差数据,进行反量化,DCT反变换  
            if (s->ps.sps->chroma_format_idc && (log2_trafo_size > 2 || s->ps.sps->chroma_format_idc == 3)) {
                //av_log(NULL, AV_LOG_INFO, "@@@@@@@@@@@@@@@@@@0\n");
                int trafo_size_h = 1 << (log2_trafo_size_c + s->ps.sps->hshift[1]);
                int trafo_size_v = 1 << (log2_trafo_size_c + s->ps.sps->vshift[1]);
                lc->tu.cross_pf  = (s->ps.pps->cross_component_prediction_enabled_flag && cbf_luma &&
                                    (lc->cu.pred_mode == MODE_INTER ||
                                     (lc->tu.chroma_mode_c ==  4)));
    
                if (lc->tu.cross_pf) {
                    hls_cross_component_pred(s, 0);
                }
                for (i = 0; i < (s->ps.sps->chroma_format_idc == 2 ? 2 : 1); i++) {
                    if (lc->cu.pred_mode == MODE_INTRA) {
                        ff_hevc_set_neighbour_available(s, x0, y0 + (i << log2_trafo_size_c), trafo_size_h, trafo_size_v);
                        s->hpc.intra_pred[log2_trafo_size_c - 2](s, x0, y0 + (i << log2_trafo_size_c), 1);
                    }
                    if (cbf_cb[i])
                        ff_hevc_hls_residual_coding(s, x0, y0 + (i << log2_trafo_size_c),
                                                    log2_trafo_size_c, scan_idx_c, 1);
                    else
                        if (lc->tu.cross_pf) {
                            ptrdiff_t stride = s->frame->linesize[1];
                            int hshift = s->ps.sps->hshift[1];
                            int vshift = s->ps.sps->vshift[1];
                            int16_t *coeffs_y = (int16_t*)lc->edge_emu_buffer;
                            int16_t *coeffs   = (int16_t*)lc->edge_emu_buffer2;
                            int size = 1 << log2_trafo_size_c;
    
                            uint8_t *dst = &s->frame->data[1][(y0 >> vshift) * stride +
                                                                  ((x0 >> hshift) << s->ps.sps->pixel_shift)];
                            for (i = 0; i < (size * size); i++) {
                                coeffs[i] = ((lc->tu.res_scale_val * coeffs_y[i]) >> 3);
                            }
                            s->hevcdsp.add_residual[log2_trafo_size_c-2](dst, coeffs, stride);//叠加残差数据  
                        }
                }
    
                if (lc->tu.cross_pf) {
                    hls_cross_component_pred(s, 1);
                }
                for (i = 0; i < (s->ps.sps->chroma_format_idc == 2 ? 2 : 1); i++) {
                    if (lc->cu.pred_mode == MODE_INTRA) {
                        ff_hevc_set_neighbour_available(s, x0, y0 + (i << log2_trafo_size_c), trafo_size_h, trafo_size_v);
                        s->hpc.intra_pred[log2_trafo_size_c - 2](s, x0, y0 + (i << log2_trafo_size_c), 2);
                    }
                    if (cbf_cr[i])
                        ff_hevc_hls_residual_coding(s, x0, y0 + (i << log2_trafo_size_c),
                                                    log2_trafo_size_c, scan_idx_c, 2);
                    else
                        if (lc->tu.cross_pf) {
                            ptrdiff_t stride = s->frame->linesize[2];
                            int hshift = s->ps.sps->hshift[2];
                            int vshift = s->ps.sps->vshift[2];
                            int16_t *coeffs_y = (int16_t*)lc->edge_emu_buffer;
                            int16_t *coeffs   = (int16_t*)lc->edge_emu_buffer2;
                            int size = 1 << log2_trafo_size_c;
    
                            uint8_t *dst = &s->frame->data[2][(y0 >> vshift) * stride +
                                                              ((x0 >> hshift) << s->ps.sps->pixel_shift)];
                            for (i = 0; i < (size * size); i++) {
                                coeffs[i] = ((lc->tu.res_scale_val * coeffs_y[i]) >> 3);
                            }
                            s->hevcdsp.add_residual[log2_trafo_size_c-2](dst, coeffs, stride);
                        }
                }
            } else if (s->ps.sps->chroma_format_idc && blk_idx == 3) {
            //此时log2_trafo_size=2且blk_idx == 3,即当前块亮度大小为4*4,上一层的8*8块只有一个4*4的cb块,所以色度必须在最后一个子块(即blk_idx == 3)处理
            //av_log(NULL, AV_LOG_INFO, "@@@@@@@@@@@@@@@@@@1\n");
                int trafo_size_h = 1 << (log2_trafo_size + 1);
                int trafo_size_v = 1 << (log2_trafo_size + s->ps.sps->vshift[1]);
                for (i = 0; i < (s->ps.sps->chroma_format_idc == 2 ? 2 : 1); i++) {
                    if (lc->cu.pred_mode == MODE_INTRA) {
                        ff_hevc_set_neighbour_available(s, xBase, yBase + (i << log2_trafo_size),
                                                        trafo_size_h, trafo_size_v);
                        s->hpc.intra_pred[log2_trafo_size - 2](s, xBase, yBase + (i << log2_trafo_size), 1);
                    }
                    if (cbf_cb[i])
                        ff_hevc_hls_residual_coding(s, xBase, yBase + (i << log2_trafo_size),
                                                    log2_trafo_size, scan_idx_c, 1);
                }
                for (i = 0; i < (s->ps.sps->chroma_format_idc == 2 ? 2 : 1); i++) {
                    if (lc->cu.pred_mode == MODE_INTRA) {
                        ff_hevc_set_neighbour_available(s, xBase, yBase + (i << log2_trafo_size),
                                                    trafo_size_h, trafo_size_v);
                        s->hpc.intra_pred[log2_trafo_size - 2](s, xBase, yBase + (i << log2_trafo_size), 2);
                    }
                    if (cbf_cr[i])
                        ff_hevc_hls_residual_coding(s, xBase, yBase + (i << log2_trafo_size),
                                                    log2_trafo_size, scan_idx_c, 2);
                }
            }
        } else if (s->ps.sps->chroma_format_idc && lc->cu.pred_mode == MODE_INTRA) {
        //av_log(NULL, AV_LOG_INFO, "@@@@@@@@@@@@@@@@@@2\n");
            if (log2_trafo_size > 2 || s->ps.sps->chroma_format_idc == 3) {
                int trafo_size_h = 1 << (log2_trafo_size_c + s->ps.sps->hshift[1]);
                int trafo_size_v = 1 << (log2_trafo_size_c + s->ps.sps->vshift[1]);
                ff_hevc_set_neighbour_available(s, x0, y0, trafo_size_h, trafo_size_v);
                s->hpc.intra_pred[log2_trafo_size_c - 2](s, x0, y0, 1);
                s->hpc.intra_pred[log2_trafo_size_c - 2](s, x0, y0, 2);
                if (s->ps.sps->chroma_format_idc == 2) {
                    ff_hevc_set_neighbour_available(s, x0, y0 + (1 << log2_trafo_size_c),
                                                    trafo_size_h, trafo_size_v);
                    s->hpc.intra_pred[log2_trafo_size_c - 2](s, x0, y0 + (1 << log2_trafo_size_c), 1);
                    s->hpc.intra_pred[log2_trafo_size_c - 2](s, x0, y0 + (1 << log2_trafo_size_c), 2);
                }
            } else if (blk_idx == 3) {
                int trafo_size_h = 1 << (log2_trafo_size + 1);
                int trafo_size_v = 1 << (log2_trafo_size + s->ps.sps->vshift[1]);
                ff_hevc_set_neighbour_available(s, xBase, yBase,
                                                trafo_size_h, trafo_size_v);
                s->hpc.intra_pred[log2_trafo_size - 2](s, xBase, yBase, 1);
                s->hpc.intra_pred[log2_trafo_size - 2](s, xBase, yBase, 2);
                if (s->ps.sps->chroma_format_idc == 2) {
                    ff_hevc_set_neighbour_available(s, xBase, yBase + (1 << (log2_trafo_size)),
                                                    trafo_size_h, trafo_size_v);
                    s->hpc.intra_pred[log2_trafo_size - 2](s, xBase, yBase + (1 << (log2_trafo_size)), 1);
                    s->hpc.intra_pred[log2_trafo_size - 2](s, xBase, yBase + (1 << (log2_trafo_size)), 2);
                }
            }
        }
    
        return 0;
    }

    //未完
    参考http://blog.csdn.net/leixiaohua1020/article/details/46412897

    展开全文
  • H.265/HEVC解码器民间项目X265工程源码最新版本2.5H.265/HEVC解码器民间项目X265工程源码最新版本2.5
  • Eleacrd最新版HEVC解码插件,需要与Elecard的Mpeg Player一起使用,可以解码HEVC/H.265的文件或实时流。
  • Windows 免费 HEVC 解码器 下载

    万次阅读 2019-11-14 15:08:10
    正规的 HEVC 解码器 在微软商店里面售价是7元,但实际上这个APP是免费的,以上是正规免费的安装地址。安装后系统自带的UWP视频播放器【电影和电视】将能够播放 H.265 视频,并对 HDR 提供支持。 当然了,很多第三方...

    免费下载:https://www.microsoft.com/store/productId/9N4WGH0Z6VHQ

    正规的 HEVC 解码器 在微软商店里面售价是7元,但实际上这个APP是免费的,以上是正规免费的安装地址。安装后系统自带的UWP视频播放器【电影和电视】将能够播放 H.265 视频,并对 HDR 提供支持。

    当然了,很多第三方软件也提供了解码支持,比如收费的PowerDVD和免费的MPC(建议附加madVR)。但是如果机器硬件支持【杜比视界】的话,在安装 Dolby Vision UWP(https://www.chiphell.com/thread-2136981-1-1.html) 之后,目前尝试的只有 Windows 自带的 【电影和电视】 能够正常支持。

    展开全文
  • 本文分析FFmpeg的HEVC解码器的主干部分。“主干部分”是相对于“CTU解码”、 “环路滤波”这些细节部分而言的。它包含了HEVC解码器直到hls_decode_entry()前面的函数调用关系(hls_decode_entry()后面就是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;
    
        //初始化CABAC
        ff_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;
        else
            s->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;
            else
                s->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取值为1
            ret = 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 */
            	//查找起始码0x000001
                while (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 size
                s->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];
            //解码NALU
            ret = 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:
        	//解析VPS
            ret = ff_hevc_decode_nal_vps(s);
            if (ret < 0)
                goto fail;
            break;
        case NAL_SPS:
        	//解析SPS
            ret = ff_hevc_decode_nal_sps(s);
            if (ret < 0)
                goto fail;
            break;
        case NAL_PPS:
        	//解析PPS
            ret = ff_hevc_decode_nal_pps(s);
            if (ret < 0)
                goto fail;
            break;
        case NAL_SEI_PREFIX:
        case NAL_SEI_SUFFIX:
        	//解析SEI
            ret = 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 Header
            ret = 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 Data
            if (s->threads_number > 1 && s->sh.num_entry_point_offsets > 0)
                ctb_addr_ts = hls_slice_data_wpp(s, nal, length);
            else
                ctb_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];
    }
    

    可以看出该函数的源代码很简单,调用了另一个函数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和y
            x_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);
            //初始化CABAC
            ff_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




    展开全文
  • =====================================================HEVC源码分析文章列表:【解码 -libavcodec HEVC 解码器】FFmpeg的HEVC解码器源码简单分析:概述FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分...

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

    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;
    
        //初始化CABAC
        ff_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;
        else
            s->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;
            else
                s->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取值为1
            ret = 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 */
            	//查找起始码0x000001
                while (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 size
                s->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];
            //解码NALU
            ret = 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:
        	//解析VPS
            ret = ff_hevc_decode_nal_vps(s);
            if (ret < 0)
                goto fail;
            break;
        case NAL_SPS:
        	//解析SPS
            ret = ff_hevc_decode_nal_sps(s);
            if (ret < 0)
                goto fail;
            break;
        case NAL_PPS:
        	//解析PPS
            ret = ff_hevc_decode_nal_pps(s);
            if (ret < 0)
                goto fail;
            break;
        case NAL_SEI_PREFIX:
        case NAL_SEI_SUFFIX:
        	//解析SEI
            ret = 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 Header
            ret = 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 Data
            if (s->threads_number > 1 && s->sh.num_entry_point_offsets > 0)
                ctb_addr_ts = hls_slice_data_wpp(s, nal, length);
            else
                ctb_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和y
            x_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);
            //初始化CABAC
            ff_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




    展开全文
  • 行业分类-设备装置-HEVC解码器在异构计算平台上的设计及节能算法研究.zip
  • FFmpeg的HEVC解码器源代码简单分析:概述 FFmpeg的HEVC解码器源代码简单分析:解析器(Parser)部分 FFmpeg的HEVC解码器源代码简单分析:解码器主干部分 FFmpeg的HEVC解码器源代码简单分析:CTU解码(CTU Decode...
  • =====================================================HEVC源码分析文章列表:【解码 -libavcodec HEVC 解码器】FFmpeg的HEVC解码器源码简单分析:概述FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分...
  • HEVC解码器的TDecCu部分

    千次阅读 2014-12-26 10:46:30
    我的这篇文章《HEVC解码器流程》给出了HEVC解码器的整体流程图,这里主要针对TDecCu部分进行讲解。 TDecCu入口主要有两部分:一部分是decodeCtu,完成的是熵解码和逆扫描;另外一部分是decompressCtu,完成的是预测...
  • ===================================================== HEVC源代码分析文章列表: 【解码 -libavcodec HEVC 解码器】 FFmpeg的HEVC解码器源代码简单分析:概述 FFmpeg的HEVC解码器源代码简单分析...
  • hevc解码过程代码实现

    2019-07-30 20:04:51
    h265解码实现,根据HEVC标准文档实现从裸流解码到slice前,slice解码暂无实现。有利于更好理解参考队列的创建与更新。
  • ===================================================== HEVC源代码分析文章列表: 【解码 -libavcodec HEVC 解码器】 FFmpeg的HEVC解码器源代码简单分析:概述 FFmpeg的HEVC解码器源代码简单分析...
  • ===================================================== HEVC源代码分析文章列表: 【解码 -libavcodec HEVC 解码器】 FFmpeg的HEVC解码器源代码简单分析:概述 FFmpeg的HEVC解码器源代码简单分析...
  • 行业分类-设备装置-基于多核平台多层次任务级与数据级并行的HEVC解码方法
  • ===================================================== HEVC源代码分析文章列表: 【解码 -libavcodec HEVC 解码器】 FFmpeg的HEVC解码器源代码简单分析:概述 FFmpeg的HEVC解码器源代码简单分析...
  • HEVC解码器HM源码阅读(一)介绍

    千次阅读 2017-05-02 17:52:58
    HEVC解码器HM源码阅读(一)介绍 之前看HM的编码器,总觉得理解得不够深入,很多地方只是了解大概的处理流程,具体含义没有理解。为了更加深入理解HEVC,现在开始从解码器的角度分析HEVC,说不定能够取得不错的...
  • =====================================================HEVC源码分析文章列表:【解码 -libavcodec HEVC 解码器】FFmpeg的HEVC解码器源码简单分析:概述FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分...
  • 在本文中,我们提出了一种基于CTU的混合了任务级并行度和数据级并行度的HEVC混合并行解码策略。 数据级并行性使不同解码阶段​​的执行时间分布更加平衡,并使任务级并行性更加高效。 我们的方法对位流没有任何限制...
  • 通过调试ffmpeg对HEVC码流(格式为es流,就是rawvideo)的解码过程,分析ffmpeg的HEVC解码过程和实现方法。 首先要说的是调试所用的工程的config为:./configure –disable-asm –disable-pthreads –enable-debug ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,724
精华内容 2,289
关键字:

hevc解码