/* 2004.02.01 first released source code for IOMP */ /* * Copyright (C) 2000-2003 the xine project * * This file is part of xine, a free video player. * * xine 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. * * xine 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * Read from a tcp network stream over a lan (put a tweaked mp1e encoder the * other end and you can watch tv anywhere in the house ..) * * $Id: input_net.c,v 1.2 2003/11/25 04:25:48 georgedon Exp $ * * how to set up mp1e for use with this plugin: * * use mp1 to capture the live stream, e.g. * mp1e -b 1200 -R 4,32 -a 0 -B 160 -v >live.mpg * * add an extra service "xine" to /etc/services and /etc/inetd.conf, e.g.: * /etc/services: * xine 1025/tcp * /etc/inetd.conf: * xine stream tcp nowait bartscgr /usr/sbin/tcpd /usr/bin/tail -f /home/bartscgr/Projects/inf.misc/live.mpg * * now restart inetd and you can use xine to watch the live stream, e.g.: * xine tcp://192.168.0.43:1025.mpg */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #ifdef ENABLE_IPV6 #include #include #endif #ifndef WIN32 #include #include #endif #include #include #include "xine_internal.h" #include "xineutils.h" #include "input_plugin.h" #include "net_buf_ctrl.h" /* #define LOG */ #define NET_BS_LEN 2324 #define BUFSIZE 1024 typedef struct { input_plugin_t input_plugin; xine_stream_t *stream; int fh; char *mrl; char *host_port; char preview[MAX_PREVIEW_SIZE]; off_t preview_size; off_t curpos; nbc_t *nbc; /* scratch buffer for forward seeking */ char seek_buf[BUFSIZE]; } net_input_plugin_t; typedef struct { input_class_t input_class; xine_t *xine; config_values_t *config; } net_input_class_t; /* **************************************************************** */ /* Private functions */ /* **************************************************************** */ static int host_connect_attempt_ipv4(struct in_addr ia, int port, xine_t *xine) { int s; struct sockaddr_in sin; s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (s==-1) { xine_log (xine, XINE_LOG_MSG, _("input_net: socket(): %s\n"), strerror(errno)); return -1; } sin.sin_family = AF_INET; sin.sin_addr = ia; sin.sin_port = htons(port); #ifndef WIN32 if (connect(s, (struct sockaddr *)&sin, sizeof(sin))==-1 && errno != EINPROGRESS) #else if (connect(s, (struct sockaddr *)&sin, sizeof(sin))==-1 && WSAGetLastError() != WSAEINPROGRESS) #endif { xine_log (xine, XINE_LOG_MSG, _("input_net: connect(): %s\n"), strerror(errno)); close(s); return -1; } return s; } static int host_connect_attempt(int family, struct sockaddr* sin, int addrlen, xine_t *xine) { int s; s = socket(family, SOCK_STREAM, IPPROTO_TCP); if (s==-1) { xine_log (xine, XINE_LOG_MSG, _("input_net: socket(): %s\n"), strerror(errno)); return -1; } #ifndef WIN32 if (connect(s, sin, addrlen)==-1 && errno != EINPROGRESS) #else if (connect(s, sin, addrlen)==-1 && WSAGetLastError() != WSAEINPROGRESS) #endif { xine_log (xine, XINE_LOG_MSG, _("input_net: connect(): %s\n"), strerror(errno)); close(s); return -1; } return s; } static int host_connect_ipv4(const char *host, int port, xine_t *xine) { struct hostent *h; int i; int s; h = gethostbyname(host); if (h==NULL) { xine_log (xine, XINE_LOG_MSG, _("input_net: unable to resolve '%s'.\n"), host); return -1; } for (i=0; h->h_addr_list[i]; i++) { struct in_addr ia; memcpy (&ia, h->h_addr_list[i],4); s = host_connect_attempt_ipv4 (ia, port, xine); if (s != -1) return s; } xine_log (xine, XINE_LOG_MSG, _("input_net: unable to connect to '%s'.\n"), host); return -1; } static int host_connect(const char *host, int port, xine_t *xine) { #ifndef ENABLE_IPV6 return host_connect_ipv4(host, port, xine); #else struct addrinfo hints, *res, *tmpaddr; int error; char strport[16]; int i; int s; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = PF_UNSPEC; snprintf(strport, sizeof(strport), "%d", port); #ifdef LOG printf("Resolving host '%s' at port '%s'\n", host, strport); #endif error = getaddrinfo(host, strport, &hints, &res); if (error) { xine_log (xine, XINE_LOG_MSG, _("input_net: unable to resolve '%s'.\n"), host); return -1; } /* We loop over all addresses and try to connect */ tmpaddr = res; while (tmpaddr) { s = host_connect_attempt (tmpaddr->ai_family, tmpaddr->ai_addr, tmpaddr->ai_addrlen, xine); if (s != -1) return s; tmpaddr = tmpaddr->ai_next; } xine_log (xine, XINE_LOG_MSG, _("input_net: unable to connect to '%s'.\n"), host); return -1; #endif } #define LOW_WATER_MARK 50 #define HIGH_WATER_MARK 100 static off_t net_plugin_read (input_plugin_t *this_gen, char *buf, off_t len) { net_input_plugin_t *this = (net_input_plugin_t *) this_gen; off_t n, total; #ifdef LOG printf ("input_net: reading %d bytes...\n", len); #endif total=0; if (this->curpos < this->preview_size) { n = this->preview_size - this->curpos; if (n > (len - total)) n = len - total; #ifdef LOG printf ("input_net: %lld bytes from preview (which has %lld bytes)\n", n, this->preview_size); #endif memcpy (&buf[total], &this->preview[this->curpos], n); this->curpos += n; total += n; } if( (len-total) > 0 ) { n = xine_read_abort (this->stream, this->fh, &buf[total], len-total); #ifdef LOG printf ("input_net: got %lld bytes (%lld/%lld bytes read)\n", n,total,len); #endif if (n < 0) { xine_message(this->stream, XINE_MSG_READ_ERROR, this->host_port, NULL); return 0; } this->curpos += n; total += n; } return total; } static buf_element_t *net_plugin_read_block (input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t todo) { /* net_input_plugin_t *this = (net_input_plugin_t *) this_gen; */ buf_element_t *buf = fifo->buffer_pool_alloc (fifo); off_t total_bytes; buf->content = buf->mem; buf->type = BUF_DEMUX_BLOCK; total_bytes = net_plugin_read (this_gen, buf->content, todo); if (total_bytes != todo) { buf->free_buffer (buf); return NULL; } buf->size = total_bytes; return buf; } static off_t net_plugin_get_length (input_plugin_t *this_gen) { return 0; } static uint32_t net_plugin_get_capabilities (input_plugin_t *this_gen) { return INPUT_CAP_PREVIEW; } static uint32_t net_plugin_get_blocksize (input_plugin_t *this_gen) { return NET_BS_LEN; } static off_t net_plugin_get_current_pos (input_plugin_t *this_gen){ net_input_plugin_t *this = (net_input_plugin_t *) this_gen; return this->curpos; } static off_t net_plugin_seek (input_plugin_t *this_gen, off_t offset, int origin) { net_input_plugin_t *this = (net_input_plugin_t *) this_gen; if ((origin == SEEK_CUR) && (offset >= 0)) { for (;((int)offset) - BUFSIZE > 0; offset -= BUFSIZE) { if( !this_gen->read (this_gen, this->seek_buf, BUFSIZE) ) return this->curpos; } this_gen->read (this_gen, this->seek_buf, offset); } if (origin == SEEK_SET) { if (offset < this->curpos) { if( this->curpos <= this->preview_size ) this->curpos = offset; else printf ("input_net: cannot seek back! (%lld > %lld)\n", this->curpos, offset); } else { offset -= this->curpos; for (;((int)offset) - BUFSIZE > 0; offset -= BUFSIZE) { if( !this_gen->read (this_gen, this->seek_buf, BUFSIZE) ) return this->curpos; } this_gen->read (this_gen, this->seek_buf, offset); } } return this->curpos; } static char* net_plugin_get_mrl (input_plugin_t *this_gen) { net_input_plugin_t *this = (net_input_plugin_t *) this_gen; return this->mrl; } static int net_plugin_get_optional_data (input_plugin_t *this_gen, void *data, int data_type) { net_input_plugin_t *this = (net_input_plugin_t *) this_gen; switch (data_type) { case INPUT_OPTIONAL_DATA_PREVIEW: memcpy (data, this->preview, this->preview_size); return this->preview_size; break; } return INPUT_OPTIONAL_UNSUPPORTED; } static void net_plugin_dispose (input_plugin_t *this_gen ) { net_input_plugin_t *this = (net_input_plugin_t *) this_gen; if (this->fh != -1) { close(this->fh); this->fh = -1; } free (this->mrl); free (this->host_port); if (this->nbc) { nbc_close (this->nbc); this->nbc = NULL; } free (this_gen); } static int net_plugin_open (input_plugin_t *this_gen ) { net_input_plugin_t *this = (net_input_plugin_t *) this_gen; char *filename; char *pptr; int port = 7658; filename = this->host_port; pptr=strrchr(filename, ':'); if(pptr) { *pptr++ = 0; sscanf(pptr,"%d", &port); } this->fh = host_connect(filename, port, this->stream->xine); this->curpos = 0; if (this->fh == -1) { return 0; } /* * fill preview buffer */ #ifndef WIN32 this->preview_size = read (this->fh, this->preview, MAX_PREVIEW_SIZE); #else this->preview_size = recv (this->fh, this->preview, MAX_PREVIEW_SIZE, 0); #endif this->curpos = 0; return 1; } static input_plugin_t *net_class_get_instance (input_class_t *cls_gen, xine_stream_t *stream, const char *mrl) { /* net_input_plugin_t *this = (net_input_plugin_t *) this_gen; */ net_input_plugin_t *this; nbc_t *nbc = NULL; char *filename; if (!strncasecmp (mrl, "tcp://", 6)) { filename = (char *) &mrl[6]; if((!filename) || (strlen(filename) == 0)) { return NULL; } nbc = nbc_init (stream); } else if (!strncasecmp (mrl, "slave://", 8)) { filename = (char *) &mrl[8]; if((!filename) || (strlen(filename) == 0)) { return NULL; } /* the only difference for slave:// is that network buffering control * is not used. otherwise, dvd still menus are not displayed (it freezes * with "buffering..." all the time) */ nbc = NULL; } else { return NULL; } this = xine_xmalloc(sizeof(net_input_plugin_t)); this->mrl = strdup(mrl); this->host_port = strdup(filename); this->stream = stream; this->fh = -1; this->curpos = 0; this->nbc = nbc; this->preview_size = 0; this->input_plugin.open = net_plugin_open; this->input_plugin.get_capabilities = net_plugin_get_capabilities; this->input_plugin.read = net_plugin_read; this->input_plugin.read_block = net_plugin_read_block; this->input_plugin.seek = net_plugin_seek; this->input_plugin.get_current_pos = net_plugin_get_current_pos; this->input_plugin.get_length = net_plugin_get_length; this->input_plugin.get_blocksize = net_plugin_get_blocksize; this->input_plugin.get_mrl = net_plugin_get_mrl; this->input_plugin.get_optional_data = net_plugin_get_optional_data; this->input_plugin.dispose = net_plugin_dispose; this->input_plugin.input_class = cls_gen; return &this->input_plugin; } /* * net plugin class */ static char *net_class_get_description (input_class_t *this_gen) { return _("net input plugin as shipped with xine"); } static char *net_class_get_identifier (input_class_t *this_gen) { return "TCP"; } static void net_class_dispose (input_class_t *this_gen) { net_input_class_t *this = (net_input_class_t *) this_gen; free (this); } static void *init_class (xine_t *xine, void *data) { net_input_class_t *this; this = (net_input_class_t *) xine_xmalloc(sizeof(net_input_class_t)); this->config = xine->config; this->xine = xine; this->input_class.get_instance = net_class_get_instance; this->input_class.get_description = net_class_get_description; this->input_class.get_identifier = net_class_get_identifier; this->input_class.get_dir = NULL; this->input_class.get_autoplay_list = NULL; this->input_class.dispose = net_class_dispose; this->input_class.eject_media = NULL; return this; } /* * exported plugin catalog entry */ plugin_info_t xine_plugin_info[] = { /* type, API, "name", version, special_info, init_function */ { PLUGIN_INPUT, 13, "tcp", XINE_VERSION_CODE, NULL, init_class }, { PLUGIN_NONE, 0, "", 0, NULL, NULL } };