车载USB DVR(行车记录仪)的源码架构浅析(基于AndroiidM)

Posted 言并肃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了车载USB DVR(行车记录仪)的源码架构浅析(基于AndroiidM)相关的知识,希望对你有一定的参考价值。

基于AndroiidM的USB DVR的源码架构浅析,主要讲述大概流程,以便于分析问题。

APP层

源码路径:

androidM/vendor/mediatek/proprietary/packages/apps/DVR

主要 DVR操作的两个类 FrontView.java MainActivity.java

1、 FrontView 继承了 SurfaceView 用于摄像头视频的预览,把 SurfaceView 通过调用 JNI 的 DVR.java 类对象方法 setFrontSurface(SurfaceHolder sh) 传入到底层,交给底层来实现在 SurfaceView 上的视频渲染。FrontView在主布局中被调用。

2、 MainActivity 中通过调用 JNI 的 DVR.java 类对象,实现了众多功能,包括解析度的改变(720p,1080p),录制时长的改变(10mins,5mins,3mins),视频预览的开始与停止,视频的录制,快照的截取,紧急录像,设置摄像头时间、坐标等等。主要在于实例化了一个 JNI 层的 DVR 类对象来调用底层的对于摄像头基本功能的实现。
应用设置 Handler 用来接收 framework 返回的消息,类型是DVRNativeEvent,需 要 注 意 接 收 到 DVRNativeEvent.DVR_UI_MSG_PREVIEW_ERROR等错误时需要做出容错措施。用到的DVR框架的主要方法为:DVR.SetFrontSurfaceDVR.StartRecordDVR.StopRecordDVR.StartPreviewDVR.StopPreview

FrameWork 层

源码路径:

AndroidM/vendor/mediatek/proprietary/frameworks/base/camera/dvr/…

主要的两个类为DVR.javaandroid_media_DVR.cpp

1、DVR.java中加载了drv_jni库,封装了几个关于摄像头属性的类,坐标、时间、VideoInfo、SinkInfo。根据摄像头底层功能函数定义了各个native方法,然后再用 java 方法把这些native方法再次封装起来,提供给上层对DVR类实例化后的调用。
2、android_media_DVR.cpp中定义各个函数用于对底层 dvrav.cpp 中的对于摄像头基本功能函数进行封装,然后将这些封装好的方法和 DVR.java中native方法映射起来。

Hardware层

源码路径:

AndroidM/vendor/mediatek/proprietary/hardware/mtkcam/dvr

主要的三个类为dvrav.cppPreviewRenderer.cppdvr_preview.cpp
1、 dvrav.cpp

该类被 JNI 层调用的方法最终通过 sendMsg 的方式来分别实现各个功能。在这个文件中,相当于底层与 JNI 的接口,所有的最终实现是通过这个文件中的方法分发下去具体各个类的函数中去实现功能函数。例如:start_preview,JNI 调用到 dvrav.cpp 中的 DVR_StartPreview()方法,通过 Msg 中的枚举量找到具体实现的地方,然后再分发下去调用 dvr_preview.cpp中的DVR_Preview_StartPreview(),在此方法中实例化一个结构体中PreviewRenderer对象mRenderer用于对视频的渲染。

2、 PreviewRenderer.cpp 该类主要功能是用MediaCodec对视频流数据进行硬解码操作,并且在SurfaceView上渲染视频。改变视频流格式的解码格式主要通过对MediaCodec进行配置,目前使用的是video/avc"(H.264)格式。

这篇文章有介绍使用MediaCodec硬解码MJPEG格式帧可能会遇到的问题:MediaCodec硬解码

3、dvr_preview.cpp
该类中函数主要功能是操作PreviewRenderer类对象,配置并初始化MediaCodec,对PreviewRenderer类对象中方法的实现与一些功能拓展。围绕创建实例,释放实例,获取预览实例,创建渲染Looper,释放渲染Looper,设置SurfaceView,开始和停止预览这几个功能。

底层实现中还包括了,在dvr_audiorecord.cpp中实现了关于音频记录的功能,在dvr_avimuxer.cpp中实现了音频和视频的整合,在dvr_recorder.cpp中实现了视频的记录和紧急视频的录制,在dvr_snapshot.cpp 实现视频快照的截取并保存等等,DVR中所有的基本都由底层来实现,上层只是调用。

V4L2驱动层

获取 USB 记录仪视频:采用 linux 下的V4L2(video for linux 2)标准接口,dvr_v4l2.hdvr_v4l2.cpp

open 设备节点(/dev/video21)拿的数是 H.264。

int cameraFd;
cameraFd = open("/dev/video0", O_RDWR, 0);```

打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:

extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;

__fd:设备的ID,例如刚才用open函数打开视频通道后返回的cameraFd;

__request:具体的命令标志符。

在进行V4L2开发中,一般会用到以下的命令标志符:

VIDIOC_REQBUFS:分配内存
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP:查询驱动功能
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
VIDIOC_S_FMT:设置当前驱动的频捕获格式
VIDIOC_G_FMT:读取当前驱动的频捕获格式
VIDIOC_TRY_FMT:验证当前驱动的显示格式
VIDIOC_CROPCAP:查询驱动的修剪能力
VIDIOC_S_CROP:设置视频信号的边框
VIDIOC_G_CROP:读取视频信号的边框
VIDIOC_QBUF:把数据从缓存中读取出来
VIDIOC_DQBUF:把数据放回缓存队列
VIDIOC_STREAMON:开始视频显示函数
VIDIOC_STREAMOFF:结束视频显示函数
VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。

检查当前视频设备支持的标准,使用VIDIOC_QUERYSTD来检测:

v4l2_std_id std;
do 
  ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
 while (ret == -1 && errno == EAGAIN);
switch (std) 
    case V4L2_STD_NTSC:
        //……
    case V4L2_STD_PAL:
        //……
    case V4L2_STD_AHD:
        //……

设置视频捕获格式:

struct v4l2_format    fmt;
memset ( &fmt, 0, sizeof(fmt) );
fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width       = 720;
fmt.fmt.pix.height      = 576;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) 
  return -1;


v4l2_format结构体定义如下:

struct v4l2_format

    enum v4l2_buf_type type;    // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
    union
    
        struct v4l2_pix_format    pix; 
        struct v4l2_window        win; 
        struct v4l2_vbi_format    vbi; 
        __u8    raw_data[200];         
     fmt;
;
struct v4l2_pix_format

    __u32                   width;         // 宽,必须是16的倍数
    __u32                   height;        // 高,必须是16的倍数
    __u32                   pixelformat;   // 视频数据存储类型,例如是YUV4:2:2还是RGB
    enum v4l2_field         field;
    __u32                   bytesperline;   
    __u32                   sizeimage;
    enum v4l2_colorspace    colorspace;
    __u32                   priv;      
;

获取并记录缓存的物理空间

typedef struct VideoBuffer 
    void   *start;
    size_t  length;
 VideoBuffer;

VideoBuffer*          buffers = calloc( req.count, sizeof(*buffers) );
struct v4l2_buffer    buf;

for (numBufs = 0; numBufs < req.count; numBufs++) 
    memset( &buf, 0, sizeof(buf) );
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = numBufs;
    // 读取缓存
    if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) 
        return -1;
    

    buffers[numBufs].length = buf.length;
    // 转换成相对地址
    buffers[numBufs].start = mmap(NULL, buf.length,
        PROT_READ | PROT_WRITE,
        MAP_SHARED,
        fd, buf.m.offset);

    if (buffers[numBufs].start == MAP_FAILED) 
        return -1;
    

    // 放入缓存队列
    if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) 
        return -1;
    

处理采集数据

struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;

//读取缓存
if (ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -1)

    return -1;

//…………视频处理算法
//重新放入缓存队列
if (ioctl(cameraFd, VIDIOC_QBUF, &buf) == -1) 

    return -1;

更多V4L2介绍参考下面的博文:

v4l2的学习建议和流程解析

V4L2视频采集的基本流程

开源工程系列之USB电源延时缓启动器

年初我买了一个行车记录仪,它配的车载充电器是塑料壳的,不符合我对散热的“极致追求”,于是乎我将其换成了先前买的金属车载充电器。然而,金属车充与行车记录仪不合,车子电源启动后车充指示灯反复闪烁,行车记录仪也没能正常上电。很明显,车充出现了过流保护的情况,罪魁祸首就是行车记录仪里的大电容。解决办法是让5V缓启动,即拉长电压从0V爬升到5V的时间。进一步的,还可以在车充的5V输出稳定后一段时间再给行车记录仪供电。于是,我用电源监控芯片+PMOS做了一个带延时上电和缓启动功能的玩意。3D打印了一个外壳(经过实验,车内温度很高时PLA材料会软化、变形,需使用ABS材料。考虑到ABS耐紫外线性差,又喷了一层珍珠漆)。

用于延时的RC和MOS管栅极都涂了松香以防止潮湿环境下漏电电流对延时时间产生较大的影响。

带载10Ω电阻并880µF电容的条件下MOS管开通瞬间栅极与漏极的波形如下:

可以看出,车充输出的5V电源稳定2.5s后缓启动器开始对外输出,而电压爬升时间也拉长到了310ms。310ms的爬升时间对于某些芯片可能太长,不过这个行车记录仪能接受。由于短时间内找不到电流探头或者差分探头,我也没有验证MOS管是否工作在SOA内就直接用上缓启动器了。效果不错,唯一的副作用就是增加了几十毫欧的线路电阻。

 

附件(原理图、光绘文件、外壳图纸)

 

使用闲鱼扫描此二维码可进店购买空白PCB

以上是关于车载USB DVR(行车记录仪)的源码架构浅析(基于AndroiidM)的主要内容,如果未能解决你的问题,请参考以下文章

环卫车载DVR国产方案—推进垃圾分类利国利民

USB 驱动架构浅析

USB 驱动架构浅析

如何选择行车记录仪内存卡和格式?

车载监控需求向视频化转变,TSINGSEE车载监控平台助力车辆高效监管

车载监控需求日益上升, 浅析车载远程视频监控解决方案