树莓派学习专题<13>:在树莓派上使用x264

树莓派上x264的使用与编码性能

0. 项目代码

可以从下面获取到项目中的代码。
https://github.com/cdsmakc/h264_codec_base_rpi4b_rpi5_rv1106_visual_studio.git

1. 代码下载

代码下载参考上一篇文章。

2. 编译

进入到x264目录,使用如下两条命令编译

./configure
make

编译大约需要3分钟。编译完成后,路径下有如下文件:

njl@raspberrypi:~/test/x264 $ ls -l
total 4640
-rw-r--r-- 1 njl njl    1999 Jun 28 04:05 AUTHORS
-rw-r--r-- 1 njl njl   10641 Jun 28 04:05 autocomplete.c
-rw-r--r-- 1 njl njl     159 Jun 28 04:10 autocomplete.d
-rw-r--r-- 1 njl njl   26408 Jun 28 04:10 autocomplete.o
drwxr-xr-x 9 njl njl    4096 Jun 28 04:13 common
-rwxr-xr-x 1 njl njl   45520 Jun 28 04:05 config.guess
-rw-r--r-- 1 njl njl    1397 Jun 28 04:09 config.h
-rw-r--r-- 1 njl njl   11712 Jun 28 04:09 config.log
-rw-r--r-- 1 njl njl     956 Jun 28 04:09 config.mak
-rwxr-xr-x 1 njl njl   35843 Jun 28 04:05 config.sub
-rwxr-xr-x 1 njl njl   57274 Jun 28 04:05 configure
-rw-r--r-- 1 njl njl   17992 Jun 28 04:05 COPYING
drwxr-xr-x 2 njl njl    4096 Jun 28 04:05 doc
drwxr-xr-x 2 njl njl    4096 Jun 28 04:13 encoder
-rw-r--r-- 1 njl njl    4202 Jun 28 04:05 example.c
drwxr-xr-x 2 njl njl    4096 Jun 28 04:05 extras
drwxr-xr-x 3 njl njl    4096 Jun 28 04:10 filters
drwxr-xr-x 2 njl njl    4096 Jun 28 04:10 input
-rw-r--r-- 1 njl njl 2282866 Jun 28 04:13 libx264.a
-rw-r--r-- 1 njl njl   13393 Jun 28 04:05 Makefile
drwxr-xr-x 2 njl njl    4096 Jun 28 04:10 output
drwxr-xr-x 3 njl njl    4096 Jun 28 04:05 tools
-rwxr-xr-x 1 njl njl     854 Jun 28 04:05 version.sh
-rwxr-xr-x 1 njl njl 1890416 Jun 28 04:13 x264
-rw-r--r-- 1 njl njl   92888 Jun 28 04:05 x264.c
-rw-r--r-- 1 njl njl    3182 Jun 28 04:05 x264cli.h
-rw-r--r-- 1 njl njl     243 Jun 28 04:09 x264_config.h
-rw-r--r-- 1 njl njl     234 Jun 28 04:10 x264.d
-rw-r--r-- 1 njl njl    1910 Jun 28 04:05 x264dll.c
-rw-r--r-- 1 njl njl   49056 Jun 28 04:05 x264.h
-rw-r--r-- 1 njl njl  102488 Jun 28 04:10 x264.o
-rw-r--r-- 1 njl njl     271 Jun 28 04:09 x264.pc
-rw-r--r-- 1 njl njl     572 Jun 28 04:05 x264res.manifest
-rw-r--r-- 1 njl njl    2709 Jun 28 04:05 x264res.rc

其中:

  1. x264文件可以直接在命令行中使用,例如将一个yuv文件编码成264文件。
  2. libx264.a这个文件,如果自己编写使用x264编码的应用代码,则编译时需要加入这个库文件一起编译。
  3. 自己编写代码时,代码中需要包含x264.h头文件。

此外,路径下还有个example.c文件,这个文件是如何使用x264编写代码进行264编码的示例,可以单独编译。如果需要自己编写代码,主要参考这个文件内的写法即可。

njl@raspberrypi:~/test/x264 $ make example

3. 使用x264编写代码

3.1 设置编码器预置

如下代码设置编码器预设。

if(0 > x264_param_default_preset(&g_stParam, RCE_ENC_CAMERA_ENCODER_PRESET, NULL))
{
    printf("File %s, func %s, line %d : set x264 param default fail.\n", __FILE__, __func__, __LINE__) ;

    return -1 ;
}

x264_param_default_preset的原型是:

X264_API int x264_param_default_preset( x264_param_t *, const char *preset, const char *tune );

这个函数设置编码器的缺省工作参数。主要设置preset,可用的选项有:

static const char * const x264_preset_names[] = { "ultrafast", "superfast", "veryfast", 
"faster", "fast", "medium", "slow", "slower", "veryslow", "placebo", 0 };

设置的越快,编码器编码越快,对CPU的占用也越低,但是编码质量也越差。
该函数执行完成后,g_stParam会存储着缺省配置。根据项目实际情况,需要修改这些配置,并应用。

3.2 设置编码器Profile

如下代码设置编码器的Profile。

g_stParam.i_bitdepth       = 8 ;
g_stParam.i_csp            = X264_CSP_I420 ;
g_stParam.i_width          = g_stRCEConfig.usWidth ;
g_stParam.i_height         = g_stRCEConfig.usHeight ;
g_stParam.b_vfr_input      = 0 ;
g_stParam.b_repeat_headers = 1 ;
g_stParam.b_annexb         = 1 ;

if(0 > x264_param_apply_profile(&g_stParam, RCE_ENC_CAMERA_ENCODER_PROFILE))
{
    printf("File %s, func %s, line %d : apply x264 profile fail.\n", __FILE__, __func__, __LINE__) ;

    return -1 ;
}

函数x264_param_apply_profile的原型是:

X264_API int x264_param_apply_profile( x264_param_t *, const char *profile );
  1. i_bitdepth指图像位深度,8位是最常见的。不论原始图像是从文件读入,还是从摄像头获取,原始图像的位深度要和这里匹配。
  2. i_cspcolor space,颜色空间。x264支持多种颜色空间。对于我的应用,我从摄像头获得的图像是X264_CSP_I420格式的,它将1帧图像分为3个数据块(3个平面),分别存储Y份量、U分量、V分量。很多第三方的程序中,各种空间具有相同的格式但名称不同,使用时需要搞清楚。如果不确定,可以在代码中设置好摄像头输出格式后,保存一小段图像,然后复制到windows上,找个可以播放原始图像的工具(我用Raw Player,但是支持的格式有限)播放一下,以确认格式。
#define X264_CSP_MASK           0x00ff  /* */
#define X264_CSP_NONE           0x0000  /* Invalid mode     */
#define X264_CSP_I400           0x0001  /* monochrome 4:0:0 */
#define X264_CSP_I420           0x0002  /* yuv 4:2:0 planar */
#define X264_CSP_YV12           0x0003  /* yvu 4:2:0 planar */
#define X264_CSP_NV12           0x0004  /* yuv 4:2:0, with one y plane and one packed u+v */
#define X264_CSP_NV21           0x0005  /* yuv 4:2:0, with one y plane and one packed v+u */
#define X264_CSP_I422           0x0006  /* yuv 4:2:2 planar */
#define X264_CSP_YV16           0x0007  /* yvu 4:2:2 planar */
#define X264_CSP_NV16           0x0008  /* yuv 4:2:2, with one y plane and one packed u+v */
#define X264_CSP_YUYV           0x0009  /* yuyv 4:2:2 packed */
#define X264_CSP_UYVY           0x000a  /* uyvy 4:2:2 packed */
#define X264_CSP_V210           0x000b  /* 10-bit yuv 4:2:2 packed in 32 */
#define X264_CSP_I444           0x000c  /* yuv 4:4:4 planar */
#define X264_CSP_YV24           0x000d  /* yvu 4:4:4 planar */
#define X264_CSP_BGR            0x000e  /* packed bgr 24bits */
#define X264_CSP_BGRA           0x000f  /* packed bgr 32bits */
#define X264_CSP_RGB            0x0010  /* packed rgb 24bits */
#define X264_CSP_MAX            0x0011  /* end of list */
#define X264_CSP_VFLIP          0x1000  /* the csp is vertically flipped */
#define X264_CSP_HIGH_DEPTH     0x2000  /* the csp has a depth of 16 bits per pixel component */
  1. i_widthi_height设置图像的分辨率。
  2. b_repeat_headers设置在每个关键帧的前面加上SPS和PPS单元。SPS 和 PPS 是两种特殊的 NALU。在 H.264 编码过程中,很多参数对于一整个视频序列或者多张图片来说是保持不变的。如果将这些参数重复地包含在每个帧(或片)的数据中,会造成很大的数据冗余,降低压缩效率。SPS 和 PPS 的作用就是将这些共享的参数提取出来,单独存储和传输。这样,视频数据(帧/片)本身只需要引用这些参数集,而不需要包含它们的所有细节,从而大大提高了压缩效率。
  3. b_annexb在每个NALU前面加4B的起始码。
  4. profile 指定配置。264编码有多种配置,基本上来说越是低级的配置算法越简单,对CPU占用相对低,但是编码的压缩比例也低,更占用空间。高级的配置支持更多的特性(例如main profile支持B帧),对CPU占用更高,但是质量也更好。一共有如下这些profile可供选择:
static const char * const x264_profile_names[] = { "baseline", "main", "high", 
"high10", "high422", "high444", 0 };

3.3 申请和释放picture空间

picture空间用来存储待编码的图像。用以下代码来申请picture空间。

if(0 > x264_picture_alloc(&g_stPic, g_stParam.i_csp, g_stParam.i_width, g_stParam.i_height))
{
    printf("File %s, func %s, line %d : alloc picture fail.\n", __FILE__, __func__, __LINE__) ;

    return -1 ;
}

函数x264_picture_alloc的原型是:

X264_API int x264_picture_alloc( x264_picture_t *pic, int i_csp, int i_width, int i_height );

既然是用来保存未编码的原始图像,那么自然和图像格式,图像分辨率有关,这些参数都要填入。
缓存是动态分配的,程序退出前需要释放:

x264_picture_clean(&g_stPic) ;

3.4 启动和停止编码器

使用如下代码来启动编码器:

g_pstX264Handler = x264_encoder_open(&g_stParam) ;

if(NULL == g_pstX264Handler)
{
    printf("File %s, func %s, line %d : open x264 encoder fail.\n", __FILE__, __func__, __LINE__) ;

    return -1 ;
}

函数x264_encoder_open的原型是:

X264_API x264_t *x264_encoder_open( x264_param_t * );

该函数返回一个编码器的句柄,后续对编码器的操作和释放都需要用到。程序运行完毕后,用如下代码关闭编码器:

x264_encoder_close(g_pstX264Handler) ;

3.5 编码图像

在启动编码器后,就可以将图像复制到picture,并对图像编码了。

memcpy(g_stPic.img.plane[0], pvLumaPtr, szLumaSize) ;
memcpy(g_stPic.img.plane[1], pvChroma1Ptr, szChromaSize) ;
memcpy(g_stPic.img.plane[2], pvChroma2Ptr, szChromaSize) ;

由于我的原始图像为I420格式,包含有1个亮度分量和2个色度分量,因此在申请picture空间时,得到的空间也是分为3个部分的。一帧图像的3个部分需要依次拷贝到picture空间。至于原始图像的偏移,需要自己算一下。例如图像分辨率为640x480的话,则从摄像头获取到的一帧图像,一共是640x480x1.5(I420格式,每个像素一个Y分量和半个U/V分量)=460800B,那么Y分量的偏移是0,长度是307200B,U分量的偏移是307200,长度是76800,V分量的偏移是384000,长度是76800。不过对于有的平台/相机,从V4L2驱动获取图像时,图像也是分为独立Plane存储的,这时只需要逐个Plane将图像复制到Picture中即可。
图像复制完成后,可以开始编码:

iEncodeSize = x264_encoder_encode(g_pstX264Handler, &g_pstNAL, &iNALCtr, &g_stPic, &g_stPicOut) ;

函数x264_encoder_encode的原型是:

X264_API int x264_encoder_encode( x264_t *, x264_nal_t **pp_nal, int *pi_nal, x264_picture_t *pic_in, x264_picture_t *pic_out );

函数成功时,返回编码的总计的字节数。要获取编码后的图像,主要是关注g_pstNAL。例如,需要将编码的图像写入到文件:

write(g_stENCWorkarea.iStoreFile, g_pstNAL->p_payload, iEncodeSize) ;  

重复图像复制–>编码–>获取编码后图像,即可持续对摄像头数据编码。

4. 树莓派上的软编码性能

编码性能不但和编码器设置有关,还和图像的清晰度、图像内容有关(设想全黑或者全白的图像,编码应该很轻松),我测试时跑出的编码帧率不具有代表性,因此不贴出来了,只简单地描述一下现象。
我编码1280*720P的图像,superfastbaseline,大约可以实现60FPS左右,CPU的4个核心占用满。如果Preset设置为更慢一些,就很难实现60fps了。
实际上,对于树莓派4B,可以直接从V4L2驱动获取到编码后的H264图像,而无需使用x264编码。这种方式获取到的图像是硬件编码的,不需要CPU参与。下一篇描述这个特性。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值