车载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视频采集的基本流程

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言并肃

感谢大哥支持!您的鼓励是我动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值