USB摄像头录制视频

本文记录了在Android板上使用USB摄像头进行720P+、25FPS+视频录制的开发过程。面对摄像头帧率限制和设备性能约束,作者尝试了多种方案,最终通过V4L2接口获取MJPEG数据,利用libturbojpeg库解码到YUV420P,实现了30FPS的录制。尽管存在延迟问题,但通过YUV422到YUV420P的转换优化,效率得到了显著提升。

最近在做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秒左右,勉强只能这样了。

后面想到好的方式再继续优化。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值