libyuv是一个关于yuv格式图像的处理库.包括rgb和yuv格式的转换,图像的缩放,叠加混合等功能.与ffmpeg的libswscale库的功能类似.

libswscale库支持的格式更加广泛,但使用起来需要创建一个上下文结构体,再进行接口调用, 并且没有叠加功能,需要ffmpeg的libavfilter库支持, libavfilter库的使用则更加繁琐.

在色彩空间转换和图像缩放方面,libyuv的使用相比ffmpeg库更加方便直观,色彩空间转换,缩放叠加处理,接口都很直观,没有上下文结构,使用方便.

两者的效率,我实测是差不多的,都是纯cpu处理,都有汇编优化.

实际使用中的一个问题

libyuv提供了一个方便的整合接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
LIBYUV_API
int ConvertToI420(const uint8_t* sample,
                  size_t sample_size,
                  uint8_t* dst_y,
                  int dst_stride_y,
                  uint8_t* dst_u,
                  int dst_stride_u,
                  uint8_t* dst_v,
                  int dst_stride_v,
                  int crop_x,
                  int crop_y,
                  int src_width,
                  int src_height,
                  int crop_width,
                  int crop_height,
                  enum RotationMode rotation,
                  uint32_t fourcc);

这个接口是将任何libyuv支持的图像格式转换为I420格式的图像.

实际使用中我要将一个ffmpeg的AVFrame做颜色空间转换,这个AVFrame是BGR格式的,内存分配时是32位对齐的.

转换接口这样填写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
libyuv::ConvertToI420(src_frame->data[0],
			  src_frame->height * src_frame->linesize[0]/3,
			  dst_frame->data[0],
			 dst_frame->linesize[0],
			 dst_frame->data[1],
			 dst_frame->linesize[1],
 			dst_frame->data[2],
			 dst_frame->linesize[2],
			  0,
			  0,
			  src_frame->width,
			  src_frame->height,
			  src_frame->width,
			  src_frame->height,
			  libyuv::RotationMode::kRotateNone,
			  libyuv::FOURCC_24BG);

src_frame的长宽是1920x1080时,程序工作得很好,没有任何问题,但是一旦src_frame换了一个分辨率时,比如766x360这种,转换出来 的图像就变成了花屏.

接口参数review

看到这个问题,很自然的想到应该是图像数据在内存中分配对齐导致的问题,应该是接口调用时某些参数传入有误,需要重新审视一下libyuv::ConvertToI420这个接口的参数.

它的前两个参数samplesample_size是指向源数据的内存的,如果是一个766x360这样分辨率的图像,并且分配AVFrame时使用了32位对齐方式,那么实际内存布局应该是每行3 * ((766-1+32)/32), 看接口第一第二个参数好像没什么问题

dst_frame的六个参数分别是yuv分量的起始指针和长度容量,也没有问题.

crop_xcrop_y当然应该填0.

src_frame的宽高也没有问题,crop_widthcrop_height也没有问题,我需要整幅图像.

那么问题出在哪呢,再回头细想一下关于内存对齐,内存对齐是ffmpeg在分配AVFrame时处理的,libyuv并不知道接口传过来的内存布局是怎样对齐的,从这个实例来看,libyuv处理转换获取了源的samplesample_size,然后根据src_widthsrc_height去读取真正的源像素,libyuv读取了一行像素后,也就是766*3个字节之后,它是不知道要跳过多少对齐字节去读取下一行像素点的.那么它是怎么处理的呢?

使用这组参数转换宽为32倍数的图像是没有问题的,例如1920x1080的,因为这种图像每一行像素点占用的内存是32的倍数,刚好对齐,不用分配额外的内存,从这里推断,libyuv::ConvertToI420读取下一行像素应该是不进行跳跃,直接接着前一行读取.

正确的参数

这样想来,问题就是出在这个地方了,那么怎么解决这个问题呢,这个接口并没有给出一个类似linesize或者src_stride的参数, 而使用单独的转换接口libyuv::RGB24ToI420是有这样的参数的:src_stride_rgb24,那么只能使用特定格式的转换接口吗?

当然不是,再仔细看一下它的参数列表,可以这样使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
libyuv::ConvertToI420(src_frame->data[0],
				  src_frame->height * src_frame->linesize[0]/3,
				  dst_frame->data[0],
				 dst_frame->linesize[0],
				 dst_frame->data[1],
				 dst_frame->linesize[1],
	 			dst_frame->data[2],
				 dst_frame->linesize[2],
				  0,
				  0,
				  src_frame->linesize[0]/3,
				  src_frame->height,
				  src_frame->width,
				  src_frame->height,
				  libyuv::RotationMode::kRotateNone,
				  libyuv::FOURCC_24BG);

src_width参数填写为src_frame->linesize[0]/3就可以了,这样填写表示我需要处理一个宽为src_frame->linesize[0]/3的图像, 而不是宽为766的图像,然后通过crop_width参数,真正转换的图像的宽依然是766.

好了,至此这个接口的使用问题就解决了.