x264代码学习笔记(二):x264_encoder_encode函数

本文详细介绍了x264_encoder_encode函数在视频编码中的作用,包括编码顺序与播放顺序的区别、POC概念、帧类型分析、编码流程以及x264_frame_t结构体的重要参数。通过分析和处理,函数实现了从原始YUV数据到编码码流的转换,并涉及NALU封装、码率控制和统计信息记录。
摘要由CSDN通过智能技术生成

encode()函数中循环调用encode_frame()函数进行逐帧编码:

  1. 调用x264_encoder_encode()函数完成一帧编码;
  2. 将编码后的码流载入码流文件中。
static int encode_frame( x264_t *h, hnd_t hout, x264_picture_t *pic, int64_t *last_dts )
{
    x264_picture_t pic_out;  // 编码后输出帧
    x264_nal_t *nal;  // NAL数据包
    int i_nal;
    int i_frame_size = 0;

    i_frame_size = x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out );  // 编码单帧

    FAIL_IF_ERROR( i_frame_size < 0, "x264_encoder_encode failed\n" );

    if( i_frame_size )
    {
        i_frame_size = cli_output.write_frame( hout, nal[0].p_payload, i_frame_size, &pic_out ); // 将编码后的图片装入输出视频流
        *last_dts = pic_out.i_dts;   // 显示时间戳
    }

    return i_frame_size;
}

x264_encoder_encode()函数,x264中一个十分重要的函数,理解此函数对后面算法函数的深入十分关键:

  1. 首先介绍一下poc的概念。视频编码顺序与视频的播放顺序,并不完全相同。视频编码时,如果采用了B帧编码,由于B帧很多时候都是双向预测得来的,这时会先编码B帧的后向预测图像(P帧),然后再进行B帧编码,因此会把视频原来的播放顺序打乱,以新的编码顺序输出码流。而在解码端接收到码流后,需将编码顺序还原为播放顺序,以输出正确的视频播放顺序。在编解码中,视频的播放顺序序号叫做POC(picture order count)
  2. fenc为x264_frame_t结构体,用于存储待编码帧。初始化fenc变量,为其分配存储空间并初始化数值。分配方式为若有已分配好的存储空间则直接利用,否则新建一个存储空间
  3. 将原始待编码数据(YUV数据)塞入fenc变量内,以及对fenc结构体内的其他成员变量进行赋值
  4. 初始化lookahead线程, 将fenc放入lookahead.next.list[](h变量的结构体内部变量)队列,等待确定帧类型
  5. 在lookhead线程中分析图像帧,通过lookahead分析帧类型。该函数调用x264_slicetype_decide()函数确定slice类型, 调用x264_slicetype_analyse()函数计算并分析slice的统计学信息以及调用x264_slicetype_frame_cost()等函数确定不同帧类型下将产生的编码代价。经过一些列分析之后,最终确定了帧类型信息,并且将帧放入frames.current[]队列
  6. 根据编码类型进一步给h变量赋值(x264_t 结构体),如NAL结构体初始化,重建图像结构体初始化等等
  7. 一切参数准备就绪,则通过x264_slices_write()函数编码一帧数据
  8. 最后,编码结束后做一些后续处理,例如记录一些统计信息。其中调用了x264_encoder_encapsulate_nals()封装NALU(添加起始码),调用x264_frame_push_unused()将fenc重新放回frames.unused[]队列,并且调用x264_ratecontrol_end()结束码率控制,结束时做一些处理,记录一些统计信息 、输出NALU 、输出重建帧等。  
/****************************************************************************
 * x264_encoder_encode:
 *  XXX: i_poc   : is the poc of the current given picture
 *       i_frame : is the number of the frame being coded
 *  ex:  type frame poc
 *       I      0   2*0
 *       P      1   2*3
 *       B      2   2*1
 *       B      3   2*2
 *       P      4   2*6
 *       B      5   2*4
 *       B      6   2*5
 ****************************************************************************/
int     x264_encoder_encode( x264_t *h,
                             x264_nal_t **pp_nal, int *pi_nal,
                             x264_picture_t *pic_in,
                             x264_picture_t *pic_out )
{
    x264_t *thread_current, *thread_prev, *thread_oldest; // 当前线程、前一个线程、后一个线程
    int i_nal_type, i_nal_ref_idc, i_global_qp;  // NAL类型、NAL优先级、全局QP
    int overhead = NALU_OVERHEAD;  // startcode + NAL type costs 5 bytes per frame,即NAL的头信息字节个数

#if HAVE_OPENCL    // 判断是否有OPencl支持
    if( h->opencl.b_fatal_error )
        return -1;
#endif

    if( h->i_thread_frames > 1 )
    {
        thread_prev    = h->thread[ h->i_thread_phase ];
        h->i_thread_phase = (h->i_thread_phase + 1) % h->i_thread_frames;
        thread_current = h->thread[ h->i_thread_phase ];
        thread_oldest  = h->thread[ (h->i_thread_phase + 1) % h->i_thread_frames ];
        x264_thread_sync_context( thread_current, thread_prev );     // 协调三个线程之间的同步关系
        x264_thread_sync_ratecontrol( thread_current, thread_prev, thread_oldest );  // 协调三个线程之间的同步关系
        h = thread_current;   // 将编码所需相关参数赋值给h
    }
    else
    {
        thread_current =
        thread_oldest  = h;
    }
    h->i_cpb_delay_pir_offset = h->i_cpb_delay_pir_offset_next;

    /* no data out */
    *pi_nal = 0;
    *pp_nal = NULL;

    /* ------------------- Setup new frame from picture -------------------- */
    if( pic_in != NULL )
    {
        if( h->lookahead->b_exit_thread )   // lookahead 线程退出标志位
        {
            x264_log( h, X264_LOG_ERROR, "lookahead thread is already stopped\n" );
            return -1;
        }

        /* 1: Copy the picture to a frame and move it to a buffer */
		//  获取fenc的存储空间,用来存放待编码的帧 
        x264_frame_t *fenc = x264_frame_pop_unused( h, 0 );  // unused用于回收那些在编码中分配的frame空间,
        													//当有新的需要时,直接拿过来用,不用重新分配新的空间,
        													//提高效率,即对frame结构体初始化空间
        if( !fenc )
            return -1;

		// 外部像素数据传递到内部系统  
        // pic_in(外部结构体x264_picture_t)到fenc(内部结构体x264_frame_t),根据pic_in中图像的颜色空间情况进行了数据的复制
        if( x264_frame_copy_picture( h, fenc, pic_in ) < 0 )  // 将pic_in中的帧数据拷贝到fenc中
            return -1;

		//宽和高都确保是16的整数倍(宏块宽度的整数倍)
        if( h->param.i_width != 16 * h->mb.i_mb_width ||     // 分辨率不是16的倍数,参数校验
            h->param.i_height != 16 * h->mb.i_mb_height )
            x264_frame_expand_border_mod16( h, fenc );  // 则进行边界处理,扩展至16整数倍  

        fenc->i_frame = h->frames.i_input++;  // 图像的输入序号(原始顺序或播放顺序)

        if( fenc->i_frame == 0 ) // 第一帧
            h->frames.i_first_pts = fenc->i_pts;
        if( h->frames.i_bframe_delay && fenc->i_frame == h->frames.i_bframe_delay )
            h->frames.i_bframe_delay_time = fenc->i_pts - h->frames.i_first_pts;

        if( h->param.b_vfr_input && fenc->i_pts <= h->frames.i_largest_pts ) // 判断pts是否严格递增
            x264_log( h, X264_LOG_WARNING, "non-strictly-monotonic PTS\n" );

        h->frames.i_second_largest_pts = h->frames.i_largest_pts;  // 更新第二大pts值
        h->frames.i_largest_pts = fenc->i_pts;   // 更新最大pts值

		// 图像类型参数校验
        if( (fenc->i_pic_struct < PIC_STRUCT_AUTO) || (fenc->i_pic_struct > PIC_STRUCT_TRIPLE) )
            fenc->i_pic_struct = PIC_STRUCT_AUTO;

		// 确定帧结构(场、帧、帧场自适应?)
        if( fenc->i_pic_struct == PIC_STRUCT_AUTO )
        {
#if HAVE_INTERLACED
            int b_interlaced = fenc->param ? fenc->param->b_interlaced : h->param.b_interlaced;
#else
            int b_interlaced = 0;
#endif
            if( b_interlaced )
            {
                int b_tff = fenc->param ? fenc->param->b_tff : h->param.b_tff;
                fenc->i_pic_struct = b_tff ? PIC_STRUCT_TOP_BOTTOM : PIC_STRUCT_BOTTOM_TOP;
            }
            else
                fenc->i_pic_struct = PIC_STRUCT_PROGRESSIVE;
        }

		// mb_tree(宏块级率控)算法是否开启及编码多遍是否开启
        if( h->param.rc.b_mb_tree && h->param.rc.b_stat_read )  
        {
            if( x264_macroblock_tree_read( h, fenc, pic_in->prop.quant_offsets ) )
                return -1;
        }
        else
            x264_stack_align( x264_adaptive_quant_frame, h, fenc, pic_in->prop.quant_offsets );  // 自适应确定qp

        if( pic_in->prop.quant_offsets_free )
            pic_in->prop.quant_offsets_free( pic_in->prop.quant_offsets );

		//降低分辨率处理(原来的一半),线性内插  
        //注意这里并不是6抽头滤波器的半像素内插 
        if( h->frames.b_have_lowres )
            x264_frame_init_lowres( h, fenc );

        /* 2: Place the frame into the queue for its slice type decision */
        x264_lookahead_put_frame( h, fenc );  // 初始化lookahead线程,将fenc放入lookahead.next.list[]队列,等待确定帧类型

        if( h->frames.i_input <= h->frames.i_delay + 1 - h->i_thread_frames )
        {
            /* Nothing yet to encode, waiting for filling of buffers */
            pic_out->i_type = X264_TYPE_AUTO;
            return 0;
        }
    }
    else
    {
        /* signal kills for lookahead thread */
        x264_pthread_mutex_lock( &h->lookahead->ifbuf.mutex );  // 加互斥锁
        h->lookahead->b_exit_thread = 1;
        x264_pthread_cond_broadcast( &h->lookahead->ifbuf.cv_fill );
        x264_pthread_mutex_unlock( &h->lookahead->ifbuf.mutex );  // 解互斥锁
    }

    h->i_frame++;   // 编码帧号
    /* 3: The picture is analyzed in the lookahead */
    if( !h->frames.current[0] )
        x264_lookahead_get_frames( h );  // 在lookhead线程中分析图像帧,通过lookahead分析帧类型。该函数调用了x264_slicetype_decide()
        								 // x264_slicetype_analyse()和x264_slicetype_frame_cost()等函数。
        								 // 经过一些列分析之后,最终确定了帧类型信息,并且将帧放入frames.current[]队列

    if( !h->frames.current[0] && x264_lookahead_is_empty( h ) )
        return x264_encoder_frame_end( thread_oldest, thread_current, pp_nal, pi_nal, pic_out );

    /* ------------------- Get frame to be encoded ------------------------- */
    /* 4: get picture to encode */
    h->fenc = x264_frame_shift( h->frames.current );  // 从frames.current[]队列取出1帧用于编码并更新currect队列

    /* If applicable, wait for previous frame reconstruct
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lifei092

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值