车载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.SetFrontSurface
、DVR.StartRecord
、DVR.StopRecord
、DVR.StartPreview
、DVR.StopPreview
FrameWork 层
源码路径:
AndroidM/vendor/mediatek/proprietary/frameworks/base/camera/dvr/…
主要的两个类为DVR.java
和android_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.cpp
、PreviewRenderer.cpp
、dvr_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.h
和 dvr_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介绍参考下面的博文:
开源工程系列之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)的主要内容,如果未能解决你的问题,请参考以下文章