最近在做Android板上的USB摄像头录制视频的调试工作,在此记录一下开发历程和心得。
项目要求用USB摄像头录制视频,要求视频必须是720P+,25FPS+。
USB摄像头用的是海康威视摄像头,YUV只有10FPS,mjpeg可以达到30FPS。所以只能采用mjpeg方式。
开始找了网上开源的UVCCamera项目,先上连接 https://github.com/saki4510t/UVCCamera ,结果发现录制的视频帧率都达不到要求。
后面只能使用V4L2取摄像头的原始数据。参考这篇文章 安卓系统采用v4l2接口打开YUYV和MJPEG摄像头,支持热插拔。_alterli的博客-优快云博客
取到的摄像头原始数据是一张张JPEG图片,将JPG图片编码为MP4就简单很多。先将JPEG解码为BMP数据,然后用Android自带编码器MediaMuxer,选择数据源格式为COLOR_Format32bitBGRA8888。但是有个问题,获取源数据,然后解码bmp,再然后编码到MP4,这个效率就很低了。
一方面受到摄像头制约(YUV只有10FPS),另一方面受到安卓板子性能制约,如何录制高帧率的MP4视频,真的是很头疼。
查了很多资料,走了很多弯路,也试过FFmpeg去将JPEG图片编码为MP4,结果效率实在不敢恭维。
没有办法,V4L2是目前唯一能走通的路。只能牺牲掉时效性,以空间换时间,多开线程,一边获取摄像头原始数据保存为JPEG文件,一边解码JPEG为bmp数据进行视频编码。这个方式可以达到30FPS,但是缺点也有,那就是视频文件需要在摄像头录制结束一段时间后才能做好。
后面换了一块性能更惨的板子,而且还不支持COLOR_Format32bitBGRA8888格式编码视频。
查看板子编码MP4所支持的数据源,发现只有YUV420p,所以关键问题就是把JPEG解码为YUV420P格式的数据。
查了一圈,看到网上bmp转YUV420p的算法很多,也很高效,JPEG解码为bmp本身就需要时间,再转码YUV420p,显然效率很难让人满意。以1280*720的JPEG图片为例,解码为bmp,再转YUV420P, 将近200~250ms。意思是一段10秒的视频,300帧图片,花费就是60~75秒。
不考虑bmp转YUV420P,有没有直接解码JPEG到YUV420P的办法呢。答案是肯定的。
利用libturbojpeg库可以实现,上关键代码
int tjpeg2yuv(unsigned char* jpeg_buffer, int jpeg_size, unsigned char** yuv_buffer, int* yuv_size, int* yuv_type)
{
tjhandle handle = NULL;
int width, height, subsample, colorspace;
int flags = 0;
int padding = 1; // 1或4均可,但不能是0
int ret = 0;
handle = tjInitDecompress();
tjDecompressHeader3(handle, jpeg_buffer, jpeg_size, &width, &height, &subsample, &colorspace);
printf("w: %d h: %d subsample: %d color: %d\n", width, height, subsample, colorspace);
flags |= 0;
*yuv_type = subsample;
*yuv_size = tjBufSizeYUV2(width, padding, height, subsample);
*yuv_buffer =(unsigned char *)malloc(*yuv_size);
if (*yuv_buffer == NULL)
{
printf("malloc buffer for rgb failed.\n");
return -1;
}
ret = tjDecompressToYUV2(handle, jpeg_buffer, jpeg_size, *yuv_buffer, width,
padding, height, flags);
if (ret < 0)
{
printf("compress to jpeg failed: %s\n", tjGetErrorStr());
}
tjDestroy(handle);
return ret;
}
查看解码后的yuv_type,得到的是YUV422,我们需要的是YUV420P,所以貌似行不通。因为JPEG本身就是YUV编码的,所以得到的yuv_type就是JPEG用的,因此受制于摄像头,这里只能解出yuv422格式。那么只能将YUV422转为YUV420P了。
YUV422实际上有好几种,yuyv,yuv422p,yuv422sp。而YUV422转YUV420最简单有效的方式就是隔行抽取UV分量。这里不小心踩了一个坑,开始以为是隔一个取一个数据,怎么操作都不对,后来一想才反应过来应该是隔一行抽取一行。
最后验证发现果然效率提升了近一倍,编码一张JPEG图片100ms左右。一段10秒的视频,300帧图片,花费30秒左右,勉强只能这样了。
后面想到好的方式再继续优化。
本文记录了在Android板上使用USB摄像头进行720P+、25FPS+视频录制的开发过程。面对摄像头帧率限制和设备性能约束,作者尝试了多种方案,最终通过V4L2接口获取MJPEG数据,利用libturbojpeg库解码到YUV420P,实现了30FPS的录制。尽管存在延迟问题,但通过YUV422到YUV420P的转换优化,效率得到了显著提升。
401





