📄 pwc-if.c
字号:
The frame buffer is the second scheme, and is the central element here. It collects the data from a single frame from the camera (hence, the name). Frames are delimited by the USB camera with a short USB packet, so that's easy to detect. The frame buffers form a list that is filled by the camera+USB controller and drained by the user process through either read() or mmap(). The image buffer is the third scheme, in which frames are decompressed and possibly converted into planar format. For mmap() there is more than one image buffer available. The frame buffers provide the image buffering, in case the user process is a bit slow. This introduces lag and some undesired side-effects. The problem arises when the frame buffer is full. I used to drop the last frame, which makes the data in the queue stale very quickly. But dropping the frame at the head of the queue proved to be a litte bit more difficult. I tried a circular linked scheme, but this introduced more problems than it solved. Because filling and draining are completely asynchronous processes, this requires some fiddling with pointers and mutexes. Eventually, I came up with a system with 2 lists: an 'empty' frame list and a 'full' frame list: * Initially, all frame buffers but one are on the 'empty' list; the one remaining buffer is our initial fill frame. * If a frame is needed for filling, we take it from the 'empty' list, unless that list is empty, in which case we take the buffer at the head of the 'full' list. * When our fill buffer has been filled, it is appended to the 'full' list. * If a frame is needed by read() or mmap(), it is taken from the head of the 'full' list, handled, and then appended to the 'empty' list. If no buffer is present on the 'full' list, we wait. The advantage is that the buffer that is currently being decompressed/ converted, is on neither list, and thus not in our way (any other scheme I tried had the problem of old data lingering in the queue). Whatever strategy you choose, it always remains a tradeoff: with more frame buffers the chances of a missed frame are reduced. On the other hand, on slower machines it introduces lag because the queue will always be full. *//** \brief Find next frame buffer to fill. Take from empty or full list, whichever comes first. */static inline int pwc_next_fill_frame(struct pwc_device *pdev){ int ret; unsigned long flags; ret = 0; spin_lock_irqsave(&pdev->ptrlock, flags); if (pdev->fill_frame != NULL) { /* append to 'full' list */ if (pdev->full_frames == NULL) { pdev->full_frames = pdev->fill_frame; pdev->full_frames_tail = pdev->full_frames; } else { pdev->full_frames_tail->next = pdev->fill_frame; pdev->full_frames_tail = pdev->fill_frame; } } if (pdev->empty_frames != NULL) { /* We have empty frames available. That's easy */ pdev->fill_frame = pdev->empty_frames; pdev->empty_frames = pdev->empty_frames->next; } else { /* Hmm. Take it from the full list */#if PWC_DEBUG /* sanity check */ if (pdev->full_frames == NULL) { Err("Neither empty or full frames available!\n"); spin_unlock_irqrestore(&pdev->ptrlock, flags); return -EINVAL; }#endif pdev->fill_frame = pdev->full_frames; pdev->full_frames = pdev->full_frames->next; ret = 1; } pdev->fill_frame->next = NULL;#if PWC_DEBUG Trace(TRACE_SEQUENCE, "Assigning sequence number %d.\n", pdev->sequence); pdev->fill_frame->sequence = pdev->sequence++;#endif spin_unlock_irqrestore(&pdev->ptrlock, flags); return ret;} /** \brief Reset all buffers, pointers and lists, except for the image_used[] buffer. If the image_used[] buffer is cleared too, mmap()/VIDIOCSYNC will run into trouble. */static void pwc_reset_buffers(struct pwc_device *pdev){ int i; unsigned long flags; spin_lock_irqsave(&pdev->ptrlock, flags); pdev->full_frames = NULL; pdev->full_frames_tail = NULL; for (i = 0; i < default_fbufs; i++) { pdev->fbuf[i].filled = 0; if (i > 0) pdev->fbuf[i].next = &pdev->fbuf[i - 1]; else pdev->fbuf->next = NULL; } pdev->empty_frames = &pdev->fbuf[default_fbufs - 1]; pdev->empty_frames_tail = pdev->fbuf; pdev->read_frame = NULL; pdev->fill_frame = pdev->empty_frames; pdev->empty_frames = pdev->empty_frames->next; pdev->image_read_pos = 0; pdev->fill_image = 0; spin_unlock_irqrestore(&pdev->ptrlock, flags);}/** \brief Do all the handling for getting one frame: get pointer, decompress, advance pointers. */static int pwc_handle_frame(struct pwc_device *pdev){ int ret = 0; unsigned long flags; spin_lock_irqsave(&pdev->ptrlock, flags); /* First grab our read_frame; this is removed from all lists, so we can release the lock after this without problems */ if (pdev->read_frame != NULL) { /* This can't theoretically happen */ Err("Huh? Read frame still in use?\n"); } else { if (pdev->full_frames == NULL) { Err("Woops. No frames ready.\n"); } else { pdev->read_frame = pdev->full_frames; pdev->full_frames = pdev->full_frames->next; pdev->read_frame->next = NULL; } if (pdev->read_frame != NULL) {#if PWC_DEBUG Trace(TRACE_SEQUENCE, "Decompressing frame %d\n", pdev->read_frame->sequence);#endif /* Decompression is a lenghty process, so it's outside of the lock. This gives the isoc_handler the opportunity to fill more frames in the mean time. */ spin_unlock_irqrestore(&pdev->ptrlock, flags); ret = pwc_decompress(pdev); spin_lock_irqsave(&pdev->ptrlock, flags); /* We're done with read_buffer, tack it to the end of the empty buffer list */ if (pdev->empty_frames == NULL) { pdev->empty_frames = pdev->read_frame; pdev->empty_frames_tail = pdev->empty_frames; } else { pdev->empty_frames_tail->next = pdev->read_frame; pdev->empty_frames_tail = pdev->read_frame; } pdev->read_frame = NULL; } } spin_unlock_irqrestore(&pdev->ptrlock, flags); return ret;}/** \brief Advance pointers of image buffer (after each user request) */static inline void pwc_next_image(struct pwc_device *pdev){ pdev->image_used[pdev->fill_image] = 0; pdev->fill_image = (pdev->fill_image + 1) % default_mbufs;}/* 2001-10-14: The YUV420 is still there, but you can only set it from within a program (YUV420P being the default) */static int pwc_set_palette(struct pwc_device *pdev, int pal){ if ( pal == VIDEO_PALETTE_YUV420 || pal == VIDEO_PALETTE_YUV420P#if PWC_DEBUG || pal == VIDEO_PALETTE_RAW#endif ) { pdev->vpalette = pal; pwc_set_image_buffer_size(pdev); return 0; } Trace(TRACE_READ, "Palette %d not supported.\n", pal); return -1;}/* This gets called for the Isochronous pipe (video). This is done in * interrupt time, so it has to be fast, not crash, and not stall. Neat. */static void pwc_isoc_handler(purb_t urb){ struct pwc_device *pdev; int i, fst, flen; int awake; struct pwc_frame_buf *fbuf; unsigned char *fillptr, *iso_buf; pdev = (struct pwc_device *)urb->context; if (pdev == NULL) { Err("isoc_handler() called with NULL device?!\n"); return; }#ifdef PWC_MAGIC if (pdev->magic != PWC_MAGIC) { Err("isoc_handler() called with bad magic!\n"); return; }#endif if (urb->status == -ENOENT || urb->status == -ECONNRESET) { Trace(TRACE_OPEN, "pwc_isoc_handler(): URB unlinked.\n"); return; } if (urb->status != -EINPROGRESS && urb->status != 0) { char *errmsg; errmsg = "Unknown"; switch(urb->status) { case -ENOSR: errmsg = "Buffer error (overrun)"; break; case -EPIPE: errmsg = "Stalled (device not responding)"; break; case -EOVERFLOW: errmsg = "Babble (bad cable?)"; break; case -EPROTO: errmsg = "Bit-stuff error (bad cable?)"; break; case -EILSEQ: errmsg = "CRC/Timeout"; break; case -ETIMEDOUT: errmsg = "NAK (device does not respond)"; break; } Trace(TRACE_FLOW, "pwc_isoc_handler() called with status %d [%s].\n", urb->status, errmsg); return; } fbuf = pdev->fill_frame; if (fbuf == NULL) { Err("pwc_isoc_handler without valid fill frame.\n"); wake_up_interruptible(&pdev->frameq); return; } fillptr = fbuf->data + fbuf->filled; awake = 0; /* vsync: 0 = don't copy data 1 = sync-hunt 2 = synched */ /* Compact data */ for (i = 0; i < urb->number_of_packets; i++) { fst = urb->iso_frame_desc[i].status; flen = urb->iso_frame_desc[i].actual_length; iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset; if (fst == 0) { if (flen > 0) { /* if valid data... */ if (pdev->vsync > 0) { /* ...and we are not sync-hunting... */ pdev->vsync = 2; /* ...copy data to frame buffer, if possible */ if (flen + fbuf->filled > pdev->frame_size) { Trace(TRACE_FLOW, "Frame buffer overflow (flen = %d, frame_size = %d).\n", flen, pdev->frame_size); pdev->vsync = 0; /* Hmm, let's wait for an EOF (end-of-frame) */ pdev->vframes_error++; } else { memmove(fillptr, iso_buf, flen); fillptr += flen; } } fbuf->filled += flen; } /* ..flen > 0 */ if (flen < pdev->vlast_packet_size) { /* Shorter packet... We probably have the end of an image-frame; wake up read() process and let select()/poll() do something. Decompression is done in user time over there. */ if (pdev->vsync == 2) { /* The ToUCam Fun CMOS sensor causes the firmware to send 2 or 3 bogus frames on the USB wire after an exposure change. This conditition is however detected in the cam and a bit is set in the header. */ if (pdev->type == 730) { unsigned char *ptr = (unsigned char *)fbuf->data; if (ptr[1] == 1 && ptr[0] & 0x10) {#if PWC_DEBUG Debug("Hyundai CMOS sensor bug. Dropping frame %d.\n", fbuf->sequence);#endif pdev->drop_frames = 2; pdev->vframes_error++; } /* Sometimes the trailer of the 730 is still sent as a 4 byte packet after a short frame; this condition is filtered out specifically. A 4 byte frame doesn't make sense anyway. So we get either this sequence: drop_bit set -> 4 byte frame -> short frame -> good frame Or this one: drop_bit set -> short frame -> good frame So we drop either 3 or 2 frames in all! */ if (fbuf->filled == 4) pdev->drop_frames++; } /* In case we were instructed to drop the frame, do so silently. The buffer pointers are not updated either (but the counters are reset below). */ if (pdev->drop_frames) pdev->drop_frames--; else { /* Check for underflow first */ if (fbuf->filled < pdev->frame_size) { Trace(TRACE_FLOW, "Frame buffer underflow (%d bytes); discarded.\n", fbuf->filled); pdev->vframes_error++; } else { /* Send only once per EOF */ awake = 1; /* delay wake_ups */ /* Find our next frame to fill. This will always succeed, since we * nick a frame from either empty or full list, but if we had to * take it from the full list, it means a frame got dropped. */ if (pwc_next_fill_frame(pdev)) { pdev->vframes_dumped++; if ((pdev->vframe_count > FRAME_LOWMARK) && (pwc_trace & TRACE_FLOW)) { if (pdev->vframes_dumped < 20) Trace(TRACE_FLOW, "Dumping frame %d.\n", pdev->vframe_count); if (pdev->vframes_dumped == 20) Trace(TRACE_FLOW, "Dumping frame %d (last message).\n", pdev->vframe_count); } } fbuf = pdev->fill_frame; } } /* !drop_frames */ pdev->vframe_count++; } fbuf->filled = 0; fillptr = fbuf->data; pdev->vsync = 1; } /* .. flen < last_packet_size */ pdev->vlast_packet_size = flen; } /* ..status == 0 */#ifdef PWC_DEBUG /* This is normally not interesting to the user, unless you are really debugging something */ else Trace(TRACE_FLOW, "Iso frame %d of USB has error %d\n", i, fst);#endif } if (awake) wake_up_interruptible(&pdev->frameq);}static int pwc_isoc_init(struct pwc_device *pdev){ struct usb_device *udev; purb_t urb; int i, j, ret; struct usb_interface_descriptor *idesc; int cur_alt; if (pdev == NULL) return -EFAULT; if (pdev->iso_init) return 0; pdev->vsync = 0; udev = pdev->udev; /* Get the current alternate interface, adjust packet size */ if (!udev->actconfig) return -EFAULT; cur_alt = udev->actconfig->interface[0].act_altsetting; idesc = &udev->actconfig->interface[0].altsetting[cur_alt]; if (!idesc) return -EFAULT; /* Search video endpoint */ pdev->vmax_packet_size = -1; for (i = 0; i < idesc->bNumEndpoints; i++) if ((idesc->endpoint[i].bEndpointAddress & 0xF) == pdev->vendpoint) { pdev->vmax_packet_size = idesc->endpoint[i].wMaxPacketSize; break; } if (pdev->vmax_packet_size < 0) { Err("Failed to find packet size for video endpoint in current alternate setting.\n"); return -ENFILE; /* Odd error, that should be noticable */ } ret = 0; for (i = 0; i < MAX_ISO_BUFS; i++) { urb = usb_alloc_urb(ISO_FRAMES_PER_DESC); if (urb == NULL) { Err("Failed to allocate urb %d\n", i); ret = -ENOMEM; break; } pdev->sbuf[i].urb = urb; } if (ret) { /* De-allocate in reverse order */ while (i >= 0) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -