美文网首页Android开发Android中多USB摄像头解决方案Android开发经验谈
Android中多USB摄像头解决方案——UVCCamera源码

Android中多USB摄像头解决方案——UVCCamera源码

作者: Meteorwizard | 来源:发表于2019-07-15 21:16 被阅读6次

    前面几章我们分析了UVCCamera的初始化、预览相关的准备工作,本章我们则来看看startPreview的整个流程。按照惯例我们先大概看下调用的时序图:


    startPreview

    接着之前开启预览过程最终走到AbstractUVCCameraHandler.CameraThread的handleStartPreview方法,继而调用UVCCamera的startPreview,如上图所示,UVCCamera的startPreview最终调用到C层的UVCPreview的startPreview方法。

    int UVCPreview::startPreview() {
        ENTER();
    
        int result = EXIT_FAILURE;
        if (!isRunning()) {
            mIsRunning = true;
            pthread_mutex_lock(&preview_mutex);
            {
                if (LIKELY(mPreviewWindow)) {
                    result = pthread_create(&preview_thread, NULL, preview_thread_func, (void *)this);
                }
            }
            pthread_mutex_unlock(&preview_mutex);
            if (UNLIKELY(result != EXIT_SUCCESS)) {
                LOGW("UVCCamera::window does not exist/already running/could not create thread etc.");
                mIsRunning = false;
                pthread_mutex_lock(&preview_mutex);
                {
                    pthread_cond_signal(&preview_sync);
                }
                pthread_mutex_unlock(&preview_mutex);
            }
        }
        RETURN(result, int);
    }
    

    在startPreview方法中通过调用pthread_create起了一个线程,该线程中执行的内容在preview_thread_func中,于是我们继续看preview_thread_func:

    void *UVCPreview::preview_thread_func(void *vptr_args) {
        int result;
    
        ENTER();
        UVCPreview *preview = reinterpret_cast<UVCPreview *>(vptr_args);
        if (LIKELY(preview)) {
            uvc_stream_ctrl_t ctrl;
            result = preview->prepare_preview(&ctrl);
            if (LIKELY(!result)) {
                preview->do_preview(&ctrl);
            }
        }
        PRE_EXIT();
        pthread_exit(NULL);
    }
    

    这里我们就关心prepare_preview、do_preview这两个方法。先看prepare_preview:

    int UVCPreview::prepare_preview(uvc_stream_ctrl_t *ctrl) {
        uvc_error_t result;
    
        ENTER();
        result = uvc_get_stream_ctrl_format_size_fps(mDeviceHandle, ctrl,
            !requestMode ? UVC_FRAME_FORMAT_YUYV : UVC_FRAME_FORMAT_MJPEG,
            requestWidth, requestHeight, requestMinFps, requestMaxFps
        );
        if (LIKELY(!result)) {
    #if LOCAL_DEBUG
            uvc_print_stream_ctrl(ctrl, stderr);
    #endif
            uvc_frame_desc_t *frame_desc;
            result = uvc_get_frame_desc(mDeviceHandle, ctrl, &frame_desc);
            if (LIKELY(!result)) {
                frameWidth = frame_desc->wWidth;
                frameHeight = frame_desc->wHeight;
                LOGI("frameSize=(%d,%d)@%s", frameWidth, frameHeight, (!requestMode ? "YUYV" : "MJPEG"));
                pthread_mutex_lock(&preview_mutex);
                if (LIKELY(mPreviewWindow)) {
                    ANativeWindow_setBuffersGeometry(mPreviewWindow,
                        frameWidth, frameHeight, previewFormat);
                }
                pthread_mutex_unlock(&preview_mutex);
            } else {
                frameWidth = requestWidth;
                frameHeight = requestHeight;
            }
            frameMode = requestMode;
            frameBytes = frameWidth * frameHeight * (!requestMode ? 2 : 4);
            previewBytes = frameWidth * frameHeight * PREVIEW_PIXEL_BYTES;
        } else {
            LOGE("could not negotiate with camera:err=%d", result);
        }
        RETURN(result, int);
    }
    

    这个方法主要还是做了一些预览的参数设置工作,包括帧宽高、根据色彩模式配置所需要的内存空间等。其中也调用了ANativeWindow_setBuffersGeometry来更新原生窗口的参数。之后我们再看do_preview:

    void UVCPreview::do_preview(uvc_stream_ctrl_t *ctrl) {
        ENTER();
    
        uvc_frame_t *frame = NULL;
        uvc_frame_t *frame_mjpeg = NULL;
        uvc_error_t result = uvc_start_streaming_bandwidth(
            mDeviceHandle, ctrl, uvc_preview_frame_callback, (void *)this, requestBandwidth, 0);
    
        if (LIKELY(!result)) {
            clearPreviewFrame();
            pthread_create(&capture_thread, NULL, capture_thread_func, (void *)this);
    
    #if LOCAL_DEBUG
            LOGI("Streaming...");
    #endif
            if (frameMode) {
                // MJPEG mode
                for ( ; LIKELY(isRunning()) ; ) {
                    frame_mjpeg = waitPreviewFrame();
                    if (LIKELY(frame_mjpeg)) {
                        frame = get_frame(frame_mjpeg->width * frame_mjpeg->height * 2);
                        result = uvc_mjpeg2yuyv(frame_mjpeg, frame);   // MJPEG => yuyv
                        recycle_frame(frame_mjpeg);
                        if (LIKELY(!result)) {
                            frame = draw_preview_one(frame, &mPreviewWindow, uvc_any2rgbx, 4);
                            addCaptureFrame(frame);
                        } else {
                            recycle_frame(frame);
                        }
                    }
                }
            } else {
                // yuvyv mode
                for ( ; LIKELY(isRunning()) ; ) {
                    frame = waitPreviewFrame();
                    if (LIKELY(frame)) {
                        frame = draw_preview_one(frame, &mPreviewWindow, uvc_any2rgbx, 4);
                        addCaptureFrame(frame);
                    }
                }
            }
            pthread_cond_signal(&capture_sync);
    #if LOCAL_DEBUG
            LOGI("preview_thread_func:wait for all callbacks complete");
    #endif
            uvc_stop_streaming(mDeviceHandle);
    #if LOCAL_DEBUG
            LOGI("Streaming finished");
    #endif
        } else {
            uvc_perror(result, "failed start_streaming");
        }
    
        EXIT();
    }
    

    在该方法中我们看到调用了uvc_start_streaming_bandwidth,这个方式是在libuvc的stream.c中。

    /** Begin streaming video from the camera into the callback function.
     * @ingroup streaming
     *
     * @param devh UVC device
     * @param ctrl Control block, processed using {uvc_probe_stream_ctrl} or
     *             {uvc_get_stream_ctrl_format_size}
     * @param cb   User callback function. See {uvc_frame_callback_t} for restrictions.
     * @param bandwidth_factor [0.0f, 1.0f]
     * @param flags Stream setup flags, currently undefined. Set this to zero. The lower bit
     * is reserved for backward compatibility.
     */
    uvc_error_t uvc_start_streaming_bandwidth(uvc_device_handle_t *devh,
            uvc_stream_ctrl_t *ctrl, uvc_frame_callback_t *cb, void *user_ptr,
            float bandwidth_factor,
            uint8_t flags) {
        uvc_error_t ret;
        uvc_stream_handle_t *strmh;
    
        ret = uvc_stream_open_ctrl(devh, &strmh, ctrl);
        if (UNLIKELY(ret != UVC_SUCCESS))
            return ret;
    
        ret = uvc_stream_start_bandwidth(strmh, cb, user_ptr, bandwidth_factor, flags);
        if (UNLIKELY(ret != UVC_SUCCESS)) {
            uvc_stream_close(strmh);
            return ret;
        }
    
        return UVC_SUCCESS;
    }
    

    根据注释我们可以知道,这个方法作用就是将相机采集到的数据放到回调函数中,于是我们接着看传进来的回调函数:uvc_preview_frame_callback

    void UVCPreview::uvc_preview_frame_callback(uvc_frame_t *frame, void *vptr_args) {
        UVCPreview *preview = reinterpret_cast<UVCPreview *>(vptr_args);
        if UNLIKELY(!preview->isRunning() || !frame || !frame->frame_format || !frame->data || !frame->data_bytes) return;
        if (UNLIKELY(
            ((frame->frame_format != UVC_FRAME_FORMAT_MJPEG) && (frame->actual_bytes < preview->frameBytes))
            || (frame->width != preview->frameWidth) || (frame->height != preview->frameHeight) )) {
    
    #if LOCAL_DEBUG
            LOGD("broken frame!:format=%d,actual_bytes=%d/%d(%d,%d/%d,%d)",
                frame->frame_format, frame->actual_bytes, preview->frameBytes,
                frame->width, frame->height, preview->frameWidth, preview->frameHeight);
    #endif
            return;
        }
        if (LIKELY(preview->isRunning())) {
            uvc_frame_t *copy = preview->get_frame(frame->data_bytes);
            if (UNLIKELY(!copy)) {
    #if LOCAL_DEBUG
                LOGE("uvc_callback:unable to allocate duplicate frame!");
    #endif
                return;
            }
            uvc_error_t ret = uvc_duplicate_frame(frame, copy);
            if (UNLIKELY(ret)) {
                preview->recycle_frame(copy);
                return;
            }
            preview->addPreviewFrame(copy);
        }
    }
    
    

    这个方法前面一些可以先忽略,我们关心的是怎么样处理一帧数据的,可以看到uvc_frame_t *copy = preview->get_frame(frame->data_bytes);这个方法:

    /**
     * get uvc_frame_t from frame pool
     * if pool is empty, create new frame
     * this function does not confirm the frame size
     * and you may need to confirm the size
     */
    uvc_frame_t *UVCPreview::get_frame(size_t data_bytes) {
        uvc_frame_t *frame = NULL;
        pthread_mutex_lock(&pool_mutex);
        {
            if (!mFramePool.isEmpty()) {
                frame = mFramePool.last();
            }
        }
        pthread_mutex_unlock(&pool_mutex);
        if UNLIKELY(!frame) {
            LOGW("allocate new frame");
            frame = uvc_allocate_frame(data_bytes);
        }
        return frame;
    }
    

    先从全局的mFramePool中取出一帧,然后再调用libuvc中frame.c的方法—— uvc_duplicate_frame来把从相机获取到的帧数据复制到刚才mFramePool中取出的*copy中。

    /** @brief Duplicate a frame, preserving color format
     * @ingroup frame
     *
     * @param in Original frame
     * @param out Duplicate frame
     */
    uvc_error_t uvc_duplicate_frame(uvc_frame_t *in, uvc_frame_t *out) {
        if (UNLIKELY(uvc_ensure_frame_size(out, in->data_bytes) < 0))
            return UVC_ERROR_NO_MEM;
    
        out->width = in->width;
        out->height = in->height;
        out->frame_format = in->frame_format;
        if (out->library_owns_data)
            out->step = in->step;
        out->sequence = in->sequence;
        out->capture_time = in->capture_time;
        out->source = in->source;
        out->actual_bytes = in->actual_bytes;   // XXX
    
    #if USE_STRIDE   // XXX
        if (in->step && out->step) {
            const int istep = in->step;
            const int ostep = out->step;
            const int hh = in->height < out->height ? in->height : out->height;
            const int rowbytes = istep < ostep ? istep : ostep;
            register void *ip = in->data;
            register void *op = out->data;
            int h;
            for (h = 0; h < hh; h += 4) {
                memcpy(op, ip, rowbytes);
                ip += istep; op += ostep;
                memcpy(op, ip, rowbytes);
                ip += istep; op += ostep;
                memcpy(op, ip, rowbytes);
                ip += istep; op += ostep;
                memcpy(op, ip, rowbytes);
                ip += istep; op += ostep;
            }
        } else {
            // compressed format? XXX if only one of the frame in / out has step, this may lead to crash...
            memcpy(out->data, in->data, in->actual_bytes);
        }
    #else
        memcpy(out->data, in->data, in->actual_bytes); // XXX
    #endif
        return UVC_SUCCESS;
    }
    

    最后再通过UVCPreview的addPreviewFrame方法将当前帧放入previewFrames中。

    void UVCPreview::addPreviewFrame(uvc_frame_t *frame) {
    
        pthread_mutex_lock(&preview_mutex);
        if (isRunning() && (previewFrames.size() < MAX_FRAME)) {
            previewFrames.put(frame);
            frame = NULL;
            pthread_cond_signal(&preview_sync);
        }
        pthread_mutex_unlock(&preview_mutex);
        if (frame) {
            recycle_frame(frame);
        }
    }
    

    以上是UVCPreview中do_preview方法中有关于预览回调处理的逻辑。接着我们继续回到do_preview的后续代码中,核心是这一段代码:

    if (frameMode) {
                // MJPEG mode
                for ( ; LIKELY(isRunning()) ; ) {
                    frame_mjpeg = waitPreviewFrame();
                    if (LIKELY(frame_mjpeg)) {
                        frame = get_frame(frame_mjpeg->width * frame_mjpeg->height * 2);
                        result = uvc_mjpeg2yuyv(frame_mjpeg, frame);   // MJPEG => yuyv
                        recycle_frame(frame_mjpeg);
                        if (LIKELY(!result)) {
                            frame = draw_preview_one(frame, &mPreviewWindow, uvc_any2rgbx, 4);
                            addCaptureFrame(frame);
                        } else {
                            recycle_frame(frame);
                        }
                    }
                }
            } else {
                // yuvyv mode
                for ( ; LIKELY(isRunning()) ; ) {
                    frame = waitPreviewFrame();
                    if (LIKELY(frame)) {
                        frame = draw_preview_one(frame, &mPreviewWindow, uvc_any2rgbx, 4);
                        addCaptureFrame(frame);
                    }
                }
            }
    

    大概意思就是根据设置的模式来处理帧数据,其中MJPEG模式下只是比yuvyv多了一步转换工作,调用的是libuvc中frame-mjpeg.c的uvc_mjpeg2yuyv方法,这里就不展开,感兴趣的读者可以去查看一下该方法的源码。我们再回到do_preview方法中,无论是MJPEG还是yuvyv,最终都会调用draw_preview_one方法,听这个方法名字就能大概知道,这里是把最终采集到的数据绘制到原生窗口上:

    // changed to return original frame instead of returning converted frame even if convert_func is not null.
    uvc_frame_t *UVCPreview::draw_preview_one(uvc_frame_t *frame, ANativeWindow **window, convFunc_t convert_func, int pixcelBytes) {
        // ENTER();
    
        int b = 0;
        pthread_mutex_lock(&preview_mutex);
        {
            b = *window != NULL;
        }
        pthread_mutex_unlock(&preview_mutex);
        if (LIKELY(b)) {
            uvc_frame_t *converted;
            if (convert_func) {
                converted = get_frame(frame->width * frame->height * pixcelBytes);
                if LIKELY(converted) {
                    b = convert_func(frame, converted);
                    if (!b) {
                        pthread_mutex_lock(&preview_mutex);
                        copyToSurface(converted, window);
                        pthread_mutex_unlock(&preview_mutex);
                    } else {
                        LOGE("failed converting");
                    }
                    recycle_frame(converted);
                }
            } else {
                pthread_mutex_lock(&preview_mutex);
                copyToSurface(frame, window);
                pthread_mutex_unlock(&preview_mutex);
            }
        }
        return frame; //RETURN(frame, uvc_frame_t *);
    }
    

    根据源码可知,核心是将准备好的frame通过调用copyToSurface方法来绘制到ANativeWindow上:

    // transfer specific frame data to the Surface(ANativeWindow)
    int copyToSurface(uvc_frame_t *frame, ANativeWindow **window) {
        // ENTER();
        int result = 0;
        if (LIKELY(*window)) {
            ANativeWindow_Buffer buffer;
            if (LIKELY(ANativeWindow_lock(*window, &buffer, NULL) == 0)) {
                // source = frame data
                const uint8_t *src = (uint8_t *)frame->data;
                const int src_w = frame->width * PREVIEW_PIXEL_BYTES;
                const int src_step = frame->width * PREVIEW_PIXEL_BYTES;
                // destination = Surface(ANativeWindow)
                uint8_t *dest = (uint8_t *)buffer.bits;
                const int dest_w = buffer.width * PREVIEW_PIXEL_BYTES;
                const int dest_step = buffer.stride * PREVIEW_PIXEL_BYTES;
                // use lower transfer bytes
                const int w = src_w < dest_w ? src_w : dest_w;
                // use lower height
                const int h = frame->height < buffer.height ? frame->height : buffer.height;
                // transfer from frame data to the Surface
                copyFrame(src, dest, w, h, src_step, dest_step);
                ANativeWindow_unlockAndPost(*window);
            } else {
                result = -1;
            }
        } else {
            result = -1;
        }
        return result; //RETURN(result, int);
    }
    
    static void copyFrame(const uint8_t *src, uint8_t *dest, const int width, int height, const int stride_src, const int stride_dest) {
        const int h8 = height % 8;
        for (int i = 0; i < h8; i++) {
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
        }
        for (int i = 0; i < height; i += 8) {
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
            memcpy(dest, src, width);
            dest += stride_dest; src += stride_src;
        }
    }
    

    小结

    至此,UVCCamera的预览功能大概就分析到这,整个流程还是比较清晰的,但是可能我讲得不够清晰……后续我再慢慢完善,请大家见谅。

    相关文章

      网友评论

        本文标题:Android中多USB摄像头解决方案——UVCCamera源码

        本文链接:https://www.haomeiwen.com/subject/apyokctx.html