[PATCH/RFC v2 1/2] v4l2grab: Add threaded producer/consumer option

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Adds options to allow the buffer dqbuf to happen on one thread while
the qbuf happens on another. This is useful to test concurrency access to
the v4l2 features. To enable this, 3 new options were added:

t: enable threaded mode (off by default and will use the loop)
b: enable blocking io mode (off by default
s: how much the consumer thread will sleep after reading a buffer, this is to
   simulate the time that it takes to process a buffer in a real application
   (in ms)

For example, you can simulate an application that takes 1s to process a buffer
with:

v4l2grab -t -b -s 1000

Signed-off-by: Thiago Santos <ts.santos@xxxxxxxxxxxxxxxx>
---
 contrib/test/Makefile.am |   2 +-
 contrib/test/v4l2grab.c  | 261 +++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 219 insertions(+), 44 deletions(-)

diff --git a/contrib/test/Makefile.am b/contrib/test/Makefile.am
index 80c7665..c2e3860 100644
--- a/contrib/test/Makefile.am
+++ b/contrib/test/Makefile.am
@@ -25,7 +25,7 @@ pixfmt_test_CFLAGS = $(X11_CFLAGS)
 pixfmt_test_LDFLAGS = $(X11_LIBS)
 
 v4l2grab_SOURCES = v4l2grab.c
-v4l2grab_LDADD = ../../lib/libv4l2/libv4l2.la ../../lib/libv4lconvert/libv4lconvert.la
+v4l2grab_LDADD = ../../lib/libv4l2/libv4l2.la ../../lib/libv4lconvert/libv4lconvert.la -lpthread
 
 v4l2gl_SOURCES = v4l2gl.c
 v4l2gl_LDFLAGS = $(X11_LIBS) $(GL_LIBS) $(GLU_LIBS)
diff --git a/contrib/test/v4l2grab.c b/contrib/test/v4l2grab.c
index 674cbe7..3e1be3d 100644
--- a/contrib/test/v4l2grab.c
+++ b/contrib/test/v4l2grab.c
@@ -24,8 +24,10 @@
 #include <linux/videodev2.h>
 #include "../../lib/include/libv4l2.h"
 #include <argp.h>
+#include <pthread.h>
 
-#define CLEAR(x) memset(&(x), 0, sizeof(x))
+#define CLEAR_P(x,s) memset((x), 0, s)
+#define CLEAR(x) CLEAR_P(&(x), sizeof(x))
 
 struct buffer {
 	void   *start;
@@ -46,22 +48,206 @@ static void xioctl(int fh, unsigned long int request, void *arg)
 	}
 }
 
+/* Used by the multi thread capture version */
+struct buffer_queue {
+	struct v4l2_buffer *buffers;
+	int buffers_size;
+
+	int read_pos;
+	int write_pos;
+	int n_frames;
+
+	int fd;
+
+	pthread_mutex_t mutex;
+	pthread_cond_t buffer_cond;
+};
+
+/* Gets a buffer and puts it in the buffers list at write position, then
+ * notifies the consumer that a new buffer is ready to be used */
+static void *produce_buffer (void * p)
+{
+	struct buffer_queue 		*bq;
+	fd_set				fds;
+	struct timeval			tv;
+	int				i;
+	struct v4l2_buffer		*buf;
+	int				r;
+
+	bq = p;
+
+	for (i = 0; i < bq->n_frames; i++) {
+		printf ("Prod: %d\n", i);
+		buf = &bq->buffers[bq->write_pos % bq->buffers_size];
+		do {
+			FD_ZERO(&fds);
+			FD_SET(bq->fd, &fds);
+
+			/* Timeout. */
+			tv.tv_sec = 2;
+			tv.tv_usec = 0;
+
+			r = select(bq->fd + 1, &fds, NULL, NULL, &tv);
+		} while ((r == -1 && (errno == EINTR)));
+		if (r == -1) {
+			perror("select");
+			pthread_exit (NULL);
+			return NULL;
+		}
+
+		CLEAR_P(buf, sizeof(struct v4l2_buffer));
+		buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		buf->memory = V4L2_MEMORY_MMAP;
+		xioctl(bq->fd, VIDIOC_DQBUF, buf);
+
+		pthread_mutex_lock (&bq->mutex);
+		bq->write_pos++;
+		printf ("Prod: %d (done)\n", i);
+		pthread_cond_signal (&bq->buffer_cond);
+		pthread_mutex_unlock (&bq->mutex);
+
+	}
+
+	pthread_exit (NULL);
+}
+
+/* will create a separate thread that will produce the buffers and put
+ * into a circular array while this same thread will get the buffers from
+ * this array and 'render' them */
+static int capture_threads (int fd, struct buffer *buffers, int bufpool_size,
+			struct v4l2_format fmt, int n_frames,
+			char *out_dir, int sleep_ms)
+{
+	struct v4l2_buffer		buf;
+	unsigned int			i;
+	struct buffer_queue		buf_queue;
+	pthread_t			producer;
+	char				out_name[25 + strlen(out_dir)];
+	FILE				*fout;
+	struct timespec			sleeptime;
+
+	if (sleep_ms) {
+		sleeptime.tv_sec = sleep_ms / 1000;
+		sleeptime.tv_nsec = (sleep_ms % 1000) * 1000000;
+	}
+
+	buf_queue.buffers_size = bufpool_size * 2;
+	buf_queue.buffers = calloc(buf_queue.buffers_size,
+				   sizeof(struct v4l2_buffer));
+	buf_queue.fd = fd;
+	buf_queue.read_pos = 0;
+	buf_queue.write_pos = 0;
+	buf_queue.n_frames = n_frames;
+	pthread_mutex_init (&buf_queue.mutex, NULL);
+	pthread_cond_init (&buf_queue.buffer_cond, NULL);
+
+	pthread_create (&producer, NULL, produce_buffer, &buf_queue);
+
+	for (i = 0; i < n_frames; i++) {
+		printf ("Read: %d\n", i);
+
+		/* wait for a buffer to be available in the queue */
+		pthread_mutex_lock (&buf_queue.mutex);
+		while (buf_queue.read_pos == buf_queue.write_pos) {
+			pthread_cond_wait (&buf_queue.buffer_cond,
+					   &buf_queue.mutex);
+		}
+		pthread_mutex_unlock (&buf_queue.mutex);
+
+		if (sleep_ms)
+			nanosleep (&sleeptime, NULL);
+
+		sprintf(out_name, "%s/out%03d.ppm", out_dir, i);
+		fout = fopen(out_name, "w");
+		if (!fout) {
+			perror("Cannot open image");
+			exit(EXIT_FAILURE);
+		}
+		fprintf(fout, "P6\n%d %d 255\n",
+			fmt.fmt.pix.width, fmt.fmt.pix.height);
+		buf = buf_queue.buffers[buf_queue.read_pos %
+					buf_queue.buffers_size];
+		fwrite(buffers[buf.index].start, buf.bytesused, 1, fout);
+		fclose(fout);
+
+		xioctl(fd, VIDIOC_QBUF, &buf);
+
+		pthread_mutex_lock (&buf_queue.mutex);
+		buf_queue.read_pos++;
+		printf ("Read: %d (done)\n", i);
+		pthread_cond_signal (&buf_queue.buffer_cond);
+		pthread_mutex_unlock (&buf_queue.mutex);
+	}
+
+	pthread_mutex_destroy (&buf_queue.mutex);
+	pthread_cond_destroy (&buf_queue.buffer_cond);
+	free (buf_queue.buffers);
+	return 0;
+}
+
+static int capture_loop (int fd, struct buffer *buffers, struct v4l2_format fmt,
+			int n_frames, char *out_dir)
+{
+	struct v4l2_buffer		buf;
+	unsigned int			i;
+	struct timeval			tv;
+	int				r;
+	fd_set				fds;
+	FILE				*fout;
+	char				out_name[25 + strlen(out_dir)];
+
+	for (i = 0; i < n_frames; i++) {
+		do {
+			FD_ZERO(&fds);
+			FD_SET(fd, &fds);
+
+			/* Timeout. */
+			tv.tv_sec = 2;
+			tv.tv_usec = 0;
+
+			r = select(fd + 1, &fds, NULL, NULL, &tv);
+		} while ((r == -1 && (errno == EINTR)));
+		if (r == -1) {
+			perror("select");
+			return errno;
+		}
+
+		CLEAR(buf);
+		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		buf.memory = V4L2_MEMORY_MMAP;
+		xioctl(fd, VIDIOC_DQBUF, &buf);
+
+		sprintf(out_name, "%s/out%03d.ppm", out_dir, i);
+		fout = fopen(out_name, "w");
+		if (!fout) {
+			perror("Cannot open image");
+			exit(EXIT_FAILURE);
+		}
+		fprintf(fout, "P6\n%d %d 255\n",
+			fmt.fmt.pix.width, fmt.fmt.pix.height);
+		fwrite(buffers[buf.index].start, buf.bytesused, 1, fout);
+		fclose(fout);
+
+		xioctl(fd, VIDIOC_QBUF, &buf);
+	}
+	return 0;
+}
+
 static int capture(char *dev_name, int x_res, int y_res, int n_frames,
-		   char *out_dir)
+		   char *out_dir, int block, int threads, int sleep_ms)
 {
 	struct v4l2_format		fmt;
 	struct v4l2_buffer		buf;
 	struct v4l2_requestbuffers	req;
 	enum v4l2_buf_type		type;
-	fd_set				fds;
-	struct timeval			tv;
-	int				r, fd = -1;
+	int				fd = -1;
 	unsigned int			i, n_buffers;
-	char				out_name[25 + strlen(out_dir)];
-	FILE				*fout;
 	struct buffer			*buffers;
 
-	fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK, 0);
+	if (block)
+		fd = v4l2_open(dev_name, O_RDWR, 0);
+	else
+		fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK, 0);
 	if (fd < 0) {
 		perror("Cannot open device");
 		exit(EXIT_FAILURE);
@@ -119,40 +305,11 @@ static int capture(char *dev_name, int x_res, int y_res, int n_frames,
 	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
 	xioctl(fd, VIDIOC_STREAMON, &type);
-	for (i = 0; i < n_frames; i++) {
-		do {
-			FD_ZERO(&fds);
-			FD_SET(fd, &fds);
-
-			/* Timeout. */
-			tv.tv_sec = 2;
-			tv.tv_usec = 0;
-
-			r = select(fd + 1, &fds, NULL, NULL, &tv);
-		} while ((r == -1 && (errno == EINTR)));
-		if (r == -1) {
-			perror("select");
-			return errno;
-		}
-
-		CLEAR(buf);
-		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-		buf.memory = V4L2_MEMORY_MMAP;
-		xioctl(fd, VIDIOC_DQBUF, &buf);
-
-		sprintf(out_name, "%s/out%03d.ppm", out_dir, i);
-		fout = fopen(out_name, "w");
-		if (!fout) {
-			perror("Cannot open image");
-			exit(EXIT_FAILURE);
-		}
-		fprintf(fout, "P6\n%d %d 255\n",
-			fmt.fmt.pix.width, fmt.fmt.pix.height);
-		fwrite(buffers[buf.index].start, buf.bytesused, 1, fout);
-		fclose(fout);
-
-		xioctl(fd, VIDIOC_QBUF, &buf);
-	}
+	if (threads)
+		capture_threads(fd, buffers, 2, fmt, n_frames, out_dir,
+				sleep_ms);
+	else
+		capture_loop(fd, buffers, fmt, n_frames, out_dir);
 
 	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 	xioctl(fd, VIDIOC_STREAMOFF, &type);
@@ -179,6 +336,9 @@ static const struct argp_option options[] = {
 	{"xres",	'x',	"XRES",		0,	"horizontal resolution", 0},
 	{"yres",	'y',	"YRES",		0,	"vertical resolution", 0},
 	{"n-frames",	'n',	"NFRAMES",	0,	"number of frames to capture", 0},
+	{"thread-enable", 't',	"THREADS",	0,	"if different threads should capture and save", 0},
+	{"blockmode-enable", 'b', "BLOCKMODE",	0,	"if blocking mode should be used", 0},
+	{"sleep-time",	's',	"SLEEP",	0,	"how long should the consumer thread sleep to simulate the processing of a buffer (in ms)"},
 	{ 0, 0, 0, 0, 0, 0 }
 };
 
@@ -188,6 +348,9 @@ static char	*out_dir = ".";
 static int	x_res = 640;
 static int	y_res = 480;
 static int	n_frames = 20;
+static int	threads = 0;
+static int	block = 0;
+static int	sleep_ms = 0;
 
 static error_t parse_opt(int k, char *arg, struct argp_state *state)
 {
@@ -215,6 +378,17 @@ static error_t parse_opt(int k, char *arg, struct argp_state *state)
 		if (val)
 			n_frames = val;
 		break;
+	case 't':
+		threads = 1;
+		break;
+	case 'b':
+		block = 1;
+		break;
+	case 's':
+		val = atoi(arg);
+		if (val)
+			sleep_ms = val;
+		break;
 	default:
 		return ARGP_ERR_UNKNOWN;
 	}
@@ -232,5 +406,6 @@ int main(int argc, char **argv)
 {
 	argp_parse(&argp, argc, argv, 0, 0, 0);
 
-	return capture(dev_name, x_res, y_res, n_frames, out_dir);
+	return capture(dev_name, x_res, y_res, n_frames, out_dir, block,
+		       threads, sleep_ms);
 }
-- 
2.0.0

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux