Andoid系统usb相机的使用方法

    公司最近在做新款产品的时候,需要同时用到四路镜头,但是目前高通的开发板上只有三个口,故打算再加上一个USB镜头。于是翻看了一下android的USB镜头的使用,发现得自己写JNI代码的,特此记录一下。

    大概流程如下:

打开USB镜头-> 获取USB镜头的信息并设置相应的属性->申请一个图像数据的缓冲区-> 开始捕获数据(让USB往缓冲区写数据)-> 循环从缓冲区获取一帧数据-> 关闭USB镜头

    根据这个流程,为了方便理解,我们先定义调用jni层的class文件 然后按照顺序一步一步去实现

import android.util.Log;

/**
 * Created by 601042 on 2018/2/28.
 */

public class NativeTest {
    static {
        System.loadLibrary("native-lib");
    }

    /**
     * 打开USB镜头
     *
     * @return   0:成功
     * */
    public native int openCamera();

    /**
     * 获取USB镜头的信息并设置相应的属性
     *
     * @return   0:成功
     * */
    public native String getDevicInfo();
    /**
     * 申请一个图像数据的缓冲区
     *
     * @return   0:成功
     * */
    public native int getCache();

    /**
     * 开始捕获数据
     *
     * @return   0:成功
     * */
    public native int startCapture();

    /**
     * 获取一帧数据
     *
     * @return   0:成功
     * */
    public native int getOneFrame();

    /**
     * 关闭USB镜头
     *
     * @return   0:成功
     * */
    public native int closeCamera();


    /**
     * 开启图像callback
     *
     * @return   0:成功
     * */
    public  native void start();
    /**
     * 图像callback
     *
     * @param data:一帧图像数据
     * @param length:一帧图像数据的长度
     * */
    public  void myCallback(byte[] data,int length) {
        Log.e("Test", "Callback: " + data.length + "   "+length);
    }

}

    现在先来实现第一步:打开USB镜头

    linux是一个文件系统,外接设备也是以一个文件的形式存在,在/dev/目录下可以找到这些,我现在板上有三个镜头的插口,/dev/目录下则有video1、video2、video33、video34四个文件存在,我猜应该是支持四路相机的,只是系统层做了限制,只能同时开三路。现在将USB镜头接到板上,发现/dev/目录下多个video3,拔掉USB镜头又没了,故这个video3就是USB镜头,要打开这个镜头就跟打开一个文件是一样。

static int fd = -1;                        //镜头ID
static char *dev_name = "/dev/video3";     //镜头名

/*************************************************
Function:       openUSBCamera
Description:    打开USB摄像头
*************************************************/
    int openUSBCamera() {
        struct stat st;

        //先判断该文件的状态
        if (-1 == stat(dev_name, &st)) {
            LOGE("Cannot identify '%s': %d, %s\n", dev_name, errno, strerror(errno));
            return -1;
        }
       //再判断该文件是不是设备
        if (!S_ISCHR(st.st_mode)) {
            LOGE("%s is not device/n", dev_name);
            return -1;
        }
       //打开设备
        fd = open(dev_name, O_RDWR /* required */| O_NONBLOCK, 0);
        //判断是否打开成功
        if (-1 == fd) {
            LOGI("Cannot open '%s': %d, %s\n", dev_name, errno, strerror(errno));
            return -1;
        }
        canCallback = true;
        //返回该设备的ID
        return fd;

    };
    
    
    extern "C"
    JNIEXPORT jint JNICALL
    Java_demo_xu_usbcanerademo_NativeTest_openCamera(JNIEnv *env, jobject instance) {
        // TODO
        return openUSBCamera();

    }

    好了,到这里我们就打开USB镜头了。

    接下来我们来获取USB镜头的信息并设置

/*************************************************
Function:       getInfo
Description:    获取USB摄像头的信息
Return:         设备信息
*************************************************/
    string getInfo() {
        //查看设备名称等信息
        struct v4l2_capability cap;
        ioctl(fd, VIDIOC_QUERYCAP, &cap);
        //把结果封装一下
        std::stringstream ss;
        ss << "DriverName:" << cap.driver << "     Card Name:" << cap.card << "     Bus info:"
           << cap.bus_info << "     DriverVersion:" << ((cap.version >> 16) & 0XFF)
           << ((cap.version >> 8) & 0XFF) << (cap.version & 0xff);

        //查看支持格式
        struct v4l2_fmtdesc fmtdesc;
        fmtdesc.index = 0;
        fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        //获取支持的格式列表  并把结果封装一下
        ss << "     Supportformat:\n";
        while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
            //把结果封装一下
            ss << (fmtdesc.index + 1) << ":" << (fmtdesc.description) << "\n";
            fmtdesc.index++;
        }
        //如果需要设置的话 使用 ioctl(fd, VIDIOC_S_FMT, &fmtdesc);

        //查看当前帧的相关信息(长宽)
        struct v4l2_format fmt;
        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        ioctl(fd, VIDIOC_G_FMT, &fmt);
        //把结果封装一下
        ss << "Currentdata format information:\ntwidth:" << fmt.fmt.pix.width << "     height:"
           << fmt.fmt.pix.height;
        //如果需要设置的话 使用 ioctl(fd, VIDIOC_S_FMT, &fmt);
        return ss.str();
    }

    extern "C"
    JNIEXPORT jstring JNICALL
    Java_demo_xu_usbcanerademo_NativeTest_getDevicInfo(JNIEnv *env, jobject instance) {
        // TODO
        string resule = getInfo();
        return env->NewStringUTF(resule.c_str());
    }

    由于我的镜头没有支持多种格式或者属性,所以我就只获取一下信息,不进行设置。如果需要设置的话需要记住,一定要先获取设备支持的属性或格式列表,再从中获取到满足要求的设置进去,不然设置设备不支持的属性或者格式会无法正常工作的。
    接下来我们申请一个图形数据缓冲区

   应用程序和设备有三种交换数据的方法,直接read/write ,内存映射(memorymapping) ,用户指针。我使用的是内存映射的方式。

/*************************************************
Function:       getCache
Description:    申请一个缓冲区(4帧) 并用buffers把指针存起来
Return:         结果
*************************************************/
    int getCache(){
        //定义buffer(缓冲区)的属性
        struct v4l2_requestbuffers req;
        //缓存多少帧
        req.count=4;
        //buffer的类型
        req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
        //采用内存映射的方式
        req.memory=V4L2_MEMORY_MMAP;
        //申请缓冲区
        int result_getcache = ioctl(fd,VIDIOC_REQBUFS,&req);
        //如果为-1则说明申请失败
        if(result_getcache == -1){
            return -1;
        }
       //用buffer存储缓冲区的指针
        buffers =(buffer*)calloc (req.count, sizeof (*buffers));
        if (!buffers) {
            fprintf (stderr,"Out of memory/n");
            return -2;

        }
        //开始映射  四帧图像的区域都要
        for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
            struct v4l2_buffer buf;
            memset(&buf,0,sizeof(buf));
            buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory =V4L2_MEMORY_MMAP;
            buf.index =n_buffers;
           // 查询序号为n_buffers 的缓冲区,得到其起始物理地址和大小 -1则说明失败
            if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)){
                return -3;
            }
            buffers[n_buffers].length= buf.length;
            // 映射内存
            buffers[n_buffers].start=mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);
            //如果映射失败则直接返回
            if (MAP_FAILED== buffers[n_buffers].start){
                return -4;
            }
        }
        return 0;
    }

    extern "C"
    JNIEXPORT jint JNICALL
    Java_demo_xu_usbcanerademo_NativeTest_getCache(JNIEnv *env, jobject instance) {
        // TODO
        return getCache();
    }

    缓冲区申请好了我们开始捕获数据(让设备往缓冲区里写数据)

/*************************************************
Function:       startCapture
Description:    开始捕获数据(数据会存入缓冲区)
Return:         结果
*************************************************/
    int startCapture(void) {
        unsigned int i;
        enum v4l2_buf_type type;
        //把四个帧放入队列
        for (i = 0; i < n_buffers; ++i) {
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(buf));
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            buf.index = i;
            //把帧放入队列
            if (-1 == ioctl(fd, VIDIOC_QBUF, &buf)){
                LOGE("VIDIOC_QBUF error %d, %s\n", errno, strerror(errno));
                return -1;
            }
        }
        //类型设置为捕获数据
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        //启动数据流
        if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)){
            LOGE("VIDIOC_STREAMON error %d, %s\n", errno, strerror(errno));
            return -2;
        }
        return 0;
    }

    extern "C"
    JNIEXPORT jint JNICALL
    Java_demo_xu_usbcanerademo_NativeTest_startCapture(JNIEnv *env, jobject instance) {
        // TODO
        return startCapture();
    }

    开启了捕获之后,镜头就会一直往缓冲区里写数据了,数据的格式就是之前你设置的,比如我的就是YUV422(我没设置,因为我的镜头就只有这种格式的)。

    现在我们要拿图像数据出来显示,就得从缓存区里拿数据,循环地从缓冲区里拿一帧帧的图像出来显示就是镜头的预览了。

    我们先来讲讲如何冲缓冲区获取一帧图像数据出来

  struct buffer *buffers = NULL;              //图像数据
    void *framebuf = NULL;                      //一帧图像数据
/*************************************************
Function:       getOneFrame
Description:    从缓冲区获取一帧数据
Return:         结果
*************************************************/
    static int getOneFrame() {
        struct v4l2_buffer buf;
        unsigned int i;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        //从队列中取数据到缓冲区
        if (-1 == ioctl(fd, VIDIOC_DQBUF, &buf)) {
            LOGE("VIDIOC_DQBUF error %d , %s", errno, strerror(errno));
        }
        //
        assert(buf.index < n_buffers);
        if (buf.bytesused <= 0xaf) {
            /* Prevent crash on empty image */
            LOGI("Ignoring empty buffer ...\n");
            return -1;
        }
        oneFrameLength = buf.bytesused;
        framebuf = (void *) malloc(oneFrameLength);
        pthread_mutex_lock(&lock);
        //从视频数据copy到framebuf中
        memcpy(framebuf, buffers[buf.index].start, oneFrameLength);
        pthread_mutex_unlock(&lock);
        //填充队列
        if (-1 == ioctl(fd, VIDIOC_QBUF, &buf))
            LOGE("VIDIOC_QBUF error %d, %s", errno, strerror(errno));
        return 0;


    }


    extern "C"
    JNIEXPORT jint JNICALL
    Java_demo_xu_usbcanerademo_NativeTest_getOneFrame(JNIEnv *env, jobject instance) {
        // TODO
        return getOneFrame();
    }

    这里使用到了一个自己定义结构体buffer

    struct buffer {
        void *start;
        size_t length;
    };

    到这里我们就可以获取带USB镜头的一帧图像了,在demo,我使用了jni层回调java代码的方式,通过一个线程把循环从缓冲区里拿数据,然后调用java层的callback给传递出去。

/*************************************************
Function:       thread_entry
Description:    获取图像并callback到java层的线程
*************************************************/
    void *thread_entry(void* data)
    {
        //获取全局的*env;
        JNIEnv *env;
        mjavaVM->AttachCurrentThread(&env,NULL);
        jclass clazz = env->GetObjectClass(mjavaobject);
        //获取到java的方法ID
        jmethodID mID = env->GetMethodID(clazz, "myCallback", "([BI)V");

        //开始while循环获取图像并callback出去,用canCallback这个变量控制是否停止
        while(canCallback) {
            if(env != NULL && mID != NULL && mjavaobject != NULL){
                struct v4l2_buffer buf;
                unsigned int i;
                memset(&buf, 0, sizeof(buf));
                buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf.memory = V4L2_MEMORY_MMAP;
                  //从队列中取数据到缓冲区
                if (-1 == ioctl(fd, VIDIOC_DQBUF, &buf)) {
                    LOGE("VIDIOC_DQBUF error %d , %s", errno, strerror(errno));
                    usleep(50000);
                    continue;
                }
                //判断获取到的数据是否有效
                assert(buf.index < n_buffers);
                if (buf.bytesused <= 0xaf) {
                    LOGI("Ignoring empty buffer ...\n");
                    usleep(50000);
                    continue;
                }
                //记录下获取到数据的长度
                oneFrameLength = buf.bytesused;
                
                pthread_mutex_lock(&lock);
                //实例化一个数组
                jbyteArray temp_result = env->NewByteArray(oneFrameLength);
                //把获取到的图像数据赋值给数组
                env->SetByteArrayRegion(temp_result, 0, oneFrameLength,(jbyte *)buffers[buf.index].start);
                //调用java层的代码
                env->CallVoidMethod(mjavaobject, mID, temp_result,oneFrameLength);
                //销毁掉创建出来的两个临时变量
                env->DeleteLocalRef(temp_result);
                pthread_mutex_unlock(&lock);
                //填充队列
                if (-1 == ioctl(fd, VIDIOC_QBUF, &buf)){
                    LOGE("VIDIOC_QBUF error %d, %s", errno, strerror(errno));
                    usleep(50000);
                    continue;
                }
            }else{
                LOGD("menv == NULL || obj == NULL || mID == NULL...\n");
            }
            //间隔50毫秒
            //单位:微秒   1000微秒=1毫秒
            usleep(50000);
        }
        //销毁掉
        mjavaVM->DetachCurrentThread();
    }

    extern "C"
    JNIEXPORT void JNICALL
    Java_demo_xu_usbcanerademo_NativeTest_start(JNIEnv *env, jobject instance) {
        // TODO
        
        env->GetJavaVM(&mjavaVM);
        mjavaobject = env->NewGlobalRef(instance);
        pthread_t trecv;
        int result = pthread_create(&trecv,NULL,thread_entry,NULL);
        LOGD("result%d",result);
    }

   拿到了数据,大家想用什么方法显示都可以,保存成文件也可以,这里就不多讲了。

   最后一步就是你用完了就得关闭镜头

/*************************************************
Function:       closeUSBCamera
Description:    关闭USB摄像头
Return:         结果
*************************************************/
    int closeUSBCamera() {
        if(fd == -1){
            return 0;
        }
        //释放申请的缓冲
        unsigned int i;
        for (i = 0; i < n_buffers; ++i){
            if (-1 == munmap(buffers[i].start, buffers[i].length)){
                LOGE("munmap error %d , %s", errno, strerror(errno));
            }
            free(buffers);
        }
        canCallback = false;
        //关闭镜头
        return close(fd);
    };

    extern "C"
    JNIEXPORT jint JNICALL
    Java_demo_xu_usbcanerademo_NativeTest_closeCamera(JNIEnv *env, jobject instance) {
        // TODO
        return closeUSBCamera();
    }

   好了,到这里基本整个USB相机的使用就讲完了。由于代码不多也不复杂,demo就不放了,毕竟现在下载都要积分不能免费了,如果实在是需要demo的话,私信联系我吧。

   参考资料:https://blog.csdn.net/eastmoon502136/article/details/8190262

猜你喜欢

转载自blog.csdn.net/a287574014/article/details/79757092
今日推荐