Notas de estudio del proyecto de videovigilancia con cámara USB

La llamada al sistema de una aplicación de monitoreo de cámara es la siguiente:

/ * open
 * VIDIOC_QUERYCAP determina si se trata de un dispositivo de captura de video, cuya interfaz admite (transmisión / lectura, escritura)
 * VIDIOC_ENUM_FMT consulta qué formato es compatible
 * VIDIOC_S_FMT establece qué formato utiliza la cámara
 * VIDIOC_REQBUFS buffer de aplicación
 para la transmisión:
 * VIDIOC_QUERYBUF determina cada uno información del búfer y mmap
 * VIDIOC_QBUF se puso en la cola
 * VIDIOC_STREAMON inicia el dispositivo
 * encuesta esperando datos
 * VIDIOC_DQBUF sale de la cola
 * procesando ...
 * VIDIOC_QBUF se puso en la cola
 * ....
 * VIDIOC_STREAMOFF detiene el dispositivo
 *
 * /

 

Este artículo es una nota de un proyecto de monitoreo de cámara realizado previamente por el autor. El objetivo principal es registrar el proceso de aprendizaje, lo cual es conveniente para una futura revisión. Combinamos la aplicación y el controlador de la cámara uvc para analizar los datos generados por la cámara uvc desde el hardware hasta la aplicación superior El proceso por el cual un programa adquiere datos.

 

Uno: VIDIOC_QUERYCAP

La función VIDIOC_QUERYCAP es principalmente para establecer la variable de estructura v4l2_capability tV4l2Cap que se pasa por APP (se pasa por puntero, los elementos básicos involucrados en este artículo se pasan básicamente por puntero. Para la conveniencia de la narrativa, simplemente dígalo como la variable entrante), y luego APP según la configuración El valor determina si se trata de un dispositivo de video, un dispositivo de transmisión, etc.

Programa de aplicación:

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);
}

Conductor:

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;
	}

P: ¿Sobre qué base establece el controlador subyacente las variables que pasamos?

Respuesta: descriptor del dispositivo.

P: ¿De dónde viene el descriptor del dispositivo?

R: Cuando nuestra cámara uvc se conecta a la placa de desarrollo, el controlador del bus USB generará una estructura usb_device y la colgará en la cola del controlador del bus USB. Esta estructura usb_device contiene nuestra información de hardware, y Este es el descriptor.

 

Dos: VIDIOC_ENUM_FMT

La función VIDIOC_ENUM_FMT es principalmente para establecer la variable de estructura v4l2_fmtdesc tFmtDesc pasada por la aplicación, y luego la aplicación juzga si este formato de píxel es compatible. Si lo admite, se asigna al tVideoDevice de la aplicación (ptVideoDevice es su puntero) -> iPixelFormat. .

Programa de aplicación:

        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;        
    }

Conductor:

	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;
	}

 

Tres: VIDIOC_S_FMT

Primero obtenemos la resolución LCD y tVideoDevice-> iPixelFormat (como YUYV, MJPEG, etc.) en la APLICACIÓN, y luego la pasamos al controlador a través de la variable de estructura v4l2_format tV4l2Fmt . Los valores pasados ​​no se han configurado realmente en el hardware. Solo se guarda temporalmente , y realmente se envía al hardware cuando se transmite.

P: ¿Por qué primero necesita obtener la resolución de la pantalla LCD en la aplicación y luego pasarla al controlador?

R: Nuestro proyecto es obtener los datos recopilados por la cámara y mostrarlos en la pantalla LCD. Para mejorar el efecto de visualización, establecemos la resolución de la imagen recopilada por la cámara en la misma que nuestra pantalla LCD a través de los parámetros entrantes (si la cámara no admite esta resolución Luego, el controlador encontrará una resolución cercana al valor que establecimos).

    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;

Conductor:

	/* 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;

 

Cuatro: VIDIOC_REQBUFS

VIDIOC_REQBUFS primero establece algunos valores en la aplicación, como el número de buffers que se aplicarán , y luego se pasa al controlador a través de la variable de estructura tV4l2ReqBuffs de tipo v4l2_requestbuffers, el controlador de acuerdo con el número de dwMaxVideoFrameSize (sizeimage) entrante y el tercer paso dwMaxVideoFrameSize (sizeimage) Para solicitar un búfer , y luego establecer la información sobre el búfer en la cola (como el número y el desplazamiento de cada búfer), y finalmente obtener el número de búferes aplicados realmente a la aplicación, lo guardamos en ptVideoDevice -> iVideoBufCnt (ptVideoDevice-> iVideoBufCnt = tV4l2ReqBuffs.count;)

Programa de aplicación:

    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;

Conductor

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

Parte del código de uvc_alloc_buffers es el siguiente: 

/* 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;

 

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

VIDIOC_QUERYBUF 和 mmap

}

VIDIOC_QUERYBUF y mmap, primero establezca algunos valores en la aplicación para la variable de estructura v4l2_buffer tV4l2Buf, por ejemplo: tV4l2Buf.index ( que indica qué información de búfer queremos consultar (¿qué información? Valor de compensación y tamaño de página (tamaño de imagen), etc.)) Luego, pase tV4l2Buf al controlador, y el controlador copiará la información del búfer correspondiente a la cola a tV4l2Buf de acuerdo con el índice tV4l2Buf.y regresará a la APLICACIÓN. La aplicación guarda tV4l2Buf.length en tVideoDevice-> iVideoBufMaxLen.

Luego llame a mmap para pasar tV4l2Buf.length, tV4l2Buf.m.offset al controlador, y después de alguna comparación, el controlador encuentra que el búfer es mmap ( los búferes aplicados en el cuarto paso), y pasa la primera dirección del búfer El valor de retorno se asigna a ptVideoDevice-> pucVideBuf [i] , y después de un bucle, la matriz ptVideoDevice-> pucVideBuf apunta a la primera dirección de cada búfer.

Programa de aplicación:

        	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;
        	}

Conductor:

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;
}

 

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

VIDIOC_QBUF

}

Primero borre la variable tV4l2Buf del quinto paso y configure tV4l2Buf.index para que pase al controlador. El controlador encuentra el búfer de cada búfer de acuerdo con tV4l2Buf.index (mire el código, en realidad contiene la información del búfer. El lugar donde se almacenan realmente los datos es queme-> mem), cuelgue su secuencia y la cola en la cola principal e irqqueue de la cola para formar dos colas.

De hecho, es una lista doblemente vinculada. La imagen de arriba es solo para una fácil comprensión. 

Programa de aplicación:

        	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;
        	}

Conductor: 


/*
 * 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;
}

 

Siete: streamon

El trabajo de este paso es principalmente inicializar la URB e iniciar la cámara. Buscamos urb-> complete en el código del kernel para encontrar la función uvc_video_complete en Uvc_video.c. Cuando el hardware genera un marco de datos para la URB, regresa a la función uvc_video_complete

La función uvc_video_complete es la siguiente:

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);
	}
}

Buscar stream-> decode en el código fuente del kernel, encontraremos stream-> decode = uvc_video_decode_isoc; analizando el código fuente de la función uvc_video_decode_isoc, vemos la siguiente parte, el papel de esta parte del código es poner los datos en la URB (es decir, los datos del siguiente código ) Copie en el búfer correspondiente al primer nodo de la cola irqqueue, porque el proceso de llamada es más engorroso, en resumen, el análisis detallado depende del código fuente.

	/* 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;

Luego elimine este nodo de la cola irqqueue y active el proceso de la APLICACIÓN (vaya a dormir cuando espere datos)

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

 

Ocho: VIDIOC_DQBUF

Cuando se despierta la aplicación, se llama a VIDIOC_DQBUF y la aplicación transfiere la variable de estructura tV4l2Buf de tipo v4l2_buffer al controlador. El controlador copia los datos del primer nodo de la cola de la cola principal a tV4l2Buf (principalmente utilizamos tV4l2Buf.index) , Y luego elimine este nodo de la cola de la cola principal.

Con este tV4l2Buf.index, la aplicación sabe que ptVideoDevice-> pucVideBuf [i] tiene datos, y asigna su dirección a ptVideoBuf-> tPixelDatas.aucPixelDatas (usado para apuntar al caché con datos de imagen cada vez). En este punto, obtendremos una imagen del hardware a la APLICACIÓN.

Programa de aplicación: 

    /* 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];  

Algunos conductores:

        //取出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);

 

Nueve: VIDIOC_QBUF nuevamente, para poder recorrer las imágenes, tenemos que llamar a VIDIOC_QBUF para insertar el nodo que se eliminó de las dos colas.

Ejecute tV4l2Buf.index = ptVideoDevice-> iVideoBufCurIndex (tV4l2Buf.index registrado en el octavo paso); recupere qué búfer se eliminó de la cola la última vez, y luego vuelva a insertarlo en la cola, luego espere el siguiente marco de datos Viniendo

    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;
}

 

 

 

 

 

Finalmente, adjunte algunas estructuras más utilizadas anteriormente para una fácil visualización

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;
};

 

 

42 artículos originales publicados · Me gusta 10 · Visitantes más de 10,000

Supongo que te gusta

Origin blog.csdn.net/qq_37659294/article/details/104326223
Recomendado
Clasificación