Notes d'étude du projet de surveillance vidéo par caméra USB

L'appel système d'une application de surveillance par caméra est le suivant:

/ * open
 * VIDIOC_QUERYCAP détermine s'il s'agit d'un périphérique de capture vidéo, dont l'interface prend en charge (streaming / lecture, écriture)
 * VIDIOC_ENUM_FMT requête quel format est pris en charge
 * VIDIOC_S_FMT définit le format utilisé par la caméra
 * VIDIOC_REQBUFS tampon d'application
 pour le streaming:
 * VIDIOC_QUERYBUF détermine chacun informations de tampon et mmap
 * VIDIOC_QBUF mis dans la file d'attente
 * VIDIOC_STREAMON démarrer le périphérique
 * interrogation en attente de données
 * VIDIOC_DQBUF sortir de la file d'attente
 * traitement ....
 * VIDIOC_QBUF placé dans la file d'attente
 * ....
 * VIDIOC_STREAMOFF arrêter le périphérique
 *
 * /

 

Cet article est une note d'un projet de surveillance de caméra précédemment réalisé par l'auteur. Le but principal est d'enregistrer le processus d'apprentissage, ce qui est pratique pour un examen ultérieur. Nous combinons l'application et le pilote de la caméra uvc pour analyser les données générées par la caméra uvc du matériel à l'application supérieure Processus par lequel un programme acquiert des données.

 

Un: VIDIOC_QUERYCAP

La fonction VIDIOC_QUERYCAP consiste principalement à définir la variable de structure v4l2_capability tV4l2Cap qui est transmise par APP (transmise par pointeur, les bases impliquées dans cet article sont essentiellement transmises par pointeur. Pour la commodité du récit, dites-le simplement comme variable entrante), puis APP en fonction des paramètres La valeur détermine s'il s'agit d'un périphérique vidéo, d'un périphérique de streaming, etc.

Programme APP:

struct v4l2_capability tV4l2Cap;
memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
    DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
    goto err_exit;
}

if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
    DBG_PRINTF("%s is not a video capture device\n", strDevName);
    goto err_exit;
}

if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
	DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}
    
if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
	DBG_PRINTF("%s supports read i/o\n", strDevName);
}

Pilote:

case VIDIOC_QUERYCAP:        
	{
		struct v4l2_capability *cap = arg;    //获取APP传入的指针

		memset(cap, 0, sizeof *cap);
		strlcpy(cap->driver, "uvcvideo", sizeof cap->driver);
		strlcpy(cap->card, vdev->name, sizeof cap->card);
		usb_make_path(stream->dev->udev,
			      cap->bus_info, sizeof(cap->bus_info));
		cap->version = DRIVER_VERSION_NUMBER;        
		if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)    //如果插入的设备是摄像头
			cap->capabilities = V4L2_CAP_VIDEO_CAPTURE      //设置传入的结构体变量的信息 
					  | V4L2_CAP_STREAMING;
		else
			cap->capabilities = V4L2_CAP_VIDEO_OUTPUT
					  | V4L2_CAP_STREAMING;
		break;
	}

Q: Sur quelle base le pilote sous-jacent définit-il les variables que nous transmettons?

Réponse: descripteur de périphérique.

Q: D'où vient le descripteur d'appareil?

R: Lorsque notre caméra uvc est juste connectée à la carte de développement, le pilote de bus USB génère une structure usb_device et la suspend dans la file d'attente du pilote de bus USB. Cette structure usb_device contient nos informations matérielles, et Ceci est le descripteur.

 

Deux: VIDIOC_ENUM_FMT

La fonction VIDIOC_ENUM_FMT consiste principalement à définir la variable de structure v4l2_fmtdesc tFmtDesc transmise par l'APP, puis l'APP juge si ce format de pixel est pris en charge. S'il le prend en charge, il est affecté au tVideoDevice de l'APP (ptVideoDevice est son pointeur) -> iPixelFormat. .

Programme APP:

        struct v4l2_fmtdesc tFmtDesc;
    memset(&tFmtDesc, 0, sizeof(tFmtDesc));
	tFmtDesc.index = 0;
	tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    //枚举摄像头硬件支持的各种格式,若我们的应用程序支持这种格式则跳出循环
	while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {
        if (isSupportThisFormat(tFmtDesc.pixelformat))//这个函数在下面定义了
        {
            ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;
            break;
        }
		tFmtDesc.index++;//index++再传入驱动即可查看硬件所支持的下一种格式
	}

    if (!ptVideoDevice->iPixelFormat)//无法支持
    {
    	DBG_PRINTF("can not support the format of this device\n");
        goto err_exit;        
    }

Pilote:

	case VIDIOC_ENUM_FMT:
	{
		struct v4l2_fmtdesc *fmt = arg;    //获取APP传入的指针
		struct uvc_format *format;
		enum v4l2_buf_type type = fmt->type;
		__u32 index = fmt->index;

        //检查APP传入的数据
		if (fmt->type != stream->type ||
		    fmt->index >= stream->nformats)    //类型不对或者超过硬件支持的种数了
			return -EINVAL;

		memset(fmt, 0, sizeof(*fmt));
		fmt->index = index;
		fmt->type = type;

        //根据硬件的信息设置APP传入的变量
		format = &stream->format[fmt->index];
		fmt->flags = 0;
		if (format->flags & UVC_FMT_FLAG_COMPRESSED)
			fmt->flags |= V4L2_FMT_FLAG_COMPRESSED;
		strlcpy(fmt->description, format->name,
			sizeof fmt->description);
		fmt->description[sizeof fmt->description - 1] = 0;
		fmt->pixelformat = format->fcc;        //设置pixelformat,如YUYV、RGB、MJPEG等等...
		break;
	}

 

Trois: VIDIOC_S_FMT

Nous obtenons d'abord la résolution LCD et tVideoDevice-> iPixelFormat (tels que YUYV, MJPEG, etc.) dans l'APP, puis les transmettons au pilote via la variable de structure v4l2_format tV4l2Fmt . Les valeurs transmises n'ont pas été réellement définies sur le matériel. Il n'est enregistré que temporairement , et il est réellement envoyé au matériel lors de la diffusion.

Q: Pourquoi devez-vous d'abord obtenir la résolution de l'écran LCD dans l'APP, puis la transmettre au pilote?

R: Notre projet consiste à obtenir les données collectées par la caméra et à les afficher sur l'écran LCD. Afin d'améliorer l'effet d'affichage, nous définissons la résolution de l'image collectée par la caméra sur la même valeur que notre LCD via les paramètres entrants (si la caméra ne prend pas en charge cette résolution , Ensuite, le pilote trouvera une résolution proche de la valeur que nous avons définie).

    int iLcdWidth;
    int iLcdHeigt;
    int iLcdBpp;
    struct v4l2_format  tV4l2Fmt;

    /* set format in */
    GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);//获取LCD分辨率
    memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
    tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;
    tV4l2Fmt.fmt.pix.width       = iLcdWidth;
    tV4l2Fmt.fmt.pix.height      = iLcdHeigt;
    tV4l2Fmt.fmt.pix.field       = V4L2_FIELD_ANY;

    /* 如果驱动程序发现无法某些参数(比如分辨率),
     * 它会调整这些参数, 并且返回给应用程序
     */
    iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt); 
    if (iError) 
    {
    	DBG_PRINTF("Unable to set format\n");
        goto err_exit;        
    }
	/* 读出调整后的参数 */
    ptVideoDevice->iWidth  = tV4l2Fmt.fmt.pix.width;
    ptVideoDevice->iHeight = tV4l2Fmt.fmt.pix.height;

Pilote:

	/* Find the closest image size. The distance between image sizes is
	 * the size in pixels of the non-overlapping regions between the
	 * requested size and the frame-specified size.
	 */
        //找到和我们传入的值最接近的分辨率(fmt是我们在APP里传入的那个变量)
	rw = fmt->fmt.pix.width;
	rh = fmt->fmt.pix.height;
	maxd = (unsigned int)-1;

	for (i = 0; i < format->nframes; ++i) {
		__u16 w = format->frame[i].wWidth;
		__u16 h = format->frame[i].wHeight;

		d = min(w, rw) * min(h, rh);
		d = w*h + rw*rh - 2*d;
		if (d < maxd) {
			maxd = d;
			frame = &format->frame[i];
		}

		if (maxd == 0)
			break;
	}            //此处代码没贴全(因为太长了)
        ...
        ...

        
	fmt->fmt.pix.width = frame->wWidth;
	fmt->fmt.pix.height = frame->wHeight;
	fmt->fmt.pix.field = V4L2_FIELD_NONE;
	fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;
	fmt->fmt.pix.sizeimage = probe->dwMaxVideoFrameSize;
	fmt->fmt.pix.colorspace = format->colorspace;
	fmt->fmt.pix.priv = 0;

 

Quatre: VIDIOC_REQBUFS

VIDIOC_REQBUFS a d' abord défini certaines valeurs dans l'APP, telles que le nombre de tampons à appliquer , puis passé dans le pilote via la variable de structure tV4l2ReqBuffs de type v4l2_requestbuffers, le pilote selon le nombre d'entrées et la troisième étape dwMaxVideoFrameSize (image de taille) Pour demander un tampon , puis définir les informations sur le tampon dans la file d'attente (telles que le nombre et le décalage de chaque tampon), et enfin récupérer le nombre de tampons réellement appliqués à l'APP, nous l'enregistrons dans ptVideoDevice -> iVideoBufCnt (ptVideoDevice-> iVideoBufCnt = tV4l2ReqBuffs.count;)

Programme APP:

    struct v4l2_requestbuffers tV4l2ReqBuffs;
    /* request buffers */
    memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
    tV4l2ReqBuffs.count = NB_BUFFER;
    tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;

    iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
    if (iError) 
    {
    	DBG_PRINTF("Unable to allocate buffers.\n");
        goto err_exit;        
    }
    /* 申请buffer不一定能成功,真正申请到的buffer个数记录在tV4l2ReqBuffs.count */
    ptVideoDevice->iVideoBufCnt = tV4l2ReqBuffs.count;

Chauffeur

struct v4l2_requestbuffers *rb = arg;
ret = uvc_alloc_buffers(&stream->queue, rb->count,stream->ctrl.dwMaxVideoFrameSize);

Une partie du code de uvc_alloc_buffers est la suivante: 

/* Decrement the number of buffers until allocation succeeds. */
//如果分配不成功,则减少要分配的缓冲区个数,然后再尝试一下
	for (; nbuffers > 0; --nbuffers) {
		mem = vmalloc_32(nbuffers * bufsize);
		if (mem != NULL)
			break;
	}
//一个缓冲区都分配不成功,返回错误信息
	if (mem == NULL) {
		ret = -ENOMEM;
		goto done;
	}

//设置队列queue里各个缓冲区的信息,例如偏移量,号数index等等...
	for (i = 0; i < nbuffers; ++i) {
		memset(&queue->buffer[i], 0, sizeof queue->buffer[i]);
		queue->buffer[i].buf.= i;
		queue->buffer[i].buf.m.offset = i * bufsize;
		queue->buffer[i].buf.length = buflength;
		queue->buffer[i].buf.type = queue->type;
		queue->buffer[i].buf.field = V4L2_FIELD_NONE;
		queue->buffer[i].buf.memory = V4L2_MEMORY_MMAP;
		queue->buffer[i].buf.flags = 0;
		init_waitqueue_head(&queue->buffer[i].wait);
	}

	queue->mem = mem;        //真正存放图片数据的地址在这里,队列里面的buffer数组只是记录各个缓冲区的偏移量之类的信息
	queue->count = nbuffers;
	queue->buf_size = bufsize;
	ret = nbuffers;

done:
	mutex_unlock(&queue->mutex);
	return ret;

 

五 、 pour (i = 0; i <ptVideoDevice-> iVideoBufCnt; i ++) {

VIDIOC_QUERYBUF 和 mmap

}

VIDIOC_QUERYBUF et mmap, définissez d'abord certaines valeurs dans l'APP à la variable de structure v4l2_buffer tV4l2Buf, par exemple: tV4l2Buf.index ( indiquant quelles informations de tampon nous voulons interroger (quelles informations? Valeur de décalage et taille de page (image de taille), etc.) Ensuite, transmettez tV4l2Buf au pilote et le pilote copie les informations de tampon correspondant à la file d'attente dans tV4l2Buf conformément au tV4l2Buf.index et retourne à l'APP. L'APP enregistre tV4l2Buf.length dans tVideoDevice-> iVideoBufMaxLen.

Ensuite, appelez mmap pour passer tV4l2Buf.length, tV4l2Buf.m.offset au pilote, et après une comparaison, le pilote trouve le tampon comme mmap ( les tampons appliqués à l'étape 4) et transmet la première adresse du tampon La valeur de retour est affectée à ptVideoDevice-> pucVideBuf [i] , et après une boucle, le tableau ptVideoDevice-> pucVideBuf pointe vers la première adresse de chaque tampon.

Programme APP:

        	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
        	tV4l2Buf.index = i;
        	tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
        	iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
        	if (iError) 
            {
        	    DBG_PRINTF("Unable to query buffer.\n");
        	    goto err_exit;
        	}

            ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;
        	ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,tV4l2Buf.m.offset);
        	if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED) 
            {
        	    DBG_PRINTF("Unable to map buffer\n");
        	    goto err_exit;
        	}

Pilote:

int uvc_query_buffer(struct uvc_video_queue *queue,
		struct v4l2_buffer *v4l2_buf)
{
	int ret = 0;

	mutex_lock(&queue->mutex);
	if (v4l2_buf->index >= queue->count) {
		ret = -EINVAL;
		goto done;
	}

        //根据传入的v4l2_buf->index决定要查询哪个缓冲区的信息
	__uvc_query_buffer(&queue->buffer[v4l2_buf->index], v4l2_buf);//函数定义在下面

done:
	mutex_unlock(&queue->mutex);
	return ret;
}
static void __uvc_query_buffer(struct uvc_buffer *buf,
		struct v4l2_buffer *v4l2_buf)
{
        //把内核里面缓冲区的信息拷贝到APP传进来的地址
	memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf);

	if (buf->vma_use_count)
		v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;

        //设置flag
	switch (buf->state) {
	case UVC_BUF_STATE_ERROR:
	case UVC_BUF_STATE_DONE:
		v4l2_buf->flags |= V4L2_BUF_FLAG_DONE;
		break;
	case UVC_BUF_STATE_QUEUED:
	case UVC_BUF_STATE_ACTIVE:
	case UVC_BUF_STATE_READY:
		v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
		break;
	case UVC_BUF_STATE_IDLE:
	default:
		break;
	}
}

/*
 * Memory-map a video buffer.
 *
 * This function implements video buffers memory mapping and is intended to be
 * used by the device mmap handler.
 */
int uvc_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
{
	struct uvc_buffer *uninitialized_var(buffer);
	struct page *page;
	unsigned long addr, start, size;
	unsigned int i;
	int ret = 0;

	start = vma->vm_start;
	size = vma->vm_end - vma->vm_start;

	mutex_lock(&queue->mutex);

        
    /* 应用程序调用mmap函数时, 会传入offset参数
     * 根据这个offset找出指定的缓冲区
     */
	for (i = 0; i < queue->count; ++i) {
		buffer = &queue->buffer[i];
		if ((buffer->buf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)//把内核里的缓冲区信息和传进来的参数进行某种对比,如果符合则证明就是要mmap这个缓冲区
			break;
	}

	if (i == queue->count || PAGE_ALIGN(size) != queue->buf_size) {
		ret = -EINVAL;
		goto done;
	}

	/*
	 * VM_IO marks the area as being an mmaped region for I/O to a
	 * device. It also prevents the region from being core dumped.
	 */
	vma->vm_flags |= VM_IO;

    /* 根据虚拟地址找到缓冲区对应的page构体 */
	addr = (unsigned long)queue->mem + buffer->buf.m.offset;
#ifdef CONFIG_MMU
	while (size > 0) {
		page = vmalloc_to_page((void *)addr);

        /* 把page和APP传入的虚拟地址挂构 */
		if ((ret = vm_insert_page(vma, start, page)) < 0)
			goto done;

		start += PAGE_SIZE;
		addr += PAGE_SIZE;
		size -= PAGE_SIZE;
	}
#endif

	vma->vm_ops = &uvc_vm_ops;
	vma->vm_private_data = buffer;
	uvc_vm_open(vma);

done:
	mutex_unlock(&queue->mutex);
	return ret;
}

 

六 、 pour (i = 0; i <ptVideoDevice-> iVideoBufCnt; i ++) {

VIDIOC_QBUF

}

Effacez d'abord la variable tV4l2Buf de la cinquième étape et définissez tV4l2Buf.index pour qu'il passe dans le pilote. Le pilote trouve le tampon de chaque tampon selon tV4l2Buf.index (regardez le code, il contient en fait les informations du tampon. Le véritable endroit pour stocker les données est queme-> mem), suspend leur flux et leur file d'attente à la file d'attente principale et à la file d'attente de la file d'attente pour former deux files d'attente.

En fait, c'est une liste doublement liée. L'image ci-dessus est juste pour une compréhension facile. 

Programme APP:

        	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
        	tV4l2Buf.index = i;
        	tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
        	iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);
        	if (iError)
            {
        	    DBG_PRINTF("Unable to queue buffer.\n");
        	    goto err_exit;
        	}

Pilote: 


/*
 * Queue a video buffer. Attempting to queue a buffer that has already been
 * queued will return -EINVAL.
 */
int uvc_queue_buffer(struct uvc_video_queue *queue,
	struct v4l2_buffer *v4l2_buf)
{
	struct uvc_buffer *buf;
	unsigned long flags;
	int ret = 0;

	uvc_trace(UVC_TRACE_CAPTURE, "Queuing buffer %u.\n", v4l2_buf->index);

        //判断传入的参数是否正确
	if (v4l2_buf->type != queue->type ||
	    v4l2_buf->memory != V4L2_MEMORY_MMAP) {
		uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer type (%u) "
			"and/or memory (%u).\n", v4l2_buf->type,
			v4l2_buf->memory);
		return -EINVAL;
	}

	mutex_lock(&queue->mutex);
        //判断传入的参数是否正确
	if (v4l2_buf->index >= queue->count) {
		uvc_trace(UVC_TRACE_CAPTURE, "[E] Out of range index.\n");
		ret = -EINVAL;
		goto done;
	}

        //找到对应的buffer
	buf = &queue->buffer[v4l2_buf->index];
	if (buf->state != UVC_BUF_STATE_IDLE) {
		uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state "
			"(%u).\n", buf->state);
		ret = -EINVAL;
		goto done;
	}

	if (v4l2_buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
	    v4l2_buf->bytesused > buf->buf.length) {
		uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n");
		ret = -EINVAL;
		goto done;
	}

	spin_lock_irqsave(&queue->irqlock, flags);
	if (queue->flags & UVC_QUEUE_DISCONNECTED) {
		spin_unlock_irqrestore(&queue->irqlock, flags);
		ret = -ENODEV;
		goto done;
	}
	buf->state = UVC_BUF_STATE_QUEUED;
	if (v4l2_buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
		buf->buf.bytesused = 0;
	else
		buf->buf.bytesused = v4l2_buf->bytesused;

        //把buffer的stream和queue分别挂到队列queue的mainqueue和irqqueue
	list_add_tail(&buf->stream, &queue->mainqueue);
	list_add_tail(&buf->queue, &queue->irqqueue);
	spin_unlock_irqrestore(&queue->irqlock, flags);

done:
	mutex_unlock(&queue->mutex);
	return ret;
}

 

Sept: streamon

Le travail de cette étape consiste principalement à initialiser l'URB et à démarrer la caméra. Nous recherchons urb-> complete dans le code du noyau pour trouver la fonction uvc_video_complete dans Uvc_video.c. Lorsque le matériel génère une trame de données vers l'URB, il revient à la fonction uvc_video_complete

La fonction uvc_video_complete est la suivante:

static void uvc_video_complete(struct urb *urb)
{
	struct uvc_streaming *stream = urb->context;
	struct uvc_video_queue *queue = &stream->queue;
	struct uvc_buffer *buf = NULL;
	unsigned long flags;
	int ret;

	switch (urb->status) {
	case 0:
		break;

	default:
		uvc_printk(KERN_WARNING, "Non-zero status (%d) in video "
			"completion handler.\n", urb->status);

	case -ENOENT:		/* usb_kill_urb() called. */
		if (stream->frozen)
			return;

	case -ECONNRESET:	/* usb_unlink_urb() called. */
	case -ESHUTDOWN:	/* The endpoint is being disabled. */
		uvc_queue_cancel(queue, urb->status == -ESHUTDOWN);
		return;
	}

	spin_lock_irqsave(&queue->irqlock, flags);
        //如果irqqueue队列不为空,则让buf指向它第一个节点,准备把数据从URB拷贝到第一个节点
	if (!list_empty(&queue->irqqueue))
		buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,queue);
	spin_unlock_irqrestore(&queue->irqlock, flags);

        //decode是解码的意思,其实内部就是拷贝URB上的数据到irqqueue队列的第一个节点
	stream->decode(urb, stream, buf);

        //重新提交URB
	if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
		uvc_printk(KERN_ERR, "Failed to resubmit video URB (%d).\n",
			ret);
	}
}

Recherchez stream-> decode dans le code source du noyau, nous trouverons stream-> decode = uvc_video_decode_isoc; en analysant le code source de la fonction uvc_video_decode_isoc, nous voyons la partie suivante, le rôle de cette partie du code est de mettre les données sur l'URB ) Copiez dans le tampon correspondant au premier nœud de la file d'attente irqqueue, car le processus d'appel est plus lourd, bref ici, l'analyse détaillée dépend du code source.

	/* Copy the video data to the buffer. */
	maxlen = buf->buf.length - buf->buf.bytesused;
	mem = queue->mem + buf->buf.m.offset + buf->buf.bytesused;
	nbytes = min((unsigned int)len, maxlen);
	memcpy(mem, data, nbytes);
	buf->buf.bytesused += nbytes;

Ensuite, supprimez ce nœud de la file d'attente irqqueue et réveillez le processus APP (mettez-vous en veille en attendant les données)

list_del(&buf->queue);
wake_up(&buf->wait);

 

Huit: VIDIOC_DQBUF

Lorsque l'application est réveillée, VIDIOC_DQBUF est appelée et l'application transfère la variable de structure tV4l2Buf de type v4l2_buffer au pilote. Le pilote copie les données du premier nœud de la file d'attente principale vers tV4l2Buf (nous utilisons principalement tV4l2Buf.index) , Puis supprimez ce nœud de la file d'attente principale.

Avec ce tV4l2Buf.index, l'APP sait que ptVideoDevice-> pucVideBuf [i] a des données et affecte son adresse à ptVideoBuf-> tPixelDatas.aucPixelDatas (utilisé pour pointer vers le cache avec les données d'image à chaque fois). À ce stade, nous obtiendrons une image du matériel vers l'application.

Programme APP: 

    /* VIDIOC_DQBUF */
    memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
    tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Buf.memory = V4L2_MEMORY_MMAP;
    iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);
    if (iRet < 0) 
    {
    	DBG_PRINTF("Unable to dequeue buffer.\n");
    	return -1;
    }
    ptVideoDevice->iVideoBufCurIndex = tV4l2Buf.index;

    ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;
    ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;
    ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;
    ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \
                                        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 :  \
                                        0;
    ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;
    ptVideoBuf->tPixelDatas.iTotalBytes   = tV4l2Buf.bytesused;
        //获取图片缓存的地址
    ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[tV4l2Buf.index];  

Quelques pilotes:

        //取出mainqueue队列的第一个节点
	buf = list_first_entry(&queue->mainqueue, struct uvc_buffer, stream);
        //把这个节点从mainqueue队列中删除
	list_del(&buf->stream);
        //和第五步的VIDIOC_QUERYBUF一样,它的作用其实就是把buf(也就是mainqueue队列第一个节点)的数据拷贝到v4l2_buf(我们主要用到v4l2_buf.index)
	__uvc_query_buffer(buf, v4l2_buf);

 

Neuf: VIDIOC_QBUF à nouveau, pour pouvoir parcourir les images, nous devons appeler VIDIOC_QBUF pour insérer le nœud qui a été supprimé des deux files d'attente.

Exécutez tV4l2Buf.index = ptVideoDevice-> iVideoBufCurIndex (enregistré tV4l2Buf.index à la huitième étape); récupérez le tampon qui a été supprimé de la file d'attente la dernière fois, puis réinsérez-le dans la file d'attente, puis attendez la prochaine trame de données À venir.

    struct v4l2_buffer tV4l2Buf;
    int iError;
    
	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
	tV4l2Buf.index  = ptVideoDevice->iVideoBufCurIndex;
	tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
	iError = ioctl(ptVideoDevice->iFd, VIDIOC_QBUF, &tV4l2Buf);
	if (iError) 
    {
	    DBG_PRINTF("Unable to queue buffer.\n");
	    return -1;
	}
    return 0;

/*
 * Queue a video buffer. Attempting to queue a buffer that has already been
 * queued will return -EINVAL.
 */
int uvc_queue_buffer(struct uvc_video_queue *queue,
	struct v4l2_buffer *v4l2_buf)
{
	struct uvc_buffer *buf;
	unsigned long flags;
	int ret = 0;

	uvc_trace(UVC_TRACE_CAPTURE, "Queuing buffer %u.\n", v4l2_buf->index);

        //判断传入的参数是否正确
	if (v4l2_buf->type != queue->type ||
	    v4l2_buf->memory != V4L2_MEMORY_MMAP) {
		uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer type (%u) "
			"and/or memory (%u).\n", v4l2_buf->type,
			v4l2_buf->memory);
		return -EINVAL;
	}

	mutex_lock(&queue->mutex);
        //判断传入的参数是否正确
	if (v4l2_buf->index >= queue->count) {
		uvc_trace(UVC_TRACE_CAPTURE, "[E] Out of range index.\n");
		ret = -EINVAL;
		goto done;
	}

        //找到对应的buffer
	buf = &queue->buffer[v4l2_buf->index];
	if (buf->state != UVC_BUF_STATE_IDLE) {
		uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state "
			"(%u).\n", buf->state);
		ret = -EINVAL;
		goto done;
	}

	if (v4l2_buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
	    v4l2_buf->bytesused > buf->buf.length) {
		uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n");
		ret = -EINVAL;
		goto done;
	}

	spin_lock_irqsave(&queue->irqlock, flags);
	if (queue->flags & UVC_QUEUE_DISCONNECTED) {
		spin_unlock_irqrestore(&queue->irqlock, flags);
		ret = -ENODEV;
		goto done;
	}
	buf->state = UVC_BUF_STATE_QUEUED;
	if (v4l2_buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
		buf->buf.bytesused = 0;
	else
		buf->buf.bytesused = v4l2_buf->bytesused;

        //把buffer的stream和queue分别挂到队列queue的mainqueue和irqqueue
	list_add_tail(&buf->stream, &queue->mainqueue);
	list_add_tail(&buf->queue, &queue->irqqueue);
	spin_unlock_irqrestore(&queue->irqlock, flags);

done:
	mutex_unlock(&queue->mutex);
	return ret;
}

 

 

 

 

 

Enfin, attachez quelques autres structures utilisées ci-dessus pour une visualisation facile

1.T_VideoBuf

typedef struct VideoBuf {
    T_PixelDatas tPixelDatas;
    int iPixelFormat;
}T_VideoBuf, *PT_VideoBuf;



/* 图片的象素数据 */
typedef struct PixelDatas {
	int iWidth;   /* 宽度: 一行有多少个象素 */
	int iHeight;  /* 高度: 一列有多少个象素 */
	int iBpp;     /* 一个象素用多少位来表示 */
	int iLineBytes;  /* 一行数据有多少字节 */
	int iTotalBytes; /* 所有字节数 */ 
	unsigned char *aucPixelDatas;  /* 象素数据存储的地方,每次都是指向有新数据的缓冲区 */
}T_PixelDatas, *PT_PixelDatas;

2.

struct VideoDevice {
    int iFd;
    int iPixelFormat;
    int iWidth;
    int iHeight;

    int iVideoBufCnt;
    int iVideoBufMaxLen;
    int iVideoBufCurIndex;    
    unsigned char *pucVideBuf[NB_BUFFER];    //通过mmap函数映射,该数组的每个变量都指向了缓冲区的地址(比如 men + 1 * offset 、men + 2 * offset)

    /* 函数 */
    PT_VideoOpr ptOPr;
};

 

3.uvc_video_queue 

struct uvc_video_queue {
	enum v4l2_buf_type type;

	void *mem;            //正真存放图片数据的地方
	unsigned int flags;

	unsigned int count;
	unsigned int buf_size;
	unsigned int buf_used;
	struct uvc_buffer buffer[UVC_MAX_VIDEO_BUFFERS];    //n个buffer,里面记录了每个缓冲区的偏移值和号数index等信息
	struct mutex mutex;	/* protects buffers and mainqueue */
	spinlock_t irqlock;	/* protects irqqueue */

	struct list_head mainqueue;    //供APP使用的队列(头节点)
	struct list_head irqqueue;    //供驱动使用的队列(头节点)
};

 4.uvc_buffer 

struct uvc_buffer {
	unsigned long vma_use_count;
	struct list_head stream;    //供APP使用的队列(普通节点)

	/* Touched by interrupt handler. */
	struct v4l2_buffer buf;
	struct list_head queue;    //供驱动使用的队列(普通节点)
	wait_queue_head_t wait;
	enum uvc_buffer_state state;
	unsigned int error;
};

5.v4l2_buffer  

struct v4l2_buffer {
	__u32			index;    //记录buffer的号数
	enum v4l2_buf_type      type;
	__u32			bytesused;
	__u32			flags;
	enum v4l2_field		field;
	struct timeval		timestamp;
	struct v4l2_timecode	timecode;
	__u32			sequence;

	/* memory location */
	enum v4l2_memory        memory;
	union {
		__u32           offset;    //记录各个缓冲区的偏移值,正真的图片数据在mem + n * offset
		unsigned long   userptr;
		struct v4l2_plane *planes;
	} m;
	__u32			length;
	__u32			input;
	__u32			reserved;
};

 

 

A publié 42 articles originaux · Comme 10 · Visiteurs 10 000+

Je suppose que tu aimes

Origine blog.csdn.net/qq_37659294/article/details/104326223
conseillé
Classement