Android 利用V4L2 预览MJPEG格式 USB camera

介绍

上一篇文章Android 利用V4L2 调用camera介绍了使用V4L2 接口预览camera的基本方法。目前接触过的usb camera支持的图像格式基本上只包括3种:

  • YUV
  • MJPEG
  • H264
    其中YUV是原始数据,MJPEG和H264都是压缩编码的数据。所以对于MJPEG和H264需要先解码为原始图像数据,才能给到android显示。这篇文章主要介绍对MJPEG数据的处理

准备

先看一下此次修改的效果图

GIF 2022-1-18 15-35-09.gif
  • UI
    让用户自己选择对应的格式和分辨率
  • 解码
    一般可采用的有opencv,ffmpeg,libyuv。此次采用libyuv,libyuv是一个谷歌的开源项目,跨平台,处理速度很快。针对此次MJPEG处理是比较合适的。libyuv解码MJPEG需要用到libjpeg

camera使用的基本流程在文章Android 利用V4L2 调用camera已经介绍过,

正常的流程主要是以下几步

  1. SurfaceView创建
  2. SurfaceView创建成功回调
  3. 打开camera
  4. camera打开成功回调
  5. 获取camera参数
  6. 弹框用户选择对应分辨率
  7. 设置对应pixformat和分辨率
  8. 开始预览
  9. 获取到MJPEG数据后,利用libyuv解码
  10. Android nativieWindow显示

此次大体流程没有变化,着重介绍修改的地方

让用户选择预览图像格式和分辨率

在camera打开成功的回调种,即 CameraStateCallback 的回调onOpened 获取usb camera支持的参数,camera参数格式可以参见文章Android 利用V4L2 调用camera中的获取camera的参数

class CameraStateCallback implements IStateCallback {

        @Override
        public void onOpened() {
            Log.d(TAG, "onOpened");

            parameters = adCamera.getCameraParameters();

            pixformats = new String[parameters.size()];
            resolutions = new String[parameters.size()][];

            String sPixFormat;
            for (int i = 0; i < parameters.size(); i++) {
                Log.e(TAG, "format:" + parameters.get(i).pixFormat);
                switch (parameters.get(i).pixFormat) {
                    case YUYV:
                        sPixFormat = "YUYV";
                        break;
                    case MJPEG:
                        sPixFormat = "MJPEG";
                        break;
                    case H264:
                        sPixFormat = "H264";
                        break;
                    default:
                        sPixFormat = "UNKNOW";
                        break;
                }

                pixformats[i] = sPixFormat;

                int resolutionSize = parameters.get(i).frames.size();

                resolutions[i] = new String[resolutionSize];

                for (int j = 0; j < resolutionSize; j++) {
                    String resolution = parameters.get(i).frames.get(j).width + "*" + parameters.get(i).frames.get(j).height;
                    resolutions[i][j] = resolution;
                }
            }

            showDialog();
        }

将获取到的参数解析到以下两个数组中

String[] pixformats;
    String[][] resolutions;

dialog采用加载ExpandableListView,这里就不详细介绍。
点击确认后,设置预览参数并开始预览

ret = adCamera.setPreviewParameter(previewWidth, previewHeight, parameters.get(pixClick).pixFormat);

surfaceView.setAspectRatio(previewWidth, previewHeight);
...
adCamera.setSurface(surfaceHolder);
...
cameraDataCallback = new CameraDataCallback();
adCamera.startPreview(cameraDataCallback);

libjpeg库的编译移植与使用

这里使用AS编译libJPEG-turbo源码

  • 新建Android工程libjpeg,并将libjpeg-turbo源码全部拷贝到src/main/cpp目录下

    libjpeg.png
  • 修改Android工程的build.gradle,配置libjpeg-turbo的CmakeLists.txt

defaultConfig {
        applicationId "com.test.libjpeg"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        externalNativeBuild {
            cmake {
                cppFlags ""
                // 配置编译的平台版本
                abiFilters "armeabi-v7a", "arm64-v8a"
            }
        }

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    externalNativeBuild {
        cmake {
            path "src\\main\\cpp\\CMakeLists.txt"//路径改为cpp文件夹下CMakeList的路径
        }

    }
  • 编译工程,生成对应的so和.h 文件
    编译过程很顺利,没碰到什么问题。倒是从网上下载下来的总是有编译错误,所以这里就不提供工程了。

将libjpeg 生成的so和h文件添加到AnV4L2Camera工程中

  • 在v4l2camera模块cpp文件下新建libjpeg文件夹,将libjpeg几个相关头文件拷贝到该目录下

    includelibjpeg.png

其中 jconfig.h 在工程libjpeg以下目录

./app/.cxx/cmake/debug/arm64-v8a/jconfig.h
./app/.cxx/cmake/debug/armeabi-v7a/jconfig.h

其他头文件都在在工程libjpeg以下目录

./app/src/main/cpp/
  • 在 v4l2camera模块新建文件夹src\main\jniLibs,将libjpeg.so拷贝到该文件夹下

    libjpegso.png
  • 修改v4l2camera模块 CMakeLists.txt

# 指定libjpeg头文件的路径
include_directories(include libjpeg)

# 指定libjpeg动态库路径
set(jpeglibs "${CMAKE_SOURCE_DIR}/../jniLibs")

# 导入第三方库:libjpeg.so
add_library(libjpeg SHARED IMPORTED)
set_target_properties(libjpeg PROPERTIES
        IMPORTED_LOCATION "${jpeglibs}/${ANDROID_ABI}/libjpeg.so")

target_link_libraries( # Specifies the target library.
        v4l-android
        android
        libjpeg

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

libyuv移植

这里采用直接将libyuv源码导入到AnV4L2Camera工程中

  • 下载libyuv https://chromium.googlesource.com/libyuv/libyuv
  • 将 libyuv 源码 include 目录下的 libyuv 目录下的头文件和 libyuv.h 一起拷贝到 v4l2camera模块下的 src\main\cpp\include目录
  • 将 libyuv 源码 source 目录下的全部文件拷贝到 v4l2camera模块新建文件夹src\main\cpp\libyuv
libyuv.png
  • 修改v4l2camera模块 CMakeLists.txt
# 导入libyuv头文件路径
include_directories(include libjpeg)

# 打开宏HAVE_JPEG,libyuv才会去编译和使用libjpeg
add_definitions(-DHAVE_JPEG)

# 导入libyuv 源文件路径
file(GLOB src_files *.cpp  libyuv/source/*.cc)

libyuv解码MJPEG

int src_width = 0;
        int src_height = 0;

        libyuv::MJPGSize(raw, rSize, &src_width, &src_height);

        //经图片保存,16进制查看保存的改格式为   64 82 35 ff    --   B G R A
        //stride 跨距, 它描述一行像素中, 该颜色分量所占的 byte 数目
        libyuv::MJPGToARGB(raw, rSize, preview, width * 4, src_width,
                                 src_height, width, height);

#ifdef SAVE_JPEG
        if (!WRITE_FILE) {
            const char *path = "/sdcard/argb.bmp"; // 路径
            bmp_write(preview, width, height, path);
            WRITE_FILE = true;
        }
#endif

        //WINDOW_FORMAT_RGBA_8888  排列顺序为 R G B A
        unsigned char temp;
        for (int i = 0; i < width * height * 4; i = i + 4) {
            temp = preview[i+2];
            preview[i+2] = preview[i];
            preview[i] = temp;
        }

raw是通过v4l2获取到的mjpeg格式数据,主要通过libyuv::MJPGToARGB将数据转换成rgba数据。 通过将转换后的数据保存成bmp,用hex格式打开发现,数据保存的格式为BGRA,这个可能windows上或bmp格式的数据就是用这种方式保存的,属于big endian。 android上WINDOW_FORMAT_RGBA_8888 排列顺序为 RGBA,所以还需要做下转换,颜色才能正常。

相关代码已经更新到demo https://github.com/yizhongliu/AnV4L2Camera

参考https://blog.csdn.net/AndrExpert/article/details/100123845https://blog.csdn.net/tyyj90/article/details/120294921

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是使用v4l2获取mjpeg格式图片的C语言代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <linux/videodev2.h> #define CAMERA_DEVICE "/dev/video0" #define CAPTURE_FILE "capture.jpeg" #define BUFFER_COUNT 4 struct buffer { void *start; size_t length; }; int main() { int fd; struct v4l2_capability cap; struct v4l2_format fmt; struct v4l2_requestbuffers req; struct v4l2_buffer buf; enum v4l2_buf_type type; struct buffer *buffers; int i, n_buffers; FILE *fp; // 打开摄像头设备 fd = open(CAMERA_DEVICE, O_RDWR); if (fd == -1) { perror("Failed to open camera device"); return EXIT_FAILURE; } // 查询摄像头设备的能力 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { perror("Failed to query camera device"); return EXIT_FAILURE; } // 检查是否支持视频捕获 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { fprintf(stderr, "Camera device does not support video capture\n"); return EXIT_FAILURE; } // 检查是否支持流形式的视频捕获 if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "Camera device does not support streaming\n"); return EXIT_FAILURE; } // 设置摄像头设备的格式 memset(&fmt, 0, sizeof(fmt)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 640; fmt.fmt.pix.height = 480; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { perror("Failed to set camera device format"); return EXIT_FAILURE; } // 请求摄像头设备的缓冲区 memset(&req, 0, sizeof(req)); req.count = BUFFER_COUNT; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { perror("Failed to request camera device buffers"); return EXIT_FAILURE; } // 映射摄像头设备的缓冲区 buffers = calloc(req.count, sizeof(*buffers)); for (i = 0, n_buffers = 0; i < req.count; i++) { memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) { perror("Failed to query camera device buffer"); return EXIT_FAILURE; } buffers[i].length = buf.length; buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffers[i].start == MAP_FAILED) { perror("Failed to map camera device buffer"); return EXIT_FAILURE; } n_buffers++; } // 启动摄像头设备的视频流 for (i = 0; i < n_buffers; i++) { memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { perror("Failed to enqueue camera device buffer"); return EXIT_FAILURE; } } type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) { perror("Failed to start camera device stream"); return EXIT_FAILURE; } // 从摄像头设备中获取图片 memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) { perror("Failed to dequeue camera device buffer"); return EXIT_FAILURE; } // 保存图片到文件 fp = fopen(CAPTURE_FILE, "wb"); if (fp == NULL) { perror("Failed to open capture file"); return EXIT_FAILURE; } fwrite(buffers[buf.index].start, buf.bytesused, 1, fp); fclose(fp); // 停止摄像头设备的视频流 type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) { perror("Failed to stop camera device stream"); return EXIT_FAILURE; } // 释放摄像头设备的缓冲区 for (i = 0; i < n_buffers; i++) { if (munmap(buffers[i].start, buffers[i].length) == -1) { perror("Failed to unmap camera device buffer"); return EXIT_FAILURE; } } free(buffers); // 关闭摄像头设备 if (close(fd) == -1) { perror("Failed to close camera device"); return EXIT_FAILURE; } return EXIT_SUCCESS; } ``` 这段代码使用v4l2库调用摄像头设备的API,请求摄像头设备的缓冲区,映射缓冲区,启动视频流,从视频流中获取一张图片,保存图片到文件,停止视频流,释放缓冲区,最后关闭摄像头设备。请注意,此代码中的图片格式为MJPEG,如果你需要获取其他格式的图片,需要修改代码中的像素格式

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值