summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarren Salt <linux@youmustbejoking.demon.co.uk>2010-01-26 01:34:32 +0000
committerDarren Salt <linux@youmustbejoking.demon.co.uk>2010-01-26 01:34:32 +0000
commit7a6f9b2927283b12d3438ad4fc60054650ac1554 (patch)
tree89cb07f063e532faf2dfb93dd7d5aa92c7a70e04
parent16cd864eb5fec7e7462dd446a5ddd7e9ffcfacd2 (diff)
parent98e1acfb30599e6b8fc823e2d402b3e1e775565a (diff)
downloadxine-lib-7a6f9b2927283b12d3438ad4fc60054650ac1554.tar.gz
xine-lib-7a6f9b2927283b12d3438ad4fc60054650ac1554.tar.bz2
Merge v4l2 support.
-rw-r--r--configure.ac18
-rw-r--r--doc/man/en/xine.52
-rw-r--r--src/input/Makefile.am7
-rw-r--r--src/input/input_v4l2.c438
4 files changed, 463 insertions, 2 deletions
diff --git a/configure.ac b/configure.ac
index 3355da1fd..6ca597502 100644
--- a/configure.ac
+++ b/configure.ac
@@ -676,10 +676,10 @@ dnl ----------------------------------------------
dnl Check for usable video-for-linux (v4l) support
dnl ----------------------------------------------
AC_ARG_ENABLE([v4l],
- AS_HELP_STRING([--disable-v4l], [do not build Video4Linux input plugin]))
+ AS_HELP_STRING([--disable-v4l], [do not build Video4Linux input plugins]))
if test "x$enable_v4l" != "xno"; then
- AC_CHECK_HEADERS([linux/videodev.h], [have_v4l=yes], [have_v4l=no])
+ AC_CHECK_HEADERS([linux/videodev.h linux/videodev2.h], [have_v4l=yes], [have_v4l=no])
AC_CHECK_HEADERS([asm/types.h])
if test "x$enable_v4l" = "xyes" && test "x$have_v4l" = "xno"; then
AC_MSG_ERROR([Video4Linux support requested, but prerequisite headers not found.])
@@ -689,6 +689,20 @@ fi
AM_CONDITIONAL(HAVE_V4L, [test "x$have_v4l" = "xyes"])
dnl ----------------------------------------------
+dnl Check for libv4l support
+dnl ----------------------------------------------
+AC_ARG_ENABLE([libv4l],
+ AS_HELP_STRING([--disable-libv4l], [do not build with libv4l support]))
+
+if test "x$enable_libv4l" != "xno"; then
+ AC_CHECK_HEADERS([libv4l2.h], [have_libv4l=yes], [have_libv4l=no])
+ if test "x$enable_libv4l" = "xyes" && test "x$have_libv4l" = "xno"; then
+ AC_MSG_ERROR([libv4l requested but not found.])
+ fi
+fi
+
+
+dnl ----------------------------------------------
dnl Check for Xv and XvMC support
dnl ----------------------------------------------
diff --git a/doc/man/en/xine.5 b/doc/man/en/xine.5
index 195fb1596..3f80ea575 100644
--- a/doc/man/en/xine.5
+++ b/doc/man/en/xine.5
@@ -51,6 +51,8 @@ to read from. Valid MRLs may be plain file names or one of the following
.br
.BI v4l://[ tuner_device / frequency ]
.br
+.BI v4l2:// tuner_device
+.br
.BI dvb:// channel_number
.br
(nth channel in your channels.conf)
diff --git a/src/input/Makefile.am b/src/input/Makefile.am
index 0748c8f29..3c700ea46 100644
--- a/src/input/Makefile.am
+++ b/src/input/Makefile.am
@@ -21,6 +21,7 @@ endif
if HAVE_V4L
in_v4l = xineplug_inp_v4l.la
+in_v4l2 = xineplug_inp_v4l2.la
in_pvr = xineplug_inp_pvr.la
endif
@@ -60,6 +61,7 @@ xineplug_LTLIBRARIES = \
$(in_dvd) \
$(in_vcd) \
$(in_v4l) \
+ $(in_v4l2) \
$(in_gnome_vfs) \
$(in_smb) \
xineplug_inp_mms.la \
@@ -138,6 +140,11 @@ xineplug_inp_v4l_la_LIBADD = $(XINE_LIB) $(ALSA_LIBS) $(LTLIBINTL)
xineplug_inp_v4l_la_CFLAGS = $(VISIBILITY_FLAG) $(AM_CFLAGS)
xineplug_inp_v4l_la_LDFLAGS = -avoid-version -module
+xineplug_inp_v4l2_la_SOURCES = input_v4l2.c
+xineplug_inp_v4l2_la_LIBADD = $(XINE_LIB) $(ALSA_LIBS)
+xineplug_inp_v4l2_la_CFLAGS = $(VISIBILITY_FLAG) $(AM_CFLAGS)
+xineplug_inp_v4l2_la_LDFLAGS = -avoid-version -module
+
xineplug_inp_gnome_vfs_la_SOURCES = input_gnome_vfs.c net_buf_ctrl.c
xineplug_inp_gnome_vfs_la_LIBADD = $(XINE_LIB) $(GNOME_VFS_LIBS) $(PTHREAD_LIBS) $(LTLIBINTL)
xineplug_inp_gnome_la_CFLAGS = $(VISIBILITY_FLAG) $(AM_CFLAGS)
diff --git a/src/input/input_v4l2.c b/src/input/input_v4l2.c
new file mode 100644
index 000000000..69d0a3b75
--- /dev/null
+++ b/src/input/input_v4l2.c
@@ -0,0 +1,438 @@
+/*
+* Copyright (C) 2010 the xine project
+* Copyright (C) 2010 Trever Fischer <tdfischer@fedoraproject.org>
+*
+* This file is part of xine, a free video player.
+*
+* xine is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* xine is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+*
+* v4l2 input plugin
+*/
+
+#define LOG_MODULE "v4l2"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define LOG
+
+#include "input_plugin.h"
+#include "xine_plugin.h"
+#include "xine_internal.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/videodev2.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <errno.h>
+
+#ifdef HAVE_LIBV4L2_H
+# include <libv4l2.h>
+#else
+# include <unistd.h>
+# include <sys/ioctl.h>
+# define v4l2_open(f,d) open(f,d)
+# define v4l2_ioctl(f,c,a) ioctl(f,c,a)
+# define v4l2_mmap(p,l,d,m,f,o) mmap(p,l,d,m,f,o)
+# define v4l2_munmap(s,l) munmap(s,l)
+# define v4l2_close(f) close(f)
+#endif
+
+typedef struct {
+ void *start;
+ size_t length;
+} buffer_data;
+
+typedef struct {
+ int width;
+ int height;
+} resolution_t;
+
+typedef struct {
+ buffer_data *buffers;
+ int bufcount;
+ resolution_t resolution;
+ struct v4l2_buffer inbuf;
+ off_t index;
+ int headerSent;
+} v4l2_video_t;
+
+typedef struct {
+ buffer_data *buffers;
+ int bufcount;
+} v4l2_radio_t;
+
+typedef struct {
+ input_plugin_t input_plugin;
+
+ int fd;
+ char* mrl;
+ struct v4l2_capability cap;
+ xine_stream_t *stream;
+
+ xine_event_queue_t *events;
+ v4l2_video_t* video;
+ v4l2_radio_t* radio;
+} v4l2_input_plugin_t;
+
+static void v4l2_input_enqueue_video_buffer(v4l2_input_plugin_t *this, int idx);
+static int v4l2_input_dequeue_video_buffer(v4l2_input_plugin_t *this, buf_element_t *input);
+static int v4l2_input_setup_video_streaming(v4l2_input_plugin_t *this);
+
+
+static int v4l2_input_open(input_plugin_t *this_gen) {
+ v4l2_input_plugin_t *this = (v4l2_input_plugin_t*) this_gen;
+ lprintf("Opening %s\n", this->mrl);
+ this->fd = v4l2_open(this->mrl, O_RDWR);
+ if (this->fd) {
+ /* TODO: Clean up this mess */
+ this->events = xine_event_new_queue(this->stream);
+ v4l2_ioctl(this->fd, VIDIOC_QUERYCAP, &(this->cap));
+ if (this->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
+ this->video = malloc(sizeof(v4l2_video_t));
+ this->video->headerSent = 0;
+ this->video->bufcount = 0;
+ }
+ if (this->cap.capabilities & V4L2_CAP_STREAMING) {
+ lprintf("Supports streaming. Allocating buffers...\n");
+ if (this->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
+ if (v4l2_input_setup_video_streaming(this)) {
+ lprintf("Video streaming ready.\n");
+ return 1;
+ } else {
+ /* TODO: Fallbacks */
+ lprintf("Video streaming setup failed.\n");
+ return 0;
+ }
+ } else {
+ /* TODO: Radio streaming */
+ lprintf("Sorry, only video is supported for now.\n");
+ return 0;
+ }
+ } else {
+ lprintf("Device doesn't support streaming. Prod the author to support the other methods.\n");
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+}
+
+static int v4l2_input_setup_video_streaming(v4l2_input_plugin_t *this) {
+ this->video->bufcount = 0;
+ struct v4l2_requestbuffers reqbuf;
+ unsigned int i;
+ memset(&reqbuf, 0, sizeof(reqbuf));
+ reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ reqbuf.memory = V4L2_MEMORY_MMAP;
+ reqbuf.count = 25;
+
+ if (-1 == v4l2_ioctl(this->fd, VIDIOC_REQBUFS, &reqbuf)) {
+ lprintf("Buffer request failed. Is streaming supported?\n");
+ return 0;
+ }
+
+ this->video->bufcount = reqbuf.count;
+ lprintf("Got %i buffers for stremaing.\n", reqbuf.count);
+
+ this->video->buffers = calloc(this->video->bufcount, sizeof(buffer_data));
+ _x_assert(this->video->buffers);
+ for (i = 0;i < this->video->bufcount;i++) {
+ struct v4l2_buffer buffer;
+ memset(&buffer, 0, sizeof(buffer));
+ buffer.type = reqbuf.type;
+ buffer.memory = reqbuf.memory;
+ buffer.index = i;
+
+ if (-1 == v4l2_ioctl(this->fd, VIDIOC_QUERYBUF, &buffer)) {
+ lprintf("Couldn't allocate buffer %i\n", i);
+ return 0;
+ }
+
+ this->video->buffers[i].length = buffer.length;
+ this->video->buffers[i].start = (void*)v4l2_mmap(NULL, buffer.length,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED,
+ this->fd, buffer.m.offset);
+ if (MAP_FAILED == this->video->buffers[i].start) {
+ lprintf("Couldn't mmap buffer %i\n", i);
+ int j;
+ for(j = 0;j<i;j++) {
+ v4l2_munmap(this->video->buffers[i].start, this->video->buffers[i].length);
+ }
+ free(this->video->buffers);
+ this->video->bufcount = 0;
+ return 0;
+ }
+ v4l2_input_enqueue_video_buffer(this, i);
+ }
+
+ struct v4l2_format fmt;
+ memset(&fmt, 0, sizeof(fmt));
+ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ /* TODO: Other formats? MPEG support? */
+ fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+ if (-1 == v4l2_ioctl(this->fd, VIDIOC_S_FMT, &fmt))
+ goto fail;
+ this->video->resolution.width = fmt.fmt.pix.width;
+ this->video->resolution.height = fmt.fmt.pix.height;
+ if (-1 == v4l2_ioctl(this->fd, VIDIOC_STREAMON, &reqbuf.type))
+ goto fail;
+
+ _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, 1);
+ _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO, 0);
+
+ return 1;
+
+ fail:
+ lprintf("Couldn't start streaming: %s\n", strerror(errno));
+ return 0;
+}
+
+
+static buf_element_t* v4l2_input_read_block(input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t len) {
+ lprintf("Reading block\n");
+ v4l2_input_plugin_t *this = (v4l2_input_plugin_t*)this_gen;
+ buf_element_t *buf = fifo->buffer_pool_alloc(fifo);
+ if (!this->video->headerSent) {
+ struct timeval tv;
+ xine_monotonic_clock(&tv, NULL);
+ buf->pts = (int64_t) tv.tv_sec * 90000 + (int64_t) tv.tv_usec * 9 / 100;
+
+ lprintf("Sending video header\n");
+ xine_bmiheader bih;
+ bih.biSize = sizeof(xine_bmiheader);
+ /* HACK: Why do I need to do this and why is it magic? */
+ bih.biWidth = this->video->resolution.width*2;
+ bih.biHeight = this->video->resolution.height*2;
+ lprintf("Getting size of %ix%i\n", this->video->resolution.width, this->video->resolution.height);
+ buf->size = sizeof(xine_bmiheader);
+ buf->decoder_flags = BUF_FLAG_HEADER|BUF_FLAG_STDHEADER|BUF_FLAG_FRAME_START;
+ memcpy(buf->content, &bih, sizeof(xine_bmiheader));
+ this->video->headerSent = 1;
+ this->video->index = 0;
+ buf->type = BUF_VIDEO_YUY2;
+ } else {
+ lprintf("Sending video frame (sent %d of %d)\n", this->video->index, this->video->buffers[this->video->inbuf.index].length);
+ /* TODO: Add audio support */
+ this->video->headerSent = v4l2_input_dequeue_video_buffer(this, buf);
+ }
+ return buf;
+}
+
+static uint32_t v4l2_input_blocksize(input_plugin_t *this_gen) {
+ /* HACK */
+ return 0;
+ v4l2_input_plugin_t *this = (v4l2_input_plugin_t*)this_gen;
+ if (this->video->headerSent) {
+ lprintf("Returning block size of %zu\n",this->video->buffers[0].length);
+ return this->video->buffers[0].length;
+ } else {
+ lprintf("Returning block size of %zu\n",sizeof(xine_bmiheader));
+ return sizeof(xine_bmiheader);
+ }
+}
+
+static int v4l2_input_dequeue_video_buffer(v4l2_input_plugin_t *this, buf_element_t *output)
+{
+ if (!this->video->index)
+ {
+ memset (&this->video->inbuf, 0, sizeof (this->video->inbuf));
+ this->video->inbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ this->video->inbuf.memory = V4L2_MEMORY_MMAP;
+ v4l2_ioctl(this->fd, VIDIOC_DQBUF, &this->video->inbuf);
+ output->decoder_flags = BUF_FLAG_FRAME_START;
+ }
+ else
+ output->decoder_flags = 0;
+
+ output->content = output->mem;
+ output->type = BUF_VIDEO_YUY2;
+
+ output->size = this->video->buffers[this->video->inbuf.index].length - this->video->index;
+ if (output->size > output->max_size)
+ output->size = output->max_size;
+
+ xine_fast_memcpy (output->content, this->video->buffers[this->video->inbuf.index].start + this->video->index, output->size);
+
+ this->video->index += output->size;
+ if (this->video->index == this->video->buffers[this->video->inbuf.index].length)
+ {
+ output->decoder_flags |= BUF_FLAG_FRAME_END;
+ v4l2_input_enqueue_video_buffer(this, this->video->inbuf.index);
+ return 0;
+ }
+
+ return 1;
+}
+
+static void v4l2_input_enqueue_video_buffer(v4l2_input_plugin_t *this, int idx) {
+ struct v4l2_buffer buf;
+ memset(&buf, 0, sizeof(buf));
+ buf.index = idx;
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ v4l2_ioctl(this->fd, VIDIOC_QBUF, &buf);
+}
+
+static void v4l2_input_dispose(input_plugin_t *this_gen) {
+ lprintf("Disposing of myself.\n");
+ v4l2_input_plugin_t* this = (v4l2_input_plugin_t*)this_gen;
+
+ if (this->video != NULL) {
+ int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (-1 == v4l2_ioctl(this->fd, VIDIOC_STREAMOFF, &type)) {
+ lprintf("Couldn't stop streaming. Uh oh.\n");
+ }
+ if (this->video->bufcount > 0) {
+ int i;
+ for(i = 0;i<this->video->bufcount;i++) {
+ v4l2_munmap(this->video->buffers[i].start, this->video->buffers[i].length);
+ }
+ free(this->video->buffers);
+ }
+ free(this->video);
+ }
+ v4l2_close(this->fd);
+ free(this->mrl);
+ free(this);
+}
+
+static off_t v4l2_input_read(input_plugin_t *this_gen, char *buf, off_t nlen) {
+ /* Only block reads are supported. */
+ return 0;
+}
+
+static uint32_t v4l2_input_get_capabilities(input_plugin_t* this_gen) {
+ return INPUT_CAP_BLOCK;
+}
+
+static const char* v4l2_input_get_mrl(input_plugin_t* this_gen) {
+ v4l2_input_plugin_t* this = (v4l2_input_plugin_t*)this_gen;
+ /* HACK HACK HACK HACK */
+ /* So far, the only way to get the yuv_frames demuxer to work with this */
+ return "v4l:/";
+ //return this->mrl;
+}
+
+static int v4l2_input_get_optional_data(input_plugin_t *this_gen, void *data, int data_type) {
+ return INPUT_OPTIONAL_UNSUPPORTED;
+}
+
+/* Seeking not supported. */
+static off_t v4l2_input_seek(input_plugin_t *this_gen, off_t offset, int origin) {
+ return -1;
+}
+
+static off_t v4l2_input_seek_time(input_plugin_t *this_gen, int time_offset, int origin) {
+ return -1;
+}
+
+static off_t v4l2_input_pos(input_plugin_t *this_gen) {
+ /* TODO */
+ return 0;
+}
+
+static int v4l2_input_time(input_plugin_t *this_gen) {
+ /* TODO */
+ return 0;
+}
+
+static off_t v4l2_input_length(input_plugin_t *this_gen) {
+ return -1;
+}
+
+typedef struct {
+ input_class_t input_class;
+} v4l2_input_class_t;
+
+static input_plugin_t *v4l2_class_get_instance(input_class_t *gen_cls, xine_stream_t *stream, const char *mrl) {
+ v4l2_input_plugin_t *this;
+ if (strncasecmp (mrl, "v4l2:/", 6))
+ return NULL;
+ mrl += 5;
+ while (*++mrl == '/') /**/;
+ --mrl; /* point at the last slash */
+ /* TODO: Radio devices */
+ /* FIXME: Don't require devices to be of /dev/videoXXX */
+ if (strncmp(mrl, "/dev/video", 10) != 0)
+ return NULL;
+ lprintf("We can handle %s!\n", mrl);
+
+ this = calloc(1, sizeof(v4l2_input_plugin_t));
+ _x_assert(this);
+ this->mrl = strdup(mrl);
+ this->input_plugin.open = v4l2_input_open;
+ this->input_plugin.get_capabilities = v4l2_input_get_capabilities;
+ this->input_plugin.get_blocksize = v4l2_input_blocksize;
+ this->input_plugin.get_mrl = v4l2_input_get_mrl;
+ this->input_plugin.dispose = v4l2_input_dispose;
+ this->input_plugin.read = v4l2_input_read;
+ this->input_plugin.read_block = v4l2_input_read_block;
+ this->input_plugin.seek = v4l2_input_seek;
+ this->input_plugin.seek_time = v4l2_input_seek_time;
+ this->input_plugin.get_current_pos = v4l2_input_pos;
+ this->input_plugin.get_current_time = v4l2_input_time;
+ this->input_plugin.get_length = v4l2_input_length;
+ this->input_plugin.get_optional_data = v4l2_input_get_optional_data;
+ this->input_plugin.input_class = gen_cls;
+ this->stream = stream;
+
+ this->video = NULL;
+ this->radio = NULL;
+ lprintf("Ready to read!\n");
+
+ return &this->input_plugin;
+}
+
+static const char *v4l2_class_get_description(input_class_t *this_gen) {
+ /* TODO: Translatable with _() */
+ return "v4l2 input plugin";
+}
+
+static const char *v4l2_class_get_identifier(input_class_t *this_gen) {
+ return "v4l2";
+}
+
+static void v4l2_class_dispose(input_class_t *this_gen) {
+ free(this_gen);
+}
+
+static void *v4l2_init_class(xine_t *xine, void *data) {
+ v4l2_input_class_t *this;
+ this = malloc(sizeof(v4l2_input_class_t));
+ this->input_class.get_instance = v4l2_class_get_instance;
+ this->input_class.get_description = v4l2_class_get_description;
+ this->input_class.get_identifier = v4l2_class_get_identifier;
+ this->input_class.get_dir = NULL;
+ this->input_class.get_autoplay_list = NULL;
+ this->input_class.dispose = v4l2_class_dispose;
+ this->input_class.eject_media = NULL;
+ return &this->input_class;
+}
+
+const input_info_t input_info_v4l2 = {
+ 4000
+};
+
+const plugin_info_t xine_plugin_info[] EXPORTED = {
+ /* type, API, "name", version, special_info, init_function */
+ { PLUGIN_INPUT, 17, "v4l2", XINE_VERSION_CODE, &input_info_v4l2, v4l2_init_class },
+ { PLUGIN_NONE, 0, "", 0, NULL, NULL }
+};