Our primary video output resolution is 640x240 combined with two 320x240 resized image from both cameras.
the format is yuv422 / 8 bits,so the frame size will be 640 * 240 * 8 * 4 = 4.6875Mbits per frame.
Under USB2.0 high speed, we hope that the frame rate reach to 15 fps if no other device occupies the USB bandwidth.
Besides, we do not very care about the latency because the uvc camera is just for calibration and simulation with bigger latency tolerance.
Current webcam gadget driver bind to the V4L2 driver after module insertion. The V4L2 driver layer provides some APIs for UVC related operation for user space. Once USB UVC interrupt appears, the V4L2 driver will generate the related event, then if you subscribe the event, the application will be noticed to do some responses.
Following steps shows how to handle the UVC event.
- Open device special file /dev/video0
- Subscribe uvc event by calling API “VIDIOC_SUBSCRIBE_EVENT”
- Use select( ) to detect event
- Get SETUP(8 bytes) packet by calling API “VIDIOC_DQEVENT”
- Parse the SETUP packet, if its GET request, calling API “UVCIOC_SEND_RESPONSE” to response it. (The SET request is fail and under investigation now.)
our testing code shows below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/* getopt_long() */
#include <getopt.h>
/* low-level i/o */
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <linux/videodev2.h>
#include "uvc.h"
#include <stdio.h>
#include <pthread.h>
//#include "uvc_def.h"
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
#define UVC_OUTPUT "/dev/video0"
#define CLEAR(x) memset(&(x), 0, sizeof(x))
enum io_method {
IO_METHOD_READ,
IO_METHOD_MMAP,
IO_METHOD_USERPTR,
};
struct buffer {
void *start;
size_t length;
};
static char *dev_name = (char*)UVC_OUTPUT;;
static enum io_method io = IO_METHOD_MMAP;
static int fd = -1;
struct buffer *buffers;
static unsigned int n_buffers;
static int out_buf;
static int force_format;
static void errno_exit(const char *s)
{
fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
exit(EXIT_FAILURE);
}
static void close_device(void)
{
if (-1 == close(fd))
errno_exit("close");
fd = -1;
}
int isready(int fd)
{
int rc;
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(fd, &fds);
tv.tv_sec = 0;
tv.tv_usec = 100;
//printf("select!fd = %d\n", fd);
//if (select (fd +1, NULL, NULL, &fds, &tv) < 0)
if (select (fd +1, NULL, NULL,&fds , &tv) < 0)
return -1;
//printf("select end!fd = %d\n", fd);
return FD_ISSET(fd, &fds)?1:0;
}
int iclass_specific_get_inft_request(uvc_event* req_data)
{
usb_ctrlrequest req;
if (NULL == req_data)
return -1;
memcpy (&req, &req_data->req, sizeof(usb_ctrlrequest));
req_data->data.length = req.wLength;
switch (req.bRequest)
{
case GET_CUR: // TODO
break;
case GET_MIN: // TODO
break;
case GET_MAX: // TODO
break;
case GET_RES: // TODO
break;
case GET_LEN: // TODO
break;
case GET_INFO:
if (req.wValue == 0x0400)
{
req_data->data.data[0] = 0x03;
}
else if(req.wValue == 0x0900)
{
req_data->data.data[0] = 0x03;
}
else if(req.wValue == 0x0200)
{
req_data->data.data[0] = 0x03;
}
break;
case GET_DEF: // TODO
break;
default:
break;
}
//printf ("req data s = 0x%x\n", req_data->data.data[0] );
return 0;
}
int iclass_specific_request (int fd, usb_ctrlrequest* uvc_req)
{
uvc_event req_data;
v4l2_event V4l2_event;
int iret = 0;
if (fd == -1)
return -1;
if (NULL == uvc_req)
return -1;
memcpy (&req_data.req, uvc_req, sizeof(usb_ctrlrequest));
printf("usb_ctrlreques:0x%02x, 0x%02x, 0x%04x, 0x%04x, 0x%04x\n", req_data.req.bRequestType, req_data.req.bRequest, req_data.req.wValue, req_data.req.wIndex, req_data.req.wLength);
//GET Request
if (req_data.req.bRequestType & 0x80)
{
if ((req_data.req.bRequestType & 0xf) == 0x01) // interface
{
iret = iclass_specific_get_inft_request(&req_data);
}
else if ((req_data.req.bRequestType & 0xf) == 0x10)
{
// endpoint -- TODO
}
// response data to usb host
if (iret >= 0)
{
printf ("data length, = 0x%x, req data = 0x%x\n",req_data.data.length, req_data.data.data[0]);
iret = ioctl (fd, UVCIOC_SEND_RESPONSE, &req_data.data);
}
}
//SET Request -- TODO
else
{
// dequeue event
iret = ioctl (fd, VIDIOC_DQEVENT, &V4l2_event); // test
printf("itet = %d, type:%d, ID=%d\n",iret, V4l2_event.type, V4l2_event.id ); //test
}
return iret;
}
int isubscribe_event (int fd, unsigned int uitype, unsigned int uiID, unsigned int uiflag)
{
v4l2_event_subscription V4l2_sub;
int iret = 0;
if (fd == -1)
return -1;
V4l2_sub.type = uitype;
printf("subcription type = %d\n", V4l2_sub.type );
V4l2_sub.id = uiID;
V4l2_sub.flags = uiflag;
iret = ioctl (fd, VIDIOC_SUBSCRIBE_EVENT, &V4l2_sub);
return iret;
}
static void* uvc_control_thread (void* arg)
{
char* uargv;
v4l2_event V4l2_event;
usb_ctrlrequest uvc_req;
int iret = 0;
printf("create thread\r\n");
// subscribe uvc class request event
iret = isubscribe_event (fd, UVC_EVENT_SETUP, 0, V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK);
//iret = isubscribe_event (fd, UVC_EVENT_DATA, 0, V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK);
// Check event loop
do
{
// check event entering
if (isready(fd) > 0)
{
// dequeue event
iret = ioctl (fd, VIDIOC_DQEVENT, &V4l2_event);
printf("type:%d, ID=%d\n",V4l2_event.type, V4l2_event.id );
memcpy(&uvc_req, V4l2_event.u.data, sizeof(usb_ctrlrequest));
iret = iclass_specific_request (fd, &uvc_req);
}
usleep (100);
}
while( 1);
return uargv;
}
int main(int argc, char **argv)
{
struct stat st;
struct pollfd fds;
int iret = 0;
int i;
pthread_t id;
// Open deive
if (-1 == stat(dev_name, &st))
{
fprintf(stderr, "Cannot identify '%s': %d, %s\n", dev_name, errno, strerror(errno));
exit(EXIT_FAILURE);
}
if (!S_ISCHR(st.st_mode))
{
fprintf(stderr, "%s is no device\n", dev_name);
exit(EXIT_FAILURE);
}
fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
if (-1 == fd)
{
fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_name, errno, strerror(errno));
exit(EXIT_FAILURE);
}
// create thread for uvc control
iret=pthread_create(&id, NULL, &uvc_control_thread, NULL);
if(iret!=0)
{
printf ("Create pthread error!\n");
exit (1);
}
pthread_join(id,NULL);
close_device();
return 0;
}
BTW, We have connected to USB analyzer to observe the USB bus activity at run time.
below link shows analyzer recorded for this issue.
Observing the USB bus by analyzer. Once we call the “VIDIOC_DQEVENT” to get the SETUP “packet”, the ack will be generated from udc PHY to complete the SETUP transition, then the DATA_OUT packet will follow up (but not complete). Unlike the “SETUP” packet, no interrupt happens while the “DATA-OUT” packet is coming.
As a result, maybe we have to add some code in tegra_udc.c to trigger interrupt? Or maybe the interrupt is shared with “SETUP” and we ust need to know how to get the data?