树莓派学习专题<13>:树莓派学习专题<13>:在树莓派上使用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
其中:
- x264文件可以直接在命令行中使用,例如将一个yuv文件编码成264文件。
- libx264.a这个文件,如果自己编写使用x264编码的应用代码,则编译时需要加入这个库文件一起编译。
- 自己编写代码时,代码中需要包含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 );
- i_bitdepth指图像位深度,8位是最常见的。不论原始图像是从文件读入,还是从摄像头获取,原始图像的位深度要和这里匹配。
- i_csp即color 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 */
- i_width和i_height设置图像的分辨率。
- b_repeat_headers设置在每个关键帧的前面加上SPS和PPS单元。SPS 和 PPS 是两种特殊的 NALU。在 H.264 编码过程中,很多参数对于一整个视频序列或者多张图片来说是保持不变的。如果将这些参数重复地包含在每个帧(或片)的数据中,会造成很大的数据冗余,降低压缩效率。SPS 和 PPS 的作用就是将这些共享的参数提取出来,单独存储和传输。这样,视频数据(帧/片)本身只需要引用这些参数集,而不需要包含它们的所有细节,从而大大提高了压缩效率。
- b_annexb在每个NALU前面加4B的起始码。
- 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的图像,superfast,baseline,大约可以实现60FPS左右,CPU的4个核心占用满。如果Preset设置为更慢一些,就很难实现60fps了。
实际上,对于树莓派4B,可以直接从V4L2驱动获取到编码后的H264图像,而无需使用x264编码。这种方式获取到的图像是硬件编码的,不需要CPU参与。下一篇描述这个特性。
树莓派上x264的使用与编码性能

5023

被折叠的 条评论
为什么被折叠?



