Android中多USB摄像头解决方案——UVCCamera源码分析(三)

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


Android中多USB摄像头解决方案——UVCCamera源码分析(三)_第1张图片
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(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(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源码分析(三))