前面几章我们分析了UVCCamera的初始化、预览相关的准备工作,本章我们则来看看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的预览功能大概就分析到这,整个流程还是比较清晰的,但是可能我讲得不够清晰……后续我再慢慢完善,请大家见谅。