aboutsummaryrefslogtreecommitdiffstats
path: root/util/gnutv/gnutv_data.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--util/gnutv/gnutv_data.c459
1 files changed, 459 insertions, 0 deletions
diff --git a/util/gnutv/gnutv_data.c b/util/gnutv/gnutv_data.c
new file mode 100644
index 0000000..7ac0f23
--- /dev/null
+++ b/util/gnutv/gnutv_data.c
@@ -0,0 +1,459 @@
+/*
+ gnutv utility
+
+ Copyright (C) 2004, 2005 Manu Abraham <abraham.manu@gmail.com>
+ Copyright (C) 2006 Andrew de Quincey (adq_dvb@lidskialf.net)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE_SOURCE 1
+#define _LARGEFILE64_SOURCE 1
+
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pthread.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <libdvbapi/dvbdemux.h>
+#include <libdvbapi/dvbaudio.h>
+#include <libucsi/mpeg/section.h>
+#include "gnutv.h"
+#include "gnutv_dvb.h"
+#include "gnutv_ca.h"
+#include "gnutv_data.h"
+
+static void *fileoutputthread_func(void* arg);
+static void *udpoutputthread_func(void* arg);
+
+static int gnutv_data_create_decoder_filter(int adapter, int demux, uint16_t pid, int pestype);
+static int gnutv_data_create_dvr_filter(int adapter, int demux, uint16_t pid);
+
+static void gnutv_data_decoder_pmt(struct mpeg_pmt_section *pmt);
+static void gnutv_data_dvr_pmt(struct mpeg_pmt_section *pmt);
+
+static void gnutv_data_append_pid_fd(int pid, int fd);
+static void gnutv_data_free_pid_fds(void);
+
+static pthread_t outputthread;
+static int outfd = -1;
+static int dvrfd = -1;
+static int pat_fd_dvrout = -1;
+static int pmt_fd_dvrout = -1;
+static int outputthread_shutdown = 0;
+
+static int usertp = 0;
+static int adapter_id = -1;
+static int demux_id = -1;
+static int output_type = 0;
+static struct addrinfo *outaddrs = NULL;
+
+struct pid_fd {
+ int pid;
+ int fd;
+};
+static struct pid_fd *pid_fds = NULL;
+static int pid_fds_count = 0;
+
+void gnutv_data_start(int _output_type,
+ int ffaudiofd, int _adapter_id, int _demux_id,
+ char *outfile,
+ char* outif, struct addrinfo *_outaddrs, int _usertp)
+{
+ usertp = _usertp;
+ demux_id = _demux_id;
+ adapter_id = _adapter_id;
+ output_type = _output_type;
+
+ // setup output
+ switch(output_type) {
+ case OUTPUT_TYPE_DECODER:
+ case OUTPUT_TYPE_DECODER_ABYPASS:
+ dvbaudio_set_bypass(ffaudiofd, (output_type == OUTPUT_TYPE_DECODER_ABYPASS) ? 1 : 0);
+ close(ffaudiofd);
+ break;
+
+ case OUTPUT_TYPE_STDOUT:
+ case OUTPUT_TYPE_FILE:
+ if (output_type == OUTPUT_TYPE_FILE) {
+ // open output file
+ outfd = open(outfile, O_WRONLY|O_CREAT|O_LARGEFILE|O_TRUNC, 0644);
+ if (outfd < 0) {
+ fprintf(stderr, "Failed to open output file\n");
+ exit(1);
+ }
+ } else {
+ outfd = STDOUT_FILENO;
+ }
+
+ // open dvr device
+ dvrfd = dvbdemux_open_dvr(adapter_id, 0, 1, 0);
+ if (dvrfd < 0) {
+ fprintf(stderr, "Failed to open DVR device\n");
+ exit(1);
+ }
+
+ pthread_create(&outputthread, NULL, fileoutputthread_func, NULL);
+ break;
+
+ case OUTPUT_TYPE_UDP:
+ outaddrs = _outaddrs;
+
+ // open output socket
+ outfd = socket(outaddrs->ai_family, outaddrs->ai_socktype, outaddrs->ai_protocol);
+ if (outfd < 0) {
+ fprintf(stderr, "Failed to open output socket\n");
+ exit(1);
+ }
+
+ // bind to local interface if requested
+ if (outif != NULL) {
+ if (setsockopt(outfd, SOL_SOCKET, SO_BINDTODEVICE, outif, strlen(outif)) < 0) {
+ fprintf(stderr, "Failed to bind to interface %s\n", outif);
+ exit(1);
+ }
+ }
+
+ // open dvr device
+ dvrfd = dvbdemux_open_dvr(adapter_id, 0, 1, 0);
+ if (dvrfd < 0) {
+ fprintf(stderr, "Failed to open DVR device\n");
+ exit(1);
+ }
+
+ pthread_create(&outputthread, NULL, udpoutputthread_func, NULL);
+ break;
+ }
+
+ // output PAT to DVR if requested
+ switch(output_type) {
+ case OUTPUT_TYPE_DVR:
+ case OUTPUT_TYPE_FILE:
+ case OUTPUT_TYPE_STDOUT:
+ case OUTPUT_TYPE_UDP:
+ pat_fd_dvrout = gnutv_data_create_dvr_filter(adapter_id, demux_id, TRANSPORT_PAT_PID);
+ }
+}
+
+void gnutv_data_stop()
+{
+ // shutdown output thread if necessary
+ if (dvrfd != -1) {
+ outputthread_shutdown = 1;
+ pthread_join(outputthread, NULL);
+ }
+ gnutv_data_free_pid_fds();
+ if (pat_fd_dvrout != -1)
+ close(pat_fd_dvrout);
+ if (pmt_fd_dvrout != -1)
+ close(pmt_fd_dvrout);
+ if (outaddrs)
+ freeaddrinfo(outaddrs);
+}
+
+void gnutv_data_new_pat(int pmt_pid)
+{
+ // output PMT to DVR if requested
+ switch(output_type) {
+ case OUTPUT_TYPE_DVR:
+ case OUTPUT_TYPE_FILE:
+ case OUTPUT_TYPE_STDOUT:
+ case OUTPUT_TYPE_UDP:
+ if (pmt_fd_dvrout != -1)
+ close(pmt_fd_dvrout);
+ pmt_fd_dvrout = gnutv_data_create_dvr_filter(adapter_id, demux_id, pmt_pid);
+ }
+}
+
+int gnutv_data_new_pmt(struct mpeg_pmt_section *pmt)
+{
+ // close all old PID FDs
+ gnutv_data_free_pid_fds();
+
+ // deal with the PMT appropriately
+ switch(output_type) {
+ case OUTPUT_TYPE_DECODER:
+ case OUTPUT_TYPE_DECODER_ABYPASS:
+ gnutv_data_decoder_pmt(pmt);
+ break;
+
+ case OUTPUT_TYPE_DVR:
+ case OUTPUT_TYPE_FILE:
+ case OUTPUT_TYPE_STDOUT:
+ case OUTPUT_TYPE_UDP:
+ gnutv_data_dvr_pmt(pmt);
+ break;
+ }
+
+ return 1;
+}
+
+static void *fileoutputthread_func(void* arg)
+{
+ (void)arg;
+ uint8_t buf[4096];
+ struct pollfd pollfd;
+ int written;
+
+ pollfd.fd = dvrfd;
+ pollfd.events = POLLIN|POLLPRI|POLLERR;
+
+ while(!outputthread_shutdown) {
+ if (poll(&pollfd, 1, 1000) != 1)
+ continue;
+ if (pollfd.revents & POLLERR) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "DVR device read failure\n");
+ return 0;
+ }
+
+ int size = read(dvrfd, buf, sizeof(buf));
+ if (size < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "DVR device read failure\n");
+ return 0;
+ }
+
+ written = 0;
+ while(written < size) {
+ int tmp = write(outfd, buf + written, size - written);
+ if (tmp == -1) {
+ if (errno != EINTR) {
+ fprintf(stderr, "Write error: %m\n");
+ break;
+ }
+ } else {
+ written += tmp;
+ }
+ }
+ }
+
+ return 0;
+}
+
+#define TS_PAYLOAD_SIZE (188*7)
+
+static void *udpoutputthread_func(void* arg)
+{
+ (void)arg;
+ uint8_t buf[12 + TS_PAYLOAD_SIZE];
+ struct pollfd pollfd;
+ int bufsize = 0;
+ int bufbase = 0;
+ int readsize;
+ uint16_t rtpseq = 0;
+
+ pollfd.fd = dvrfd;
+ pollfd.events = POLLIN|POLLPRI|POLLERR;
+
+ if (usertp) {
+ srandom(time(NULL));
+ int ssrc = random();
+ rtpseq = random();
+ buf[0x0] = 0x80;
+ buf[0x1] = 0x21;
+ buf[0x4] = 0x00; // }
+ buf[0x5] = 0x00; // } FIXME: should really be a valid stamp
+ buf[0x6] = 0x00; // }
+ buf[0x7] = 0x00; // }
+ buf[0x8] = ssrc >> 24;
+ buf[0x9] = ssrc >> 16;
+ buf[0xa] = ssrc >> 8;
+ buf[0xb] = ssrc;
+ bufbase = 12;
+ }
+
+ while(!outputthread_shutdown) {
+ if (poll(&pollfd, 1, 1000) != 1)
+ continue;
+ if (pollfd.revents & POLLERR) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "DVR device read failure\n");
+ return 0;
+ }
+
+ readsize = TS_PAYLOAD_SIZE - bufsize;
+ readsize = read(dvrfd, buf + bufbase + bufsize, readsize);
+ if (readsize < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "DVR device read failure\n");
+ return 0;
+ }
+ bufsize += readsize;
+
+ if (bufsize == TS_PAYLOAD_SIZE) {
+ if (usertp) {
+ buf[2] = rtpseq >> 8;
+ buf[3] = rtpseq;
+ }
+ if (sendto(outfd, buf, bufbase + bufsize, 0, outaddrs->ai_addr, outaddrs->ai_addrlen) < 0) {
+ if (errno != EINTR) {
+ fprintf(stderr, "Socket send failure: %m\n");
+ return 0;
+ }
+ }
+ rtpseq++;
+ bufsize = 0;
+ }
+ }
+
+ if (bufsize) {
+ if (usertp) {
+ buf[2] = rtpseq >> 8;
+ buf[3] = rtpseq;
+ }
+ if (sendto(outfd, buf, bufbase + bufsize, 0, outaddrs->ai_addr, outaddrs->ai_addrlen) < 0) {
+ if (errno != EINTR)
+ fprintf(stderr, "Socket send failure: %m\n");
+ }
+ }
+
+ return 0;
+}
+
+static int gnutv_data_create_decoder_filter(int adapter, int demux, uint16_t pid, int pestype)
+{
+ int demux_fd = -1;
+
+ // open the demuxer
+ if ((demux_fd = dvbdemux_open_demux(adapter, demux, 0)) < 0) {
+ return -1;
+ }
+
+ // create a section filter
+ if (dvbdemux_set_pes_filter(demux_fd, pid, DVBDEMUX_INPUT_FRONTEND, DVBDEMUX_OUTPUT_DECODER, pestype, 1)) {
+ close(demux_fd);
+ return -1;
+ }
+
+ // done
+ return demux_fd;
+}
+
+static int gnutv_data_create_dvr_filter(int adapter, int demux, uint16_t pid)
+{
+ int demux_fd = -1;
+
+ // open the demuxer
+ if ((demux_fd = dvbdemux_open_demux(adapter, demux, 0)) < 0) {
+ return -1;
+ }
+
+ // create a section filter
+ if (dvbdemux_set_pid_filter(demux_fd, pid, DVBDEMUX_INPUT_FRONTEND, DVBDEMUX_OUTPUT_DVR, 1)) {
+ close(demux_fd);
+ return -1;
+ }
+
+ // done
+ return demux_fd;
+}
+
+static void gnutv_data_decoder_pmt(struct mpeg_pmt_section *pmt)
+{
+ int audio_pid = -1;
+ int video_pid = -1;
+ struct mpeg_pmt_stream *cur_stream;
+ mpeg_pmt_section_streams_for_each(pmt, cur_stream) {
+ switch(cur_stream->stream_type) {
+ case 1:
+ case 2: // video
+ video_pid = cur_stream->pid;
+ break;
+
+ case 3:
+ case 4: // audio
+ audio_pid = cur_stream->pid;
+ break;
+ }
+ }
+
+ if (audio_pid != -1) {
+ int fd = gnutv_data_create_decoder_filter(adapter_id, demux_id, audio_pid, DVBDEMUX_PESTYPE_AUDIO);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to create dvr filter for PID %i\n", audio_pid);
+ } else {
+ gnutv_data_append_pid_fd(audio_pid, fd);
+ }
+ }
+ if (video_pid != -1) {
+ int fd = gnutv_data_create_decoder_filter(adapter_id, demux_id, video_pid, DVBDEMUX_PESTYPE_VIDEO);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to create dvr filter for PID %i\n", video_pid);
+ } else {
+ gnutv_data_append_pid_fd(video_pid, fd);
+ }
+ }
+ int fd = gnutv_data_create_decoder_filter(adapter_id, demux_id, pmt->pcr_pid, DVBDEMUX_PESTYPE_PCR);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to create dvr filter for PID %i\n", pmt->pcr_pid);
+ } else {
+ gnutv_data_append_pid_fd(pmt->pcr_pid, fd);
+ }
+}
+
+static void gnutv_data_dvr_pmt(struct mpeg_pmt_section *pmt)
+{
+ struct mpeg_pmt_stream *cur_stream;
+ mpeg_pmt_section_streams_for_each(pmt, cur_stream) {
+ int fd = gnutv_data_create_dvr_filter(adapter_id, demux_id, cur_stream->pid);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to create dvr filter for PID %i\n", cur_stream->pid);
+ } else {
+ gnutv_data_append_pid_fd(cur_stream->pid, fd);
+ }
+ }
+}
+
+static void gnutv_data_append_pid_fd(int pid, int fd)
+{
+ struct pid_fd *tmp;
+ if ((tmp = realloc(pid_fds, (pid_fds_count +1) * sizeof(struct pid_fd))) == NULL) {
+ fprintf(stderr, "Out of memory when adding a new pid_fd\n");
+ exit(1);
+ }
+ tmp[pid_fds_count].pid = pid;
+ tmp[pid_fds_count].fd = fd;
+ pid_fds_count++;
+ pid_fds = tmp;
+}
+
+static void gnutv_data_free_pid_fds()
+{
+ if (pid_fds_count) {
+ int i;
+ for(i=0; i< pid_fds_count; i++) {
+ close(pid_fds[i].fd);
+ }
+ }
+ if (pid_fds)
+ free(pid_fds);
+
+ pid_fds_count = 0;
+ pid_fds = NULL;
+}