[vlc-devel] [PATCH] Add support for reading EIA-608 closed captions via V4L2 VBI devices
Devin Heitmueller
dheitmueller at kernellabs.com
Sun Oct 28 22:44:50 CET 2012
This patch introduces support for using V4L2 VBI devices for reading
NTSC closed captions.
This patch is a modified version of a patch series I sent on Oct 21, 2012,
which takes into account the feedback received from the VLC developers.
---
modules/access/Modules.am | 5 +-
modules/access/v4l2/access.c | 3 +-
modules/access/v4l2/demux.c | 229 +++++++++++++++++++++++++++++++-----------
modules/access/v4l2/v4l2.c | 8 ++
modules/access/v4l2/v4l2.h | 14 ++-
modules/access/v4l2/video.c | 21 ++--
6 files changed, 210 insertions(+), 70 deletions(-)
diff --git a/modules/access/Modules.am b/modules/access/Modules.am
index 5665477..9dc2a80 100644
--- a/modules/access/Modules.am
+++ b/modules/access/Modules.am
@@ -132,14 +132,15 @@ libv4l2_plugin_la_SOURCES = \
v4l2/videodev2.h \
v4l2/v4l2.c \
v4l2/video.c \
+ v4l2/vbi.c \
v4l2/demux.c \
v4l2/access.c \
v4l2/radio.c \
v4l2/controls.c \
v4l2/lib.c \
v4l2/v4l2.h
-libv4l2_plugin_la_CFLAGS = $(AM_CFLAGS)
-libv4l2_plugin_la_LIBADD = $(AM_LIBADD) $(LIBDL) $(LIBM)
+libv4l2_plugin_la_CFLAGS = $(AM_CFLAGS) $(ZVBI_CFLAGS)
+libv4l2_plugin_la_LIBADD = $(AM_LIBADD) $(LIBDL) $(LIBM) $(ZVBI_LIBS)
if HAVE_V4L2
libvlc_LTLIBRARIES += libv4l2_plugin.la
endif
diff --git a/modules/access/v4l2/access.c b/modules/access/v4l2/access.c
index df5add0..d4676f5 100644
--- a/modules/access/v4l2/access.c
+++ b/modules/access/v4l2/access.c
@@ -103,7 +103,8 @@ int InitVideo (access_t *access, int fd, uint32_t caps)
return -1;
}
- if (SetupInput (VLC_OBJECT(access), fd))
+ v4l2_std_id std;
+ if (SetupInput (VLC_OBJECT(access), fd, &std))
return -1;
/* NOTE: The V4L access_demux expects a VLC FOURCC as "chroma". It is used to set the
diff --git a/modules/access/v4l2/demux.c b/modules/access/v4l2/demux.c
index 15b540f..12c8690 100644
--- a/modules/access/v4l2/demux.c
+++ b/modules/access/v4l2/demux.c
@@ -58,22 +58,33 @@ struct demux_sys_t
es_out_id_t *es;
vlc_v4l2_ctrl_t *controls;
mtime_t start;
+
+#ifdef ZVBI_COMPILED
+ vbi_capture *vbi_cap;
+ es_out_id_t *p_es_subt[VBI_NUM_CC_STREAMS];
+#endif
};
static void *UserPtrThread (void *);
static void *MmapThread (void *);
static void *ReadThread (void *);
static int DemuxControl( demux_t *, int, va_list );
-static int InitVideo (demux_t *, int fd, uint32_t caps);
+static int InitVideo (demux_t *, int fd, uint32_t caps, v4l2_std_id *std,
+ void *(**entry) (void *));
+#ifdef ZVBI_COMPILED
+static int InitVBI (demux_t *);
+#endif
int DemuxOpen( vlc_object_t *obj )
{
demux_t *demux = (demux_t *)obj;
-
+ void *(*entry) (void *);
+
demux_sys_t *sys = malloc (sizeof (*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
demux->p_sys = sys;
+ sys->vbi_cap = NULL;
ParseMRL( obj, demux->psz_location );
@@ -88,12 +99,32 @@ int DemuxOpen( vlc_object_t *obj )
goto error;
sys->fd = fd;
- if (InitVideo (demux, fd, caps))
+ v4l2_std_id std;
+ if (InitVideo (demux, fd, caps, &std, &entry))
{
v4l2_close (fd);
goto error;
}
+#ifdef ZVBI_COMPILED
+ char *vbi_path = var_InheritString (obj, CFG_PREFIX"vbidev");
+ if (vbi_path != NULL && (std & V4L2_STD_NTSC_M))
+ {
+ sys->vbi_cap = OpenVBIDev (obj, vbi_path);
+ if (sys->vbi_cap)
+ InitVBI(demux);
+ free(vbi_path);
+ }
+#endif
+
+ /* Now that everything is setup, start the reading thread */
+ if (vlc_clone (&sys->thread, entry, demux, VLC_THREAD_PRIORITY_INPUT))
+ {
+ if (sys->bufv != NULL)
+ StopMmap (sys->fd, sys->bufv, sys->bufc);
+ goto error;
+ }
+
sys->controls = ControlsInit (VLC_OBJECT(demux), fd);
sys->start = mdate ();
demux->pf_demux = NULL;
@@ -261,7 +292,8 @@ static void GetAR (int fd, unsigned *restrict num, unsigned *restrict den)
*den = cropcap.pixelaspect.denominator;
}
-static int InitVideo (demux_t *demux, int fd, uint32_t caps)
+static int InitVideo (demux_t *demux, int fd, uint32_t caps, v4l2_std_id *std,
+ void *(**entry) (void *))
{
demux_sys_t *sys = demux->p_sys;
@@ -271,7 +303,7 @@ static int InitVideo (demux_t *demux, int fd, uint32_t caps)
return -1;
}
- if (SetupInput (VLC_OBJECT(demux), fd))
+ if (SetupInput (VLC_OBJECT(demux), fd, std))
return -1;
/* Picture format negotiation */
@@ -399,7 +431,6 @@ static int InitVideo (demux_t *demux, int fd, uint32_t caps)
sys->es = es_out_Add (demux->out, &es_fmt);
/* Init I/O method */
- void *(*entry) (void *);
if (caps & V4L2_CAP_STREAMING)
{
if (StartUserPtr (VLC_OBJECT(demux), fd) == 0)
@@ -411,7 +442,7 @@ static int InitVideo (demux_t *demux, int fd, uint32_t caps)
sys->blocksize = (fmt.fmt.pix.sizeimage + pagemask) & ~pagemask;
sys->bufv = NULL;
- entry = UserPtrThread;
+ *entry = UserPtrThread;
msg_Dbg (demux, "streaming with %"PRIu32"-bytes user buffers",
sys->blocksize);
}
@@ -421,7 +452,7 @@ static int InitVideo (demux_t *demux, int fd, uint32_t caps)
sys->bufv = StartMmap (VLC_OBJECT(demux), fd, &sys->bufc);
if (sys->bufv == NULL)
return -1;
- entry = MmapThread;
+ *entry = MmapThread;
msg_Dbg (demux, "streaming with %"PRIu32" memory-mapped buffers",
sys->bufc);
}
@@ -430,7 +461,7 @@ static int InitVideo (demux_t *demux, int fd, uint32_t caps)
{
sys->blocksize = fmt.fmt.pix.sizeimage;
sys->bufv = NULL;
- entry = ReadThread;
+ *entry = ReadThread;
msg_Dbg (demux, "reading %"PRIu32" bytes at a time", sys->blocksize);
}
else
@@ -439,14 +470,33 @@ static int InitVideo (demux_t *demux, int fd, uint32_t caps)
return -1;
}
- if (vlc_clone (&sys->thread, entry, demux, VLC_THREAD_PRIORITY_INPUT))
+ return 0;
+}
+
+#ifdef ZVBI_COMPILED
+static int InitVBI (demux_t *demux)
+{
+ demux_sys_t *sys = demux->p_sys;
+
+ for (int i = 0; i < VBI_NUM_CC_STREAMS; i++)
{
- if (sys->bufv != NULL)
- StopMmap (sys->fd, sys->bufv, sys->bufc);
- return -1;
+ es_format_t fmt;
+
+ es_format_Init( &fmt, SPU_ES, VLC_FOURCC('c','c',0x31 + i,' ') );
+ if (asprintf(&fmt.psz_description, "CC%d", i + 1) >= 0)
+ {
+ msg_Dbg( demux, "new spu es %4.4s", (char*)&fmt.i_codec );
+ sys->p_es_subt[i] = es_out_Add( demux->out, &fmt );
+ }
}
+
+ /* Do a single read and throw away the results so that ZVBI calls
+ the STREAMON ioctl() */
+ GrabVBI(demux, sys->vbi_cap, sys->p_es_subt, VBI_NUM_CC_STREAMS);
+
return 0;
}
+#endif
void DemuxClose( vlc_object_t *obj )
{
@@ -459,6 +509,15 @@ void DemuxClose( vlc_object_t *obj )
StopMmap (sys->fd, sys->bufv, sys->bufc);
ControlsDeinit( obj, sys->controls );
v4l2_close (sys->fd);
+
+#ifdef ZVBI_COMPILED
+ if( sys->vbi_cap )
+ {
+ close(vbi_capture_fd(sys->vbi_cap));
+ vbi_capture_delete( sys->vbi_cap );
+ }
+#endif
+
free( sys );
}
@@ -503,11 +562,22 @@ static void *UserPtrThread (void *data)
demux_t *demux = data;
demux_sys_t *sys = demux->p_sys;
int fd = sys->fd;
- struct pollfd ufd[1];
-
+ struct pollfd ufd[2];
+ nfds_t numfds = 1;
+
ufd[0].fd = fd;
ufd[0].events = POLLIN;
+#ifdef ZVBI_COMPILED
+ if ( sys->vbi_cap )
+ {
+ ufd[1].fd = vbi_capture_fd(sys->vbi_cap);
+ ufd[1].events = POLLIN;
+ ufd[1].revents = 0;
+ numfds++;
+ }
+#endif
+
int canc = vlc_savecancel ();
for (;;)
{
@@ -522,25 +592,32 @@ static void *UserPtrThread (void *data)
/* Wait for data */
vlc_restorecancel (canc);
block_cleanup_push (block);
- while (poll (ufd, 1, -1) == -1)
+ while (poll (ufd, numfds, -1) == -1)
if (errno != EINTR)
msg_Err (demux, "poll error: %m");
vlc_cleanup_pop ();
canc = vlc_savecancel ();
- if (v4l2_ioctl (fd, VIDIOC_DQBUF, &buf) < 0)
+ if( ufd[0].revents & POLLIN )
{
- msg_Err (demux, "cannot dequeue buffer: %m");
- block_Release (block);
- continue;
+ if (v4l2_ioctl (fd, VIDIOC_DQBUF, &buf) < 0)
+ {
+ msg_Err (demux, "cannot dequeue buffer: %m");
+ block_Release (block);
+ continue;
+ }
+
+ assert (block->p_buffer == (void *)buf.m.userptr);
+ block->i_buffer = buf.length;
+ block->i_pts = block->i_dts = mdate ();
+ block->i_flags |= sys->block_flags;
+ es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
+ es_out_Send (demux->out, sys->es, block);
}
-
- assert (block->p_buffer == (void *)buf.m.userptr);
- block->i_buffer = buf.length;
- block->i_pts = block->i_dts = mdate ();
- block->i_flags |= sys->block_flags;
- es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
- es_out_Send (demux->out, sys->es, block);
+#ifdef ZVBI_COMPILED
+ if( sys->vbi_cap && (ufd[1].revents & POLLIN) )
+ GrabVBI(demux, sys->vbi_cap, sys->p_es_subt, VBI_NUM_CC_STREAMS);
+#endif
}
vlc_restorecancel (canc); /* <- hmm, this is purely cosmetic */
return NULL;
@@ -551,31 +628,49 @@ static void *MmapThread (void *data)
demux_t *demux = data;
demux_sys_t *sys = demux->p_sys;
int fd = sys->fd;
- struct pollfd ufd[1];
+ struct pollfd ufd[2];
+ nfds_t numfds = 1;
ufd[0].fd = fd;
ufd[0].events = POLLIN;
+#ifdef ZVBI_COMPILED
+ if ( sys->vbi_cap )
+ {
+ ufd[1].fd = vbi_capture_fd(sys->vbi_cap);
+ ufd[1].events = POLLIN;
+ ufd[1].revents = 0;
+ numfds++;
+ }
+#endif
+
for (;;)
{
/* Wait for data */
- if (poll (ufd, 1, -1) == -1)
+ if (poll (ufd, numfds, -1) == -1)
{
if (errno != EINTR)
msg_Err (demux, "poll error: %m");
continue;
}
- int canc = vlc_savecancel ();
- block_t *block = GrabVideo (VLC_OBJECT(demux), fd, sys->bufv);
- if (block != NULL)
+ if( ufd[0].revents & POLLIN )
{
- block->i_pts = block->i_dts = mdate ();
- block->i_flags |= sys->block_flags;
- es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
- es_out_Send (demux->out, sys->es, block);
+ int canc = vlc_savecancel ();
+ block_t *block = GrabVideo (VLC_OBJECT(demux), fd, sys->bufv);
+ if (block != NULL)
+ {
+ block->i_pts = block->i_dts = mdate ();
+ block->i_flags |= sys->block_flags;
+ es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
+ es_out_Send (demux->out, sys->es, block);
+ }
+ vlc_restorecancel (canc);
}
- vlc_restorecancel (canc);
+#ifdef ZVBI_COMPILED
+ if( sys->vbi_cap && (ufd[1].revents & POLLIN) )
+ GrabVBI(demux, sys->vbi_cap, sys->p_es_subt, VBI_NUM_CC_STREAMS);
+#endif
}
assert (0);
@@ -586,42 +681,60 @@ static void *ReadThread (void *data)
demux_t *demux = data;
demux_sys_t *sys = demux->p_sys;
int fd = sys->fd;
- struct pollfd ufd[1];
-
+ struct pollfd ufd[2];
+ nfds_t numfds = 1;
+
ufd[0].fd = fd;
ufd[0].events = POLLIN;
+#ifdef ZVBI_COMPILED
+ if ( sys->vbi_cap )
+ {
+ ufd[1].fd = vbi_capture_fd(sys->vbi_cap);
+ ufd[1].events = POLLIN;
+ ufd[1].revents = 0;
+ numfds++;
+ }
+#endif
+
for (;;)
{
/* Wait for data */
- if (poll (ufd, 1, -1) == -1)
+ if (poll (ufd, numfds, -1) == -1)
{
if (errno != EINTR)
msg_Err (demux, "poll error: %m");
continue;
}
- block_t *block = block_Alloc (sys->blocksize);
- if (unlikely(block == NULL))
+ if( ufd[0].revents & POLLIN )
{
- msg_Err (demux, "read error: %m");
- v4l2_read (fd, NULL, 0); /* discard frame */
- continue;
- }
- block->i_pts = block->i_dts = mdate ();
- block->i_flags |= sys->block_flags;
+ block_t *block = block_Alloc (sys->blocksize);
+ if (unlikely(block == NULL))
+ {
+ msg_Err (demux, "read error: %m");
+ v4l2_read (fd, NULL, 0); /* discard frame */
+ continue;
+ }
+ block->i_pts = block->i_dts = mdate ();
+ block->i_flags |= sys->block_flags;
- int canc = vlc_savecancel ();
- ssize_t val = v4l2_read (fd, block->p_buffer, block->i_buffer);
- if (val != -1)
- {
- block->i_buffer = val;
- es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
- es_out_Send (demux->out, sys->es, block);
+ int canc = vlc_savecancel ();
+ ssize_t val = v4l2_read (fd, block->p_buffer, block->i_buffer);
+ if (val != -1)
+ {
+ block->i_buffer = val;
+ es_out_Control (demux->out, ES_OUT_SET_PCR, block->i_pts);
+ es_out_Send (demux->out, sys->es, block);
+ }
+ else
+ block_Release (block);
+ vlc_restorecancel (canc);
}
- else
- block_Release (block);
- vlc_restorecancel (canc);
+#ifdef ZVBI_COMPILED
+ if( sys->vbi_cap && (ufd[1].revents & POLLIN) )
+ GrabVBI(demux, sys->vbi_cap, sys->p_es_subt, VBI_NUM_CC_STREAMS);
+#endif
}
assert (0);
}
diff --git a/modules/access/v4l2/v4l2.c b/modules/access/v4l2/v4l2.c
index 878fb81..73044ea 100644
--- a/modules/access/v4l2/v4l2.c
+++ b/modules/access/v4l2/v4l2.c
@@ -43,6 +43,10 @@
#define VIDEO_DEVICE_TEXT N_( "Video capture device" )
#define VIDEO_DEVICE_LONGTEXT N_("Video capture device node." )
+#define VBI_DEVICE_TEXT N_("VBI capture device")
+#define VBI_DEVICE_LONGTEXT N_( \
+ "The device node where VBI data can be read " \
+ " (for closed captions) " )
#define STANDARD_TEXT N_( "Standard" )
#define STANDARD_LONGTEXT N_( \
"Video standard (Default, SECAM, PAL, or NTSC)." )
@@ -277,6 +281,10 @@ vlc_module_begin ()
add_loadfile( CFG_PREFIX "dev", "/dev/video0",
VIDEO_DEVICE_TEXT, VIDEO_DEVICE_LONGTEXT, false )
change_safe()
+#ifdef ZVBI_COMPILED
+ add_loadfile( CFG_PREFIX "vbidev", NULL,
+ VBI_DEVICE_TEXT, VBI_DEVICE_LONGTEXT, false )
+#endif
add_string( CFG_PREFIX "standard", "",
STANDARD_TEXT, STANDARD_LONGTEXT, false )
change_string_list( standards_vlc, standards_user )
diff --git a/modules/access/v4l2/v4l2.h b/modules/access/v4l2/v4l2.h
index fa0e4e7..35685f3 100644
--- a/modules/access/v4l2/v4l2.h
+++ b/modules/access/v4l2/v4l2.h
@@ -20,6 +20,10 @@
#include "videodev2.h"
+#ifdef ZVBI_COMPILED
+# include <libzvbi.h>
+#endif
+
/* libv4l2 functions */
extern int v4l2_fd_open (int, int);
extern int (*v4l2_close) (int);
@@ -44,7 +48,7 @@ int OpenDevice (vlc_object_t *, const char *, uint32_t *);
v4l2_std_id var_InheritStandard (vlc_object_t *, const char *);
/* video.c */
-int SetupInput (vlc_object_t *, int fd);
+int SetupInput (vlc_object_t *, int fd, v4l2_std_id *std);
int SetupFormat (vlc_object_t *, int, uint32_t,
struct v4l2_format *, struct v4l2_streamparm *);
#define SetupFormat(o,fd,fcc,fmt,p) \
@@ -57,6 +61,14 @@ void StopMmap (int, struct buffer_t *, uint32_t);
block_t* GrabVideo (vlc_object_t *, int, const struct buffer_t *);
+#ifdef ZVBI_COMPILED
+/* vbi.c */
+vbi_capture* OpenVBIDev( vlc_object_t *, const char *);
+void GrabVBI( demux_t *p_demux, vbi_capture *vbi_cap,
+ es_out_id_t **p_es_subt, int num_streams);
+#define VBI_NUM_CC_STREAMS 4
+#endif
+
/* demux.c */
int DemuxOpen(vlc_object_t *);
void DemuxClose(vlc_object_t *);
diff --git a/modules/access/v4l2/video.c b/modules/access/v4l2/video.c
index 7636937..a38a172 100644
--- a/modules/access/v4l2/video.c
+++ b/modules/access/v4l2/video.c
@@ -39,7 +39,8 @@
#include "v4l2.h"
static int SetupStandard (vlc_object_t *obj, int fd,
- const struct v4l2_input *restrict input)
+ const struct v4l2_input *restrict input,
+ v4l2_std_id *std)
{
if (!(input->capabilities & V4L2_IN_CAP_STD))
{
@@ -47,18 +48,22 @@ static int SetupStandard (vlc_object_t *obj, int fd,
return 0;
}
- v4l2_std_id std = var_InheritStandard (obj, CFG_PREFIX"standard");
- if (std == V4L2_STD_UNKNOWN)
+ *std = var_InheritStandard (obj, CFG_PREFIX"standard");
+ if (*std == V4L2_STD_UNKNOWN)
{
msg_Warn (obj, "video standard not set");
+
+ /* Grab the currently selected standard */
+ if (v4l2_ioctl (fd, VIDIOC_G_STD, std) < 0)
+ msg_Err (obj, "cannot get video standard");
return 0;
}
- if (v4l2_ioctl (fd, VIDIOC_S_STD, &std) < 0)
+ if (v4l2_ioctl (fd, VIDIOC_S_STD, std) < 0)
{
- msg_Err (obj, "cannot set video standard 0x%"PRIx64": %m", std);
+ msg_Err (obj, "cannot set video standard 0x%"PRIx64": %m", *std);
return -1;
}
- msg_Dbg (obj, "video standard set to 0x%"PRIx64":", std);
+ msg_Dbg (obj, "video standard set to 0x%"PRIx64":", *std);
return 0;
}
@@ -230,7 +235,7 @@ static int ResetCrop (vlc_object_t *obj, int fd)
return 0;
}
-int SetupInput (vlc_object_t *obj, int fd)
+int SetupInput (vlc_object_t *obj, int fd, v4l2_std_id *std)
{
struct v4l2_input input;
@@ -263,7 +268,7 @@ int SetupInput (vlc_object_t *obj, int fd)
}
msg_Dbg (obj, "selected input %"PRIu32, input.index);
- SetupStandard (obj, fd, &input);
+ SetupStandard (obj, fd, &input, std);
switch (input.type)
{
--
1.7.9.5
More information about the vlc-devel
mailing list