Commit bf4eb094 authored by Devin Heitmueller's avatar Devin Heitmueller Committed by Rémi Denis-Courmont

Add NTSC EIA-608 caption rendering support via V4L2 VBI devices

This patch introduces support for using V4L2 VBI devices for
reading NTSC EIA-608 closed captions.

This patch includes feedback from the vlc-devel mailing list from
the Oct 21, 2012, Oct 28, 2012, and Nov 18, 2012 submission attempts.
Signed-off-by: Rémi Denis-Courmont's avatarRémi Denis-Courmont <remi@remlab.net>
parent 45059b5a
......@@ -134,14 +134,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
......
......@@ -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
......
......@@ -58,6 +58,11 @@ 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 *);
......@@ -65,6 +70,9 @@ 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);
#ifdef ZVBI_COMPILED
static int InitVBI (demux_t *);
#endif
int DemuxOpen( vlc_object_t *obj )
{
......@@ -74,6 +82,7 @@ int DemuxOpen( vlc_object_t *obj )
if (unlikely(sys == NULL))
return VLC_ENOMEM;
demux->p_sys = sys;
sys->vbi_cap = NULL;
ParseMRL( obj, demux->psz_location );
......@@ -264,6 +273,7 @@ static void GetAR (int fd, unsigned *restrict num, unsigned *restrict den)
static int InitVideo (demux_t *demux, int fd, uint32_t caps)
{
demux_sys_t *sys = demux->p_sys;
v4l2_std_id std;
if (!(caps & V4L2_CAP_VIDEO_CAPTURE))
{
......@@ -271,7 +281,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 */
......@@ -439,6 +449,17 @@ static int InitVideo (demux_t *demux, int fd, uint32_t caps)
return -1;
}
#ifdef ZVBI_COMPILED
char *vbi_path = var_InheritString (demux, CFG_PREFIX"vbidev");
if (vbi_path != NULL && (std & V4L2_STD_NTSC_M))
{
sys->vbi_cap = OpenVBIDev ((vlc_object_t *) demux, vbi_path);
if (sys->vbi_cap)
InitVBI(demux);
}
free(vbi_path);
#endif
if (vlc_clone (&sys->thread, entry, demux, VLC_THREAD_PRIORITY_INPUT))
{
if (sys->bufv != NULL)
......@@ -448,6 +469,31 @@ static int InitVideo (demux_t *demux, int fd, uint32_t caps)
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++)
{
es_format_t fmt;
es_format_Init( &fmt, SPU_ES, VLC_FOURCC('c', 'c', '1' + i, ' ') );
if (asprintf(&fmt.psz_description, "Closed captions %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 )
{
demux_t *demux = (demux_t *)obj;
......@@ -459,6 +505,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 +558,21 @@ 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;
numfds++;
}
#endif
int canc = vlc_savecancel ();
for (;;)
{
......@@ -522,25 +587,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 )
{
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 )
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 +623,48 @@ 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;
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 )
{
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 )
GrabVBI(demux, sys->vbi_cap, sys->p_es_subt, VBI_NUM_CC_STREAMS);
#endif
}
assert (0);
......@@ -586,42 +675,59 @@ 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;
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 )
{
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 )
GrabVBI(demux, sys->vbi_cap, sys->p_es_subt, VBI_NUM_CC_STREAMS);
#endif
}
assert (0);
}
......
......@@ -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 )
......
......@@ -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 *);
......
/*****************************************************************************
* vbi.c : Video4Linux2 VBI input module for vlc
*****************************************************************************
* Copyright (C) 2012 the VideoLAN team
*
* Author: Devin Heitmueller <dheitmueller at kernellabs dot com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <vlc_common.h>
#include <vlc_block.h>
#include <vlc_fs.h>
#include <vlc_demux.h>
#include "v4l2.h"
#ifdef ZVBI_COMPILED
vbi_capture* OpenVBIDev( vlc_object_t *p_obj, const char *psz_device)
{
vbi_capture *cap;
char* errstr = NULL;
//Can put more in here. See osd.c in zvbi package.
unsigned int services = VBI_SLICED_CAPTION_525;
int rawfd = vlc_open (psz_device, O_RDWR);
if (rawfd == -1)
{
msg_Err ( p_obj, "cannot open device '%s': %m", psz_device );
return NULL;
}
cap = vbi_capture_v4l2k_new (psz_device,
rawfd,
/* buffers */ 5,
&services,
/* strict */ 1,
&errstr,
/* verbose */ 1);
if (cap == NULL)
{
msg_Err( p_obj, "Cannot capture vbi data with v4l2 interface (%s)",
errstr );
free(errstr);
}
return cap;
}
void GrabVBI( demux_t *p_demux, vbi_capture *vbi_cap,
es_out_id_t **p_es_subt, int num_streams)
{
block_t *p_block=NULL;
vbi_capture_buffer *sliced_bytes;
struct timeval timeout={0,0}; /* poll */
int n_lines;
int r = vbi_capture_pull_sliced (vbi_cap, &sliced_bytes, &timeout);
switch (r) {
case -1:
msg_Err( p_demux, "Error reading VBI (%m)" );
break;
case 0: /* nothing avail */
break;
case 1: /* got data */
n_lines = sliced_bytes->size / sizeof(vbi_sliced);
if (n_lines)
{
int sliced_size = 2; /* Number of bytes per sliced line */
int size = (sliced_size + 1) * n_lines;
if( !( p_block = block_Alloc( size ) ) )
{
msg_Err( p_demux, "cannot get block" );
}
else
{
int field;
uint8_t* data = p_block->p_buffer;
vbi_sliced *sliced_array = sliced_bytes->data;
for(field=0; field<n_lines; field++)
{
*data = field;
data++;
memcpy(data, sliced_array[field].data, sliced_size);
data += sliced_size;
}
p_block->i_buffer = size;
p_block->i_pts = mdate();
}
}
}
if( p_block )
{
for (int i = 0; i < num_streams; i++)
{
block_t *p_sblock;
if (p_es_subt[i] == NULL)
continue;
p_sblock = block_Duplicate(p_block);
if (p_sblock)
es_out_Send(p_demux->out, p_es_subt[i], p_sblock);
}
block_Release(p_block);
}
return;
}
#endif
......@@ -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 *restrict 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)
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment