libv4l: Fix reqbuf Device or Resource busy error when using read
authorhans@rhel5-devel.localdomain <hans@rhel5-devel.localdomain>
Fri, 14 Aug 2009 07:50:33 +0000 (09:50 +0200)
committerhans@rhel5-devel.localdomain <hans@rhel5-devel.localdomain>
Fri, 14 Aug 2009 07:50:33 +0000 (09:50 +0200)
From: Hans de Goede <hdegoede@redhat.com>

Some applications such as v4l2-apps/test/capture-example.c, in read mode
use select() together with read() and do a select() before the first read().

This causes issues together with certain drivers (gspca for example),
do not allow switching from read mode to mmap mode and they assume read()
mode if a select or poll() is done before any buffers are requested.

When not using libv4l2, this is not an issue but libv4l2 uses mmap mode
under the hood when converting as that safes a memcpy for each frame read.

This fails with such drivers when the application has done a select() before
the first read() as the driver now is in "read mode" and disallows switching
to mmap mode.

This patch fixes this by falling back to using read() for v4l2_read() when
using mmap mode fails.

Priority: normal

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
lib/ChangeLog
lib/libv4l2/libv4l2-priv.h
lib/libv4l2/libv4l2.c

index 458c146..93371a1 100644 (file)
@@ -4,6 +4,7 @@ libv4l-0.6.1
 * Makefile changes to make life easier for the Debian package (Gregor Jasny)
 * Bugfix: fixup 320x240 output for pac7302 cameras
 * README improvements / clarifications (Bifferos)
+* Bugfix: fix reqbuf Device or Resource busy error when using v4l2_read()
 
 libv4l-0.6.0
 ------------
index d976093..8256cfd 100644 (file)
@@ -80,6 +80,9 @@ struct v4l2_dev_info {
   int frame_queued; /* 1 status bit per frame */
   /* mapping tracking of our fake (converting mmap) frame buffers */
   unsigned char frame_map_count[V4L2_MAX_NO_FRAMES];
+  /* buffer when doing conversion and using read() for read() */
+  int readbuf_size;
+  unsigned char *readbuf;
 };
 
 /* From log.c */
index b7e9ae7..07bd948 100644 (file)
@@ -77,6 +77,7 @@
 #define V4L2_SUPPORTS_READ             0x0800
 #define V4L2_IS_UVC                    0x1000
 #define V4L2_STREAM_TOUCHED            0x2000
+#define V4L2_USE_READ_FOR_READ         0x4000
 
 #define V4L2_MMAP_OFFSET_MAGIC      0xABCDEF00u
 
@@ -101,7 +102,7 @@ static int v4l2_request_read_buffers(int index)
   req.memory = V4L2_MEMORY_MMAP;
   if ((result = SYS_IOCTL(devices[index].fd, VIDIOC_REQBUFS, &req)) < 0){
     int saved_err = errno;
-    V4L2_LOG_ERR("requesting %u buffers: %s\n", req.count, strerror(errno));
+    V4L2_LOG("warning reqbuf (%u) failed: %s\n", req.count, strerror(errno));
     errno = saved_err;
     return result;
   }
@@ -305,6 +306,63 @@ static int v4l2_dequeue_and_convert(int index, struct v4l2_buffer *buf,
   return result;
 }
 
+static int v4l2_read_and_convert(int index, unsigned char *dest, int dest_size)
+{
+  const int max_tries = 10;
+  int result, buf_size, tries = max_tries;
+
+  buf_size = devices[index].dest_fmt.fmt.pix.sizeimage;
+  buf_size = (buf_size + 8191) & ~8191;
+
+  if (devices[index].readbuf_size < buf_size) {
+    unsigned char *new_buf;
+
+    new_buf = realloc(devices[index].readbuf, buf_size);
+    if (!new_buf)
+      return -1;
+
+    devices[index].readbuf = new_buf;
+    devices[index].readbuf_size = buf_size;
+  }
+
+  do {
+    result = SYS_READ(devices[index].fd, devices[index].readbuf, buf_size);
+    if (result <= 0) {
+      if (result && errno != EAGAIN) {
+       int saved_err = errno;
+       V4L2_LOG_ERR("reading: %s\n", strerror(errno));
+       errno = saved_err;
+      }
+      return result;
+    }
+
+    result = v4lconvert_convert(devices[index].convert,
+          &devices[index].src_fmt, &devices[index].dest_fmt,
+          devices[index].readbuf, result, dest, dest_size);
+
+    if (result < 0) {
+      int saved_err = errno;
+
+      if(errno == EAGAIN)
+       V4L2_LOG("warning error while converting frame data: %s\n",
+         v4lconvert_get_error_message(devices[index].convert));
+      else
+       V4L2_LOG_ERR("converting / decoding frame data: %s\n",
+         v4lconvert_get_error_message(devices[index].convert));
+
+      errno = saved_err;
+    }
+    tries--;
+  } while (result < 0 && errno == EAGAIN && tries);
+
+  if (result < 0 && errno == EAGAIN) {
+    V4L2_LOG_ERR("got %d consecutive frame decode errors, last error: %s\n",
+      max_tries, v4lconvert_get_error_message(devices[index].convert));
+  }
+
+  return result;
+}
+
 static int v4l2_queue_read_buffers(int index)
 {
   unsigned int i;
@@ -332,6 +390,11 @@ static int v4l2_activate_read_stream(int index)
 {
   int result;
 
+  if ((devices[index].flags & V4L2_STREAMON) || devices[index].frame_queued) {
+    errno = EBUSY;
+    return -1;
+  }
+
   if ((result = v4l2_request_read_buffers(index)))
     return result;
 
@@ -528,6 +591,8 @@ int v4l2_fd_open(int fd, int v4l2_flags)
     devices[index].frame_map_count[i] = 0;
   }
   devices[index].frame_queued = 0;
+  devices[index].readbuf = NULL;
+  devices[index].readbuf_size = 0;
 
   if (index >= devices_used)
     devices_used = index + 1;
@@ -585,6 +650,9 @@ int v4l2_close(int fd)
     devices[index].convert_mmap_buf = MAP_FAILED;
   }
   v4lconvert_destroy(devices[index].convert);
+  free(devices[index].readbuf);
+  devices[index].readbuf = NULL;
+  devices[index].readbuf_size = 0;
 
   /* Remove the fd from our list of managed fds before closing it, because as
      soon as we've done the actual close the fd maybe returned by an open in
@@ -1070,7 +1138,6 @@ ssize_t v4l2_read (int fd, void* dest, size_t n)
 {
   ssize_t result;
   int index;
-  struct v4l2_buffer buf;
 
   if ((index = v4l2_get_index(fd)) == -1)
     return SYS_READ(fd, dest, n);
@@ -1085,23 +1152,33 @@ ssize_t v4l2_read (int fd, void* dest, size_t n)
     goto leave;
   }
 
-  if (!(devices[index].flags & V4L2_STREAM_CONTROLLED_BY_READ)) {
-    if ((devices[index].flags & V4L2_STREAMON) ||
-       devices[index].frame_queued) {
-      errno = EBUSY;
-      result = -1;
-      goto leave;
-    }
+  /* Since we need to do conversion try to use mmap (streaming) mode under
+     the hood as that safes a memcpy for each frame read.
+
+     Note sometimes this will fail as some drivers (atleast gspca) do not allow
+     switching from read mode to mmap mode and they assume read() mode if a
+     select or poll() is done before any buffers are requested. So using mmap
+     mode under the hood will fail if a select() or poll() is done before the
+     first emulated read() call. */
+  if (!(devices[index].flags & V4L2_STREAM_CONTROLLED_BY_READ) &&
+      !(devices[index].flags & V4L2_USE_READ_FOR_READ)) {
     if ((result = v4l2_activate_read_stream(index)))
-      goto leave;
+      /* Activating mmap mode failed, use read() instead */
+      devices[index].flags |= V4L2_USE_READ_FOR_READ;
   }
 
-  buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-  buf.memory = V4L2_MEMORY_MMAP;
-  result = v4l2_dequeue_and_convert(index, &buf, dest, n);
+  if (devices[index].flags & V4L2_USE_READ_FOR_READ) {
+    result = v4l2_read_and_convert(index, dest, n);
+  } else {
+    struct v4l2_buffer buf;
 
-  if (result >= 0)
-    v4l2_queue_read_buffer(index, buf.index);
+    buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buf.memory = V4L2_MEMORY_MMAP;
+    result = v4l2_dequeue_and_convert(index, &buf, dest, n);
+
+    if (result >= 0)
+      v4l2_queue_read_buffer(index, buf.index);
+  }
 
 leave:
   pthread_mutex_unlock(&devices[index].stream_lock);