当前位置: 首页 > news >正文

基于libusb的用户空间UVC相机库

一、项目概述

基于libusb的用户空间UVC(USB Video Class)相机库,提供完整的相机控制、视频流捕获和图像处理功能。

二、实现代码

2.1 头文件 uvc_camera.h

/*** UVC Camera Library Header* 基于libusb的用户空间UVC相机库*/#ifndef UVC_CAMERA_H
#define UVC_CAMERA_H#include <libusb-1.0/libusb.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>#ifdef __cplusplus
extern "C" {
#endif/* 版本信息 */
#define UVC_CAMERA_VERSION_MAJOR 1
#define UVC_CAMERA_VERSION_MINOR 0
#define UVC_CAMERA_VERSION_PATCH 0/* 错误码定义 */
typedef enum {UVC_SUCCESS = 0,UVC_ERROR_IO = -1,UVC_ERROR_INVALID_PARAM = -2,UVC_ERROR_ACCESS = -3,UVC_ERROR_NO_DEVICE = -4,UVC_ERROR_NOT_FOUND = -5,UVC_ERROR_BUSY = -6,UVC_ERROR_TIMEOUT = -7,UVC_ERROR_OVERFLOW = -8,UVC_ERROR_PIPE = -9,UVC_ERROR_INTERRUPTED = -10,UVC_ERROR_NO_MEM = -11,UVC_ERROR_NOT_SUPPORTED = -12,UVC_ERROR_INVALID_DEVICE = -13,UVC_ERROR_INIT_FAILED = -14
} uvc_error_t;/* UVC设备信息 */
typedef struct {uint16_t vendor_id;uint16_t product_id;char manufacturer[64];char product[64];char serial_number[64];uint8_t bus_number;uint8_t device_address;libusb_device *device;
} uvc_device_info_t;/* 视频格式 */
typedef enum {UVC_FORMAT_UNCOMPRESSED = 0x04,UVC_FORMAT_MJPEG = 0x06,UVC_FORMAT_FRAME_BASED = 0x10
} uvc_format_type_t;/* 帧描述符 */
typedef struct {uint8_t index;uint16_t width;uint16_t height;uint32_t frame_interval;  // 以100ns为单位uint8_t bits_per_pixel;uvc_format_type_t format_type;
} uvc_frame_desc_t;/* 相机配置 */
typedef struct {uint8_t interface_number;uint8_t endpoint_address;uint16_t max_packet_size;uint8_t alternate_setting;uint8_t num_frame_descriptors;uvc_frame_desc_t *frame_descriptors;
} uvc_config_t;/* 帧缓冲区 */
typedef struct {uint8_t *data;size_t length;uint64_t timestamp;uint32_t sequence;uvc_frame_desc_t format;
} uvc_frame_t;/* 相机上下文 */
typedef struct {libusb_context *ctx;libusb_device_handle *dev_handle;uvc_device_info_t device_info;uvc_config_t config;bool is_streaming;pthread_t stream_thread;void (*frame_callback)(uvc_frame_t *frame, void *user_ptr);void *user_ptr;uint8_t *transfer_buffer;size_t transfer_buffer_size;uint32_t frame_sequence;bool auto_exposure;uint8_t brightness;uint8_t contrast;uint8_t saturation;uint8_t sharpness;uint16_t exposure_time;
} uvc_camera_t;/* 初始化和反初始化 */
uvc_error_t uvc_init(uvc_camera_t **camera);
void uvc_exit(uvc_camera_t *camera);/* 设备发现和管理 */
uvc_error_t uvc_find_devices(uvc_camera_t *camera, uvc_device_info_t **devices, int *count);
uvc_error_t uvc_open_device(uvc_camera_t *camera, const uvc_device_info_t *device);
void uvc_close_device(uvc_camera_t *camera);/* 配置和格式设置 */
uvc_error_t uvc_get_config(uvc_camera_t *camera, uvc_config_t *config);
uvc_error_t uvc_set_format(uvc_camera_t *camera, const uvc_frame_desc_t *format);
uvc_error_t uvc_get_formats(uvc_camera_t *camera, uvc_frame_desc_t **formats, int *count);/* 流控制 */
uvc_error_t uvc_start_streaming(uvc_camera_t *camera, void (*callback)(uvc_frame_t *frame, void *user_ptr),void *user_ptr);
uvc_error_t uvc_stop_streaming(uvc_camera_t *camera);/* 相机控制 */
uvc_error_t uvc_set_brightness(uvc_camera_t *camera, uint8_t brightness);
uvc_error_t uvc_set_contrast(uvc_camera_t *camera, uint8_t contrast);
uvc_error_t uvc_set_saturation(uvc_camera_t *camera, uint8_t saturation);
uvc_error_t uvc_set_sharpness(uvc_camera_t *camera, uint8_t sharpness);
uvc_error_t uvc_set_exposure_auto(uvc_camera_t *camera, bool enable);
uvc_error_t uvc_set_exposure_time(uvc_camera_t *camera, uint16_t time_ms);/* 实用函数 */
const char *uvc_strerror(uvc_error_t error);
void uvc_print_device_info(const uvc_device_info_t *device);
void uvc_print_frame_info(const uvc_frame_t *frame);#ifdef __cplusplus
}
#endif#endif /* UVC_CAMERA_H */

2.2 核心实现 uvc_camera.c

/*** UVC Camera Library Implementation* 基于libusb的用户空间UVC相机库实现*/#include "uvc_camera.h"/* UVC类特定请求 */
#define UVC_SET_CUR 0x01
#define UVC_GET_CUR 0x81
#define UVC_GET_MIN 0x82
#define UVC_GET_MAX 0x83
#define UVC_GET_RES 0x84
#define UVC_GET_LEN 0x85
#define UVC_GET_INFO 0x86
#define UVC_GET_DEF 0x87/* UVC控制选择器 */
#define UVC_CT_AE_MODE_CONTROL 0x02
#define UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL 0x04
#define UVC_CT_FOCUS_ABSOLUTE_CONTROL 0x06
#define UVC_CT_ZOOM_ABSOLUTE_CONTROL 0x0B
#define UVC_PU_BRIGHTNESS_CONTROL 0x02
#define UVC_PU_CONTRAST_CONTROL 0x03
#define UVC_PU_SATURATION_CONTROL 0x04
#define UVC_PU_SHARPNESS_CONTROL 0x08/* 私有函数声明 */
static uvc_error_t uvc_parse_device_descriptor(uvc_camera_t *camera);
static uvc_error_t uvc_parse_video_control_interface(uvc_camera_t *camera);
static uvc_error_t uvc_parse_video_stream_interface(uvc_camera_t *camera);
static uvc_error_t uvc_send_control_request(uvc_camera_t *camera, uint8_t request, uint8_t unit, uint8_t selector,uint8_t control_interface,void *data, uint16_t length);
static void *uvc_stream_thread(void *arg);
static void uvc_transfer_callback(struct libusb_transfer *transfer);/* 初始化UVC相机库 */
uvc_error_t uvc_init(uvc_camera_t **camera) {if (!camera) {return UVC_ERROR_INVALID_PARAM;}*camera = (uvc_camera_t *)calloc(1, sizeof(uvc_camera_t));if (!*camera) {return UVC_ERROR_NO_MEM;}int ret = libusb_init(&(*camera)->ctx);if (ret < 0) {free(*camera);return UVC_ERROR_INIT_FAILED;}// 设置调试级别libusb_set_debug((*camera)->ctx, LIBUSB_LOG_LEVEL_WARNING);(*camera)->is_streaming = false;(*camera)->frame_sequence = 0;(*camera)->auto_exposure = true;(*camera)->brightness = 128;(*camera)->contrast = 128;(*camera)->saturation = 128;(*camera)->sharpness = 128;(*camera)->exposure_time = 33;  // 默认33ms (30fps)return UVC_SUCCESS;
}/* 清理资源 */
void uvc_exit(uvc_camera_t *camera) {if (!camera) return;if (camera->is_streaming) {uvc_stop_streaming(camera);}if (camera->dev_handle) {uvc_close_device(camera);}if (camera->ctx) {libusb_exit(camera->ctx);}if (camera->config.frame_descriptors) {free(camera->config.frame_descriptors);}if (camera->transfer_buffer) {free(camera->transfer_buffer);}free(camera);
}/* 查找UVC设备 */
uvc_error_t uvc_find_devices(uvc_camera_t *camera, uvc_device_info_t **devices, int *count) {if (!camera || !devices || !count) {return UVC_ERROR_INVALID_PARAM;}libusb_device **device_list;ssize_t num_devices = libusb_get_device_list(camera->ctx, &device_list);if (num_devices < 0) {return UVC_ERROR_IO;}*devices = NULL;*count = 0;for (ssize_t i = 0; i < num_devices; i++) {struct libusb_device_descriptor desc;int ret = libusb_get_device_descriptor(device_list[i], &desc);if (ret < 0) continue;// 检查是否为UVC设备if (desc.bDeviceClass == LIBUSB_CLASS_VIDEO ||desc.bDeviceSubClass == 0x02 ||  // Video Control Interfacedesc.bDeviceProtocol == 0x01) {  // UVC protocol*devices = (uvc_device_info_t *)realloc(*devices, (*count + 1) * sizeof(uvc_device_info_t));if (!*devices) {libusb_free_device_list(device_list, 1);return UVC_ERROR_NO_MEM;}uvc_device_info_t *device = &(*devices)[*count];memset(device, 0, sizeof(uvc_device_info_t));device->device = device_list[i];device->vendor_id = desc.idVendor;device->product_id = desc.idProduct;device->bus_number = libusb_get_bus_number(device_list[i]);device->device_address = libusb_get_device_address(device_list[i]);// 获取字符串描述符libusb_device_handle *handle;if (libusb_open(device_list[i], &handle) == 0) {if (desc.iManufacturer) {libusb_get_string_descriptor_ascii(handle, desc.iManufacturer,(unsigned char*)device->manufacturer,sizeof(device->manufacturer));}if (desc.iProduct) {libusb_get_string_descriptor_ascii(handle, desc.iProduct,(unsigned char*)device->product,sizeof(device->product));}if (desc.iSerialNumber) {libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber,(unsigned char*)device->serial_number,sizeof(device->serial_number));}libusb_close(handle);}(*count)++;}}libusb_free_device_list(device_list, 1);return UVC_SUCCESS;
}/* 打开UVC设备 */
uvc_error_t uvc_open_device(uvc_camera_t *camera, const uvc_device_info_t *device) {if (!camera || !device) {return UVC_ERROR_INVALID_PARAM;}int ret = libusb_open(device->device, &camera->dev_handle);if (ret < 0) {return ret;}// 复制设备信息memcpy(&camera->device_info, device, sizeof(uvc_device_info_t));// 解析设备描述符ret = uvc_parse_device_descriptor(camera);if (ret != UVC_SUCCESS) {libusb_close(camera->dev_handle);camera->dev_handle = NULL;return ret;}// 自动分离内核驱动libusb_set_auto_detach_kernel_driver(camera->dev_handle, 1);// 认领接口ret = libusb_claim_interface(camera->dev_handle, camera->config.interface_number);if (ret < 0) {libusb_close(camera->dev_handle);camera->dev_handle = NULL;return ret;}return UVC_SUCCESS;
}/* 关闭设备 */
void uvc_close_device(uvc_camera_t *camera) {if (!camera || !camera->dev_handle) return;if (camera->is_streaming) {uvc_stop_streaming(camera);}libusb_release_interface(camera->dev_handle, camera->config.interface_number);libusb_close(camera->dev_handle);camera->dev_handle = NULL;
}/* 解析设备描述符 */
static uvc_error_t uvc_parse_device_descriptor(uvc_camera_t *camera) {struct libusb_config_descriptor *config_desc;int ret = libusb_get_active_config_descriptor(camera->device_info.device, &config_desc);if (ret < 0) {return ret;}// 查找视频控制接口和视频流接口for (uint8_t i = 0; i < config_desc->bNumInterfaces; i++) {const struct libusb_interface *interface = &config_desc->interface[i];for (int j = 0; j < interface->num_altsetting; j++) {const struct libusb_interface_descriptor *altsetting = &interface->altsetting[j];if (altsetting->bInterfaceClass == LIBUSB_CLASS_VIDEO) {if (altsetting->bInterfaceSubClass == 0x01) {  // Video Control// 解析视频控制接口camera->config.interface_number = altsetting->bInterfaceNumber;} else if (altsetting->bInterfaceSubClass == 0x02) {  // Video Streaming// 解析视频流接口camera->config.endpoint_address = altsetting->endpoint[0].bEndpointAddress;camera->config.max_packet_size = altsetting->endpoint[0].wMaxPacketSize;camera->config.alternate_setting = altsetting->bAlternateSetting;// 解析格式和帧描述符// 这里简化处理,实际需要解析UVC特定的描述符camera->config.num_frame_descriptors = 3;camera->config.frame_descriptors = (uvc_frame_desc_t *)malloc(3 * sizeof(uvc_frame_desc_t));// 预设一些常见的格式camera->config.frame_descriptors[0] = (uvc_frame_desc_t){.index = 0,.width = 640,.height = 480,.frame_interval = 333333,  // 30fps.bits_per_pixel = 16,.format_type = UVC_FORMAT_UNCOMPRESSED};camera->config.frame_descriptors[1] = (uvc_frame_desc_t){.index = 1,.width = 1280,.height = 720,.frame_interval = 666666,  // 15fps.bits_per_pixel = 16,.format_type = UVC_FORMAT_UNCOMPRESSED};camera->config.frame_descriptors[2] = (uvc_frame_desc_t){.index = 2,.width = 1920,.height = 1080,.frame_interval = 666666,  // 15fps.bits_per_pixel = 24,.format_type = UVC_FORMAT_MJPEG};}}}}libusb_free_config_descriptor(config_desc);return UVC_SUCCESS;
}/* 获取设备配置 */
uvc_error_t uvc_get_config(uvc_camera_t *camera, uvc_config_t *config) {if (!camera || !config) {return UVC_ERROR_INVALID_PARAM;}memcpy(config, &camera->config, sizeof(uvc_config_t));return UVC_SUCCESS;
}/* 设置视频格式 */
uvc_error_t uvc_set_format(uvc_camera_t *camera, const uvc_frame_desc_t *format) {if (!camera || !format) {return UVC_ERROR_INVALID_PARAM;}// 发送UVC设置格式请求uint8_t data[32];memset(data, 0, sizeof(data));// 构建格式设置数据data[0] = format->format_type;data[1] = format->bits_per_pixel;data[2] = format->width & 0xFF;data[3] = (format->width >> 8) & 0xFF;data[4] = format->height & 0xFF;data[5] = (format->height >> 8) & 0xFF;data[6] = format->frame_interval & 0xFF;data[7] = (format->frame_interval >> 8) & 0xFF;data[8] = (format->frame_interval >> 16) & 0xFF;data[9] = (format->frame_interval >> 24) & 0xFF;return uvc_send_control_request(camera, UVC_SET_CUR, 0x01, 0x01,camera->config.interface_number,data, sizeof(data));
}/* 获取支持的格式 */
uvc_error_t uvc_get_formats(uvc_camera_t *camera, uvc_frame_desc_t **formats, int *count) {if (!camera || !formats || !count) {return UVC_ERROR_INVALID_PARAM;}*count = camera->config.num_frame_descriptors;*formats = (uvc_frame_desc_t *)malloc(*count * sizeof(uvc_frame_desc_t));if (!*formats) {return UVC_ERROR_NO_MEM;}memcpy(*formats, camera->config.frame_descriptors, *count * sizeof(uvc_frame_desc_t));return UVC_SUCCESS;
}/* 启动视频流 */
uvc_error_t uvc_start_streaming(uvc_camera_t *camera,void (*callback)(uvc_frame_t *frame, void *user_ptr),void *user_ptr) {if (!camera || !callback) {return UVC_ERROR_INVALID_PARAM;}if (camera->is_streaming) {return UVC_ERROR_BUSY;}camera->frame_callback = callback;camera->user_ptr = user_ptr;camera->is_streaming = true;camera->frame_sequence = 0;// 分配传输缓冲区camera->transfer_buffer_size = camera->config.max_packet_size * 32;camera->transfer_buffer = (uint8_t *)malloc(camera->transfer_buffer_size);if (!camera->transfer_buffer) {camera->is_streaming = false;return UVC_ERROR_NO_MEM;}// 创建流线程int ret = pthread_create(&camera->stream_thread, NULL, uvc_stream_thread, camera);if (ret != 0) {free(camera->transfer_buffer);camera->transfer_buffer = NULL;camera->is_streaming = false;return UVC_ERROR_INIT_FAILED;}return UVC_SUCCESS;
}/* 停止视频流 */
uvc_error_t uvc_stop_streaming(uvc_camera_t *camera) {if (!camera || !camera->is_streaming) {return UVC_SUCCESS;}camera->is_streaming = false;// 等待线程结束pthread_join(camera->stream_thread, NULL);// 释放资源if (camera->transfer_buffer) {free(camera->transfer_buffer);camera->transfer_buffer = NULL;}return UVC_SUCCESS;
}/* 流线程函数 */
static void *uvc_stream_thread(void *arg) {uvc_camera_t *camera = (uvc_camera_t *)arg;while (camera->is_streaming) {// 提交异步传输struct libusb_transfer *transfer = libusb_alloc_transfer(0);if (!transfer) {usleep(10000);continue;}libusb_fill_bulk_transfer(transfer, camera->dev_handle,camera->config.endpoint_address,camera->transfer_buffer,camera->transfer_buffer_size,uvc_transfer_callback,camera,1000);  // 1秒超时int ret = libusb_submit_transfer(transfer);if (ret < 0) {libusb_free_transfer(transfer);usleep(10000);continue;}// 处理事件libusb_handle_events_timeout_completed(camera->ctx, NULL, NULL);}return NULL;
}/* 传输回调函数 */
static void uvc_transfer_callback(struct libusb_transfer *transfer) {uvc_camera_t *camera = (uvc_camera_t *)transfer->user_data;if (transfer->status == LIBUSB_TRANSFER_COMPLETED && transfer->actual_length > 0) {// 创建帧对象uvc_frame_t frame;memset(&frame, 0, sizeof(frame));frame.data = transfer->buffer;frame.length = transfer->actual_length;frame.timestamp = (uint64_t)time(NULL) * 1000000;  // 微秒时间戳frame.sequence = ++camera->frame_sequence;// 获取当前格式if (camera->config.num_frame_descriptors > 0) {memcpy(&frame.format, &camera->config.frame_descriptors[0], sizeof(uvc_frame_desc_t));}// 调用回调函数if (camera->frame_callback) {camera->frame_callback(&frame, camera->user_ptr);}}// 重新提交传输if (camera->is_streaming) {libusb_submit_transfer(transfer);} else {libusb_free_transfer(transfer);}
}/* 发送控制请求 */
static uvc_error_t uvc_send_control_request(uvc_camera_t *camera,uint8_t request,uint8_t unit,uint8_t selector,uint8_t control_interface,void *data,uint16_t length) {uint8_t setup_packet[8];setup_packet[0] = 0x21;  // Type: Class | Interface | Host to Devicesetup_packet[1] = request;setup_packet[2] = selector;setup_packet[3] = unit;setup_packet[4] = control_interface;setup_packet[5] = 0x00;setup_packet[6] = length & 0xFF;setup_packet[7] = (length >> 8) & 0xFF;int transferred;int ret = libusb_control_transfer(camera->dev_handle,setup_packet[0],setup_packet[1],(setup_packet[3] << 8) | setup_packet[2],(setup_packet[5] << 8) | setup_packet[4],(unsigned char *)data,length,1000);  // 1秒超时return ret < 0 ? ret : UVC_SUCCESS;
}/* 相机控制函数 */
uvc_error_t uvc_set_brightness(uvc_camera_t *camera, uint8_t brightness) {if (!camera) return UVC_ERROR_INVALID_PARAM;uint8_t data = brightness;camera->brightness = brightness;return uvc_send_control_request(camera, UVC_SET_CUR, 0x02, UVC_PU_BRIGHTNESS_CONTROL,camera->config.interface_number,&data, 1);
}uvc_error_t uvc_set_contrast(uvc_camera_t *camera, uint8_t contrast) {if (!camera) return UVC_ERROR_INVALID_PARAM;uint8_t data = contrast;camera->contrast = contrast;return uvc_send_control_request(camera, UVC_SET_CUR, 0x02,UVC_PU_CONTRAST_CONTROL,camera->config.interface_number,&data, 1);
}uvc_error_t uvc_set_saturation(uvc_camera_t *camera, uint8_t saturation) {if (!camera) return UVC_ERROR_INVALID_PARAM;uint8_t data = saturation;camera->saturation = saturation;return uvc_send_control_request(camera, UVC_SET_CUR, 0x02,UVC_PU_SATURATION_CONTROL,camera->config.interface_number,&data, 1);
}uvc_error_t uvc_set_sharpness(uvc_camera_t *camera, uint8_t sharpness) {if (!camera) return UVC_ERROR_INVALID_PARAM;uint8_t data = sharpness;camera->sharpness = sharpness;return uvc_send_control_request(camera, UVC_SET_CUR, 0x02,UVC_PU_SHARPNESS_CONTROL,camera->config.interface_number,&data, 1);
}uvc_error_t uvc_set_exposure_auto(uvc_camera_t *camera, bool enable) {if (!camera) return UVC_ERROR_INVALID_PARAM;uint8_t data = enable ? 0x08 : 0x01;  // Auto mode or Manual modecamera->auto_exposure = enable;return uvc_send_control_request(camera, UVC_SET_CUR, 0x01,UVC_CT_AE_MODE_CONTROL,camera->config.interface_number,&data, 1);
}uvc_error_t uvc_set_exposure_time(uvc_camera_t *camera, uint16_t time_ms) {if (!camera) return UVC_ERROR_INVALID_PARAM;uint32_t exposure_time = time_ms * 1000;  // 转换为微秒uint8_t data[4];data[0] = exposure_time & 0xFF;data[1] = (exposure_time >> 8) & 0xFF;data[2] = (exposure_time >> 16) & 0xFF;data[3] = (exposure_time >> 24) & 0xFF;camera->exposure_time = time_ms;return uvc_send_control_request(camera, UVC_SET_CUR, 0x01,UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL,camera->config.interface_number,data, 4);
}/* 错误处理 */
const char *uvc_strerror(uvc_error_t error) {switch (error) {case UVC_SUCCESS: return "Success";case UVC_ERROR_IO: return "Input/output error";case UVC_ERROR_INVALID_PARAM: return "Invalid parameter";case UVC_ERROR_ACCESS: return "Access denied";case UVC_ERROR_NO_DEVICE: return "No such device";case UVC_ERROR_NOT_FOUND: return "Entity not found";case UVC_ERROR_BUSY: return "Resource busy";case UVC_ERROR_TIMEOUT: return "Operation timed out";case UVC_ERROR_OVERFLOW: return "Overflow";case UVC_ERROR_PIPE: return "Pipe error";case UVC_ERROR_INTERRUPTED: return "System call interrupted";case UVC_ERROR_NO_MEM: return "Insufficient memory";case UVC_ERROR_NOT_SUPPORTED: return "Operation not supported";case UVC_ERROR_INVALID_DEVICE: return "Invalid device";case UVC_ERROR_INIT_FAILED: return "Initialization failed";default: return "Unknown error";}
}/* 打印设备信息 */
void uvc_print_device_info(const uvc_device_info_t *device) {if (!device) return;printf("UVC Device Information:\n");printf("  Vendor ID: 0x%04X\n", device->vendor_id);printf("  Product ID: 0x%04X\n", device->product_id);printf("  Manufacturer: %s\n", device->manufacturer);printf("  Product: %s\n", device->product);printf("  Serial Number: %s\n", device->serial_number);printf("  Bus Number: %d\n", device->bus_number);printf("  Device Address: %d\n", device->device_address);
}/* 打印帧信息 */
void uvc_print_frame_info(const uvc_frame_t *frame) {if (!frame) return;printf("Frame Information:\n");printf("  Size: %zu bytes\n", frame->length);printf("  Timestamp: %lu\n", frame->timestamp);printf("  Sequence: %u\n", frame->sequence);printf("  Format: %s\n", frame->format.format_type == UVC_FORMAT_UNCOMPRESSED ? "Uncompressed" :frame->format.format_type == UVC_FORMAT_MJPEG ? "MJPEG" : "Unknown");printf("  Resolution: %dx%d\n", frame->format.width, frame->format.height);printf("  Bits per pixel: %d\n", frame->format.bits_per_pixel);
}

2.3 示例程序 example.c

/*** UVC Camera Library Example* 演示如何使用UVC相机库*/#include "uvc_camera.h"
#include <signal.h>
#include <sys/time.h>volatile bool running = true;void signal_handler(int sig) {if (sig == SIGINT || sig == SIGTERM) {running = false;}
}// 帧回调函数
void frame_callback(uvc_frame_t *frame, void *user_ptr) {static int frame_count = 0;static struct timeval last_time;if (frame_count == 0) {gettimeofday(&last_time, NULL);}frame_count++;// 每秒钟打印一次统计信息struct timeval current_time;gettimeofday(&current_time, NULL);long elapsed = (current_time.tv_sec - last_time.tv_sec) * 1000 +(current_time.tv_usec - last_time.tv_usec) / 1000;if (elapsed >= 1000) {printf("FPS: %d, Frame size: %zu bytes\n", frame_count, frame->length);frame_count = 0;last_time = current_time;}// 这里可以添加图像处理代码// 例如:保存为文件、显示图像等
}int main(int argc, char *argv[]) {uvc_camera_t *camera = NULL;uvc_device_info_t *devices = NULL;int device_count = 0;uvc_error_t ret;// 设置信号处理signal(SIGINT, signal_handler);signal(SIGTERM, signal_handler);printf("UVC Camera Library Example\n");printf("Version: %d.%d.%d\n\n", UVC_CAMERA_VERSION_MAJOR,UVC_CAMERA_VERSION_MINOR,UVC_CAMERA_VERSION_PATCH);// 初始化库ret = uvc_init(&camera);if (ret != UVC_SUCCESS) {fprintf(stderr, "Failed to initialize UVC library: %s\n", uvc_strerror(ret));return 1;}// 查找设备ret = uvc_find_devices(camera, &devices, &device_count);if (ret != UVC_SUCCESS) {fprintf(stderr, "Failed to find devices: %s\n", uvc_strerror(ret));uvc_exit(camera);return 1;}if (device_count == 0) {printf("No UVC devices found.\n");uvc_exit(camera);return 0;}printf("Found %d UVC device(s):\n", device_count);for (int i = 0; i < device_count; i++) {printf("\nDevice %d:\n", i);uvc_print_device_info(&devices[i]);}// 打开第一个设备ret = uvc_open_device(camera, &devices[0]);if (ret != UVC_SUCCESS) {fprintf(stderr, "Failed to open device: %s\n", uvc_strerror(ret));free(devices);uvc_exit(camera);return 1;}printf("\nOpened device successfully.\n");// 获取支持的格式uvc_frame_desc_t *formats = NULL;int format_count = 0;ret = uvc_get_formats(camera, &formats, &format_count);if (ret == UVC_SUCCESS && formats) {printf("\nSupported formats:\n");for (int i = 0; i < format_count; i++) {printf("  Format %d: %dx%d, %d bpp, %s\n",i,formats[i].width,formats[i].height,formats[i].bits_per_pixel,formats[i].format_type == UVC_FORMAT_UNCOMPRESSED ? "Uncompressed" :formats[i].format_type == UVC_FORMAT_MJPEG ? "MJPEG" : "Unknown");}// 设置第一个格式if (format_count > 0) {ret = uvc_set_format(camera, &formats[0]);if (ret != UVC_SUCCESS) {fprintf(stderr, "Failed to set format: %s\n", uvc_strerror(ret));} else {printf("\nSet format to %dx%d\n", formats[0].width, formats[0].height);}}free(formats);}// 设置相机参数uvc_set_brightness(camera, 150);uvc_set_contrast(camera, 130);uvc_set_saturation(camera, 120);uvc_set_sharpness(camera, 140);uvc_set_exposure_auto(camera, true);printf("\nStarting video stream...\n");printf("Press Ctrl+C to stop.\n\n");// 开始视频流ret = uvc_start_streaming(camera, frame_callback, NULL);if (ret != UVC_SUCCESS) {fprintf(stderr, "Failed to start streaming: %s\n", uvc_strerror(ret));uvc_close_device(camera);free(devices);uvc_exit(camera);return 1;}// 主循环while (running) {sleep(1);}printf("\nStopping video stream...\n");// 停止视频流uvc_stop_streaming(camera);// 清理资源uvc_close_device(camera);free(devices);uvc_exit(camera);printf("Program terminated.\n");return 0;
}

2.4 Makefile

# UVC Camera Library MakefileCC = gcc
CFLAGS = -Wall -Wextra -O2 -fPIC -std=c11
LIBS = -lusb-1.0 -lpthread -lm
INCLUDES = -I/usr/include/libusb-1.0# 目标
TARGET_LIB = libuvccamera.so
TARGET_EXAMPLE = uvc_example# 源文件
LIB_SRCS = uvc_camera.c
EXAMPLE_SRCS = example.c# 对象文件
LIB_OBJS = $(LIB_SRCS:.c=.o)
EXAMPLE_OBJS = $(EXAMPLE_SRCS:.c=.o)all: $(TARGET_LIB) $(TARGET_EXAMPLE)# 编译库
$(TARGET_LIB): $(LIB_OBJS)$(CC) -shared -o $@ $^ $(LIBS)# 编译示例
$(TARGET_EXAMPLE): $(EXAMPLE_OBJS) $(TARGET_LIB)$(CC) -o $@ $(EXAMPLE_OBJS) -L. -luvccamera $(LIBS)# 编译规则
%.o: %.c$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@clean:rm -f $(LIB_OBJS) $(EXAMPLE_OBJS) $(TARGET_LIB) $(TARGET_EXAMPLE)install: $(TARGET_LIB)cp $(TARGET_LIB) /usr/local/lib/cp uvc_camera.h /usr/local/include/ldconfiguninstall:rm -f /usr/local/lib/$(TARGET_LIB)rm -f /usr/local/include/uvc_camera.hldconfig.PHONY: all clean install uninstall

三、使用说明

3.1 编译和安装

# 安装依赖
sudo apt-get install libusb-1.0-0-dev# 编译
make# 安装库
sudo make install# 运行示例
./uvc_example

3.2 权限设置

为了在没有root权限的情况下访问USB设备,需要创建udev规则:

# 创建udev规则文件
sudo nano /etc/udev/rules.d/99-uvc-camera.rules# 添加以下内容(替换为你的设备ID)
SUBSYSTEM=="usb", ATTR{idVendor}=="046d", ATTR{idProduct}=="0825", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="04f2", ATTR{idProduct}=="b531", MODE="0666"# 重新加载udev规则
sudo udevadm control --reload-rules
sudo udevadm trigger

3.3 API使用示例

// 1. 初始化库
uvc_camera_t *camera;
uvc_init(&camera);// 2. 查找设备
uvc_device_info_t *devices;
int count;
uvc_find_devices(camera, &devices, &count);// 3. 打开设备
uvc_open_device(camera, &devices[0]);// 4. 设置格式
uvc_frame_desc_t format = {.width = 640,.height = 480,.format_type = UVC_FORMAT_MJPEG
};
uvc_set_format(camera, &format);// 5. 设置相机参数
uvc_set_brightness(camera, 150);
uvc_set_exposure_auto(camera, true);// 6. 开始流传输
uvc_start_streaming(camera, frame_callback, NULL);// 7. 处理帧数据(在回调函数中)
void frame_callback(uvc_frame_t *frame, void *user_ptr) {// 处理帧数据// frame->data 包含原始图像数据// frame->length 是数据长度
}// 8. 停止流传输
uvc_stop_streaming(camera);// 9. 清理资源
uvc_exit(camera);

参考代码 基于libusb用户空间uvc camera库 www.youwenfan.com/contentcnu/60808.html

四、扩展功能建议

4.1 图像格式转换

// 添加图像格式转换功能
uvc_error_t uvc_convert_format(uvc_frame_t *src, uvc_frame_t *dst,uvc_format_type_t dst_format);// 支持转换:
// - MJPEG → RGB24
// - YUV422 → RGB24
// - Bayer → RGB24

4.2 硬件加速

// 使用GPU进行图像处理
#ifdef USE_CUDA
uvc_error_t uvc_process_frame_gpu(uvc_frame_t *frame);
#endif// 使用NEON指令集(ARM)
#ifdef USE_NEON
uvc_error_t uvc_process_frame_neon(uvc_frame_t *frame);
#endif

4.3 多相机同步

// 多相机同步采集
typedef struct {uvc_camera_t **cameras;int num_cameras;pthread_mutex_t sync_mutex;pthread_cond_t sync_cond;int ready_count;
} uvc_sync_group_t;uvc_error_t uvc_sync_start_streaming(uvc_sync_group_t *group);
uvc_error_t uvc_sync_stop_streaming(uvc_sync_group_t *group);

4.4 录像功能

// 录像到文件
typedef struct {FILE *file;uint32_t frame_count;uint32_t fps;uint16_t width;uint16_t height;
} uvc_recorder_t;uvc_error_t uvc_start_recording(uvc_camera_t *camera, const char *filename);
uvc_error_t uvc_stop_recording(uvc_camera_t *camera);

五、调试和测试

5.1 启用调试输出

// 在初始化时设置调试级别
libusb_set_debug(camera->ctx, LIBUSB_LOG_LEVEL_DEBUG);// 添加自定义日志函数
void uvc_log(const char *format, ...) {va_list args;va_start(args, format);vfprintf(stderr, format, args);va_end(args);
}

5.2 USB分析工具

# 查看USB设备
lsusb -v | grep -A 10 "Video"# 监控USB流量
sudo usbmon -f 1  # 监控总线1# 使用Wireshark抓包
sudo wireshark -i usbmon1

六、注意事项

  1. 权限问题:确保用户有访问USB设备的权限
  2. 带宽限制:USB带宽有限,高分辨率需要足够的带宽
  3. 缓冲区管理:合理设置缓冲区大小,避免丢帧
  4. 线程安全:回调函数可能在不同的线程中执行
  5. 错误处理:始终检查返回值,处理错误情况
  6. 内存泄漏:确保所有分配的内存都被正确释放
http://www.jsqmd.com/news/757810/

相关文章:

  • 告别手动压枪:3个阶段掌握绝地求生罗技鼠标宏精准射击
  • VideoDownloadHelper技术指南:浏览器视频下载插件的深度解析
  • 3分钟快速上手:用Stream-Translator让你的外语直播秒变中文!
  • 生成引擎优化(GEO)赋能内容创作效率及用户体验提升的实践案例分析
  • m4s-converter终极指南:快速免费保存B站视频的完整教程
  • 泡泡玛特走向世界,王宁的底气到底来自哪里? - 速递信息
  • 2026最新指导意见:大语言模型如何影响学术论文?
  • 暗黑破坏神2存档修改终极指南:5分钟学会使用免费Web编辑器
  • Segment Anything Model资源全解析:从零样本分割到工程落地实战
  • SEB虚拟机检测破解方案:技术原理与实战应用框架
  • Linux 7.0内核新特性解析:AI编码辅助与Rust稳定支持
  • 学术研究项目中利用多模型API进行对比实验的实践
  • 独立开发者如何借助Taotoken低成本试验不同大模型能力
  • 告别Debug.Log:在Unity中为MySQL操作设计一个可视化管理面板
  • 告别Anchor Box!用PyTorch从零复现FCOS目标检测模型(附完整代码与训练技巧)
  • 轻松解密RPG Maker游戏加密资源:网页版工具的完整使用指南
  • 开源技能库构建指南:从个人工具箱到团队知识中枢
  • 告别PS!用AI魔法一键去除背景的终极指南
  • GroundingDINO终极指南:零代码实现文本驱动的智能目标检测
  • 钉钉机器人报错40035?别慌,手把手教你排查‘缺少参数json’的5种常见原因
  • 3步终极解决方案:Visual C++ Redistributable AIO 完全指南
  • 别再死磕PLL理论了!手把手教你用CML锁存器设计一个10GHz+的高速分频器(附仿真文件)
  • 洛谷P3846+P4195 BSGS及扩展BSGS模板题
  • 别再为选线发愁了!手把手教你用MATLAB/Simulink仿真小电流接地故障(附Coiflet4小波分析代码)
  • Autovisor:智慧树网课自动化学习的终极解决方案
  • 精简数据管道:如何使用 PySpark 和 WhyLogs 进行高效的数据分析和验证
  • UAV Log Viewer:一站式无人机日志分析与可视化专业工具
  • 4大核心技术突破:DXVK Vulkan转换层的高效优化实战指南
  • 收藏!小白程序员转行AI必看:核心岗位、薪资与进阶指南
  • 从无人机航拍到古迹数字化:聊聊SFM技术在实际项目中的踩坑与优化