Android行车记录仪开发实战:从零搭建USB DVR的完整流程(基于AndroidM)
Android行车记录仪开发实战从零搭建USB DVR的完整流程基于AndroidM行车记录仪已经成为现代汽车不可或缺的智能设备之一。对于Android开发者而言基于AndroidM平台开发USB DVR行车记录仪应用不仅能够深入理解多媒体处理流程还能掌握从应用层到底层驱动的完整技术栈。本文将带你从零开始逐步构建一个功能完整的行车记录仪应用。1. 开发环境准备与项目架构设计在开始编码之前我们需要搭建合适的开发环境并规划项目整体架构。AndroidM平台虽然相对较旧但在嵌入式设备领域仍有广泛应用特别是在车载系统方面。基础开发环境要求Android Studio 3.0AndroidM SDK (API Level 23)NDK r12b或更高版本USB摄像头支持UVC协议测试设备建议使用真机而非模拟器项目采用典型的三层架构设计├── app/ # 应用层 │ ├── src/main/ │ │ ├── java/ # Java业务逻辑 │ │ ├── jni/ # JNI接口代码 │ │ └── res/ # 界面资源 ├── framework/ # 框架层 │ ├── dvr/ # DVR核心框架 ├── hardware/ # 硬件抽象层 │ ├── mtkcam/dvr/ # 摄像头驱动交互提示建议在项目根目录创建jni文件夹存放所有本地代码保持项目结构清晰。2. 应用层开发UI与业务逻辑实现应用层主要负责用户交互和业务逻辑处理需要实现视频预览、录制控制、设置管理等核心功能。2.1 视频预览SurfaceView实现FrontView类继承自SurfaceView负责视频预览的显示public class FrontView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private DVR mDvrInstance; public FrontView(Context context, DVR dvr) { super(context); mDvrInstance dvr; mHolder getHolder(); mHolder.addCallback(this); mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } Override public void surfaceCreated(SurfaceHolder holder) { mDvrInstance.setFrontSurface(holder); } Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // 处理surface变化 } Override public void surfaceDestroyed(SurfaceHolder holder) { mDvrInstance.stopPreview(); } }2.2 主活动功能集成MainActivity整合所有核心功能通过JNI接口与底层交互public class MainActivity extends AppCompatActivity { private DVR mDvr; private Handler mHandler new Handler(Looper.getMainLooper()) { Override public void handleMessage(Message msg) { switch (msg.what) { case DVRNativeEvent.DVR_UI_MSG_PREVIEW_ERROR: handlePreviewError(); break; case DVRNativeEvent.DVR_UI_MSG_RECORD_COMPLETE: onRecordComplete(); break; // 其他事件处理... } } }; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDvr new DVR(); mDvr.setEventHandler(mHandler); FrontView frontView findViewById(R.id.front_view); frontView.setDvrInstance(mDvr); setupControls(); } private void setupControls() { findViewById(R.id.btn_record).setOnClickListener(v - { if (mDvr.isRecording()) { mDvr.stopRecord(); } else { mDvr.startRecord(); } }); // 其他控件初始化... } }3. JNI层与框架层开发JNI层是连接Java应用和本地代码的桥梁框架层则封装了核心多媒体处理逻辑。3.1 JNI接口设计与实现创建DVR.java作为JNI接口类public class DVR { static { System.loadLibrary(dvr_jni); } private long mNativeContext; public native void setFrontSurface(SurfaceHolder holder); public native void startPreview(); public native void stopPreview(); public native void startRecord(); public native void stopRecord(); // 其他原生方法... }对应的C实现android_media_DVR.cpp#define LOG_TAG DVR_JNI #include android_runtime/AndroidRuntime.h #include utils/Log.h #include android/native_window_jni.h static struct { jclass clazz; jmethodID postEvent; } gDVRClassInfo; static void drv_setFrontSurface(JNIEnv* env, jobject thiz, jobject holder) { spANativeWindow window NULL; if (holder ! NULL) { window ANativeWindow_fromSurface(env, holder); } // 传递给底层处理... } static JNINativeMethod gMethods[] { {setFrontSurface, (Landroid/view/SurfaceHolder;)V, (void*)drv_setFrontSurface}, // 其他方法映射... }; int register_android_media_DVR(JNIEnv* env) { jclass clazz env-FindClass(com/example/dvr/DVR); gDVRClassInfo.clazz (jclass)env-NewGlobalRef(clazz); gDVRClassInfo.postEvent env-GetMethodID(clazz, postEventFromNative, (I)V); return AndroidRuntime::registerNativeMethods(env, com/example/dvr/DVR, gMethods, NELEM(gMethods)); }3.2 媒体编解码器配置PreviewRenderer.cpp负责使用MediaCodec进行视频硬解码status_t PreviewRenderer::configureCodec(const spSurface surface) { AString mime video/avc; spAMessage format new AMessage; format-setString(mime, mime.c_str()); format-setInt32(width, mVideoWidth); format-setInt32(height, mVideoHeight); format-setInt32(color-format, OMX_COLOR_FormatYUV420SemiPlanar); mCodec MediaCodec::CreateByType(mCodecLooper, mime.c_str(), true); if (mCodec NULL) { ALOGE(Failed to create codec); return UNKNOWN_ERROR; } status_t err mCodec-configure(format, surface, NULL, 0); if (err ! OK) { ALOGE(Failed to configure codec: %d, err); return err; } err mCodec-start(); if (err ! OK) { ALOGE(Failed to start codec: %d, err); return err; } return OK; }4. 硬件层与V4L2驱动交互硬件层直接与摄像头驱动交互获取原始视频数据并处理。4.1 V4L2设备初始化流程dvr_v4l2.cpp实现与V4L2驱动的交互int V4L2Camera::openDevice(const char* devName) { struct v4l2_capability cap; int fd open(devName, O_RDWR | O_NONBLOCK, 0); if (fd 0) { ALOGE(Failed to open device %s: %s, devName, strerror(errno)); return -1; } if (ioctl(fd, VIDIOC_QUERYCAP, cap) 0) { ALOGE(VIDIOC_QUERYCAP failed: %s, strerror(errno)); close(fd); return -1; } if (!(cap.capabilities V4L2_CAP_VIDEO_CAPTURE)) { ALOGE(Device does not support video capture); close(fd); return -1; } // 设置视频格式 struct v4l2_format fmt; memset(fmt, 0, sizeof(fmt)); fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width 1280; fmt.fmt.pix.height 720; fmt.fmt.pix.pixelformat V4L2_PIX_FMT_H264; fmt.fmt.pix.field V4L2_FIELD_ANY; if (ioctl(fd, VIDIOC_S_FMT, fmt) 0) { ALOGE(Failed to set format: %s, strerror(errno)); close(fd); return -1; } return fd; }4.2 视频数据采集与处理实现视频帧的采集和传递void V4L2Camera::startCapture(int fd) { struct v4l2_requestbuffers req; memset(req, 0, sizeof(req)); req.count 4; req.type V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, req) 0) { ALOGE(VIDIOC_REQBUFS failed: %s, strerror(errno)); return; } // 内存映射和缓冲区队列初始化 // ... enum v4l2_buf_type type V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMON, type) 0) { ALOGE(VIDIOC_STREAMON failed: %s, strerror(errno)); return; } // 启动采集线程 mCaptureThread new CaptureThread(fd); mCaptureThread-run(V4L2Capture); }5. 常见问题与性能优化在实际开发过程中会遇到各种性能问题和兼容性问题需要针对性优化。5.1 内存泄漏排查使用Android Studio的内存分析工具定期检查内存使用情况特别注意JNI全局引用未释放Surface未正确释放MediaCodec实例未回收V4L2缓冲区未释放5.2 性能优化技巧视频处理性能对比优化措施帧率提升CPU占用降低内存占用使用MediaCodec硬解码45%60%轻微增加双缓冲机制15%20%增加50%降低分辨率30%40%降低30%优化V4L2参数10%15%无变化关键优化代码示例// 使用双缓冲提高渲染效率 void PreviewRenderer::renderFrame(const spABuffer buffer) { if (mCurrentBuffer NULL) { mCurrentBuffer buffer; return; } // 后台线程处理前一帧 std::thread processThread([this, prevBuffer mCurrentBuffer] { processBuffer(prevBuffer); }); processThread.detach(); mCurrentBuffer buffer; }5.3 兼容性问题处理不同USB摄像头的兼容性处理策略格式探测先尝试H.264失败后尝试MJPEG分辨率适配从最高分辨率开始尝试逐步降低帧率调整动态调整帧率保持流畅性备用方案当硬解码失败时切换软解码public void autoConfigCamera() { int[] formats {ImageFormat.H264, ImageFormat.MJPEG}; int[] resolutions {1080, 720, 480}; for (int format : formats) { for (int res : resolutions) { if (tryConfig(format, res)) { return; } } } throw new RuntimeException(Failed to configure camera); }在开发过程中我发现最耗时的部分往往是不同硬件设备的适配工作。建议建立完善的设备测试矩阵覆盖主流USB摄像头型号提前发现并解决兼容性问题。对于性能关键路径使用systrace工具分析帧处理时间找出瓶颈点进行优化。