Commit d0a36c30 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

DTV: work-in-progress Linux DVBv5 input

This is incomplete and hence not automatically compiled. Conditional
Access, DVB-S and scanning are missing. DVB-T and ATSC are not tested.
DVB-C works.

It is not clear to me what the problem is, but it seems VLC does not
like the Linux DVB kernel demux. Only budget mode seems to work
correctly at the moment.
parent 882e06e0
......@@ -116,6 +116,15 @@ if HAVE_XCB
libvlc_LTLIBRARIES += libxcb_screen_plugin.la
endif
libdtv_plugin_la_SOURCES = \
dtv/dtv.h \
dtv/linux.c \
dtv/access.c
libdtv_plugin_la_CFLAGS = $(AM_CFLAGS)
libdtv_plugin_la_LIBADD = $(AM_LIBADD)
libdtv_plugin_la_DEPENDENCIES =
EXTRA_LTLIBRARIES += \
libaccess_rtmp_plugin.la \
libdtv_plugin.la \
libaccess_shm_plugin.la
/**
* @file access.c
* @brief Digital broadcasting input module for VLC media player
*/
/*****************************************************************************
* Copyright © 2011 Rémi Denis-Courmont
*
* This library 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 library 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 Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <vlc_common.h>
#include <vlc_access.h>
#include <vlc_plugin.h>
#include <vlc_dialog.h>
#include "dtv/dtv.h"
#define CACHING_TEXT N_("Caching value (ms)")
#define CACHING_LONGTEXT N_( \
"The cache size (delay) for digital broadcasts (in milliseconds).")
#define ADAPTER_TEXT N_("DVB adapter")
#define ADAPTER_LONGTEXT N_( \
"If there is more than one digital broadcasting adapter, " \
"the adapter number must be selected. Numbering start from zero.")
#define DEVICE_TEXT N_("DVB device")
#define DEVICE_LONGTEXT N_( \
"If the selected adapter has more than one tuner, " \
"the tuner number must be selected. Numbering start from zero.")
#define BUDGET_TEXT N_("Do not demultiplex")
#define BUDGET_LONGTEXT N_( \
"Only useful programs are normally demultiplexed from the transponder. " \
"This option will disable demultiplexing and receive all programs.")
#define FREQ_TEXT N_("Frequency (Hz)")
#define FREQ_LONGTEXT N_( \
"TV channels are grouped by transponder (a.k.a. multiplex) " \
"on a given frequency. This is required to tune the receiver.")
#define MODULATION_TEXT N_("Modulation / Constellation")
#define MODULATION_LONGTEXT N_( \
"The digital signal can be modulated according with different " \
"constellations (depending on the delivery system). " \
"If the demodulator cannot detect the constellation automatically, " \
"it needs to be configured manually.")
static const char *const modulation_vlc[] = { "",
"QAM", "16QAM", "32QAM", "64QAM", "128QAM", "256QAM",
"8VSB", "16VSB",
"QPSK", "DQPSK", "8PSK", "16APSK", "32APSK",
};
static const char *const modulation_user[] = { N_("Undefined"),
"Auto QAM", "16-QAM", "32-QAM", "64-QAM", "128-QAM", "256-QAM",
"8-VSB", "16-VSB",
"QPSK", "DQPSK", "8-PSK", "16-APSK", "32-APSK",
};
#define SRATE_TEXT N_("Symbol rate (bauds)")
#define SRATE_LONGTEXT N_( \
"The symbol rate must be specified manually for some systems, " \
"notably DVB-C, DVB-S and DVB-S2.")
#define INVERSION_TEXT N_("Spectrum inversion")
#define INVERSION_LONGTEXT N_( \
"If the demodulator cannot detect spectral inversion correctly, " \
"it needs to be configured manually.")
//TODO const int inversion_linux[] = { INVERSION_AUTO,
// INVERSION_OFF, INVERSION_ON,
//};
const int inversion_vlc[] = { -1, 0, 1 };
static const char *const auto_off_on[] = { N_("Automatic"),
N_("Off"), N_("On") };
#define CODE_RATE_TEXT N_("FEC code rate")
#define CODE_RATE_HP_TEXT N_("High-priority code rate")
#define CODE_RATE_LP_TEXT N_("Low-priority code rate")
#define CODE_RATE_LONGTEXT N_( \
"The code rate for Forward Error Correction can be specified.")
static const char *const code_rate_vlc[] = { "",
"none", /*"1/4", "1/3",*/ "1/2", "3/5", "2/3", "3/4",
"4/5", "5/6", "6/7", "7/8", "8/9", "9/10",
};
static const char *const code_rate_user[] = { N_("Automatic"),
N_("None"), /*"1/4", "1/3",*/ "1/2", "3/5", "2/3", "3/4",
"4/5", "5/6", "6/7", "7/8", "8/9", "9/10",
};
#define TRANSMISSION_TEXT N_("Transmission mode")
const int transmission_vlc[] = { -1,
2, 4, 8, /*16, 32,*/
};
static const char *const transmission_user[] = { N_("Automatic"),
"2k", "4k", "8k", /*"16k", "32k", */
};
#define BANDWIDTH_TEXT N_("Bandwidth (MHz)")
const int bandwidth_vlc[] = { 0,
8, 7, 6,
};
static const char *const bandwidth_user[] = { N_("Automatic"),
N_("8 MHz"), N_("7 MHz"), N_("6 MHz"),
};
#define GUARD_TEXT N_("Guard interval")
const char *const guard_vlc[] = { "",
/*"1/128",*/ "1/32", "1/16", /*"19/128",*/ "1/8", /*"9/256",*/ "1/4",
};
static const char *const guard_user[] = { N_("Automatic"),
/*"1/128",*/ "1/32", "1/16", /*"19/128",*/ "1/8", /*"9/256",*/ "1/4",
};
#define HIERARCHY_TEXT N_("Hierarchy mode")
const int hierarchy_vlc[] = { -1,
0, 1, 2, 4,
};
static const char *const hierarchy_user[] = { N_("Automatic"),
N_("None"), "1", "2", "4",
};
static int Open (vlc_object_t *);
static void Close (vlc_object_t *);
vlc_module_begin ()
set_shortname (N_("DTV"))
set_description (N_("Digital Television and Radio (Linux DVB)"))
set_category (CAT_INPUT)
set_subcategory (SUBCAT_INPUT_ACCESS)
set_capability ("access", 0)
set_callbacks (Open, Close)
add_shortcut ("dtv", "tv", "dvb", /* "radio", "dab",*/
"cable", "dvb-c", /*"satellite", "dvb-s", "dvb-s2",*/
"terrestrial", "dvb-t", "atsc")
/* All options starting with dvb- can be overriden in the MRL, so they
* must all be "safe". Nevertheless, we do not mark as safe those that are
* really specific to the local system (e.g. device ID...).
* It wouldn't make sense to deliver those through a playlist. */
add_integer ("dvb-caching", DEFAULT_PTS_DELAY / 1000,
CACHING_TEXT, CACHING_LONGTEXT, true)
change_integer_range (0, 60000)
change_safe ()
#ifdef __linux__
add_integer ("dvb-adapter", 0, ADAPTER_TEXT, ADAPTER_LONGTEXT, false)
change_integer_range (0, 255)
add_integer ("dvb-device", 0, DEVICE_TEXT, DEVICE_LONGTEXT, false)
change_integer_range (0, 255)
add_bool ("dvb-budget-mode", false, BUDGET_TEXT, BUDGET_LONGTEXT, true)
#endif
add_integer ("dvb-frequency", 0, FREQ_TEXT, FREQ_LONGTEXT, false)
change_integer_range (0, UINT64_C(0xffffffff) * 1000)
change_safe ()
add_string ("dvb-modulation", 0,
MODULATION_TEXT, MODULATION_LONGTEXT, false)
change_string_list (modulation_vlc, modulation_user, NULL)
change_safe ()
/* Legacy modulation option for backward compatibility: TODO */
/*add_integer ("dvb-modulation", 0, " ", " ", true)
change_integer_range (-1, 256)
change_private ()
change_safe ()*/
add_integer ("dvb-inversion", -1, INVERSION_TEXT, INVERSION_LONGTEXT, true)
change_integer_list (inversion_vlc, auto_off_on)
change_safe ()
set_section (N_("Terrestrial reception parameters"), NULL)
add_integer ("dvb-bandwidth", -1, BANDWIDTH_TEXT, BANDWIDTH_TEXT, true)
change_integer_list (bandwidth_vlc, bandwidth_user)
change_safe ()
add_string ("dvb-code-rate-hp", "",
CODE_RATE_HP_TEXT, CODE_RATE_LONGTEXT, false)
change_string_list (code_rate_vlc, code_rate_user, NULL)
change_safe ()
add_string ("dvb-code-rate-lp", "",
CODE_RATE_LP_TEXT, CODE_RATE_LONGTEXT, false)
change_string_list (code_rate_vlc, code_rate_user, NULL)
change_safe ()
add_integer ("dvb-transmission", 0,
TRANSMISSION_TEXT, TRANSMISSION_TEXT, true)
change_integer_list (transmission_vlc, transmission_user)
change_safe ()
add_string ("dvb-guard", "", GUARD_TEXT, GUARD_TEXT, true)
change_string_list (guard_vlc, guard_user, NULL)
change_safe ()
add_integer ("dvb-hierarchy", -1, HIERARCHY_TEXT, HIERARCHY_TEXT, true)
change_integer_list (hierarchy_vlc, hierarchy_user)
change_safe ()
set_section (N_("Cable (DVB-C) and satellite (DVB-S) parameters"), NULL)
add_integer ("dvb-srate", 0, SRATE_TEXT, SRATE_LONGTEXT, false)
change_integer_range (0, UINT64_C(0xffffffff))
change_safe ()
add_string ("dvb-code-rate", "", CODE_RATE_TEXT, CODE_RATE_LONGTEXT, true)
change_string_list (code_rate_vlc, code_rate_user, NULL)
change_safe ()
/* Legacy FEC option for backward compatibility: TODO */
add_integer ("dvb-fec", 9, " ", " ", true)
change_integer_range (0, 9)
change_private ()
change_safe ()
#ifdef FIXME
set_section (N_("Satellite (DVB-S) parameters"), NULL)
add_integer ("dvb-satno", 0, SATNO_TEXT, SATNO_LONGTEXT, true)
change_integer_list (satno_vlc, satno_user)
change_safe ()
add_integer ("dvb-voltage", 13, VOLTAGE_TEXT, VOLTAGE_LONGTEXT, true)
change_integer_list (voltage_vlc, voltage_user)
change_safe ()
add_bool ("dvb-high-voltage", false,
HIGH_VOLTAGE_TEXT, HIGH_VOLTAGE_LONGTEXT, false)
add_integer ("dvb-tone", -1, TONE_TEXT, TONE_LONGTEXT, true)
change_integer_list (tone_vlc, auto_off_on)
change_safe ()
add_integer ("dvb-lnb-lof1", 0, LNB_LOF1_TEXT, LNB_LOF1_LONGTEXT, true)
change_integer_range (0, 0x7fffffff)
change_safe ()
add_integer ("dvb-lnb-lof2", 0, LNB_LOF2_TEXT, LNB_LOF2_LONGTEXT, true)
change_integer_range (0, 0x7fffffff)
change_safe ()
add_integer ("dvb-lnb-slof", 0, LNB_SLOF_TEXT, LNB_SLOF_LONGTEXT, true)
change_integer_range (0, 0x7fffffff)
change_safe ()
#endif
vlc_module_end ()
typedef struct
{
uint16_t pid;
uint16_t refs;
} pid_ref_t;
struct access_sys_t
{
dvb_device_t *dev;
pid_ref_t *refv;
size_t refc;
};
struct delsys
{
int (*tune) (vlc_object_t *, dvb_device_t *, uint64_t freq);
/* TODO: scan stuff */
};
static block_t *Read (access_t *);
static int Control (access_t *, int, va_list);
static const delsys_t *GuessSystem (const char *scheme, dvb_device_t *dev)
{
/* NOTE: We should guess the delivery system for the "cable", "satellite"
* and "terrestrial" shortcuts (i.e. DVB, ISDB, ATSC...). But there is
* seemingly no sane way to do get the info with Linux DVB version 5.2.
* In particular, the frontend infos distinguish only the modulator class
* (QPSK, QAM, OFDM or ATSC).
*
* Furthermore, if the demodulator supports 2G, we cannot guess whether
* 1G or 2G is intended. For backward compatibility, 1G is assumed
* (this is not a limitation of Linux DVB). We will probably need something
* smarter when 2G (semi automatic) scanning is implemented. */
if (!strcasecmp (scheme, "cable"))
scheme = "dvb-c";
else
if (!strcasecmp (scheme, "satellite"))
scheme = "dvb-s";
else
if (!strcasecmp (scheme, "terrestrial"))
scheme = "dvb-t";
if (!strcasecmp (scheme, "atsc"))
return &atsc;
if (!strcasecmp (scheme, "dvb-c"))
return &dvbc;
if (!strcasecmp (scheme, "dvb-s"))
return &dvbs;
if (!strcasecmp (scheme, "dvb-s2"))
return &dvbs2;
if (!strcasecmp (scheme, "dvb-t"))
return &dvbt;
return dvb_guess_system (dev);
}
static int Open (vlc_object_t *obj)
{
access_t *access = (access_t *)obj;
access_sys_t *sys = malloc (sizeof (*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
var_LocationParse (obj, access->psz_location, "dvb-");
uint64_t freq = var_InheritInteger (obj, "dvb-frequency");
dvb_device_t *dev = dvb_open (obj, freq != 0);
if (dev == NULL)
{
free (sys);
return VLC_EGENERIC;
}
sys->dev = dev;
sys->refv = NULL;
sys->refc = 0;
access->p_sys = sys;
if (freq != 0)
{
const delsys_t *delsys = GuessSystem (access->psz_access, dev);
if (delsys == NULL || delsys->tune (obj, dev, freq))
{
msg_Err (obj, "tuning to %"PRIu64" Hz failed", freq);
dialog_Fatal (obj, N_("Digital broadcasting"),
N_("The selected digital tuner does not support "
"the specified parameters. "
"Please check the preferences."));
goto error;
}
}
dvb_add_pid (dev, 0);
access->pf_block = Read;
access->pf_control = Control;
if (access->psz_demux == NULL || !access->psz_demux[0])
{
free (access->psz_demux);
access->psz_demux = strdup ("ts");
}
return VLC_SUCCESS;
error:
Close (obj);
access->p_sys = NULL;
return VLC_EGENERIC;
}
static void Close (vlc_object_t *obj)
{
access_t *access = (access_t *)obj;
access_sys_t *sys = access->p_sys;
free (sys->refv);
dvb_close (sys->dev);
free (sys);
}
static block_t *Read (access_t *access)
{
#define BUFSIZE (20*188)
block_t *block = block_Alloc (BUFSIZE);
if (unlikely(block == NULL))
return NULL;
access_sys_t *sys = access->p_sys;
ssize_t val = dvb_read (sys->dev, block->p_buffer, BUFSIZE);
if (val <= 0)
{
if (val == 0)
access->info.b_eof = true;
block_Release (block);
return NULL;
}
block->i_buffer = val;
return block;
}
/* A PID can be enabled multiple times by the demux, but packets must not be
* duplicated, so we need to do some reference counting. */
static int pid_ref (access_sys_t *sys, uint16_t pid)
{
pid_ref_t *tab = sys->refv;
size_t n = sys->refc;
for (size_t i = 0; i < n; i++)
{
if (tab[i].pid == pid)
return ++tab[i].refs;
}
tab = realloc (tab, (n + 1) * sizeof (*tab));
if (unlikely(tab == NULL))
return -1;
tab[n].pid = pid;
tab[n].refs = 1;
sys->refv = tab;
sys->refc = n;
return 0;
}
static int pid_unref (access_sys_t *sys, uint16_t pid)
{
pid_ref_t *tab = sys->refv;
size_t n = sys->refc;
/* FIXME? bound memory leak (worst case 32kb) until Close() */
for (size_t i = 0; i < n; i++)
{
if (tab[i].pid == pid)
return --tab[i].refs;
}
return -1;
}
static int Control (access_t *access, int query, va_list args)
{
access_sys_t *sys = access->p_sys;
dvb_device_t *dev = sys->dev;
switch (query)
{
case ACCESS_CAN_SEEK:
case ACCESS_CAN_FASTSEEK:
case ACCESS_CAN_PAUSE:
case ACCESS_CAN_CONTROL_PACE:
{
bool *v = va_arg (args, bool *);
*v = false;
return VLC_SUCCESS;
}
case ACCESS_GET_PTS_DELAY:
{
int64_t *v = va_arg (args, int64_t *);
*v = var_InheritInteger (access, "dvb-caching") * INT64_C(1000);
return VLC_SUCCESS;
}
case ACCESS_GET_TITLE_INFO:
case ACCESS_GET_META:
return VLC_EGENERIC;
case ACCESS_GET_CONTENT_TYPE:
{
char **pt = va_arg (args, char **);
*pt = strdup ("video/MP2T");
return VLC_SUCCESS;
}
case ACCESS_SET_PAUSE_STATE:
case ACCESS_SET_TITLE:
case ACCESS_SET_SEEKPOINT:
return VLC_EGENERIC;
case ACCESS_GET_SIGNAL:
*va_arg (args, double *) = dvb_get_snr (dev);
*va_arg (args, double *) = dvb_get_signal_strength (dev);
return VLC_SUCCESS;
case ACCESS_SET_PRIVATE_ID_STATE:
{
unsigned pid = va_arg (args, unsigned);
bool add = va_arg (args, unsigned);
if (unlikely(pid > 0x1FFF))
return VLC_EGENERIC;
if (add)
{
if (pid_ref (sys, pid) == 0 && dvb_add_pid (dev, pid))
return VLC_EGENERIC;
}
else
{
if (pid_unref (sys, pid) == 0)
dvb_remove_pid (sys->dev, pid);
}
return VLC_SUCCESS;
}
case ACCESS_SET_PRIVATE_ID_CA:
/* TODO */
return VLC_EGENERIC;
case ACCESS_GET_PRIVATE_ID_STATE:
return VLC_EGENERIC;
}
msg_Warn (access, "unimplemented query %d in control", query);
return VLC_EGENERIC;
}
/*** ATSC ***/
static int atsc_tune (vlc_object_t *obj, dvb_device_t *dev, uint64_t freq)
{
char *mod = var_InheritString (obj, "dvb-modulation");
int ret = dvb_set_atsc (dev, freq, mod);
free (mod);
if (ret == 0)
ret = dvb_tune (dev);
return ret;
}
const delsys_t atsc = { .tune = atsc_tune };
/*** DVB-C ***/
static int dvbc_tune (vlc_object_t *obj, dvb_device_t *dev, uint64_t freq)
{
char *mod = var_InheritString (obj, "dvb-modulation");
char *fec = var_InheritString (obj, "dvb-code-rate");
unsigned srate = var_InheritInteger (obj, "dvb-srate");
int ret = dvb_set_dvbc (dev, freq, mod, srate, fec);
free (fec);
free (mod);
if (ret == 0)
ret = dvb_tune (dev);
return ret;
}
const delsys_t dvbc = { .tune = dvbc_tune };
/*** DVB-S ***/
static int dvbs_tune (vlc_object_t *obj, dvb_device_t *dev, uint64_t freq)
{
(void) dev; (void) freq;
msg_Err (obj, "DVB-S not implemented");
return -1;
}
const delsys_t dvbs = { .tune = dvbs_tune };
const delsys_t dvbs2 = { .tune = dvbs_tune };
/*** DVB-T ***/
static int dvbt_tune (vlc_object_t *obj, dvb_device_t *dev, uint64_t freq)
{
char *mod = var_InheritString (obj, "dvb-modulation");
char *fec_hp = var_InheritString (obj, "dvb-code-rate-hp");
char *fec_lp = var_InheritString (obj, "dvb-code-rate-lp");
char *guard = var_InheritString (obj, "dvb-guard");
uint32_t bw = var_InheritInteger (obj, "dvb-bandwidth");
int tx = var_InheritInteger (obj, "dvb-transmission");
int h = var_InheritInteger (obj, "dvb-hierarchy");
int ret = dvb_set_dvbt (dev, freq, mod, fec_hp, fec_lp, bw, tx, guard, h);
free (guard);
free (fec_lp);
free (fec_hp);
free (mod);
if (ret == 0)
ret = dvb_tune (dev);
return ret;
}
const delsys_t dvbt = { .tune = dvbt_tune };
/**
* @file dtv.h
* @brief Digital TV module common header
*/
/*****************************************************************************
* Copyright © 2011 Rémi Denis-Courmont
*
* This library 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 library 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 Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
****************************************************************************/
typedef struct delsys delsys_t;
extern const delsys_t dvbc, dvbs, dvbs2, dvbt, atsc;
typedef struct dvb_device dvb_device_t;
dvb_device_t *dvb_open (vlc_object_t *obj, bool tune);
void dvb_close (dvb_device_t *);
ssize_t dvb_read (dvb_device_t *, void *, size_t);
int dvb_add_pid (dvb_device_t *, uint16_t);
void dvb_remove_pid (dvb_device_t *, uint16_t);
const delsys_t *dvb_guess_system (dvb_device_t *);
float dvb_get_signal_strength (dvb_device_t *);
float dvb_get_snr (dvb_device_t *);
int dvb_set_inversion (dvb_device_t *, int);
int dvb_tune (dvb_device_t *);
/* DVB-C */
int dvb_set_dvbc (dvb_device_t *, uint32_t freq, const char *mod,
uint32_t srate, const char *fec);
/* DVB-S */
int dvb_set_dvbs (dvb_device_t *, uint64_t freq, uint32_t srate,
const char *fec);
int dvb_set_dvbs2 (dvb_device_t *, uint64_t freq, const char *mod,
uint32_t srate, const char *fec, int pilot, int rolloff);
int dvb_set_sec (dvb_device_t *, bool tone, int voltage, bool high_voltage);
/* XXX^^ */
/* DVB-T */
int dvb_set_dvbt (dvb_device_t *, uint32_t freq, const char *mod,
const char *fec_hp, const char *fec_lp, uint32_t bandwidth,
int transmission, const char *guard, int hierarchy);
/* ATSC */
int dvb_set_atsc (dvb_device_t *, uint32_t freq, const char *mod);
/* ISDB-T */
typedef struct isdbt_layer
{
const char *modulation;
const char *code_rate;
uint8_t segment_count;
uint8_t time_interleaving;
} isdbt_layer_t;
typedef struct isdbt_sound
{
uint8_t subchannel_id;
uint8_t segment_index; uint8_t segment_cound;
} isdbt_sound_t;
int dvb_set_isdbt (dvb_device_t *, uint32_t freq, const isdbt_layer_t *a,
const isdbt_layer_t *b, const isdbt_layer_t *c,
const isdbt_sound_t *sb);
/**
* @file linux.c
* @brief Linux DVB API version 5
*/
/*****************************************************************************
* Copyright © 2011 Rémi Denis-Courmont
*
* This library 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 library 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 Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <vlc_common.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#include "dtv/dtv.h"
#ifndef O_SEARCH
# define O_SEARCH O_RDONLY
#endif
/** Opens the device directory for the specified DVB adapter */
static int dvb_open_adapter (uint8_t adapter)
{
char dir[20];
snprintf (dir, sizeof (dir), "/dev/dvb/adapter%"PRIu8, adapter);
return open (dir, O_SEARCH|O_DIRECTORY|O_CLOEXEC);
}
/** Opens the DVB device node of the specified type */
static int dvb_open_node (int dirfd, uint8_t dev, const char *type, int flags)
{
int fd;
char path[strlen (type) + 4];
snprintf (path, sizeof (path), "%s%"PRIu8, type, dev);
fd = openat (dirfd, path, flags|O_CLOEXEC);
if (fd != -1)
fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK);
return fd;
}
typedef struct
{
int vlc;
int linux_;
} dvb_int_map_t;
static int icmp (const void *a, const void *b)
{
int key = (intptr_t)a;
const dvb_int_map_t *entry = b;
return key - entry->vlc;
}
/** Maps a VLC config integer to a Linux DVB enum value */
static int dvb_parse_int (int i, const dvb_int_map_t *map, size_t n, int def)
{
const void *k = (const void *)(intptr_t)i;
const dvb_int_map_t *p = bsearch (k, map, n, sizeof (*map), icmp);
return (p != NULL) ? p->linux_ : def;
}
typedef const struct
{
char vlc[8];
int linux_;
} dvb_str_map_t;
static int scmp (const void *a, const void *b)
{
const char *key = a;
dvb_str_map_t *entry = b;
return strcmp (key, entry->vlc);
}
/** Maps a VLC config string to a Linux DVB enum value */
static int dvb_parse_str (const char *str, const dvb_str_map_t *map, size_t n,
int def)
{
if (str != NULL)
{
const dvb_str_map_t *p = bsearch (str, map, n, sizeof (*map), scmp);
if (p != NULL)
def = p->linux_;
}
return def;
}
/*** Modulations ***/
static int dvb_parse_modulation (const char *str, int def)
{
static const dvb_str_map_t mods[] =
{
{ "128QAM", QAM_128 },
{ "16APSK", APSK_16 },
{ "16QAM", QAM_16 },
{ "16VSB", VSB_16 },
{ "256QAM", QAM_256 },
{ "32APSK", APSK_32 },
{ "32QAM", QAM_32 },
{ "64QAM", QAM_64 },
{ "8PSK", PSK_8 },
{ "8VSB", VSB_8 },
{ "DQPSK", DQPSK },
{ "QAM", QAM_AUTO },
{ "QPSK", QPSK },
};
return dvb_parse_str (str, mods, sizeof (mods) / sizeof (*mods), def);
}
static int dvb_parse_fec (const char *str)
{
static const dvb_str_map_t rates[] =
{
{ "", FEC_AUTO },
{ "1/2", FEC_1_2 },
// TODO: 1/3
// TODO: 1/4
{ "2/3", FEC_2_3 },
{ "3/4", FEC_3_4 },
{ "4/5", FEC_4_5 },
{ "5/6", FEC_5_6 },
{ "6/7", FEC_6_7 },
{ "7/8", FEC_7_8 },
{ "8/9", FEC_8_9 },
{ "9/10", FEC_9_10 },
};
return dvb_parse_str (str, rates, sizeof (rates) / sizeof (*rates),
FEC_AUTO);
}
struct dvb_device
{
vlc_object_t *obj;
int frontend;
int demux;
int ca;
struct dvb_frontend_info info;
bool budget;
//size_t buffer_size;
};
/**
* Opens the DVB tuner
*/
dvb_device_t *dvb_open (vlc_object_t *obj, bool tune)
{
uint8_t adapter = var_InheritInteger (obj, "dvb-adapter");
uint8_t device = var_InheritInteger (obj, "dvb-device");
int dirfd = dvb_open_adapter (adapter);
if (dirfd == -1)
{
msg_Err (obj, "cannot access adapter %"PRIu8": %m", adapter);
return NULL;
}
dvb_device_t *d = malloc (sizeof (*d));
if (unlikely(d == NULL))
{
close (dirfd);
return NULL;
}
d->obj = obj;
d->demux = dvb_open_node (dirfd, device, "demux", O_RDONLY);
if (d->demux == -1)
{
msg_Err (obj, "cannot access demux %"PRIu8" of adapter %"PRIu8": %m",
device, adapter);
free (d);
close (dirfd);
return NULL;
}
/* Use the same size as socket receive buffer. This is enough for IPTV so
* it should be enough for DVB too. */
if (ioctl (d->demux, DMX_SET_BUFFER_SIZE, 1 << 18) < 0)
msg_Warn (obj, "cannot expand demultiplexing buffer: %m");
/* We need to filter at least one PID. The tap for TS demultiplexing cannot
* be configured otherwise. So add the PAT. */
d->budget = var_InheritBool (obj, "dvb-budget-mode");
{
struct dmx_pes_filter_params param;
param.pid = d->budget ? 0x2000 : 0x000;
param.input = DMX_IN_FRONTEND;
param.output = DMX_OUT_TSDEMUX_TAP;
param.pes_type = DMX_PES_OTHER;
param.flags = DMX_IMMEDIATE_START;
if (ioctl (d->demux, DMX_SET_PES_FILTER, &param) < 0)
{
msg_Err (obj, "cannot setup TS demultiplexer");
goto error;
}
}
d->frontend = -1;
d->ca = -1;
if (tune)
{
d->frontend = dvb_open_node (dirfd, device, "frontend", O_RDWR);
if (d->frontend == -1)
{
msg_Err (obj, "cannot access frontend %"PRIu8
" of adapter %"PRIu8": %m", device, adapter);
goto error;
}
if (ioctl (d->frontend, FE_GET_INFO, &d->info) < 0)
{
msg_Err (obj, "cannot get frontend info: %m");
goto error;
}
msg_Dbg (obj, "using frontend: %s", d->info.name);
msg_Dbg (obj, " type %u, capabilities 0x%08X", d->info.type,
d->info.caps);
d->ca = dvb_open_node (dirfd, device, "ca", O_RDWR);
if (d->ca == -1)
msg_Dbg (obj, "conditional access module not available (%m)");
}
close (dirfd);
return d;
error:
close (dirfd);
dvb_close (d);
return NULL;
}
void dvb_close (dvb_device_t *d)
{
if (d->ca != -1)
close (d->ca);
if (d->frontend != -1)
close (d->frontend);
close (d->demux);
free (d);
}
/**
* Reads TS data from the tuner.
* @return number of bytes read, 0 on EOF, -1 if no data (yet).
*/
ssize_t dvb_read (dvb_device_t *d, void *buf, size_t len)
{
struct pollfd ufd[2];
int n;
ufd[0].fd = d->demux;
ufd[0].events = POLLIN;
if (d->frontend != -1)
{
ufd[1].fd = d->frontend;
ufd[1].events = POLLIN;
n = 2;
}
else
n = 1;
if (poll (ufd, n, 500 /* FIXME */) < 0)
return -1;
if (d->frontend != -1 && ufd[1].revents)
{
struct dvb_frontend_event ev;
if (ioctl (d->frontend, FE_GET_EVENT, &ev) < 0)
{
if (errno == EOVERFLOW)
{
msg_Err (d->obj, "cannot dequeue events fast enough!");
return -1;
}
msg_Err (d->obj, "cannot dequeue frontend event: %m");
return 0;
}
msg_Dbg (d->obj, "frontend status: 0x%02X", (unsigned)ev.status);
}
if (ufd[0].revents)
{
ssize_t val = read (d->demux, buf, len);
if (val == -1 && (errno != EAGAIN && errno != EINTR))
{
if (errno == EOVERFLOW)
{
msg_Err (d->obj, "cannot demux data fast enough!");
return -1;
}
msg_Err (d->obj, "cannot demux: %m");
return 0;
}
return val;
}
return -1;
}
int dvb_add_pid (dvb_device_t *d, uint16_t pid)
{
if (d->budget || pid == 0)
return 0;
if (ioctl (d->demux, DMX_ADD_PID, &pid) >= 0)
return 0;
msg_Err (d->obj, "cannot add PID 0x%04"PRIu16": %m", pid);
return -1;
}
void dvb_remove_pid (dvb_device_t *d, uint16_t pid)
{
if (d->budget || pid == 0)
return;
ioctl (d->demux, DMX_REMOVE_PID, &pid);
}
const delsys_t *dvb_guess_system (dvb_device_t *d)
{
assert (d->frontend != -1);
//bool v2 = d->info.caps & FE_CAN_2G_MODULATION;
switch (d->info.type)
{
case FE_QPSK: return /*v2 ? &dvbs2 :*/ &dvbs;
case FE_QAM: return &dvbc;
case FE_OFDM: return &dvbt;
case FE_ATSC: return &atsc;
}
return NULL;
}
float dvb_get_signal_strength (dvb_device_t *d)
{
uint16_t strength;
if (ioctl (d->frontend, FE_READ_SIGNAL_STRENGTH, &strength) < 0)
return 0.;
return strength / 65535.;
}
float dvb_get_snr (dvb_device_t *d)
{
uint16_t snr;
if (ioctl (d->frontend, FE_READ_SNR, &snr) < 0)
return 0.;
return snr / 65535.;
}
static int dvb_vset_props (dvb_device_t *d, size_t n, va_list ap)
{
struct dtv_property buf[n], *prop = buf;
struct dtv_properties props = { .num = n, .props = buf };
memset (prop, 0, sizeof (prop));
while (n > 0)
{
prop->cmd = va_arg (ap, uint32_t);
prop->u.data = va_arg (ap, uint32_t);
msg_Dbg (d->obj, "setting property %"PRIu32" to %"PRIu32,
prop->cmd, prop->u.data);
prop++;
n--;
}
if (ioctl (d->frontend, FE_SET_PROPERTY, &props) < 0)
{
msg_Err (d->obj, "cannot set frontend tuning parameters: %m");
return -1;
}
return 0;
}
static int dvb_set_props (dvb_device_t *d, size_t n, ...)
{
va_list ap;
int ret;
va_start (ap, n);
ret = dvb_vset_props (d, n, ap);
va_end (ap);
return ret;
}
static int dvb_set_prop (dvb_device_t *d, uint32_t prop, uint32_t val)
{
return dvb_set_props (d, 1, prop, val);
}
int dvb_set_inversion (dvb_device_t *d, int v)
{
switch (v)
{
case 0: v = INVERSION_OFF; break;
case 1: v = INVERSION_ON; break;
default: v = INVERSION_AUTO; break;
}
return dvb_set_prop (d, DTV_INVERSION, v);
}
int dvb_tune (dvb_device_t *d)
{
return dvb_set_prop (d, DTV_TUNE, 0 /* dummy */);
}
/*** DVB-C ***/
int dvb_set_dvbc (dvb_device_t *d, uint32_t freq, const char *modstr,
uint32_t srate, const char *fecstr)
{
unsigned mod = dvb_parse_modulation (modstr, QAM_AUTO);
unsigned fec = dvb_parse_fec (fecstr);
return dvb_set_props (d, 5, DTV_CLEAR, 0,
DTV_DELIVERY_SYSTEM, SYS_DVBC_ANNEX_AC,
DTV_FREQUENCY, freq, DTV_MODULATION, mod,
DTV_SYMBOL_RATE, srate, DTV_INNER_FEC, fec);
}
/*** DVB-S ***/
/* TODO */
/*** DVB-T ***/
static int dvb_parse_transmit_mode (int i)
{
static const dvb_int_map_t tab[] = {
{ -1, TRANSMISSION_MODE_AUTO },
{ 2, TRANSMISSION_MODE_2K },
{ 4, TRANSMISSION_MODE_4K },
{ 8, TRANSMISSION_MODE_8K },
#if 0
{ 16, TRANSMISSION_MODE_16K },
{ 32, TRANSMISSION_MODE_32K },
#endif
};
return dvb_parse_int (i, tab, sizeof (tab) / sizeof (*tab),
TRANSMISSION_MODE_AUTO);
}
static int dvb_parse_guard (const char *str)
{
static const dvb_str_map_t tab[] = {
{ "", GUARD_INTERVAL_AUTO },
/*{ "1/128", GUARD_INTERVAL_1_128 },*/
{ "1/16", GUARD_INTERVAL_1_16 },
{ "1/32", GUARD_INTERVAL_1_32 },
{ "1/4", GUARD_INTERVAL_1_4 },
{ "1/8", GUARD_INTERVAL_1_8 },
/*{ "19/128", GUARD_INTERVAL_19_128 },*/
/*{ "9/256", GUARD_INTERVAL_9_256 },*/
};
return dvb_parse_str (str, tab, sizeof (tab) / sizeof (*tab),
GUARD_INTERVAL_AUTO);
}
static int dvb_parse_hierarchy (int i)
{
static const dvb_int_map_t tab[] = {
{ HIERARCHY_AUTO, -1 },
{ HIERARCHY_NONE, 0 },
{ HIERARCHY_1, 1 },
{ HIERARCHY_2, 2 },
{ HIERARCHY_4, 4 },
};
return dvb_parse_int (i, tab, sizeof (tab) / sizeof (*tab),
HIERARCHY_AUTO);
}
int dvb_set_dvbt (dvb_device_t *d, uint32_t freq, const char *modstr,
const char *fechstr, const char *feclstr, uint32_t bandwidth,
int transmit_val, const char *guardstr, int hierarchy_val)
{
uint32_t mod = dvb_parse_modulation (modstr, QAM_AUTO);
uint32_t fec_hp = dvb_parse_fec (fechstr);
uint32_t fec_lp = dvb_parse_fec (feclstr);
bandwidth *= 1000000;
uint32_t transmit_mode = dvb_parse_transmit_mode (transmit_val);
uint32_t guard_it = dvb_parse_guard (guardstr);
uint32_t hierarchy = dvb_parse_hierarchy (hierarchy_val);
return dvb_set_props (d, 10, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_DVBT,
DTV_FREQUENCY, freq, DTV_MODULATION, mod,
DTV_CODE_RATE_HP, fec_hp, DTV_CODE_RATE_LP, fec_lp,
DTV_BANDWIDTH_HZ, bandwidth,
DTV_TRANSMISSION_MODE, transmit_mode,
DTV_GUARD_INTERVAL, guard_it,
DTV_HIERARCHY, hierarchy);
}
/*** ATSC ***/
int dvb_set_atsc (dvb_device_t *d, uint32_t freq, const char *modstr)
{
unsigned mod = dvb_parse_modulation (modstr, VSB_8);
return dvb_set_props (d, 4, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_ATSC,
DTV_FREQUENCY, freq, DTV_MODULATION, mod);
}
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