在【2.4 视频监控—从0写USB摄像头驱动(2)-实现数据传输(初步)】中,我们已经完成了USB摄像头驱动的初步框架和初步的数据传输,现在来进行完善,设置urb请求块与亮度属性。
在实际的工作和应用中,尽量使用内核带有的驱动程序,对于没有适配的驱动程序在内核的基础上进行添加,这个usb驱动程序只是学习的时候使用。
若想要获取摄像头的视频数据,则驱动程序与USB摄像头之间需要通过urb结构体作为媒介,进行信息的交互。
USB请求块(USB request block,URB)是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,与网络设备驱动中的sk_buff结构体类似,是USB主机与设备之间传输数据的封装。
传输过程:
该函数主要分为如下三步:
urb_buffer
,存储数据的缓冲区(一个buffer可以存储多个帧数据的信息)urb结构体
,用来指向与描绘该缓冲区urb结构体
,指向与添加该缓冲区的描绘信息修改结构体如下:
/*!
* 存储分配的整块缓冲区
*/
typedef struct mvuvc_video_queue {
void *mem; /**< 存储分配的内存 */
int count; /**< 分配缓冲区个数 */
int buf_size; /**< 每个缓冲区(页对齐)大小 */
MYUVC_BUFFER_S buffer[32]; /**< 存储每个缓冲区的信息 */
struct urb *urb[MYMAX_PACKETS_NUM]; /**< urb_buffer的描述信息 */
char *urb_buffer[MYMAX_PACKETS_NUM]; /**< 存储数据 */
dma_addr_t urb_dma[MYMAX_PACKETS_NUM]; /**< urb_buffer的物理地址 */
unsigned int urb_size; /**< urb_buffer的数量 */
struct list_head mainqueue; /**< mainqueue队列头结点,供APP消费用 */
struct list_head irqqueue; /**< irqqueue队列头结点,供底层驱动生成用*/
}MYUVC_VIDEO_QUEUE_S;
具体实现代码:
/*!
* @brief 分配与初始化URB
* 参考: uvc_init_video_isoc()
* @return 0:成功 -ENOMEM:失败
*/
static int myuvc_alloc_init_urbs(void)
{
u16 psize;
u32 size;
int i, j;
int npackets;
struct urb *urb;
psize = s_wMaxPackSie;
size = s_myuvc_params.dwMaxVideoFrameSize; /**< 一帧数据最大长度 */
npackets = DIV_ROUND_UP(size, psize); /**< 每个urb_buffer的帧数 */
/*!
* urb_buffer中所有帧的大小
*/
size = s_myuvc_queue.urb_size = psize * npackets;
if (npackets > MYMAX_PACKETS_NUM)
npackets = MYMAX_PACKETS_NUM;
/*!
* 分配urb_buffers :存储数据的缓冲区
* 分配urb 结构体:指向urb_buffers
*/
for (i = 0; i < MYUVC_URBS; ++i) {
s_myuvc_queue.urb_buffer[i] = usb_alloc_coherent(
s_myuvc_udev, size, GFP_KERNEL | __GFP_NOWARN,
&s_myuvc_queue.urb_dma[i]);
s_myuvc_queue.urb[i] = usb_alloc_urb(npackets, GFP_KERNEL);
if (!s_myuvc_queue.urb_buffer[i] || !s_myuvc_queue.urb[i]) {
myuvc_uninit_urbs();
return -ENOMEM;
}
}
/*!
* 设置urb
*/
for (i = 0; i < MYUVC_URBS; ++i) {
urb = s_myuvc_queue.urb[i];
urb->dev = s_myuvc_udev;
urb->context = NULL;
urb->pipe = usb_rcvisocpipe(s_myuvc_udev, s_bEndpointAddress);
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
urb->interval = s_bInterval;
urb->transfer_buffer = s_myuvc_queue.urb_buffer[i];
urb->transfer_dma = s_myuvc_queue.urb_dma[i];
urb->complete = myuvc_video_complete;
urb->number_of_packets = npackets;
urb->transfer_buffer_length = size;
/* 每次传输的数据所存放的地址 */
for (j = 0; j < npackets; ++j) {
urb->iso_frame_desc[j].offset = j * psize;
urb->iso_frame_desc[j].length = psize;
}
}
return 0;
}
/*!
* 3. 提交URB以接收数据
*/
for (i = 0; i < UVC_URBS; ++i) {
if ((ret = usb_submit_urb(s_myuvc_queue.urb[i], GFP_KERNEL)) < 0) {
printk("Failed to submit URB %u (%d).\n", i, ret);
myuvc_uninit_urbs();
return ret;
}
}
对于一个USB摄像头,若想要获得/设置亮度属性(其他控制属性也通用),需要进行如下步骤:
.vidioc_reqbufs
查询设备是否支持该属性,是则获得该属性的相关值(最大值、最小值、步长值、默认值).vidioc_g_ctrl
获取设备属性的当前值.vidioc_s_ctrl
设置设备的属性的当前值那么对于驱动程序,如何分辨这些“属性”呢?,以亮度属性为例子
查询手册,查看亮度属性所在VideoControl Interface的哪个单元
对于亮度属性,在PU单元中进行设置。在PU单元的bmControls
中,每一位代表一个控制项,亮度Brightness
属于第0位。
在驱动程序中如何表示这些信息?
在uvc_ctrl.c
中的static struct uvc_control_info uvc_ctrls[]
结构体数组中:对于每一个VideoControl Interface
的单元,表示为一个entity
实体,在uvc_ctrls
结构体中,会对该entity
实体的中的控制属性selector
进行相关描述。
ctrl
usb_control_msg()
,发送控制信息获取该属性的相关值并对数据进行解析。/*!
* @brief 发起USB控制传输获得亮度的最小值、最大值、默认值、阶梯值
* 参考:uvc_query_v4l2_ctrl()
*/
static int myuvc_vidioc_queryctrl(struct file * file,
void * fh, struct v4l2_queryctrl * ctrl)
{
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret;
u8 data[2];
/* 只支持调节亮度 */
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
/*!
* 内存空间清零设置
*/
memset(ctrl, 0, sizeof *ctrl);
strcpy(ctrl->name, "MYUVC_BRIGHTNESS");
ctrl->id = V4L2_CID_BRIGHTNESS;
ctrl->type = V4L2_CTRL_TYPE_INTEGER;
ctrl->flags = 0;
/*!
* 从端口中接收数据,设置数据类型为输入
*/
pipe = usb_rcvctrlpipe(s_myuvc_udev, 0);
type = USB_DIR_IN;
/*!
* 发送数据到USB摄像头的VideoControl Interface的PU,获取最小亮度
*/
ret = usb_control_msg(s_myuvc_udev, pipe, GET_MIN, type, PU_BRIGHTNESS_CONTROL << 8,
s_PU_ID << 8 | s_myuvc_control_intf,
data, 2, MYUVC_CTRL_CONTROL_TIMEOUT);
if (ret != 2)
return -EIO;
ctrl->minimum = myuvc_get_le_value(data); /**< 解析获取的数值 */
/*!
* 获取最大亮度并解析
*/
ret = usb_control_msg(s_myuvc_udev, pipe, GET_MAX, type, PU_BRIGHTNESS_CONTROL << 8,
s_PU_ID << 8 | s_myuvc_control_intf, data, 2, MYUVC_CTRL_CONTROL_TIMEOUT);
if (ret != 2)
return -EIO;
ctrl->maximum = myuvc_get_le_value(data);
/*!
* 获取亮度调节阶梯值并解析
*/
ret = usb_control_msg(s_myuvc_udev, pipe, GET_RES, type, PU_BRIGHTNESS_CONTROL << 8,
s_PU_ID << 8 | s_myuvc_control_intf, data, 2, MYUVC_CTRL_CONTROL_TIMEOUT);
if (ret != 2)
return -EIO;
ctrl->step = myuvc_get_le_value(data);
/*!
* 获取默认亮度并解析
*/
ret = usb_control_msg(s_myuvc_udev, pipe, GET_DEF, type, PU_BRIGHTNESS_CONTROL << 8,
s_PU_ID << 8 | s_myuvc_control_intf, data, 2, MYUVC_CTRL_CONTROL_TIMEOUT);
if (ret != 2)
return -EIO;
ctrl->default_value = myuvc_get_le_value(data);
printk("Brightness: min =%d, max = %d, step = %d, default = %d\n",
ctrl->minimum, ctrl->maximum, ctrl->step, ctrl->default_value);
return 0;
}
usb_control_msg()
,启动USB传输,获取当前亮度并解析。/*!
* @brief 获取当前指定的控制信息:把USB传入的亮度值通过USB传输发送给硬件
* 参考:uvc_ctrl_get()
*/
static int myuvc_vidioc_g_ctrl(struct file * file,
void * fh, struct v4l2_control * ctrl)
{
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret = 0;
u8 data[2];
/* 只支持调节亮度 */
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
/*!
* 从端口中接收数据,设置数据类型为输入
*/
pipe = usb_rcvctrlpipe(s_myuvc_udev, 0);
type = USB_DIR_IN;
/*!
* 启动USB传输,获取当前亮度并解析
*/
ret = usb_control_msg(s_myuvc_udev, pipe, GET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
s_PU_ID << 8 | s_myuvc_control_intf, data, 2, MYUVC_CTRL_CONTROL_TIMEOUT);
if (ret != 2)
return -EIO;
ctrl->value = myuvc_get_le_value(data); /**< 解析获取的数值 */
return ret;
}
usb_control_msg()
,启动USB传输,设置当前亮度。/*!
* @brief 设置当前指定的控制信息:发起USB传输获取当前亮度值
* 参考:uvc_ctrl_set()/uvc_ctrl_commit()
*/
static int myuvc_vidioc_s_ctrl(struct file * file,
void * fh, struct v4l2_control * ctrl)
{
u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret = 0;
u8 data[2];
/* 只支持调节亮度 */
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
/*!
* 设置控制信息的数据为指定位数
*/
myuvc_set_le_value(ctrl->value, data);
/*!
* 发送数据到端口,设置数据类型为输出
*/
pipe = usb_sndctrlpipe(s_myuvc_udev, 0);
type = USB_DIR_OUT;
/*!
* 启动USB传输,设置亮度
*/
ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
s_PU_ID << 8 | s_myuvc_control_intf, data, 2, MYUVC_CTRL_CONTROL_TIMEOUT);
if (ret != 2)
return -EIO;
return ret;
}
对于此数据处理过程,有不完善的地方,在下篇博文中进行修改
数据处理过程:
1、从irqqueue队列中取出第一个空缓冲区
1.1 存储urb_buffer中的多帧数据到这缓冲区中
1.1.1 设置数据源、数据目的地、数据长度
1.1.2 判断每一帧的头部长度与错误信息
1.1.3 把数据源的数据去除头部信息,按指定大小存入缓冲区中
1.1.4 判断一帧的数据大小是否大于该缓冲区的可使用的大小,是则修改缓冲区状态位,强制传输完毕
1.1.5 判断一帧的数据是否传输完毕,是则修改缓冲区状态位,传输完毕
1.2 判断缓冲区是否接收完所有帧数据,是则从irqqueue队列中删除该缓冲区,并唤醒等待进程
2、再次提交urb
代码实现:
/*!
* @brief 每次传输完数据,从irqqueu队列中取出第一空的缓冲区,把urb_buffer中的数据存到缓冲区中
* 后再次提交URB。
* 参考:uvc_video_complete()
*/
static void myuvc_video_complete(struct urb *urb)
{
u8 *src;
u8 *dest;
int i;
int ret;
int len;
int nbytes; /**< 缓冲区中存储数据的最小字节数 */
int maxlen; /**< 缓冲区中实际可以存储的最大数据大小 */
MYUVC_BUFFER_S *buf;
switch (urb->status) {
case 0:
break;
default:
printk("Non-zero status (%d) in video "
"completion handler.\n", urb->status);
return ;
}
/* 从irqqueu队列中取出第一个空缓冲区 */
if (!list_empty(&s_myuvc_queue.irqqueue)) {
buf = list_first_entry(&s_myuvc_queue.irqqueue, MYUVC_BUFFER_S, irq);
/*!
* 每个ueb_buffer有多个帧(frame),有
* 把符合要求的urb_buffer中的所有帧的数据存储到空缓冲区中
*/
for (i = 0; i < urb->number_of_packets; ++i) {
if (urb->iso_frame_desc[i].status < 0) {
printk("USB isochronous frame "
"lost (%d).\n", urb->iso_frame_desc[i].status);
continue;
}
/* 数据源(urb_buffer的一帧) */
src = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
/* 数据最终去向(空的缓冲区的可存储数据)的地址 */
dest = s_myuvc_queue.mem + buf->buf.m.offset + buf->buf.bytesused;
/* 获取urb_buffer(一帧)的实际可使用长度 */
len = urb->iso_frame_desc[i].actual_length;
/*!
* src[0]:头部长度
* 根据头部信息判断该帧是否符合要求
*/
if (len < 2 || src[0] < 2 || src[0] > len)
continue;
/*!
* src[1]:错误状态
* 跳过有错误标记的数据包(帧)
*/
if (src[1] & UVC_STREAM_ERR) {
printk("Dropping payload (error bit set).\n");
continue;
}
len -= src[0]; /**< 除去头部长度后的urb_buffer(一帧)数据长度 */
maxlen = buf->buf.length - buf->buf.bytesused;
nbytes = min(len, maxlen);
/*!
* 把urb_buffer(一帧)中的数据(除去头部信息)
* 按照最小存储字节数nbytes存入 到 取出的空缓冲区中
*/
memcpy(dest, src + src[0], nbytes);
buf->buf.bytesused += nbytes; /**< 记录已使用的空间 */
/*!
* 判断数据是否超过最大容量
*/
if (len > maxlen) {
printk("Frame complete (overflow). \n");
buf->state = VIDEOBUF_DONE;
}
/*!
* 判断缓冲区是否接收完一帧数据
* 如果urb_buffer(一帧)设置了EOF标记 且 缓冲区已接收到数据
* 则将缓冲区状态标记为已完成
*/
if (src[1] & UVC_STREAM_EOF && buf->buf.bytesused != 0) {
printk("Frame complete (EOF found).\n");
/* 实际urb_buffer(一帧)数据为空 */
if (len == 0)
printk("EOF in empty payload.\n");
/* 修改缓冲区的状态位 */
buf->state = VIDEOBUF_DONE;
}
}
/*!
* 判断缓冲区是否接收完所有数据
* 是,则唤醒等待数据的进程
* 并从irqqueue中删除缓冲区
*/
if (buf->state == UVC_BUF_STATE_DONE ||
buf->state == UVC_BUF_STATE_ERROR) {
list_del(&buf->irq);
wake_up(&buf->wait);
}
}
/*!
* 再次提交urb
*/
if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
printk("Failed to resubmit video URB (%d).\n", ret);
}
}