一、项目概述
基于libusb的用户空间UVC(USB Video Class)相机库,提供完整的相机控制、视频流捕获和图像处理功能。
二、实现代码
2.1 头文件 uvc_camera.h
c
/**
* 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
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 Interface
desc.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 Device
setup_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 mode
camera->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
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(¤t_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
makefile
# UVC Camera Library Makefile
CC = 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/
ldconfig
uninstall:
rm -f /usr/local/lib/$(TARGET_LIB)
rm -f /usr/local/include/uvc_camera.h
ldconfig
.PHONY: all clean install uninstall
三、使用说明
3.1 编译和安装
bash
# 安装依赖
sudo apt-get install libusb-1.0-0-dev
# 编译
make
# 安装库
sudo make install
# 运行示例
./uvc_example
3.2 权限设置
为了在没有root权限的情况下访问USB设备,需要创建udev规则:
bash
# 创建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使用示例
c
// 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/contentcsu/60808.html
四、扩展功能建议
4.1 图像格式转换
c
// 添加图像格式转换功能
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 硬件加速
c
// 使用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 多相机同步
c
// 多相机同步采集
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 录像功能
c
// 录像到文件
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 启用调试输出
c
// 在初始化时设置调试级别
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分析工具
bash
# 查看USB设备
lsusb -v | grep -A 10 "Video"
# 监控USB流量
sudo usbmon -f 1 # 监控总线1
# 使用Wireshark抓包
sudo wireshark -i usbmon1
六、注意事项
- 权限问题:确保用户有访问USB设备的权限
- 带宽限制:USB带宽有限,高分辨率需要足够的带宽
- 缓冲区管理:合理设置缓冲区大小,避免丢帧
- 线程安全:回调函数可能在不同的线程中执行
- 错误处理:始终检查返回值,处理错误情况
- 内存泄漏:确保所有分配的内存都被正确释放