x264 - x264_slicetype_mb_cost




/* Output buffers are separated by 128 bytes to avoid false sharing of cachelines
 * in multithreaded lookahead. */
#define PAD_SIZE 32
/* cost_est, cost_est_aq, intra_mbs, num rows */
#define NUM_INTS 4
#define COST_EST 0
#define COST_EST_AQ 1
#define INTRA_MBS 2
#define NUM_ROWS 3
#define ROW_SATD (NUM_INTS + (h->mb.i_mb_y - h->i_threadslice_start))

// output_inter, output_intra 是各自指向的同一块
// 内存块的两个不同部分。每一部分由四部分构成
// cost_est, cost_est_aq, intra_mbs, num_rows
static void x264_slicetype_mb_cost( x264_t *h, x264_mb_analysis_t *a,
                                    x264_frame_t **frames, int p0, int p1, int b,
                                    int dist_scale_factor, int do_search[2], const x264_weight_t *w,
                                    int *output_inter, int *output_intra )
{
    // b 是带编码的帧的索引,
    // p0是前向参考帧索引,
    // p1是后向参考帧索引
    x264_frame_t *fref0 = frames[p0];
    x264_frame_t *fref1 = frames[p1];
    x264_frame_t *fenc  = frames[b];
   
    // 是否待编码的帧作为B帧来编码,
    // 如果预测编码的帧为B帧的编码代价, p1 != b, 且 b < p1
    const int b_bidir = (b < p1);
    // 获取预测宏块的 x, y 坐标(以16x16宏块为单位)
    const int i_mb_x = h->mb.i_mb_x;
    const int i_mb_y = h->mb.i_mb_y;
    // 以16x16宏块为单位的跨度
    const int i_mb_stride = h->mb.i_mb_width;
    // 二维坐标转换为一维线性坐标
    const int i_mb_xy = i_mb_x + i_mb_y * i_mb_stride;
    // 待编码子像素平面的跨度(以像素为单位)
    const int i_stride = fenc->i_stride_lowres;
    // i_mb_x, i_mb_y 是宏块坐标(以16x16宏块为单位)
    // lowres保存的是半尺寸的orig, H, V, HV像素插值,
    // 即他们的尺寸只有原图像的一半
    // i_pel_offset 就是计算的 i_mb_x, i_mb_y宏块
    // 在lowres对应的首像素坐标
    const int i_pel_offset = 8 * (i_mb_x + i_mb_y * i_stride);
    // 双向预测权重
    const int i_bipred_weight = h->param.analyse.b_weighted_bipred ? 64 - (dist_scale_factor>>2) : 32;
   
    // int16_t (*lowres_mvs[2][X264_BFRAME_MAX+1])[2];
    // b - p0 - 1 表示前向参考距
    // p1 - b - 1 表示后向参考距
    // 猜测, [0][b - p0 - 1] 表示前向参考帧距的mv(P帧)
    //        [1][p1 - b - 1] 表示后向参考帧距的mv (B帧)
    int16_t (*fenc_mvs[2])[2] = { &fenc->lowres_mvs[0][b-p0-1][i_mb_xy], &fenc->lowres_mvs[1][p1-b-1][i_mb_xy] };
   
    // int     *lowres_mv_costs[2][X264_BFRAME_MAX+1];
    // b - p0 - 1 表示前向参考距
    // p1 - b - 1 表示后向参考距
    // 猜测, [0][b - p0 - 1] 表示前向参考帧距的mv代价(P帧)
    //        [1][p1 - b - 1] 表示后向参考帧距的mv代价 (B帧)
    int (*fenc_costs[2]) = { &fenc->lowres_mv_costs[0][b-p0-1][i_mb_xy], &fenc->lowres_mv_costs[1][p1-b-1][i_mb_xy] };
    // 根据i_mb_x, i_mb_y的坐标来确定是否宏块积分
    int b_frame_score_mb = (i_mb_x > 0 && i_mb_x < h->mb.i_mb_width - 1 &&
                            i_mb_y > 0 && i_mb_y < h->mb.i_mb_height - 1) ||
                            h->mb.i_mb_width <= 2 || h->mb.i_mb_height <= 2;

    // 声明变量 pixel pixl[9*32];
    ALIGNED_ARRAY_16( pixel, pix1,[9*FDEC_STRIDE] );
    // pix2 = pix1 + 8; 即偏移8个像素
    pixel *pix2 = pix1+8;
    // 声明两个预测结构体变量
    x264_me_t m[2];
    // 初始化i_bcost = COST_MAX
    // list_used = 0;
    int i_bcost = COST_MAX;
    int list_used = 0;
    /* A small, arbitrary bias to avoid VBV problems caused by zero-residual lookahead blocks. */
    int lowres_penalty = 4;

    // 定位Y平面
    h->mb.pic.p_fenc[0] = h->mb.pic.fenc_buf;
   
    // MC_COPY( 8 )
    // h->mc.copy[PIXEL_8X8] = mc_copy_w8
    // #define MC_COPY(W) \
    // static void mc_copy_w##W( pixel *dst, intptr_t i_dst, pixel *src, intptr_t i_src, int i_height ) \
    // { \
    //   mc_copy( src, i_src, dst, i_dst, W, i_height ); \
    // }
   
    // static void mc_copy( pixel *src, intptr_t i_src_stride, pixel *dst, intptr_t i_dst_stride, int i_width, int i_height )
    // {
    //   for( int y = 0; y < i_height; y++ )
    //   {
    //       memcpy( dst, src, i_width * sizeof(pixel) );

    //       src += i_src_stride;
    //       dst += i_dst_stride;
    //   }
    // }

    // fenc->lowres[0], [1],[2],[3]分别对应整像素,
    // 水平1/2像素, 垂直1/2像素, 斜对角1/2像素平面
    // 将lowres orig 对应i_mb_x, i_mb_y宏块
    // 的宏块像素拷贝到 h->mb.pic.p_fenc[0],
    // 是一个8x8像素块的拷贝
    h->mc.copy[PIXEL_8x8]( h->mb.pic.p_fenc[0], FENC_STRIDE, &fenc->lowres[0][i_pel_offset], i_stride, 8 );

    // 如果p0 == p1, 那么待编码帧将被作I帧来编码
    if( p0 == p1 )
        goto lowres_intra_mb;

    // no need for h->mb.mv_min[]
    // 设定一些限制
    h->mb.mv_limit_fpel[0][0] = -8*h->mb.i_mb_x - 4;
    h->mb.mv_limit_fpel[1][0] = 8*( h->mb.i_mb_width - h->mb.i_mb_x - 1 ) + 4;
    h->mb.mv_min_spel[0] = 4*( h->mb.mv_limit_fpel[0][0] - 8 );
    h->mb.mv_max_spel[0] = 4*( h->mb.mv_limit_fpel[1][0] + 8 );
    if( h->mb.i_mb_x >= h->mb.i_mb_width - 2 )
    {
        h->mb.mv_limit_fpel[0][1] = -8*h->mb.i_mb_y - 4;
        h->mb.mv_limit_fpel[1][1] = 8*( h->mb.i_mb_height - h->mb.i_mb_y - 1 ) + 4;
        h->mb.mv_min_spel[1] = 4*( h->mb.mv_limit_fpel[0][1] - 8 );
        h->mb.mv_max_spel[1] = 4*( h->mb.mv_limit_fpel[1][1] + 8 );
    }

#define LOAD_HPELS_LUMA(dst, src) \
    { \
        (dst)[0] = &(src)[0][i_pel_offset]; \
        (dst)[1] = &(src)[1][i_pel_offset]; \
        (dst)[2] = &(src)[2][i_pel_offset]; \
        (dst)[3] = &(src)[3][i_pel_offset]; \
    }
#define LOAD_WPELS_LUMA(dst,src) \
    (dst) = &(src)[i_pel_offset];

#define CLIP_MV( mv ) \
    { \
        mv[0] = x264_clip3( mv[0], h->mb.mv_min_spel[0], h->mb.mv_max_spel[0] ); \
        mv[1] = x264_clip3( mv[1], h->mb.mv_min_spel[1], h->mb.mv_max_spel[1] ); \
    }
#define TRY_BIDIR( mv0, mv1, penalty ) \
    { \
        int i_cost; \
        if( h->param.analyse.i_subpel_refine <= 1 ) \
        { \
            int hpel_idx1 = (((mv0)[0]&2)>>1) + ((mv0)[1]&2); \
            int hpel_idx2 = (((mv1)[0]&2)>>1) + ((mv1)[1]&2); \
            pixel *src1 = m[0].p_fref[hpel_idx1] + ((mv0)[0]>>2) + ((mv0)[1]>>2) * m[0].i_stride[0]; \
            pixel *src2 = m[1].p_fref[hpel_idx2] + ((mv1)[0]>>2) + ((mv1)[1]>>2) * m[1].i_stride[0]; \
            h->mc.avg[PIXEL_8x8]( pix1, 16, src1, m[0].i_stride[0], src2, m[1].i_stride[0], i_bipred_weight ); \
        } \
        else \
        { \
            intptr_t stride1 = 16, stride2 = 16; \
            pixel *src1, *src2; \
            // h->mc.get_ref = get_ref
            // 1/4像素预插值
            // 依据mv0得到插值平面src1,
            // 依据mv1得到插值平面src2
            src1 = h->mc.get_ref( pix1, &stride1, m[0].p_fref, m[0].i_stride[0], \
                                  (mv0)[0], (mv0)[1], 8, 8, w ); \
            src2 = h->mc.get_ref( pix2, &stride2, m[1].p_fref, m[1].i_stride[0], \
                                  (mv1)[0], (mv1)[1], 8, 8, w ); \
            // h->mc.avg[PIXEL_8X8] = pixel_avg_8x8
            // 进行插值,并将插值结果保存在pix1
            h->mc.avg[PIXEL_8x8]( pix1, 16, src1, stride1, src2, stride2, i_bipred_weight ); \
        } \
        // h->pixf.mbcmp[PIXEL_8X8] = x264_pixel_satd_8x8
        // 求m[0].p_fenc[0] 与插值宏块之间的satd
        i_cost = penalty * a->i_lambda + h->pixf.mbcmp[PIXEL_8x8]( \
                           m[0].p_fenc[0], FENC_STRIDE, pix1, 16 ); \
        // 如果代价更小,则替换i_bcost, list_used
        COPY2_IF_LT( i_bcost, i_cost, list_used, 3 ); \
    }

    m[0].i_pixel = PIXEL_8x8;
    m[0].p_cost_mv = a->p_cost_mv;
    m[0].i_stride[0] = i_stride;
    m[0].p_fenc[0] = h->mb.pic.p_fenc[0];
    m[0].weight = w;
    m[0].i_ref = 0;
    // 将前向参考帧的lowres orig, h, v, hv平面
    // 对应i_mb_x, i_mb_y宏块的lowres宏块地址
    // 分别赋给m[0].p_fref[0], [1], [2], [3]
    LOAD_HPELS_LUMA( m[0].p_fref, fref0->lowres );
    // 将上述orig 对应到i_mb_x, i_mb_y宏块的地址
    // 赋给 p_fref_w
    m[0].p_fref_w = m[0].p_fref[0];
    // 如果w[0].weightfn不为0, 则将fenc->weighted[0]
    // 对应i_mb_x, i_mb_y宏块的地址赋值给p_fref_w
    if( w[0].weightfn )
        LOAD_WPELS_LUMA( m[0].p_fref_w, fenc->weighted[0] );

    if( b_bidir )
    {
        // mvr 指向前后向参考帧距的宏块(i_mb_xy)的运动向量
        int16_t *mvr = fref1->lowres_mvs[0][p1-p0-1][i_mb_xy];
        // 声明 int16_t dmv[2][2];
        ALIGNED_ARRAY_8( int16_t, dmv,[2],[2] );

        m[1].i_pixel = PIXEL_8x8;
        m[1].p_cost_mv = a->p_cost_mv;
        m[1].i_stride[0] = i_stride;
        m[1].p_fenc[0] = h->mb.pic.p_fenc[0];
        m[1].i_ref = 0;
        m[1].weight = x264_weight_none;
       
        // 将后向参考帧的lowres orig, h, v, hv平面
        // 对应i_mb_x, i_mb_y宏块的lowres宏块地址
        // 分别赋给m[0].p_fref[0], [1], [2], [3]
        LOAD_HPELS_LUMA( m[1].p_fref, fref1->lowres );
        // 将m[1].pfref[0], 即lowres orig对应
        // i_mb_xy宏块在lowres orig地址
        // 赋值给m[1].p_fref_w
        m[1].p_fref_w = m[1].p_fref[0];

        // dmv 表示直接预测
        // 直接预测就是采用对应参考图像宏块的mv,
        // 然后根据图像的poc距离得到的
        // dist_scale_factor = ( ((b-p0) << 8) + ((p1-p0) >> 1) ) / (p1-p0);
       
        dmv[0][0] = ( mvr[0] * dist_scale_factor + 128 ) >> 8;
        dmv[0][1] = ( mvr[1] * dist_scale_factor + 128 ) >> 8;
        dmv[1][0] = dmv[0][0] - mvr[0];
        dmv[1][1] = dmv[0][1] - mvr[1];
        CLIP_MV( dmv[0] );
        CLIP_MV( dmv[1] );
        if( h->param.analyse.i_subpel_refine <= 1 )
            M64( dmv ) &= ~0x0001000100010001ULL; /* mv & ~1 */

        TRY_BIDIR( dmv[0], dmv[1], 0 );
        if( M64( dmv ) )
        {
            int i_cost;
           
            // 在前向参考宏块, 和后向参考宏块之间进行插值
            // h->mc.avg[PIXEL_8X8] = pixel_avg_8x8
            // 进行插值,并将插值结果保存在pix1
            h->mc.avg[PIXEL_8x8]( pix1, 16, m[0].p_fref[0], m[0].i_stride[0], m[1].p_fref[0], m[1].i_stride[0], i_bipred_weight );
            // h->pixf.mbcmp[PIXEL_8X8] = x264_pixel_satd_8x8
            // 求m[0].p_fenc[0] 与插值宏块之间的satd
            i_cost = h->pixf.mbcmp[PIXEL_8x8]( m[0].p_fenc[0], FENC_STRIDE, pix1, 16 );
            COPY2_IF_LT( i_bcost, i_cost, list_used, 3 );
        }
    }
   
    // do_search[0] 前向参考搜索
    // do_search[1] 后向参考搜索
    for( int l = 0; l < 1 + b_bidir; l++ )
    {
        if( do_search[l] )
        {
            int i_mvc = 0;
            int16_t (*fenc_mv)[2] = fenc_mvs[l];
            ALIGNED_4( int16_t mvc[4][2] );

            /* Reverse-order MV prediction. */
            M32( mvc[0] ) = 0;
            M32( mvc[2] ) = 0;
           
            // 下面这段代码, 我猜是取两端的mv, 然后取中值
#define MVC(mv) { CP32( mvc[i_mvc], mv ); i_mvc++; }
            if( i_mb_x < h->mb.i_mb_width - 1 )
                MVC( fenc_mv[1] );
            if( i_mb_y < h->i_threadslice_end - 1 )
            {
                MVC( fenc_mv[i_mb_stride] );
                if( i_mb_x > 0 )
                    MVC( fenc_mv[i_mb_stride-1] );
                if( i_mb_x < h->mb.i_mb_width - 1 )
                    MVC( fenc_mv[i_mb_stride+1] );
            }
#undef MVC
            if( i_mvc <= 1 )
                CP32( m[l].mvp, mvc[0] );
            else
                x264_median_mv( m[l].mvp, mvc[0], mvc[1], mvc[2] );

            /* Fast skip for cases of near-zero residual.  Shortcut: don't bother except in the mv0 case,
             * since anything else is likely to have enough residual to not trigger the skip. */
            if( !M32( m[l].mvp ) )
            {
                // h->pixf.mbcmp[PIXEL_8x8] = x264_pixel_satd_8x8
                // 求(lowres)编码帧与其前向参考帧(lowres)之间的8x8宏块的satd
                m[l].cost = h->pixf.mbcmp[PIXEL_8x8]( m[l].p_fenc[0], FENC_STRIDE, m[l].p_fref[0], m[l].i_stride[0] );
                if( m[l].cost < 64 )
                {
                    M32( m[l].mv ) = 0;
                    goto skip_motionest;
                }
            }

            x264_me_search( h, &m[l], mvc, i_mvc );
            m[l].cost -= a->p_cost_mv[0]; // remove mvcost from skip mbs
            if( M32( m[l].mv ) )
                m[l].cost += 5 * a->i_lambda;

skip_motionest:
            CP32( fenc_mvs[l], m[l].mv );
            *fenc_costs[l] = m[l].cost;
        }
        else
        {
            CP32( m[l].mv, fenc_mvs[l] );
            m[l].cost = *fenc_costs[l];
        }
        COPY2_IF_LT( i_bcost, m[l].cost, list_used, l+1 );
    }

    if( b_bidir && ( M32( m[0].mv ) || M32( m[1].mv ) ) )
        TRY_BIDIR( m[0].mv, m[1].mv, 5 );

lowres_intra_mb:
    if( !fenc->b_intra_calculated )
    {
        // pixel edge[36];
        ALIGNED_ARRAY_16( pixel, edge,[36] );
        //  pix 指向 ???
        pixel *pix = &pix1[8+FDEC_STRIDE];
        // 源指向fenc->lowres[0][i_pel_offset];
        pixel *src = &fenc->lowres[0][i_pel_offset];
        // intra惩罚指数
        const int intra_penalty = 5 * a->i_lambda;
        int satds[3];
        int pixoff = 4 / sizeof(pixel);

        /* Avoid store forwarding stalls by writing larger chunks */
        // 拷贝宏块的左,顶, 顶右像素
        memcpy( pix-FDEC_STRIDE, src-i_stride, 16 * sizeof(pixel) );
        for( int i = -1; i < 8; i++ )
            M32( &pix[i*FDEC_STRIDE-pixoff] ) = M32( &src[i*i_stride-pixoff] );

        // 为什么调用的是色度预测???
        // h->pixf.intra_mbcmp_x3_8x8c = x264_intra_satd_x3_8x8c
        // INTRA_MBCMP(satd,  8x8,  dc, h,  v, c,, _c )
        // #define INTRA_MBCMP( mbcmp, size, pred1, pred2, pred3, chroma, cpu, cpu2 )\
        // void x264_intra_##mbcmp##_x3_##size##chroma##cpu( pixel *fenc, pixel *fdec, int res[3] )\
        // {\
        //     x264_predict_##size##chroma##_##pred1##cpu2( fdec );\
        //     res[0] = x264_pixel_##mbcmp##_##size##cpu( fdec, FDEC_STRIDE, fenc, FENC_STRIDE );\
        //     x264_predict_##size##chroma##_##pred2##cpu2( fdec );\
        //     res[1] = x264_pixel_##mbcmp##_##size##cpu( fdec, FDEC_STRIDE, fenc, FENC_STRIDE );\
        //     x264_predict_##size##chroma##_##pred3##cpu2( fdec );\
        //     res[2] = x264_pixel_##mbcmp##_##size##cpu( fdec, FDEC_STRIDE, fenc, FENC_STRIDE );\
        // }
        // 作三次不同的预测, 并与待编码帧进行预测求satd,
        // 将三个satd的结果保存在satds数组中
        h->pixf.intra_mbcmp_x3_8x8c( h->mb.pic.p_fenc[0], pix, satds );
        // 求其中最小的cost
        int i_icost = X264_MIN3( satds[0], satds[1], satds[2] );

        if( h->param.analyse.i_subpel_refine > 1 )
        {
            // h->predict_8x8c[I_PRED_CHROMA] = x264_predict_8x8c_p_c
            // 预测色度, 色度平面预测法(4:2:0)
            h->predict_8x8c[I_PRED_CHROMA_P]( pix );
           
            // h->pixf.mbcmp[PIXEL_8x8] = x264_pixel_satd_8x8
            int satd = h->pixf.mbcmp[PIXEL_8x8]( pix, FDEC_STRIDE, h->mb.pic.p_fenc[0], FENC_STRIDE );
           
            // 取小者
            i_icost = X264_MIN( i_icost, satd );
           
            // h->predict_8x8_filter = x264_predict_8x8_filter_c
            // 从源宏块抽取出边l0,...,l7; lt, t0,...,t15
            // 并保存在edge
            h->predict_8x8_filter( pix, edge, ALL_NEIGHBORS, ALL_NEIGHBORS );
            for( int i = 3; i < 9; i++ )
            {   // h->predict_8x8[i] = x264_predict_8x8_ddl_c 等
                // 用其他方法进行预测
                h->predict_8x8[i]( pix, edge );
                // h->pixf.mbcmp[PIXEL_8x8] = x264_pixel_satd_8x8
                // 求satd
                satd = h->pixf.mbcmp[PIXEL_8x8]( pix, FDEC_STRIDE, h->mb.pic.p_fenc[0], FENC_STRIDE );
                取小者
                i_icost = X264_MIN( i_icost, satd );
            }
        }

        i_icost += intra_penalty + lowres_penalty;
        // 保存当前宏块的i_icost
        fenc->i_intra_cost[i_mb_xy] = i_icost;
        int i_icost_aq = i_icost;
        if( h->param.rc.i_aq_mode ) // 调整i_icost_aq
            i_icost_aq = (i_icost_aq * fenc->i_inv_qscale_factor[i_mb_xy] + 128) >> 8;
        // 保存到output_intra内存块
        output_intra[ROW_SATD] += i_icost_aq;
        if( b_frame_score_mb )
        {
            // 保存到est, est_aq
            output_intra[COST_EST] += i_icost;
            output_intra[COST_EST_AQ] += i_icost_aq;
        }
    }
    i_bcost += lowres_penalty;

    /* forbid intra-mbs in B-frames, because it's rare and not worth checking */
    /* FIXME: Should we still forbid them now that we cache intra scores? */
    if( !b_bidir )
    {
        int i_icost = fenc->i_intra_cost[i_mb_xy];
        int b_intra = i_icost < i_bcost;
        if( b_intra )
        {
            i_bcost = i_icost;
            list_used = 0;
        }
        // 统计有多少个intra_mb 编码模式
        if( b_frame_score_mb )
            output_inter[INTRA_MBS] += b_intra;
    }

    /* In an I-frame, we've already added the results above in the intra section. */
    // 统计非I帧数据
    if( p0 != p1 )
    {
        int i_bcost_aq = i_bcost;
        if( h->param.rc.i_aq_mode )
            i_bcost_aq = (i_bcost_aq * fenc->i_inv_qscale_factor[i_mb_xy] + 128) >> 8;
        output_inter[ROW_SATD] += i_bcost_aq;
        if( b_frame_score_mb )
        {
            /* Don't use AQ-weighted costs for slicetype decision, only for ratecontrol. */
            output_inter[COST_EST] += i_bcost;
            output_inter[COST_EST_AQ] += i_bcost_aq;
        }
    }
    // 保存最小代价
    fenc->lowres_costs[b-p0][p1-b][i_mb_xy] = X264_MIN( i_bcost, LOWRES_COST_MASK ) + (list_used << LOWRES_COST_SHIFT);
}


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这个命令是一个Tcl脚本命令,用于进行物理综合前的设计优化。具体解释如下: - `ungroup -start_level 4 -all`: 将设计中的组合逻辑和时序逻辑分开,此处 `-start_level 4` 表示从第四层开始分组,`-all` 表示分组所有的单元。 - `set_cost_priority -delay`: 设置优化目标为减小时序路径上的延迟。 - `set_critical_range 0.2 $Design`: 设置时序约束的最小保留时间为0.2ns。 - `remove_unconnected_ports [get_cells -hier * ]`: 删除所有没有连接的端口,`[get_cells -hier * ]` 表示获取所有单元。 - `-blast group_path -name in2reg -from [all_inputs] -critical_range 1000.0`: 将所有输入到寄存器的路径组合在一起,命名为 `in2reg`,并设置时序约束为1000.0ns。 - `-blast group_path -name reg2out -to [all_outputs] -critical_range 1000.0`: 将所有从寄存器到输出的路径组合在一起,命名为 `reg2out`,并设置时序约束为1000.0ns。 - `-blast group_path -name in2out -from [all_inputs] -to [all_outputs] -critical_range 1000.0`: 将所有从输入到输出的路径组合在一起,命名为 `in2out`,并设置时序约束为1000.0ns。 - `-blast group_path -name reg2reg -from [all_registers] -to [all_registers] -critical_range 1000.0`: 将所有寄存器之间的路径组合在一起,命名为 `reg2reg`,并设置时序约束为1000.0ns。 这些命令将通过路径组合和时序约束的设置来优化设计,以满足时序要求并提高设计的性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值