Skip to content

Instantly share code, notes, and snippets.

@adammw
Created October 9, 2014 21:18
Show Gist options
  • Save adammw/1ebdbf94b8c431b5a273 to your computer and use it in GitHub Desktop.
Save adammw/1ebdbf94b8c431b5a273 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <inttypes.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <linux/dvb/version.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/dmx.h>
#ifndef O_SEARCH
# define O_SEARCH O_RDONLY
#endif
#define MAX_PIDS 256
#define BUFSIZE (20*188)
struct dvb_device
{
int dir;
int demux;
int frontend;
struct {
int fd;
uint16_t pid;
} pids[MAX_PIDS];
uint8_t device;
bool budget;
};
typedef struct dvb_device dvb_device_t;
dvb_device_t *dvb_open();
void dvb_close(dvb_device_t *);
/** 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);
}
/** Opens the DVB device node of the specified type */
static int dvb_open_node (dvb_device_t *d, const char *type, int flags)
{
int fd;
char path[strlen (type) + 4];
snprintf(path, sizeof (path), "%s%u", type, d->device);
fd = openat(d->dir, path, flags);
if (fd != -1) {
fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK);
}
return fd;
}
/**
* Opens the DVB tuner
*/
dvb_device_t *dvb_open() {
dvb_device_t *d = malloc(sizeof (*d));
if (d == NULL) {
return NULL;
}
uint8_t adapter = 0;
d->device = 0;
d->dir = dvb_open_adapter(adapter);
if (d->dir == -1)
{
fprintf(stderr, "cannot access adapter\n");
free(d);
return NULL;
}
d->frontend = -1;
d->budget = false;
if (d->budget)
{
d->demux = dvb_open_node (d, "demux", O_RDONLY);
if (d->demux == -1)
{
fprintf(stderr, "cannot access demultiplexer\n");
close (d->dir);
free (d);
return NULL;
}
if (ioctl (d->demux, DMX_SET_BUFFER_SIZE, 1 << 20) < 0)
fprintf(stderr, "cannot expand demultiplexing buffer\n");
/* We need to filter at least one PID. The tap for TS demultiplexing
* cannot be configured otherwise. So add the PAT. */
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)
{
fprintf(stderr, "cannot setup TS demultiplexer\n");
goto error;
}
}
else
{
for (size_t i = 0; i < MAX_PIDS; i++)
d->pids[i].pid = d->pids[i].fd = -1;
d->demux = dvb_open_node (d, "dvr", O_RDONLY);
if (d->demux == -1)
{
fprintf(stderr, "cannot access DVR\n");
close (d->dir);
free (d);
return NULL;
}
}
return d;
error:
dvb_close(d);
return NULL;
}
void dvb_close(dvb_device_t *d)
{
if (!d->budget)
{
for (size_t i = 0; i < MAX_PIDS; i++)
if (d->pids[i].fd != -1)
close (d->pids[i].fd);
}
if (d->frontend != -1)
close (d->frontend);
close (d->demux);
close (d->dir);
free (d);
}
/** Finds a frontend of the correct type */
static int dvb_open_frontend (dvb_device_t *d)
{
if (d->frontend != -1)
return 0;
int fd = dvb_open_node (d, "frontend", O_RDWR);
if (fd == -1)
{
fprintf(stderr, "cannot access frontend\n");
return -1;
}
d->frontend = fd;
return 0;
}
#define dvb_find_frontend(d, sys) (dvb_open_frontend(d))
static int dvb_vset_props (dvb_device_t *d, size_t n, va_list ap)
{
assert (n <= DTV_IOCTL_MAX_MSGS);
struct dtv_property buf[n], *prop = buf;
struct dtv_properties props = { .num = n, .props = buf };
memset (buf, 0, sizeof (buf));
while (n > 0)
{
prop->cmd = va_arg (ap, uint32_t);
prop->u.data = va_arg (ap, uint32_t);
printf("setting property %2"PRIu32" to %"PRIu32"\n", prop->cmd, prop->u.data);
prop++;
n--;
}
if (ioctl (d->frontend, FE_SET_PROPERTY, &props) < 0)
{
fprintf(stderr, "cannot set frontend tuning parameters\n");
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_add_pid (dvb_device_t *d, uint16_t pid)
{
if (d->budget)
return 0;
for (size_t i = 0; i < MAX_PIDS; i++)
{
if (d->pids[i].pid == pid)
return 0;
if (d->pids[i].fd != -1)
continue;
int fd = dvb_open_node (d, "demux", O_RDONLY);
if (fd == -1)
goto error;
/* We need to filter at least one PID. The tap for TS demultiplexing
* cannot be configured otherwise. So add the PAT. */
struct dmx_pes_filter_params param;
param.pid = pid;
param.input = DMX_IN_FRONTEND;
param.output = DMX_OUT_TS_TAP;
param.pes_type = DMX_PES_OTHER;
param.flags = DMX_IMMEDIATE_START;
if (ioctl (fd, DMX_SET_PES_FILTER, &param) < 0)
{
close (fd);
goto error;
}
d->pids[i].fd = fd;
d->pids[i].pid = pid;
return 0;
}
errno = EMFILE;
error:
fprintf(stderr, "cannot add PID 0x%04"PRIu16"\n", pid );
return -1;
}
void print_frontend_status(unsigned int status) {
printf("frontend status: ");
if (status & FE_HAS_SIGNAL) {
printf("FE_HAS_SIGNAL ");
}
if (status & FE_HAS_CARRIER) {
printf("FE_HAS_CARRIER ");
}
if (status & FE_HAS_VITERBI) {
printf("FE_HAS_VITERBI ");
}
if (status & FE_HAS_SYNC) {
printf("FE_HAS_SYNC ");
}
if (status & FE_HAS_LOCK) {
printf("FE_HAS_LOCK ");
}
if (status & FE_TIMEDOUT) {
printf("FE_TIMEDOUT ");
}
if (status & FE_REINIT) {
printf("FE_REINIT ");
}
printf("\n");
}
/**
* 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)
{
fprintf(stderr, "cannot dequeue events fast enough!\n");
return -1;
}
fprintf(stderr, "cannot dequeue frontend event\n");
return 0;
}
// printf("frontend status: 0x%02X\n", (unsigned)ev.status);
print_frontend_status((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)
{
fprintf(stderr, "cannot demux data fast enough!\n");
return -1;
}
fprintf(stderr, "cannot demux\n");
return 0;
}
return val;
}
return -1;
}
float dvb_get_snr (dvb_device_t *d)
{
uint16_t snr;
if (d->frontend == -1 || ioctl (d->frontend, FE_READ_SNR, &snr) < 0)
return 0.;
return snr / 65535.;
}
int main() {
dvb_device_t *dev = dvb_open();
if (dev == NULL) {
fprintf(stderr, "nope\n");
exit(1);
}
uint64_t freq = 177500000;
if (freq != 0) {
if (dvb_find_frontend(dev, DVB_T))
return -1;
dvb_set_props(dev, 10, DTV_CLEAR, 0, DTV_DELIVERY_SYSTEM, SYS_DVBT,
DTV_FREQUENCY, freq, DTV_MODULATION, QAM_AUTO,
DTV_CODE_RATE_HP, FEC_AUTO, DTV_CODE_RATE_LP, FEC_AUTO,
DTV_BANDWIDTH_HZ, 7000000,
DTV_TRANSMISSION_MODE, TRANSMISSION_MODE_AUTO,
DTV_GUARD_INTERVAL, GUARD_INTERVAL_AUTO,
DTV_HIERARCHY, HIERARCHY_AUTO);
dvb_set_prop(dev, DTV_INVERSION, INVERSION_AUTO);
dvb_set_prop(dev, DTV_TUNE, 0 /* dummy */);
}
dvb_add_pid(dev, 0);
void* buf = malloc(BUFSIZE);
if (buf == NULL){
fprintf(stderr, "malloc failure\n");
return -1;
}
FILE* out_file = fopen("out.ts", "wb");
bool eof = false;
while(!eof) {
ssize_t val = dvb_read(dev, buf, BUFSIZE);
if (val < 0) {
printf("0 bytes read\n");
} else if (val == 0) {
break;
} else if (val > 0) {
printf("%zu bytes read\n", val);
fwrite(buf, 1, BUFSIZE, out_file);
}
}
fclose(out_file);
dvb_close(dev);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment