/** * mod_dvb * ------- * Simple DVB-Streaming via Apache 2.0 * * This work is distributed within the terms of * creative commons attribution-share alike 2.0 germany * * See http://creativecommons.org/licenses/by-sa/2.0/ for more information * * @author Bernd Holzmueller * @revision 05 * @license http://creativecommons.org/licenses/by-sa/2.0/de/ Creative Commons Attribution-Share Alike 2.0 Germany * @homepage http://oss.tiggerswelt.net/mod_dvb/ * @copyright Copyright © 2008 tiggersWelt.net * * Usage: * Compile and load the module into your webserver and set it up as * a special location-handler (see provided httpd.conf for an example). * Point your MPEG2-capable Media-Player to the configured URL to see * the default programme. If you want to watch another station just * append its name at the end of the URL (e.g. /dvb/Das Erste). * You may also use the Playlist-Feature by using the special /playlist * handler: mplayer -playlist http://my-dvb-server/dvb/playlist * * ChangeLog: * 20071012: Added kaffine-compatible Playlist-Output * * 20070613: Made open to wait a bit for the devices * (useful when zapping) * * 20070611: Added initial support for playlists (useful with mplayer) * Added support to rewrite PIDs (defaulting to 51/64) * Wrote a lot of useless stuff to this header * Watched many hours TV and listened to radio using mod_dvb * * 20070610: Added support for channels.conf * Added support for zapping * Removed some memory leaks * Moved channel stuff to own library * * 20070603: Initial release * * Ideas: * - Convert MPEG TS-Streams with one Audio- and Video-Stream to * one single MPEG PS-Stream. * - Seperate Tunning-Stuff from Streaming-Stuff * - Allow reading/streaming multiple PIDs to multiple clients * - Add support for more than one DVB-Device */ #include #include #include #include #include #include #include #include #include #include #include #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "http_log.h" #include "mod_dvb.h" int dvb_wait_open (char *fn, int flags, dvb_config *config) { int i, ret; /* Wait n seconds while trying to open the device */ for (i = 0; i < config->dvbwait * 20; i++) { if (!((ret = open (fn, flags)) < 0)) return ret; usleep (50000); } return ret; } static int dvb_playlist_handler (request_rec *r, char *uri) { dvb_config *config = ap_get_module_config (r->per_dir_config, &dvb_module); dvb_channel *chan = config->channels; char *base; int count = 0, index = 0; for (index = 0; index < strlen (r->uri); index++) if (!strcasecmp (r->path_info, r->uri + index)) break; base = malloc (index); memset (base, 0, index); strncpy (base, r->uri + 1, index - 1); while (chan != NULL) { count++; chan = chan->next; } chan = config->channels; index = 0; if (!strncasecmp (uri, "/kaffeine", 9)) { r->content_type = "text/xml"; ap_rputs ("\n", r); ap_rputs ("\n", r); while (chan != NULL) { ap_rprintf (r, "\n", chan->name, r->hostname, base, chan->name); chan = chan->next; } ap_rputs ("\n", r); } else { r->content_type = "audio/x-scpls"; ap_rputs ("[playlist]\r\n", r); ap_rprintf (r, "NumberOfEntries=%i\r\n", count); while (chan != NULL) { ap_rprintf (r, "File%i=http://%s/%s/%s\r\n", ++index, r->hostname, base, chan->name); ap_rprintf (r, "Title%i=%s\r\n", index, chan->name); ap_rprintf (r, "Length%i=-1\r\n", index); chan = chan->next; } ap_rputs ("Version=2\r\n", r); } return 0; } static int dvb_handler (request_rec *r) { dvb_config *config = ap_get_module_config (r->per_dir_config, &dvb_module); dvb_channel *chan; struct pollfd poll_dvr[1]; unsigned char buffer[MPEG_TS_SIZE]; int dvr_fd, len, pid, remap; /* DVB Processing */ if (strcmp(r->handler, "dvb-provider")) return DECLINED; /* Check if we have to deliver a playlist */ if (!strncasecmp (r->path_info, "/playlist", 9)) return dvb_playlist_handler (r, r->path_info + 9); /* Try to find the right channel */ if (((chan = dvb_find_channel (config->channels, r->path_info + 1)) == NULL) && ((chan = config->cdefault) == NULL) && ((chan = config->channels) == NULL)) return HTTP_INTERNAL_SERVER_ERROR; /* Check for at least one valid PID */ if ((chan->vpid < 1) && (chan->apid < 1)) return HTTP_INTERNAL_SERVER_ERROR; /* Setup the frontend */ if (config->tuner == NULL) config->tuner = dvb_tuner_create (0, config->tuner); if (dvb_tuner_set (config->tuner, chan) == NULL) { ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "mod_dvb: Could not setup the tuner"); return HTTP_SERVICE_UNAVAILABLE; } /* Open the Video Pipe */ if ((dvr_fd = dvb_wait_open ("/dev/dvb/adapter0/dvr0", O_RDONLY|O_NONBLOCK, config)) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "mod_dvb: Open DVR failed (%s)", strerror(errno)); return HTTP_SERVICE_UNAVAILABLE; } poll_dvr[0].fd = dvr_fd; poll_dvr[0].events = POLLIN | POLLPRI; /* Set the content-type */ /* Nice idea, but does not work for MPEG-TS if (chan->vpid > 0) r->content_type = "video/mpeg"; else r->content_type = "audio/mpeg"; */ r->content_type = "video/mpeg"; while (!r->connection->aborted) { if (poll (poll_dvr, 1, 100) && (read(dvr_fd, buffer, MPEG_TS_SIZE) == MPEG_TS_SIZE)) { /* Check packet-identifier */ if (buffer [0] != 0x47) continue; /* Extract PID from current packet */ pid = ((((unsigned) buffer [1]) << 8) | ((unsigned) buffer [2])) & 0x1FFF; remap = 0; /* Check wheter to rewrite the PID-Value */ if ((pid == chan->vpid) && (config->vpid > 0)) remap = config->vpid; if ((pid == chan->apid) && (config->apid > 0)) remap = config->apid; /* Perform rewrite */ if ((remap > 0x0) && (remap < 0x2000)) { buffer [1] = (buffer [1] & 0xE0) | (remap >> 8); buffer [2] = (remap & 0xFF); } /* Send packet to client */ if (ap_rwrite (buffer, MPEG_TS_SIZE, r) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "mod_dvb: write error"); break; } /* You should NEVER do this - your system would run out of memory ;-) */ /* ap_rflush (r); */ } } dvb_tuner_stop (config->tuner); close (dvr_fd); } static void *dvb_create_config (apr_pool_t *p, char *dummy) { dvb_config *config = apr_palloc (p, sizeof (dvb_config)); config->tuner = NULL; config->chanfile = NULL; config->channels = NULL; config->cdefault = NULL; config->dvbwait = 5; config->vpid = 51; config->apid = 64; return (void *)config; } const char *dvb_set_channels (cmd_parms *cmd, void *dirconfig, const char *arg) { dvb_config *config = (dvb_config *)dirconfig; dvb_channel *chan; if ((chan = dvb_load_channels (arg)) == NULL) return NULL; dvb_free_channels (config->channels); config->chanfile = arg; config->channels = chan; return NULL; } const char *dvb_set_channel (cmd_parms *cmd, void *dirconfig, const char *arg) { dvb_config *config = (dvb_config *)dirconfig; dvb_channel *chan; if ((chan = dvb_find_channel (config->channels, arg)) != NULL) config->cdefault = chan; return NULL; } const char *dvb_set_vpid (cmd_parms *cmd, void *dirconfig, const char *arg) { dvb_config *config = (dvb_config *)dirconfig; config->vpid = atol (arg); return NULL; } const char *dvb_set_apid (cmd_parms *cmd, void *dirconfig, const char *arg) { dvb_config *config = (dvb_config *)dirconfig; config->apid = atol (arg); return NULL; } const char *dvb_set_iowait (cmd_parms *cmd, void *dirconfig, const char *arg) { dvb_config *config = (dvb_config *)dirconfig; config->dvbwait = atol (arg); return NULL; } static void dvb_register_hooks(apr_pool_t *p) { ap_hook_handler(dvb_handler, NULL, NULL, APR_HOOK_MIDDLE); } static const command_rec dvb_module_cmds[] = { AP_INIT_TAKE1 ("dvbChannelFile", dvb_set_channels, NULL, OR_FILEINFO, "Load a set of DVB-Channels"), AP_INIT_TAKE1 ("dvbDefaultChannel", dvb_set_channel, NULL, OR_FILEINFO, "Set the default channel"), AP_INIT_TAKE1 ("dvbRemapVideoPID", dvb_set_vpid, NULL, OR_FILEINFO, "Remap Video PID to given value"), AP_INIT_TAKE1 ("dvbRemapAudioPID", dvb_set_apid, NULL, OR_FILEINFO, "Remap Audio PID to given value"), AP_INIT_TAKE1 ("dvbOpenWait", dvb_set_iowait, NULL, OR_FILEINFO, "Wait a brunch of seconds for DVB-Devices to become available"), {NULL} }; module AP_MODULE_DECLARE_DATA dvb_module = { STANDARD20_MODULE_STUFF, dvb_create_config, /* dir config creater */ NULL, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ dvb_module_cmds, /* command table */ dvb_register_hooks /* register_hooks */ };