Created
October 9, 2014 21:18
-
-
Save adammw/1ebdbf94b8c431b5a273 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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, ¶m) < 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, ¶m) < 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