summaryrefslogtreecommitdiff
path: root/xine_input_vdr.c
diff options
context:
space:
mode:
authorphintuka <phintuka>2006-06-03 10:04:49 +0000
committerphintuka <phintuka>2006-06-03 10:04:49 +0000
commit0e345486181ef82b681dd6047f3b6ccb44c77146 (patch)
treea5401c7f97ab047a0afa890e6806d8537564102a /xine_input_vdr.c
parent321eb114c9fe9abd954ce4270595d53df6cccbae (diff)
downloadxineliboutput-0_99rc4.tar.gz
xineliboutput-0_99rc4.tar.bz2
Initial importxineliboutput-0_99rc4
Diffstat (limited to 'xine_input_vdr.c')
-rw-r--r--xine_input_vdr.c4570
1 files changed, 4570 insertions, 0 deletions
diff --git a/xine_input_vdr.c b/xine_input_vdr.c
new file mode 100644
index 00000000..b0387945
--- /dev/null
+++ b/xine_input_vdr.c
@@ -0,0 +1,4570 @@
+/*
+ * xine_input_vdr.c: xine VDR input plugin
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: xine_input_vdr.c,v 1.1 2006-06-03 10:01:18 phintuka Exp $
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/*#define LOG_UDP*/
+/*#define LOG_OSD*/
+/*#define LOG_CMD*/
+/*#define LOG_SCR*/
+/*#define LOG_TRACE*/
+
+#define ADJUST_SCR_SPEED 1
+#define INITIAL_READ_DELAY (16*(40*1000)) /* us. 16 frames, 40ms / frame */
+#define METRONOM_PREBUFFER_VAL (4 * 90000 / 25 )
+
+#define XINE_ENGINE_INTERNAL
+#define METRONOM_CLOCK_INTERNAL
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <poll.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <dlfcn.h>
+#include <linux/unistd.h> /* gettid() */
+#include <sys/resource.h> /* setpriority() */
+
+#include <xine/xine_internal.h>
+#include <xine/xineutils.h>
+#include <xine/input_plugin.h>
+#include <xine/plugin_catalog.h>
+#include <xine/io_helper.h>
+#include <xine/buffer.h>
+#include <xine/post.h>
+
+#include "xine_input_vdr.h"
+#include "xine_input_vdr_net.h"
+#include "xine_osd_command.h"
+
+/******************************* LOG ***********************************/
+
+#define LOG_MODULENAME "[input_vdr] "
+#define SysLogLevel iSysLogLevel
+#include "logdefs.h"
+
+int iSysLogLevel = 1;
+int bLogToSysLog = 0;
+int bSymbolsFound = 0;
+
+#if !defined(XINELIBOUTPUT_DEBUG_STDOUT) && \
+ !defined(XINELIBOUTPUT_DEBUG_STDERR)
+#undef x_syslog
+#define x_syslog syslog_with_tid
+
+_syscall0(pid_t, gettid)
+
+static void syslog_with_tid(int level, const char *fmt, ...)
+{
+ va_list argp;
+ char buf[512];
+ va_start(argp, fmt);
+ vsnprintf(buf, 512, fmt, argp);
+ if(!bLogToSysLog) {
+ printf(LOG_MODULENAME "%s\n", buf);
+ } else {
+ syslog(level, "[%d] " LOG_MODULENAME "%s", gettid(), buf);
+ }
+ va_end(argp);
+}
+#endif
+
+static void SetupLogLevel()
+{
+ void *lib = NULL;
+ if( !(lib = dlopen (NULL, RTLD_LAZY | RTLD_GLOBAL))) {
+ LOGERR("Can't dlopen self: %s\n", dlerror());
+ } else {
+ int *pLogToSyslog = (int*)dlsym(lib, "LogToSysLog");
+ int *pSysLogLevel = (int*)dlsym(lib, "SysLogLevel");
+ bLogToSysLog = pLogToSyslog && *pLogToSyslog;
+ iSysLogLevel = pSysLogLevel ? (*pSysLogLevel) : 2;
+ LOGDBG("Symbol SysLogLevel %s : %d",
+ pSysLogLevel ? "found" : "not found", iSysLogLevel);
+ LOGDBG("Symbol LogToSysLog %s : %s",
+ pLogToSyslog ? "found" : "not found", bLogToSysLog ? "yes" : "no");
+ bSymbolsFound = pSysLogLevel && pLogToSyslog;
+ dlclose(lib);
+ }
+}
+
+
+#ifdef LOG_SCR
+# define LOGSCR(x...) LOGMSG("SCR: " x)
+#else
+# define LOGSCR(x...)
+#endif
+#
+#ifdef LOG_OSD
+# define LOGOSD(x...) LOGMSG("OSD: " x)
+#else
+# define LOGOSD(x...)
+#endif
+#
+#ifdef LOG_UDP
+# define LOGUDP(x...) LOGMSG("UDP:" x)
+#else
+# define LOGUDP(x...)
+#endif
+#ifdef LOG_CMD
+# define LOGCMD(x...) LOGMSG("CMD:" x)
+#else
+# define LOGCMD(x...)
+#endif
+#ifdef LOG_TRACE
+# undef TRACE
+# define TRACE(x...) printf(x)
+#else
+# undef TRACE
+# define TRACE(x...)
+#endif
+
+
+/******************************* DATA ***********************************/
+
+#define KILOBYTE(x) (1024 * (x))
+
+typedef struct pvrscr_s pvrscr_t;
+typedef struct udp_data_s udp_data_t;
+
+/* plugin class */
+typedef struct vdr_input_class_s {
+ input_class_t input_class;
+ xine_t *xine;
+} vdr_input_class_t;
+
+/* input plugin */
+typedef struct vdr_input_plugin_s {
+ input_plugin_t input_plugin;
+
+ /* VDR */
+ vdr_input_plugin_funcs_t funcs;
+
+ /* plugin */
+ vdr_input_class_t *cls;
+ xine_stream_t *stream;
+ xine_event_queue_t *event_queue;
+
+ char *mrl;
+
+ xine_stream_t *pip_stream;
+ xine_stream_t *slave_stream;
+ xine_event_queue_t *slave_event_queue;
+
+ /* Sync */
+ pthread_mutex_t lock;
+ pthread_mutex_t vdr_entry_lock;
+
+ /* Playback */
+ int no_video;
+ int live_mode;
+ int still_mode;
+ int playback_finished;
+ int stream_start;
+ int send_pts;
+
+ /* SCR */
+ pvrscr_t *scr;
+ int scr_tunning;
+ int speed_before_pause;
+ int is_paused;
+
+ int I_frames; /* amount of I-frames passed to demux */
+
+ /* Network */
+ pthread_t control_thread;
+ pthread_t data_thread;
+ volatile int control_running;
+ volatile int fd_data;
+ volatile int fd_control;
+ int tcp, udp, rtp;
+ buf_element_t *read_buffer; /* used when reading from socket */
+ udp_data_t *udp_data;
+ int client_id;
+ int token;
+
+ /* buffer */
+ buf_element_t *curr_buffer; /* used in read */
+ fifo_buffer_t *block_buffer; /* blocks to be demuxed */
+ fifo_buffer_t *buffer_pool; /* stream's video fifo */
+ fifo_buffer_t *big_buffer; /* for jumbo PES */
+ off_t discard_index; /* index of next byte to feed to demux;
+ all data before this offset will
+ be discarded */
+ off_t guard_index; /* data before this offset will not be discarded */
+ off_t curpos; /* current position (demux side) */
+ int max_buffers; /* max no. of non-demuxed buffers */
+ int read_delay; /* usec to wait at next read call */
+
+ /* saved video properties */
+ int video_properties_saved;
+ int orig_hue;
+ int orig_brightness;
+ int orig_saturation;
+ int orig_contrast;
+
+ /* OSD */
+ pthread_mutex_t osd_lock;
+ int vdr_osd_width, vdr_osd_height;
+ int video_width, video_height;
+ int rescale_osd;
+ int rescale_osd_downscale;
+ int unscaled_osd;
+ int unscaled_osd_opaque;
+ int unscaled_osd_lowresvideo;
+ int osdhandle[MAX_OSD_OBJECT];
+ int64_t last_changed_vpts[MAX_OSD_OBJECT];
+ osd_command_t osddata[MAX_OSD_OBJECT];
+
+} vdr_input_plugin_t;
+
+
+/***************************** UDP DATA *********************************/
+
+struct udp_data_s {
+
+ /* receiving queue for re-ordering and re-transmissions */
+ buf_element_t *queue[UDP_SEQ_MASK+1];
+ int queued; /* count of frames in queue */
+
+ /* expected sequence number of next incoming packet */
+ int next_seq;
+
+ /* for re-send requests */
+ uint64_t queue_input_pos; /* stream position of next incoming byte */
+ int resend_requested;
+ int queue_full_signaled;
+
+ /* missing frames ratio statistics */
+ int missed_frames;
+ int received_frames;
+
+ /* SCR adjust */
+ int scr_jump_done;
+
+ /* Server address (used to validate incoming packets) */
+ struct sockaddr_in server_address;
+};
+
+/* UDP sequence number handling */
+#define NEXTSEQ(x) ((x + 1) & UDP_SEQ_MASK)
+#define PREVSEQ(x) ((x + UDP_SEQ_MASK) & UDP_SEQ_MASK)
+#define INCSEQ(x) (x = NEXTSEQ(x))
+#define ADDSEQ(x,n) ((x + UDP_SEQ_MASK + 1 + n) & UDP_SEQ_MASK)
+
+#define UDP_SIGNAL_FULL_TRESHOLD 50 /* ~100ms with DVB mpeg2
+ (2k-blocks @ 8 Mbps) */
+#define UDP_SIGNAL_NOT_FULL_TRESHOLD 100 /* ~200ms with DVB mpeg2
+ (2k-blocks @ 8 Mbps) */
+
+static udp_data_t *init_udp_data(void)
+{
+ udp_data_t *data = (udp_data_t *)xine_xmalloc(sizeof(udp_data_t));
+
+ memset(data->queue, 0, sizeof(data->queue));
+ data->next_seq = 0;
+ data->queued = 0;
+ data->queue_input_pos = 0ULL;
+ data->resend_requested = 0;
+ data->missed_frames = 0;
+ data->received_frames = -1;
+ data->scr_jump_done = 0;
+
+ return data;
+}
+
+static void free_udp_data(udp_data_t *data)
+{
+ int i;
+
+ for(i=0; i<=UDP_SEQ_MASK; i++)
+ if(data->queue[i])
+ data->queue[i]->free_buffer(data->queue[i]);
+
+ free(data);
+}
+
+#if 0
+static void flush_udp_data(udp_data_t *data)
+{
+ /* flush all data immediately even if there are gaps */
+}
+#endif
+
+#ifdef ADJUST_SCR_SPEED
+/******************************* SCR *************************************
+ *
+ * unix System Clock Reference + fine tunning
+ *
+ * pvrscr code is mostly copied from xine, input_pvr.c
+ *
+ * fine tunning is used to change playback speed in live mode
+ * to keep in sync with mpeg source
+ *************************************************************************/
+
+struct pvrscr_s {
+ scr_plugin_t scr;
+
+ struct timeval cur_time;
+ int64_t cur_pts;
+ int xine_speed;
+ double speed_factor;
+ double speed_tunning;
+
+ pthread_mutex_t lock;
+
+ struct timeval last_time;
+};
+
+static int pvrscr_get_priority (scr_plugin_t *scr)
+{
+ return 10; /* high priority */
+}
+
+/* Only call pvrscr_set_pivot when already mutex locked ! */
+static void pvrscr_set_pivot (pvrscr_t *this)
+{
+ struct timeval tv;
+ int64_t pts;
+ double pts_calc;
+
+ xine_monotonic_clock(&tv,NULL);
+
+ pts_calc = (tv.tv_sec - this->cur_time.tv_sec) * this->speed_factor;
+ pts_calc += (tv.tv_usec - this->cur_time.tv_usec) * this->speed_factor / 1e6;
+ pts = this->cur_pts + pts_calc;
+
+ /* This next part introduces a one off inaccuracy
+ * to the scr due to rounding tv to pts.
+ */
+ this->cur_time.tv_sec=tv.tv_sec;
+ this->cur_time.tv_usec=tv.tv_usec;
+ this->cur_pts=pts;
+
+ this->last_time.tv_sec = tv.tv_sec;
+ this->last_time.tv_usec = tv.tv_usec;
+
+ return ;
+}
+
+static int pvrscr_set_fine_speed (scr_plugin_t *scr, int speed)
+{
+ pvrscr_t *this = (pvrscr_t*) scr;
+
+ pthread_mutex_lock (&this->lock);
+
+ pvrscr_set_pivot( this );
+ this->xine_speed = speed;
+ this->speed_factor = (double) speed * 90000.0 /
+ (1.0*XINE_FINE_SPEED_NORMAL) *
+ this->speed_tunning;
+
+ pthread_mutex_unlock (&this->lock);
+
+ return speed;
+}
+
+static void pvrscr_speed_tunning (pvrscr_t *this, double factor)
+{
+ pthread_mutex_lock (&this->lock);
+
+ pvrscr_set_pivot( this );
+ this->speed_tunning = factor;
+ this->speed_factor = (double) this->xine_speed * 90000.0 /
+ (1.0*XINE_FINE_SPEED_NORMAL) *
+ this->speed_tunning;
+
+ pthread_mutex_unlock (&this->lock);
+}
+
+static void pvrscr_skip_frame (pvrscr_t *this)
+{
+ pthread_mutex_lock (&this->lock);
+
+ pvrscr_set_pivot( this );
+ this->cur_pts += (90000/25)*1ULL;
+
+ pthread_mutex_unlock (&this->lock);
+}
+
+static void pvrscr_adjust (scr_plugin_t *scr, int64_t vpts)
+{
+ pvrscr_t *this = (pvrscr_t*) scr;
+ struct timeval tv;
+
+ pthread_mutex_lock (&this->lock);
+
+ xine_monotonic_clock(&tv,NULL);
+ this->cur_time.tv_sec=tv.tv_sec;
+ this->cur_time.tv_usec=tv.tv_usec;
+ this->cur_pts = vpts;
+
+ this->last_time.tv_sec = tv.tv_sec;
+ this->last_time.tv_usec = tv.tv_usec;
+
+ pthread_mutex_unlock (&this->lock);
+}
+
+static void pvrscr_start (scr_plugin_t *scr, int64_t start_vpts)
+{
+ pvrscr_t *this = (pvrscr_t*) scr;
+
+ pthread_mutex_lock (&this->lock);
+
+ xine_monotonic_clock(&this->cur_time, NULL);
+ this->cur_pts = start_vpts;
+
+ this->last_time.tv_sec = this->cur_time.tv_sec;
+ this->last_time.tv_usec = this->cur_time.tv_usec;
+
+ pthread_mutex_unlock (&this->lock);
+
+ pvrscr_set_fine_speed (&this->scr, XINE_FINE_SPEED_NORMAL);
+}
+
+static int64_t pvrscr_get_current (scr_plugin_t *scr)
+{
+ pvrscr_t *this = (pvrscr_t*) scr;
+
+ struct timeval tv;
+ int64_t pts;
+ double pts_calc;
+ pthread_mutex_lock (&this->lock);
+
+ xine_monotonic_clock(&tv,NULL);
+
+#ifdef LOG_SCR
+ if(this->last_time.tv_sec+3 < tv.tv_sec && this->last_time.tv_sec) {
+ LOGMSG("ERROR - CLOCK JUMPED FORWARDS ? "
+ "(pvrscr_get_current diff %d.%06d sec)\n",
+ (int)(tv.tv_sec - this->last_time.tv_sec),
+ (int)(tv.tv_usec - this->last_time.tv_usec));
+ pthread_mutex_unlock (&this->lock);
+ pvrscr_adjust(scr,this->cur_pts);
+ pthread_mutex_lock (&this->lock);
+ }
+ else if(this->last_time.tv_sec > tv.tv_sec) {
+ LOGMSG("ERROR - CLOCK JUMPED BACKWARDS ! "
+ "(pvrscr_get_current diff %d.%06d sec)\n",
+ (int)(tv.tv_sec - this->last_time.tv_sec),
+ (int)(tv.tv_usec - this->last_time.tv_usec));
+ pthread_mutex_unlock (&this->lock);
+ pvrscr_adjust(scr,this->cur_pts);
+ pthread_mutex_lock (&this->lock);
+ }
+#endif
+
+ pts_calc = (tv.tv_sec - this->cur_time.tv_sec) * this->speed_factor;
+ pts_calc += (tv.tv_usec - this->cur_time.tv_usec) * this->speed_factor / 1e6;
+
+ pts = this->cur_pts + pts_calc;
+
+ this->last_time.tv_sec = tv.tv_sec;
+ this->last_time.tv_usec = tv.tv_usec;
+
+ pthread_mutex_unlock (&this->lock);
+
+ return pts;
+}
+
+static void pvrscr_exit (scr_plugin_t *scr)
+{
+ pvrscr_t *this = (pvrscr_t*) scr;
+
+ pthread_mutex_destroy (&this->lock);
+ free(this);
+}
+
+static pvrscr_t* pvrscr_init (void)
+{
+ pvrscr_t *this;
+
+ this = malloc(sizeof(*this));
+ memset(this, 0, sizeof(*this));
+
+ this->scr.interface_version = 3;
+ this->scr.set_fine_speed = pvrscr_set_fine_speed;
+ this->scr.get_priority = pvrscr_get_priority;
+ this->scr.adjust = pvrscr_adjust;
+ this->scr.start = pvrscr_start;
+ this->scr.get_current = pvrscr_get_current;
+ this->scr.exit = pvrscr_exit;
+
+ pthread_mutex_init (&this->lock, NULL);
+
+ pvrscr_speed_tunning(this, 1.0 );
+ pvrscr_set_fine_speed (&this->scr, XINE_SPEED_PAUSE);
+
+ LOGSCR("SCR init complete");
+
+ return this;
+}
+
+/*
+ * SCR tunning
+ */
+
+#define SCR_TUNNING_PAUSED -3
+#define SCR_TUNNING_OFF 0
+
+#ifdef LOG_SCR
+static inline const char *scr_tunning_str(int value)
+{
+ switch(value) {
+ case 2: return "SCR +1.0%";
+ case 1: return "SCR +0.5%";
+ case SCR_TUNNING_OFF: return "SCR +0.0%";
+ case -1: return "SCR -0.5%";
+ case -2: return "SCR -1.0%";
+ case SCR_TUNNING_PAUSED: return "SCR PAUSED";
+ default: break;
+ }
+ return "ERROR";
+#if 0
+ return (value ? (value < 0 ? (value == -1 ?
+ "SCR -0.5%" :
+ "SCR PAUSED")
+ : "SCR +0.5%")
+ : "SCR +0.0%");
+#endif
+}
+#endif
+
+static void scr_tunning_set_paused(vdr_input_plugin_t *this)
+{
+ if(this->scr_tunning != SCR_TUNNING_PAUSED) {
+ this->scr_tunning = SCR_TUNNING_PAUSED; /* marked as paused */
+ if(this->scr)
+ pvrscr_speed_tunning(this->scr, 1.0);
+
+ this->speed_before_pause = _x_get_fine_speed(this->stream);
+#if 1
+ if(_x_get_fine_speed(this->stream) != XINE_SPEED_PAUSE)
+ _x_set_fine_speed(this->stream, XINE_SPEED_PAUSE);
+#else
+#warning no pause
+ pvrscr_set_fine_speed(this->scr, 1);
+#endif
+ }
+}
+
+static void reset_scr_tunning(vdr_input_plugin_t *this, int new_speed)
+{
+ if(this->scr_tunning != SCR_TUNNING_OFF) {
+ this->scr_tunning = SCR_TUNNING_OFF; /* marked as normal */
+ if(this->scr)
+ pvrscr_speed_tunning(this->scr, 1.0);
+
+ if(new_speed >= 0) {
+ if(_x_get_fine_speed(this->stream) != new_speed) {
+ _x_set_fine_speed(this->stream, XINE_FINE_SPEED_NORMAL);
+ }
+pvrscr_set_fine_speed((scr_plugin_t*)this->scr, XINE_FINE_SPEED_NORMAL);
+ }
+ }
+}
+
+static int64_t monotonic_time_ms (void)
+{
+ static struct timeval tv_0;
+ static int init_done = 0;
+ struct timeval tv;
+ int64_t ms;
+
+ if(!init_done) {
+ init_done = 1;
+ xine_monotonic_clock(&tv_0, NULL);
+ }
+ xine_monotonic_clock(&tv, NULL);
+
+ ms = 1000LL * (tv.tv_sec - tv_0.tv_sec);
+ ms += tv.tv_usec / 1000;
+ return ms;
+}
+
+static void vdr_adjust_realtime_speed(vdr_input_plugin_t *this)
+{
+ /* Grab current buffer usage */
+ int num_used = this->buffer_pool->size(this->buffer_pool) +
+ this->block_buffer->size(this->block_buffer);
+ int num_free = this->buffer_pool->num_free(this->buffer_pool) +
+ this->block_buffer->num_free(this->block_buffer);
+ int scr_tunning = this->scr_tunning;
+
+ static int64_t pause_start = -1LL; /* TODO: -> this... */
+ static int pause_bufs = -1;
+
+ if(this->stream->audio_fifo)
+ num_used += this->stream->audio_fifo->size(this->stream->audio_fifo);
+ num_free -= (this->buffer_pool->buffer_pool_capacity - this->max_buffers);
+
+#ifdef LOG_SCR
+ {
+ static int fcnt=0;
+ if(!((fcnt++)%2500) ||
+ (this->scr_tunning==SCR_TUNNING_PAUSED && !(fcnt%10)) ||
+ (this->no_video && !(fcnt%50))) {
+ LOGSCR("Buffer %2d%% (%3d/%3d) %s %s",
+ 100*num_used/(num_used+num_free),
+ num_used, num_used+num_free,
+ scr_tunning_str(this->scr_tunning),
+ this->live_mode?"LIVE":"PLAY");
+ }
+ }
+
+ if(this->scr_tunning==SCR_TUNNING_PAUSED) {
+ if(_x_get_fine_speed(this->stream) != XINE_SPEED_PAUSE) {
+ LOGMSG("ERROR: SCR PAUSED ; speed=%d bool=%d",
+ _x_get_fine_speed(this->stream),
+ (int)_x_get_fine_speed(this->stream) == XINE_SPEED_PAUSE);
+ _x_set_fine_speed(this->stream, XINE_SPEED_PAUSE);
+ }
+ }
+#endif
+
+ /* If buffer is (almost) empty. pause it for a while */
+ if( num_used < 1 &&
+ scr_tunning != SCR_TUNNING_PAUSED &&
+ this->live_mode && !this->no_video && !this->still_mode) {
+/*
+ TODO:
+ - First I-frame can be delivered as soon as it is decoded
+ -> illusion of faster channel switches
+ - Clock must still be paused, but stream can be in PLAYING state
+ (if clock is not paused we will got a lot of discarded frames
+ as those are decoded too late accoording to running SCR)
+*/
+ int num_vbufs = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_BUFS_IN_FIFO);
+ if(num_vbufs < 3) {
+ pause_start = monotonic_time_ms();
+ pause_bufs = 0;
+ LOGSCR("SCR paused by adjust_speed (vbufs=%d)", num_vbufs);
+ scr_tunning_set_paused(this);
+ } else {
+ LOGSCR("adjust_speed: no pause, enough vbufs queued");
+ }
+
+ /* If currently paused, revert to normal if buffer > 50% */
+ } else if( scr_tunning == SCR_TUNNING_PAUSED) {
+
+ if(pause_bufs < 0) {
+ pause_start = monotonic_time_ms();
+ pause_bufs = 0;
+ }
+ pause_bufs++;
+
+ int num_vbufs = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_BUFS_IN_FIFO);
+
+ if( num_used/2 > num_free || (this->no_video && num_used>5) || !this->live_mode
+/*
+ TODO:
+ - Using amount of buffers is not good trigger as it depends on channel bitrate
+ - Wait time is not not good trigger as it depends on tuner lock time
+ -> maybe keep track of PTSes or wait until decoder has complete IBBBP frame sequence ?
+ - First I-frame can be delivered as soon as it is decoded
+ -> illusion of faster channel switches
+*/
+ || pause_bufs > 200 || (pause_bufs>100 && pause_start + 400 < monotonic_time_ms())
+ || num_vbufs > 5
+ || this->still_mode
+ ) {
+
+ LOGSCR("SCR tunning resetted by adjust_speed, "
+ "vbufs=%d (SCR was paused for %d bufs/%d ms)",
+ num_vbufs, pause_bufs, monotonic_time_ms()-pause_start);
+
+ pause_bufs = -1;
+ pause_start = -1LL;
+ reset_scr_tunning(this, this->speed_before_pause);
+ }
+
+ /* when playing realtime, adjust the scr to make xine buffers half full */
+ } else if( _x_get_fine_speed(this->stream) == XINE_FINE_SPEED_NORMAL &&
+ this->live_mode ) {
+
+ if(this->no_video) { /* radio stream ? */
+ if( num_used > 10 )
+ scr_tunning = +1; /* play faster */
+ else if( num_used < 5 )
+ scr_tunning = -1; /* play slower */
+ else
+ scr_tunning = SCR_TUNNING_OFF;
+ } else {
+ if( num_used > 4*num_free )
+ scr_tunning = +1; /* play .5% faster */
+ else if( num_used > 2*num_free )
+ scr_tunning = +1; /* play .5% faster */
+ else if( num_free > 4*num_used ) /* <20% */
+ scr_tunning = -2; /* play 1% slower */
+ else if( num_free > 2*num_used ) /* <33% */
+ scr_tunning = -1; /* play .5% slower */
+ else if( (scr_tunning > 0 && num_free > num_used) ||
+ (scr_tunning < 0 && num_used > num_free) )
+ scr_tunning = SCR_TUNNING_OFF;
+ }
+
+ if( scr_tunning != this->scr_tunning ) {
+ LOGSCR("scr_tunning: %s -> %s (buffer %d/%d)",
+ scr_tunning_str(this->scr_tunning),
+ scr_tunning_str(scr_tunning), num_used, num_free );
+ this->scr_tunning = scr_tunning;
+
+ /* make it play .5% / 1% faster or slower */
+ if(this->scr)
+ pvrscr_speed_tunning(this->scr, 1.0 + (0.005 * scr_tunning) );
+ }
+
+ /* If replay mode or trick speed mode, switch tunning off */
+ } else if( this->scr_tunning ) {
+ reset_scr_tunning(this, -1);
+ }
+}
+
+#else /* ADJUST_SCR_SPEED */
+
+struct pvrscr_s {
+ int dummy;
+};
+
+static void vdr_adjust_realtime_speed(vdr_input_plugin_t *this,
+ fifo_buffer_t *fifo1,
+ fifo_buffer_t *fifo2,
+ int speed )
+{
+}
+
+static void reset_scr_tunning(vdr_input_plugin_t *this, int new_speed)
+{
+}
+
+static void scr_tunning_set_paused(vdr_input_plugin_t *this,
+ int speed_before_pause)
+{
+}
+
+#endif /* ADJUST_SCR_SPEED */
+
+/******************************* TOOLS ***********************************/
+
+#define LOCKED(x) \
+do { \
+ if(!pthread_mutex_lock(&this->lock)) { \
+ x \
+ } else { \
+ LOGERR("pthread_mutex_lock failed"); \
+ abort(); \
+ } \
+ pthread_mutex_unlock(&this->lock); \
+} while(0)
+
+static void create_timeout_time(struct timespec *abstime, int timeout_ms)
+{
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ now.tv_usec += timeout_ms * 1000;
+ while (now.tv_usec >= 1000000) { /* take care of an overflow */
+ now.tv_sec++;
+ now.tv_usec -= 1000000;
+ }
+ abstime->tv_sec = now.tv_sec;
+ abstime->tv_nsec = now.tv_usec * 1000;
+}
+
+void timed_wait(int ms)
+{
+ static pthread_cond_t cond;
+ static pthread_mutex_t mutex;
+ static int initialized = 0;
+ struct timespec abstime;
+
+ if(!initialized) {
+ initialized ++;
+ pthread_mutex_init (&mutex, NULL);
+ pthread_cond_init (&cond, NULL);
+ }
+
+ create_timeout_time(&abstime, ms);
+ pthread_cond_timedwait (&cond, &mutex, &abstime);
+
+ /* or, use select(0, NULL, NULL, NULL, &abstime); */
+}
+
+static int io_select_rd (int fd)
+{
+ fd_set fdset, eset;
+ int ret;
+ struct timeval select_timeout;
+
+ if(fd<0)
+ return XIO_ERROR;
+
+ while(1) {
+ FD_ZERO (&fdset);
+ FD_ZERO (&eset);
+ FD_SET (fd, &fdset);
+ FD_SET (fd, &eset);
+
+ select_timeout.tv_sec = 0; /*timeout_ms/1000;*/
+ select_timeout.tv_usec = 500*1000; /*(timeout_ms%1000)*1000;*/
+ ret = select (fd + 1, &fdset, NULL, &eset, &select_timeout);
+
+ /*ret = select (fd + 1, &fdset, NULL, &eset, NULL);*/
+
+ if (ret == 0) {
+ return XIO_TIMEOUT;
+ } else if (ret < 0) {
+ if(errno == EINTR)
+ return XIO_TIMEOUT;
+ return XIO_ERROR;
+ } else if (ret >= 1) {
+ if(FD_ISSET(fd,&eset))
+ return XIO_ERROR;
+ if(FD_ISSET(fd,&fdset))
+ return XIO_READY;
+ }
+ }
+ /*
+ if (stream && stream->demux_action_pending)
+ return XIO_ABORTED;
+ */
+ return XIO_TIMEOUT;
+}
+
+static char *FindSubFile(const char *fname)
+{
+ char *subfile = (char*)malloc(strlen(fname)+4), *dot;
+ strcpy(subfile, fname);
+ dot = strrchr(subfile, '.');
+ if(dot) {
+ /*while(dot+1 > subfile) {*/
+ struct stat st;
+ strcpy(dot, ".sub");
+ if (stat(subfile, &st) == 0)
+ return subfile;
+ strcpy(dot, ".srt");
+ if (stat(subfile, &st) == 0)
+ return subfile;
+ strcpy(dot, ".txt");
+ if (stat(subfile, &st) == 0)
+ return subfile;
+ /* dot--; */
+ /*}*/
+ }
+ free(subfile);
+ return NULL;
+}
+
+/************************** BUFFER HANDLING ******************************/
+
+static void buffer_pool_free (buf_element_t *element)
+{
+ fifo_buffer_t *this = (fifo_buffer_t *) element->source;
+
+ pthread_mutex_lock (&this->buffer_pool_mutex);
+
+ element->next = this->buffer_pool_top;
+ this->buffer_pool_top = element;
+
+ this->buffer_pool_num_free++;
+ if (this->buffer_pool_num_free > this->buffer_pool_capacity) {
+ LOGERR("xine-lib:buffer: There has been a fatal error: TOO MANY FREE's");
+ _x_abort();
+ }
+
+ if(this->buffer_pool_num_free > 20)
+ pthread_cond_signal (&this->buffer_pool_cond_not_empty);
+
+ pthread_mutex_unlock (&this->buffer_pool_mutex);
+}
+
+#if 0
+static buf_element_t *buffer_pool_timed_alloc(fifo_buffer_t *fifo, int timeoutMs, int buffer_limit)
+{
+ struct timespec abstime;
+ create_timeout_time(&abstime, timeoutMs);
+
+ pthread_mutex_lock(&fifo->buffer_pool_mutex);
+
+ while(fifo->buffer_pool_num_free <= buffer_limit) {
+ if(pthread_cond_timedwait (&fifo->buffer_pool_cond_not_empty, &fifo->buffer_pool_mutex, &abstime) == ETIMEDOUT)
+ break;
+ }
+
+ pthread_mutex_unlock(&fifo->buffer_pool_mutex);
+
+ return fifo->buffer_pool_try_alloc(fifo);
+}
+#endif
+
+#if 0
+static buf_element_t *fifo_buffer_timed_get(fifo_buffer_t *fifo, int timeoutMs)
+{
+ struct timespec abstime;
+ create_timeout_time(&abstime, timeoutMs);
+
+ return NULL;
+}
+#endif
+
+static buf_element_t *fifo_buffer_try_get(fifo_buffer_t *fifo)
+{
+ int i;
+ buf_element_t *buf;
+
+ pthread_mutex_lock (&fifo->mutex);
+
+ if (fifo->first==NULL) {
+ pthread_mutex_unlock (&fifo->mutex);
+ return NULL;
+ }
+
+ buf = fifo->first;
+
+ fifo->first = fifo->first->next;
+ if (fifo->first==NULL)
+ fifo->last = NULL;
+
+ fifo->fifo_size--;
+ fifo->fifo_data_size -= buf->size;
+
+ for(i = 0; fifo->get_cb[i]; i++)
+ fifo->get_cb[i](fifo, buf, fifo->get_cb_data[i]);
+
+ pthread_mutex_unlock (&fifo->mutex);
+
+ return buf;
+}
+
+static int fifo_buffer_clear(fifo_buffer_t *fifo)
+{
+ int bytes = 0;
+ buf_element_t *buf, *next, *prev=NULL;
+ pthread_mutex_lock (&fifo->mutex);
+ buf = fifo->first;
+ while (buf != NULL) {
+ next = buf->next;
+ if ((buf->type & BUF_MAJOR_MASK) != BUF_CONTROL_BASE) {
+ /* remove this buffer */
+ if (prev)
+ prev->next = next;
+ else
+ fifo->first = next;
+
+ if (!next)
+ fifo->last = prev;
+
+ fifo->fifo_size--;
+ bytes += buf->size;
+ fifo->fifo_data_size -= buf->size;
+
+ buf->free_buffer(buf);
+ } else {
+ prev = buf;
+ }
+ buf = next;
+ }
+ pthread_mutex_unlock (&fifo->mutex);
+
+ return bytes;
+}
+
+static void signal_buffer_pool_not_empty(vdr_input_plugin_t *this)
+{
+ if(this->buffer_pool) {
+ pthread_mutex_lock(&this->buffer_pool->buffer_pool_mutex);
+ pthread_cond_broadcast(&this->buffer_pool->buffer_pool_cond_not_empty);
+ pthread_mutex_unlock(&this->buffer_pool->buffer_pool_mutex);
+ }
+}
+
+static void signal_buffer_not_empty(vdr_input_plugin_t *this)
+{
+ if(this->block_buffer) {
+ pthread_mutex_lock(&this->block_buffer->mutex);
+ pthread_cond_broadcast(&this->block_buffer->not_empty);
+ pthread_mutex_unlock(&this->block_buffer->mutex);
+ }
+}
+
+/*************************** slave input (PIP stream) ********************/
+
+typedef struct fifo_input_plugin_s {
+ input_plugin_t i;
+ vdr_input_plugin_t *master;
+ xine_stream_t *stream;
+ fifo_buffer_t *buffer;
+ fifo_buffer_t *buffer_pool;
+ off_t pos;
+} fifo_input_plugin_t;
+
+static int fifo_open(input_plugin_t *this_gen)
+{ return 1; }
+static uint32_t fifo_get_capabilities (input_plugin_t *this_gen)
+{ return INPUT_CAP_BLOCK; }
+static uint32_t fifo_get_blocksize (input_plugin_t *this_gen)
+{ return 2 * 2048; }
+static off_t fifo_get_current_pos (input_plugin_t *this_gen)
+{ return -1; }
+static off_t fifo_get_length (input_plugin_t *this_gen)
+{ return -1; }
+static off_t fifo_seek (input_plugin_t *this_gen, off_t offset, int origin)
+{ return offset; }
+static int fifo_get_optional_data (input_plugin_t *this_gen, void *data, int data_type)
+{ return INPUT_OPTIONAL_UNSUPPORTED; }
+static char* fifo_get_mrl (input_plugin_t *this_gen)
+{ return "xvdr:slave:"; }
+
+static off_t fifo_read (input_plugin_t *this_gen, char *buf, off_t len)
+{
+ int got = 0;
+ LOGERR("fifo_input_plugin::fifo_read() not implemented !");
+ exit(-1); /* assert(false); */
+ return got;
+}
+
+static buf_element_t *fifo_read_block (input_plugin_t *this_gen,
+ fifo_buffer_t *fifo, off_t todo)
+{
+ fifo_input_plugin_t *this = (fifo_input_plugin_t *) this_gen;
+ LOGDBG("fifo_read_block");
+
+ while(!this->stream->demux_action_pending) {
+ buf_element_t *buf = fifo_buffer_try_get(this->buffer);
+ if(buf) {
+ /* LOGDBG("fifo_read_block: got, return"); */
+ return buf;
+ }
+ /* LOGDBG("fifo_read_block: no buf, poll..."); */
+ /* poll(NULL, 0, 10); */
+ xine_usec_sleep(5*1000);
+ /* LOGDBG("fifo_read_block: poll timeout"); */
+ }
+
+ LOGDBG("fifo_read_block: return NULL !");
+ return NULL;
+}
+
+static void fifo_dispose (input_plugin_t *this_gen)
+{
+ fifo_input_plugin_t *this = (fifo_input_plugin_t *) this_gen;
+ LOGDBG("fifo_dispose");
+
+ if(this) {
+ if(this->buffer)
+ this->buffer->dispose(this->buffer);
+ free(this);
+ }
+}
+
+static input_plugin_t *fifo_class_get_instance (input_class_t *cls_gen,
+ xine_stream_t *stream,
+ const char *data)
+{
+ fifo_input_plugin_t *slave = (fifo_input_plugin_t *) xine_xmalloc (sizeof(fifo_input_plugin_t));
+ unsigned int imaster;
+ vdr_input_plugin_t *master;
+ LOGDBG("fifo_class_get_instance");
+
+ sscanf(data+4+1+5+1, "%x", &imaster);
+ master = (vdr_input_plugin_t*)imaster;
+
+ memset(slave, 0, sizeof(fifo_input_plugin_t));
+ slave->master = (vdr_input_plugin_t*)master;
+ slave->stream = stream;
+ slave->buffer_pool = stream->video_fifo;
+ slave->buffer = _x_fifo_buffer_new(4,4096);
+ slave->i.open = fifo_open;
+ slave->i.get_mrl = fifo_get_mrl;
+ slave->i.dispose = fifo_dispose;
+ slave->i.input_class = cls_gen;
+ slave->i.get_capabilities = fifo_get_capabilities;
+ slave->i.read = fifo_read;
+ slave->i.read_block = fifo_read_block;
+ slave->i.seek = fifo_seek;
+ slave->i.get_current_pos = fifo_get_current_pos;
+ slave->i.get_length = fifo_get_length;
+ slave->i.get_blocksize = fifo_get_blocksize;
+ slave->i.get_optional_data = fifo_get_optional_data;
+
+ return (input_plugin_t*)slave;
+}
+
+
+/******************************** OSD ************************************/
+
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
+static int update_video_size(vdr_input_plugin_t *this)
+{
+ int w = 0, h = 0;
+ int64_t duration;
+
+ this->stream->video_out->status(this->stream->video_out,
+ this->stream, &w, &h, &duration);
+
+ if(w>0 && h>0) {
+ if(this->video_width != w ||
+ this->video_height != h) {
+
+ LOGOSD("update_video_size: new video size (%dx%d->%dx%d)",
+ this->video_width, this->video_height, w, h);
+ this->video_width = w;
+ this->video_height = h;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* re-scale compressed RLE image */
+static xine_rle_elem_t *scale_rle_image(osd_command_t *osdcmd,
+ int new_w, int new_h)
+{
+ #define FACTORBASE 0x100
+ #define FACTOR2PIXEL(f) ((f)>>8)
+ #define SCALEX(x) FACTOR2PIXEL(factor_x*(x))
+ #define SCALEY(y) FACTOR2PIXEL(factor_y*(y))
+
+ xine_rle_elem_t *old_rle = osdcmd->data;
+ int old_w = osdcmd->w, old_h = osdcmd->h;
+ int old_y = 0, new_y = 0;
+ int factor_x = FACTORBASE*new_w/old_w;
+ int factor_y = FACTORBASE*new_h/old_h;
+
+ xine_rle_elem_t *new_rle_start, *new_rle, *tmp;
+ int rle_size = 8128;
+ int num_rle = 0;
+
+ new_rle_start = new_rle = (xine_rle_elem_t*)malloc(4*rle_size);
+
+ /* we assume rle elements are breaked at end of line */
+ while(old_y < old_h) {
+ int elems_current_line = 0;
+ int old_x = 0, new_x = 0;
+
+ while(old_x < old_w) {
+ int new_x_end = SCALEX(old_x + old_rle->len);
+
+ if(new_x_end >= new_w)
+ new_x_end = new_w;
+
+ new_rle->len = new_x_end - new_x;
+ new_rle->color = old_rle->color;
+
+ old_x += old_rle->len;
+ old_rle++;
+
+ if(new_rle->len > 0) {
+ new_x += new_rle->len;
+ new_rle++;
+
+ num_rle++;
+ elems_current_line++;
+
+ if( (num_rle + 1) >= rle_size ) {
+ rle_size *= 2;
+ new_rle_start = (xine_rle_elem_t*)realloc( new_rle_start, 4*rle_size);
+ new_rle = new_rle_start + num_rle;
+ }
+ }
+ }
+ if(new_x < new_w)
+ (new_rle-1)->len += new_w - new_x;
+ old_y++;
+ new_y++;
+
+ if(factor_y > FACTORBASE) {
+ /* scale up -- duplicate current line ? */
+ int dup = SCALEY(old_y) - new_y;
+
+ /* if no lines left in (old) rle, copy all lines still missing from new */
+ if(old_y == old_h)
+ dup = new_h - new_y - 1;
+
+ while(dup-- && (new_y+1<new_h)) {
+ xine_rle_elem_t *prevline;
+ int n;
+ if( (num_rle + elems_current_line + 1) >= rle_size ) {
+ rle_size *= 2;
+ new_rle_start = (xine_rle_elem_t*)realloc( new_rle_start, 4*rle_size);
+ new_rle = new_rle_start + num_rle;
+ }
+
+ /* duplicate previous line */
+ prevline = new_rle - elems_current_line;
+ for(n = 0; n < elems_current_line; n++) {
+ *new_rle++ = *prevline++;
+ num_rle++;
+ }
+ new_y++;
+ }
+
+ } else if(factor_y < FACTORBASE) {
+ /* scale down -- drop next line ? */
+ int skip = new_y - SCALEY(old_y);
+
+ if(old_y == old_h-1) {
+ /* one (old) line left ; don't skip it if new rle is not complete */
+ if(new_y < new_h)
+ skip = 0;
+ }
+ while(skip--) {
+ for(old_x = 0; old_x < old_w;) {
+ old_x += old_rle->len;
+ old_rle++;
+ }
+ old_y++;
+ }
+ }
+ }
+
+ tmp = osdcmd->data;
+
+ osdcmd->data = new_rle_start;
+ osdcmd->datalen = num_rle*4;
+
+ if(old_w != new_w) {
+ osdcmd->x = (0x100*osdcmd->x * new_w/old_w)>>8;
+ osdcmd->w = new_w;
+ }
+ if(old_h != new_h) {
+ osdcmd->y = (0x100*osdcmd->y * new_h/old_h)>>8;
+ osdcmd->h = new_h;
+ }
+
+ return tmp;
+}
+
+static int exec_osd_command(vdr_input_plugin_t *this, osd_command_t *cmd)
+{
+ video_overlay_event_t ov_event;
+ vo_overlay_t ov_overlay;
+ video_overlay_manager_t *ovl_manager;
+ int handle = -1, i;
+
+ /* Caller must have locked this->osd_lock ! */
+
+ LOGOSD("exec_osd_command %d", cmd ? cmd->cmd : -1);
+
+ /* Check parameters */
+
+ if(!cmd || !this || !this->stream) {
+ LOGMSG("exec_osd_command: Stream not initialized !");
+ return -3;
+ }
+
+ if(cmd->wnd < 0 || cmd->wnd >= MAX_OSD_OBJECT) {
+ LOGMSG("exec_osd_command: OSD window handle %d out of range !", cmd->wnd);
+ return -2;
+ }
+
+ handle = this->osdhandle[cmd->wnd];
+
+ if(handle < 0 && cmd->cmd == OSD_Close) {
+ LOGMSG("exec_osd_command: Attempt to close non-existing OSD (%d) !", cmd->wnd);
+ return -2;
+ }
+
+ ovl_manager =
+ this->stream->video_out->get_overlay_manager(this->stream->video_out);
+
+ if(!ovl_manager) {
+ LOGMSG("exec_osd_command: Stream has no overlay manager !");
+ return -3;
+ }
+
+ memset(&ov_event, 0, sizeof(ov_event));
+
+ /* calculate exec time */
+ if(cmd->pts || cmd->delay_ms) {
+ int64_t vpts = xine_get_current_vpts(this->stream);
+ if(cmd->pts) {
+ ov_event.vpts = cmd->pts +
+ this->stream->metronom->get_option(this->stream->metronom,
+ METRONOM_VPTS_OFFSET);
+ } else {
+ if(this->last_changed_vpts[cmd->wnd])
+ ov_event.vpts = this->last_changed_vpts[cmd->wnd] + cmd->delay_ms*90;
+ }
+ /* execution time must be in future */
+ if(ov_event.vpts < vpts)
+ ov_event.vpts = 0;
+ /* limit delay to 5 seconds (because of seeks and channel switches ...) */
+ if(ov_event.vpts > vpts + 5*90000)
+ ov_event.vpts = vpts + 5*90000;
+ }
+
+ /* Execute command */
+
+ if(cmd->cmd == OSD_Size) {
+ this->vdr_osd_width = cmd->w;
+ this->vdr_osd_height = cmd->h;
+
+ } else if(cmd->cmd == OSD_Nop) {
+ this->last_changed_vpts[cmd->wnd] = xine_get_current_vpts(this->stream);
+
+ } else if(cmd->cmd == OSD_SetPalette) {
+ /* TODO */
+ } else if(cmd->cmd == OSD_Move) {
+ /* TODO */
+ } else if(cmd->cmd == OSD_Set_YUV) {
+ /* TODO */
+ } else if(cmd->cmd == OSD_Close) {
+ ov_event.event_type = OVERLAY_EVENT_FREE_HANDLE;
+ ov_event.object.handle = handle;
+ this->osdhandle[cmd->wnd] = -1;
+
+ if(this->osddata[cmd->wnd].data) {
+ free(this->osddata[cmd->wnd].data);
+ this->osddata[cmd->wnd].data = NULL;
+ }
+ if(this->osddata[cmd->wnd].palette) {
+ free(this->osddata[cmd->wnd].palette);
+ this->osddata[cmd->wnd].palette = NULL;
+ }
+
+ ovl_manager->add_event(ovl_manager, (void *)&ov_event);
+
+ this->last_changed_vpts[cmd->wnd] = 0;
+
+ } else if(cmd->cmd == OSD_Set_RLE) {
+
+ int use_unscaled = 0;
+ int rle_scaled = 0;
+ int semitransparent = 0;
+ int xmove = 0, ymove = 0;
+ int unscaled_supported = 1;
+
+ if(handle < 0)
+ handle = this->osdhandle[cmd->wnd] =
+ ovl_manager->get_handle(ovl_manager,0);
+
+ ov_event.event_type = OVERLAY_EVENT_SHOW;
+ ov_event.object.handle = handle;
+ ov_event.object.overlay = &ov_overlay;
+ memset( ov_event.object.overlay, 0, sizeof(*ov_event.object.overlay) );
+
+#if XINE_VERSION_CODE < 10101
+ ov_event.object.overlay->clip_top = -1;
+ ov_event.object.overlay->clip_bottom = 0;
+ ov_event.object.overlay->clip_left = 0;
+ ov_event.object.overlay->clip_right = 0;
+#else
+ ov_event.object.overlay->hili_top = -1;
+ ov_event.object.overlay->hili_bottom = 0;
+ ov_event.object.overlay->hili_left = 0;
+ ov_event.object.overlay->hili_right = 0;
+#endif
+
+ /* palette must contain YUV values for each color index */
+ for(i=0; i<cmd->colors; i++) {
+ uint32_t *tmp = (uint32_t*)(cmd->palette + i);
+ ov_event.object.overlay->color[i] = *tmp & 0xffffff;
+ ov_event.object.overlay->trans[i] = (cmd->palette[i].alpha + 0x7)/0xf;
+ if(ov_event.object.overlay->trans[i] > 0 &&
+ ov_event.object.overlay->trans[i] < 0xf)
+ semitransparent = 1;
+ }
+
+ if(!(this->stream->video_out->get_capabilities(this->stream->video_out) &
+ VO_CAP_UNSCALED_OVERLAY))
+ unscaled_supported = 0;
+ else if(this->unscaled_osd ||
+ (this->unscaled_osd_opaque && !semitransparent))
+ use_unscaled = 1;
+
+ /* store osd for later rescaling (done if video size changes) */
+ if(!use_unscaled && this->rescale_osd) {
+ if(this->osddata[cmd->wnd].data) {
+ free(this->osddata[cmd->wnd].data);
+ this->osddata[cmd->wnd].data = NULL;
+ }
+ if(this->osddata[cmd->wnd].palette) {
+ free(this->osddata[cmd->wnd].palette);
+ this->osddata[cmd->wnd].palette = NULL;
+ }
+ memcpy(&this->osddata[cmd->wnd], cmd, sizeof(osd_command_t));
+ if(cmd->palette) {
+ this->osddata[cmd->wnd].palette = malloc(4*cmd->colors);
+ memcpy(this->osddata[cmd->wnd].palette, cmd->palette, 4*cmd->colors);
+ }
+ this->osddata[cmd->wnd].data = NULL;
+ }
+
+ /* if video size differs from expected (VDR osd is designed for 720x576),
+ scale osd to video size or use unscaled (display resolution)
+ blending */
+ if(!use_unscaled) {
+ int w_hi = this->vdr_osd_width * 1100 / 1000;
+ int w_lo = this->vdr_osd_width * 950 / 1000;
+ int h_hi = this->vdr_osd_height * 1100 / 1000;
+ int h_lo = this->vdr_osd_height * 950 / 1000;
+ int width_diff = 0, height_diff = 0;
+
+ update_video_size(this);
+ LOGOSD("video size %dx%d, margins %d..%dx%d..%d",
+ this->video_width, this->video_height, w_lo, w_hi, h_lo, h_hi);
+ if(this->video_width < w_lo) width_diff = -1;
+ else if(this->video_width > w_hi) width_diff = 1;
+ if(this->video_height < h_lo) height_diff = -1;
+ else if(this->video_height > h_hi) height_diff = 1;
+
+ if(width_diff || height_diff) {
+ int new_w = (0x100*cmd->w * this->video_width
+ / this->vdr_osd_width)>>8;
+ int new_h = (0x100*cmd->h * this->video_height
+ / this->vdr_osd_height)>>8;
+ LOGOSD("Size out of margins, rescaling rle image");
+ if(width_diff < 0 || height_diff < 0)
+ if(unscaled_supported && this->unscaled_osd_lowresvideo)
+ use_unscaled = 1;
+
+ if(this->rescale_osd) {
+
+ if(!this->rescale_osd_downscale) {
+ if(width_diff<0) {
+ width_diff = 0;
+ new_w = cmd->w;
+ }
+ if(height_diff<0) {
+ height_diff = 0;
+ new_h = cmd->h;
+ }
+ }
+ if(height_diff || width_diff) {
+ this->osddata[cmd->wnd].data = cmd->data;
+ this->osddata[cmd->wnd].datalen = cmd->datalen;
+
+ rle_scaled = 1;
+ scale_rle_image(cmd, new_w, new_h);
+ } else {
+ LOGOSD("osd_command: size out of margins, using UNSCALED\n");
+ use_unscaled = unscaled_supported;
+ }
+ }
+ }
+ if(!use_unscaled && !rle_scaled) {
+ /* no scaling required, but may still need to re-center OSD */
+ if(this->video_width != this->vdr_osd_width)
+ xmove = (this->video_width - this->vdr_osd_width)/2;
+ if(this->video_height != this->vdr_osd_height)
+ ymove = (this->video_height - this->vdr_osd_height)/2;
+ }
+ }
+
+ if(use_unscaled) {
+ int win_width = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_WIDTH);
+ int win_height = this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_WINDOW_HEIGHT);
+ if(this->rescale_osd) {
+ // it is not nice to have subs in _middle_ of display when using 1440x900 etc...
+
+ if(win_width > 240 && win_height > 196) {
+ if(this->rescale_osd) {
+ if(win_width != this->vdr_osd_width || win_height != this->vdr_osd_height) {
+
+ int new_w = (0x100*cmd->w * win_width
+ / this->vdr_osd_width)>>8;
+ int new_h = (0x100*cmd->h * win_height
+ / this->vdr_osd_height)>>8;
+
+ this->osddata[cmd->wnd].data = cmd->data;
+ this->osddata[cmd->wnd].datalen = cmd->datalen;
+
+ rle_scaled = 1;
+ scale_rle_image(cmd, new_w, new_h);
+ }
+ }
+ }
+ }
+ if(!rle_scaled) {
+ /* no scaling required, but may still need to re-center OSD */
+ if(win_width != this->vdr_osd_width)
+ xmove = (win_width - this->vdr_osd_width)/2;
+ if(win_height != this->vdr_osd_height)
+ ymove = (win_height - this->vdr_osd_height)/2;
+ }
+ }
+
+ /* set position and size for this overlay */
+ ov_event.object.overlay->x = cmd->x + xmove;
+ ov_event.object.overlay->y = cmd->y + ymove;
+ ov_event.object.overlay->width = cmd->w;
+ ov_event.object.overlay->height = cmd->h;
+
+ /* RLE image */
+ ov_event.object.overlay->unscaled = use_unscaled;
+ ov_event.object.overlay->rle = (rle_elem_t*)cmd->data;
+ ov_event.object.overlay->num_rle = cmd->datalen/4; /* two uint_16's in one element */
+ ov_event.object.overlay->data_size = cmd->datalen;
+
+ /* store rle for later scaling (done if video size changes) */
+ if(!use_unscaled && this->rescale_osd &&
+ !this->osddata[cmd->wnd].data &&
+ !rle_scaled /*if scaled, we already have a copy (original data)*/ ) {
+ this->osddata[cmd->wnd].data = malloc(cmd->datalen);
+ memcpy(this->osddata[cmd->wnd].data, cmd->data, cmd->datalen);
+ }
+ cmd->data = NULL;/* we 'consume' data (ownership goes for osd manager) */
+
+ /* send event to overlay manager */
+ ovl_manager->add_event(ovl_manager, (void *)&ov_event);
+
+ this->last_changed_vpts[cmd->wnd] = xine_get_current_vpts(this->stream);
+
+ } else {
+ LOGMSG("Unknown OSD command %d", cmd->cmd);
+ return -2;
+ }
+
+ LOGOSD("OSD command %d done", cmd->cmd);
+ return 0;
+}
+
+static int vdr_plugin_exec_osd_command(input_plugin_t *this_gen,
+ osd_command_t *cmd)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ int result;
+
+ pthread_mutex_lock (&this->osd_lock);
+ result = exec_osd_command(this, cmd);
+ pthread_mutex_unlock (&this->osd_lock);
+
+ return result;
+}
+
+static void vdr_scale_osds(vdr_input_plugin_t *this,
+ int video_width, int video_height)
+{
+ if(this->video_width != video_width ||
+ this->video_height != video_height) {
+ LOGOSD("New video size (%dx%d->%dx%d)",
+ this->video_width, this->video_height,
+ video_width, video_height);
+
+ this->video_width = video_width;
+ this->video_height = video_height;
+
+ if(! pthread_mutex_lock(&this->osd_lock))
+ {
+ int i;
+ /* just call exec_osd_command for all stored osd's.
+ scaling is done automatically if required. */
+ for(i=0; i<MAX_OSD_OBJECT; i++)
+ if(this->osdhandle[i] >= 0 &&
+ this->osddata[i].data) {
+ osd_command_t tmp;
+ memcpy(&tmp, &this->osddata[i], sizeof(osd_command_t));
+ memset(&this->osddata[i], 0, sizeof(osd_command_t));
+
+ exec_osd_command(this, &tmp);
+
+ if(tmp.palette)
+ free(tmp.palette);
+ if(tmp.data)
+ free(tmp.data);
+ }
+ pthread_mutex_unlock(&this->osd_lock);
+ } else {
+LOGMSG(" vdr_scale_osds lock failed");
+ }
+ }
+}
+
+/******************************* Control *********************************/
+
+static void suspend_demuxer(vdr_input_plugin_t *this)
+{
+ this->stream->demux_action_pending = 1;
+ signal_buffer_not_empty(this);
+ pthread_mutex_lock( &this->stream->demux_lock );
+ /* must be paired with resume_demuxer !!! */
+}
+
+static void resume_demuxer(vdr_input_plugin_t *this)
+{
+ /* must be paired with suspend_demuxer !!! */
+ this->stream->demux_action_pending = 0;
+ pthread_mutex_unlock( &this->stream->demux_lock );
+}
+
+static void vdr_x_demux_flush_engine (xine_stream_t *stream, vdr_input_plugin_t *this)
+{
+ buf_element_t *buf;
+
+ if(this->curpos > this->discard_index) {
+#if 0
+#warning Check this
+ LOGMSG("Possibly flushing too much !!! (diff=%lld bytes, guard @%lld)",
+ this->curpos - this->discard_index, this->guard_index);
+#endif
+ if(this->curpos < this->guard_index) {
+ LOGMSG("Guard > current position, decoder flush skipped");
+ return;
+ }
+ }
+
+ stream->xine->port_ticket->acquire(stream->xine->port_ticket, 1);
+
+ if (stream->video_out) {
+ stream->video_out->set_property(stream->video_out, VO_PROP_DISCARD_FRAMES, 1);
+ }
+ if (stream->audio_out) {
+ stream->audio_out->set_property(stream->audio_out, AO_PROP_DISCARD_BUFFERS, 1);
+ }
+
+ fifo_buffer_clear(stream->video_fifo);
+ fifo_buffer_clear(stream->audio_fifo);
+
+ buf = stream->video_fifo->buffer_pool_alloc (stream->video_fifo);
+ buf->type = BUF_CONTROL_RESET_DECODER;
+ stream->video_fifo->put (stream->video_fifo, buf);
+
+ buf = stream->audio_fifo->buffer_pool_alloc (stream->audio_fifo);
+ buf->type = BUF_CONTROL_RESET_DECODER;
+ stream->audio_fifo->put (stream->audio_fifo, buf);
+
+ /* on seeking we must wait decoder fifos to process before doing flush.
+ * otherwise we flush too early (before the old data has left decoders)
+ */
+ _x_demux_control_headers_done (stream);
+
+ if (stream->video_out) {
+ stream->video_out->flush(stream->video_out);
+ stream->video_out->set_property(stream->video_out,
+ VO_PROP_DISCARD_FRAMES, 0);
+ }
+
+ if (stream->audio_out) {
+ stream->audio_out->flush(stream->audio_out);
+ stream->audio_out->set_property(stream->audio_out,
+ AO_PROP_DISCARD_BUFFERS, 0);
+ }
+
+ stream->xine->port_ticket->release(stream->xine->port_ticket, 1);
+}
+
+static void vdr_x_demux_control_newpts( xine_stream_t *stream, int64_t pts,
+ uint32_t flags ) {
+
+ buf_element_t *buf;
+
+ buf = stream->audio_fifo->buffer_pool_try_alloc (stream->audio_fifo);
+ if(buf) {
+ buf->type = BUF_CONTROL_NEWPTS;
+ buf->decoder_flags = flags;
+ buf->disc_off = pts;
+ stream->video_fifo->put (stream->video_fifo, buf);
+ } else {
+ LOGMSG("vdr_x_demux_control_newpts BUFFER FULL!");
+ }
+ if(buf) {
+ buf = stream->audio_fifo->buffer_pool_try_alloc (stream->audio_fifo);
+ buf->type = BUF_CONTROL_NEWPTS;
+ buf->decoder_flags = flags;
+ buf->disc_off = pts;
+ stream->audio_fifo->put (stream->audio_fifo, buf);
+ } else {
+ LOGMSG("vdr_x_demux_control_newpts BUFFER FULL!");
+ }
+}
+
+static void vdr_flush_engine(vdr_input_plugin_t *this)
+{
+ if(!this->stream_start) {
+ /* suspend demuxer */
+ pthread_mutex_unlock( &this->lock ); /* to let demuxer return from vdr_plugin_read_* */
+ suspend_demuxer(this);
+ pthread_mutex_lock( &this->lock );
+
+ reset_scr_tunning(this, this->speed_before_pause);
+
+#if 0
+/* net mode: may already have important data received as data and control are not synchronized.
+ So, do discard in read_block.
+ Another possibility is to flush up to discard_index.
+
+ * buffer size can't be added to curpos (network headers ; possible missing packets).
+ * local mode: size must be added if flushed ...
+*/
+ this->curpos += (uint64_t)fifo_buffer_clear(this->block_buffer);
+ if(this->curr_buffer) { /* safe, but only now, as we know demuxer can't be in middle of PES frame now */
+ this->curpos += (uint64_t)this->curr_buffer->size;
+ this->curr_buffer->free_buffer(this->curr_buffer);
+ this->curr_buffer = NULL;
+ }
+#endif
+
+ vdr_x_demux_flush_engine (this->stream, this);
+
+#if 0
+ /* disabled _x_demux_control_start as it causes alsa output driver to exit now and then ...*/
+ _x_demux_control_start(this->stream);
+#endif
+ this->stream_start = 1;
+
+ resume_demuxer(this);
+ } else {
+ /*LOGMSG("vdr_flush_engine: stream_start=true, skipped flush");*/
+ }
+}
+
+static int set_deinterlace_method(vdr_input_plugin_t *this, char *method_name)
+{
+ int method = 0;
+ if(!strncmp(method_name,"bob",3)) { method = 1;
+ } else if(!strncmp(method_name,"weave",5)) { method = 2;
+ } else if(!strncmp(method_name,"greedy",6)) { method = 3;
+ } else if(!strncmp(method_name,"onefield",8)) { method = 4;
+ } else if(!strncmp(method_name,"onefield_xv",11)) { method = 5;
+ } else if(!strncmp(method_name,"linearblend",11)) { method = 6;
+ } else if(!strncmp(method_name,"none",4)) { method = 0;
+ } else if(!*method_name) { method = 0;
+ } else if(!strncmp(method_name,"tvtime",6)) { method = 0;
+ /* old deinterlacing system must be switched off.
+ tvtime will be configured as all other post plugins with
+ "POST tvtime ..." control message */
+ } else return -2;
+
+ this->stream->xine->config->update_num(this->stream->xine->config,
+ "video.output.xv_deinterlace_method",
+ method);
+ xine_set_param(this->stream, XINE_PARAM_VO_DEINTERLACE, method ? 1 : 0);
+
+ return 0;
+}
+
+static int set_video_properties(vdr_input_plugin_t *this,
+ int hue, int saturation,
+ int brightness, int contrast)
+{
+ pthread_mutex_lock(&this->lock);
+
+ /* when changed first time, save original/default values */
+ if(!this->video_properties_saved &&
+ (hue>=0 || saturation>=0 || contrast>=0 || brightness>=0)) {
+ this->video_properties_saved = 1;
+ this->orig_hue = xine_get_param(this->stream,
+ XINE_PARAM_VO_HUE );
+ this->orig_saturation = xine_get_param(this->stream,
+ XINE_PARAM_VO_SATURATION );
+ this->orig_brightness = xine_get_param(this->stream,
+ XINE_PARAM_VO_BRIGHTNESS );
+ this->orig_contrast = xine_get_param(this->stream,
+ XINE_PARAM_VO_CONTRAST );
+ }
+
+ /* set new values, or restore default/original values */
+ if(hue>=0 || this->video_properties_saved)
+ xine_set_param(this->stream, XINE_PARAM_VO_HUE,
+ hue>=0 ? hue : this->orig_hue );
+ if(saturation>=0 || this->video_properties_saved)
+ xine_set_param(this->stream, XINE_PARAM_VO_SATURATION,
+ saturation>0 ? saturation : this->orig_saturation );
+ if(brightness>=0 || this->video_properties_saved)
+ xine_set_param(this->stream, XINE_PARAM_VO_BRIGHTNESS,
+ brightness>=0 ? brightness : this->orig_brightness );
+ if(contrast>=0 || this->video_properties_saved)
+ xine_set_param(this->stream, XINE_PARAM_VO_CONTRAST,
+ contrast>=0 ? contrast : this->orig_contrast );
+
+ if(hue<0 && saturation<0 && contrast<0 && brightness<0)
+ this->video_properties_saved = 0;
+
+ pthread_mutex_unlock(&this->lock);
+ return 0;
+}
+
+static int set_live_mode(vdr_input_plugin_t *this, int onoff)
+{
+ pthread_mutex_lock(&this->lock);
+ if(this->live_mode != onoff) {
+ config_values_t *config = this->stream->xine->config;
+ this->live_mode = onoff;
+ if(!this->live_mode) {
+ config->update_num(this->stream->xine->config,
+ "audio.synchronization.av_sync_method",0);
+ this->max_buffers = (this->buffer_pool->buffer_pool_capacity >> 1) - 10;
+ if(this->scr_tunning != SCR_TUNNING_OFF) {
+ LOGSCR("reset scr tunning by set_live_mode");
+ reset_scr_tunning(this, this->speed_before_pause=XINE_FINE_SPEED_NORMAL);
+ }
+ } else {
+ config->update_num(this->stream->xine->config,
+ "audio.synchronization.av_sync_method",1);
+#ifdef INITIAL_READ_DELAY
+ this->read_delay = INITIAL_READ_DELAY;
+#endif
+ this->max_buffers = this->buffer_pool->buffer_pool_capacity - 10;
+ this->stream->metronom->set_option(this->stream->metronom,
+ METRONOM_PREBUFFER, METRONOM_PREBUFFER_VAL);
+ if(this->scr_tunning != SCR_TUNNING_PAUSED) {
+ LOGSCR("pause scr tunning by set_live_mode");
+ scr_tunning_set_paused(this);
+ }
+ }
+ }
+
+#if 1
+ if(this->live_mode) {
+ if(this->scr_tunning != SCR_TUNNING_PAUSED) {
+ LOGSCR("pause scr tunning by set_live_mode");
+ scr_tunning_set_paused(this);
+ }
+ }
+#endif
+
+ pthread_mutex_unlock(&this->lock);
+
+ signal_buffer_pool_not_empty(this);
+ return 0;
+}
+
+static int set_playback_speed(vdr_input_plugin_t *this, int speed)
+{
+/* speed:
+ <0 - show each abs(n)'th frame (drop other frames)
+ * no audio
+ 0 - paused
+ * audio back if mute != 0
+ >0 - show each frame n times
+ * no audio
+ 1 - normal
+*/
+ pthread_mutex_lock(&this->lock);
+ this->is_paused = 0;
+ if(speed == 0) {
+ this->is_paused = 1;
+ } else if(speed>64 || speed<-64) {
+ pthread_mutex_unlock(&this->lock);
+ return -2;
+ }
+
+ if(speed>0)
+ speed = this->speed_before_pause = XINE_FINE_SPEED_NORMAL/speed;
+ else
+ speed = this->speed_before_pause = XINE_FINE_SPEED_NORMAL*(-speed);
+
+ if(this->scr_tunning != SCR_TUNNING_PAUSED &&
+ _x_get_fine_speed(this->stream) != speed) {
+ _x_set_fine_speed (this->stream, speed);
+ }
+
+ if(this->slave_stream)
+ _x_set_fine_speed (this->slave_stream, speed);
+
+ pthread_mutex_unlock(&this->lock);
+ return 0;
+}
+
+static void vdr_event_cb (void *user_data, const xine_event_t *event);
+
+static int handle_control_playfile(vdr_input_plugin_t *this, char *cmd)
+{
+ char filename[1024]="", *pt = cmd + 9, *subs = NULL;
+ int loop = 0, pos = 0, err = 0;
+
+ while(*pt==' ') pt++;
+
+ if(!strncmp(pt, "Loop ", 5)) {
+ loop = 1;
+ pt += 5;
+ while(*pt==' ') pt++;
+ }
+
+ pos = atoi(pt);
+
+ while(*pt && *pt != ' ') pt++;
+ while(*pt == ' ') pt++;
+
+ strncpy(filename, pt, 1023);
+ if(strchr(filename,'\r'))
+ *strchr(filename,'\r') = 0;
+ if(strchr(filename,'\n'))
+ *strchr(filename,'\n') = 0;
+
+ LOGMSG("PLAYFILE (Loop: %d, Offset: %ds, File: %s)",
+ loop, pos, *filename ? filename : "<STOP>" );
+
+ if(*filename) {
+ subs = FindSubFile(filename);
+ if(subs) {
+ LOGMSG("Found subtitles: %s", subs);
+ strcat(filename, "#subtitle:");
+ strcat(filename, subs);
+ free(subs);
+ } else {
+ LOGDBG("Subtitles not found for %s", filename);
+ }
+ this->slave_stream = xine_stream_new(this->stream->xine,
+ this->stream->audio_out,
+ this->stream->video_out);
+
+ this->slave_event_queue = xine_event_new_queue (this->slave_stream);
+ xine_event_create_listener_thread (this->slave_event_queue,
+ vdr_event_cb, this);
+
+ err = !xine_open(this->slave_stream, filename);
+ if(!err)
+ err = !xine_play(this->slave_stream, 0, 1000 * pos);
+ if(!err) {
+ set_live_mode(this, 0);
+ set_playback_speed(this, 1);
+ if(this->funcs.fe_control) {
+ char tmp[128];
+ sprintf(tmp, "SLAVE 0x%x\r\n", (int)this->slave_stream);
+ (*(this->funcs.fe_control))(this->funcs.fe_handle, tmp);
+ }
+ } else {
+ LOGMSG("Error playing file ! (File not found ? Unknown format ?)");
+ *filename = 0;
+ }
+ }
+
+ if(!*filename) {
+ LOGMSG("PLAYFILE <STOP>: Closing slave stream");
+ if(this->slave_stream) {
+ if (this->slave_event_queue) {
+ xine_event_dispose_queue (this->slave_event_queue);
+ this->slave_event_queue = NULL;
+ }
+ if(this->funcs.fe_control)
+ (*(this->funcs.fe_control))(this->funcs.fe_handle, "SLAVE 0x0\r\n");
+ xine_stop(this->slave_stream);
+ xine_close(this->slave_stream);
+ xine_dispose(this->slave_stream);
+ this->slave_stream = NULL;
+ }
+ }
+
+ return err ? CONTROL_PARAM_ERROR : CONTROL_OK;
+}
+
+static int handle_control_substream(vdr_input_plugin_t *this, char *cmd)
+{
+ unsigned int pid;
+ if(1 == sscanf(cmd, "SUBSTREAM 0x%x", &pid)) {
+ pthread_mutex_lock(&this->lock);
+
+ if(!this->funcs.fe_control)
+ LOGERR("ERROR - no fe_control set !");
+
+ if((pid & 0xf0) == 0xe0 && this->funcs.fe_control) { /* video 0...15 */
+ if(!this->pip_stream) {
+LOGMSG("create pip stream %s", cmd);
+ this->pip_stream =
+ (*(this->funcs.fe_control))(this->funcs.fe_handle, cmd);
+LOGMSG(" pip stream created");
+ }
+ } else {
+ /*} else if(audio) {*/
+ if(this->pip_stream && this->funcs.fe_control) {
+ LOGMSG("close pip stream");
+
+ this->pip_stream = NULL;
+ (*(this->funcs.fe_control))(this->funcs.fe_handle, cmd);
+ /* xine_stop(this->pip_stream); */
+ /* xine_close(this->pip_stream); */
+ /* xine_dispose(this->pip_stream); */
+ }
+ }
+ pthread_mutex_unlock(&this->lock);
+ return CONTROL_OK;
+ }
+ return CONTROL_PARAM_ERROR;
+}
+
+static int handle_control_osdscaling(vdr_input_plugin_t *this, char *cmd)
+{
+ int err = CONTROL_OK;
+ pthread_mutex_lock(&this->lock);
+ if(1 == sscanf(cmd, "OSDSCALING %d", &this->rescale_osd)) {
+ this->rescale_osd_downscale = strstr(cmd, "NoDownscale") ? 0 : 1;
+ this->unscaled_osd = strstr(cmd, "UnscaledAlways") ? 1 : 0;
+ this->unscaled_osd_opaque = strstr(cmd, "UnscaledOpaque") ? 1 : 0;
+ this->unscaled_osd_lowresvideo = strstr(cmd, "UnscaledLowRes") ? 1 : 0;
+ } else
+ err = CONTROL_PARAM_ERROR;
+ pthread_mutex_unlock(&this->lock);
+ return err;
+}
+
+static int control_read_data(vdr_input_plugin_t *this,
+ unsigned char *buf, int len);
+
+static int handle_control_osdcmd(vdr_input_plugin_t *this, char *cmd)
+{
+ osd_command_t osdcmd;
+ int err = CONTROL_OK;
+
+ if(this->fd_control < 0)
+ return CONTROL_DISCONNECTED;
+
+ if(control_read_data(this, (unsigned char*)&osdcmd, sizeof(osd_command_t))
+ != sizeof(osd_command_t)) {
+ LOGMSG("control: error reading OSDCMD data");
+ return CONTROL_DISCONNECTED;
+ }
+
+ /*if(0x12345678 != ntohl(0x12345678)) {*/
+ /* -> host order */
+ osdcmd.cmd = ntohl(osdcmd.cmd);
+ osdcmd.wnd = ntohl(osdcmd.wnd);
+ osdcmd.pts = ntohll(osdcmd.pts);
+ osdcmd.delay_ms = ntohl(osdcmd.delay_ms);
+ osdcmd.x = ntohs(osdcmd.x);
+ osdcmd.y = ntohs(osdcmd.y);
+ osdcmd.w = ntohs(osdcmd.w);
+ osdcmd.h = ntohs(osdcmd.h);
+ osdcmd.datalen = ntohl(osdcmd.datalen);
+ osdcmd.colors = ntohl(osdcmd.colors);
+ /*}*/
+
+ if(osdcmd.palette && osdcmd.colors>0) {
+ int bytes = sizeof(xine_clut_t)*(osdcmd.colors);
+ osdcmd.palette = malloc(bytes);
+ if(control_read_data(this, (unsigned char *)osdcmd.palette, bytes)
+ != bytes) {
+ LOGMSG("control: error reading OSDCMD palette");
+ err = CONTROL_DISCONNECTED;
+ }
+ } else {
+ osdcmd.palette = NULL;
+ }
+
+ if(err == CONTROL_OK && osdcmd.data && osdcmd.datalen>0) {
+ osdcmd.data = (xine_rle_elem_t*)malloc(osdcmd.datalen);
+ if(control_read_data(this, (unsigned char *)osdcmd.data, osdcmd.datalen)
+ != osdcmd.datalen) {
+ LOGMSG("control: error reading OSDCMD bitmap");
+ err = CONTROL_DISCONNECTED;
+ } else {
+ if(0x1234 != ntohs(0x1234)) {
+ int i;
+ for(i=0; i<osdcmd.datalen/4; i++) {
+ osdcmd.data[i].len = ntohs(osdcmd.data[i].len);
+ osdcmd.data[i].color = ntohs(osdcmd.data[i].color);
+ }
+ }
+ }
+ } else {
+ osdcmd.data = NULL;
+ }
+
+ if(err == CONTROL_OK) {
+ /*pthread_mutex_lock (&this->osd_lock);*/ /* done in exec_osd_command */
+ err = vdr_plugin_exec_osd_command((input_plugin_t*)this, &osdcmd);
+ /* pthread_mutex_unlock (&this->osd_lock); */
+ }
+
+ if(osdcmd.data)
+ free(osdcmd.data);
+ if(osdcmd.palette)
+ free(osdcmd.palette);
+
+ return err;
+}
+
+/************************** Control from VDR ******************************/
+
+static int control_read_cmd(vdr_input_plugin_t *this, char *buf, int maxlen)
+{
+ /* read next command */
+ int num_bytes=0, total_bytes=0, err /*, timeouts=0*/;
+
+ *buf=0;
+ while(total_bytes < maxlen-1 ) {
+ /* err = _x_io_select(NULL, fd, XIO_READ_READY, 1000); */
+ err = io_select_rd(this->fd_control);
+
+ if(this->fd_control < 0)
+ return -1;
+
+ if(err == XIO_TIMEOUT)
+ continue;
+
+#if 0
+ if(err == XIO_TIMEOUT) {
+ if(!*buf || timeouts++>2)
+ return 0;
+ continue;
+ }
+#endif
+ if(err == XIO_ABORTED) {
+ LOGERR("control_read_cmd XIO_ABORTED at [%d]", num_bytes);
+ continue;
+ /* return 0; */
+ }
+ if(err != XIO_READY /* == XIO_ERROR */) {
+ LOGERR("control_read_cmd read error at [%d]", num_bytes);
+ return -1;
+ }
+
+ /* num_bytes = _x_io_tcp_read (NULL, fd, buf + total_bytes, 1); */
+ num_bytes = read (this->fd_control, buf + total_bytes, 1);
+ if (num_bytes <= 0) {
+ LOGERR("control_read_cmd read error at [%d]", num_bytes);
+ if(num_bytes < 0 && errno == EINTR && this->fd_control >= 0) {
+LOGMSG("EINTR - continue");
+ continue;
+ }
+LOGMSG("return -1");
+ return -1;
+ }
+
+ if(buf[total_bytes]) {
+ if(buf[total_bytes] == '\r') {
+ buf[total_bytes] = 0;
+ } else if(buf[total_bytes] == '\n') {
+ buf[total_bytes] = 0;
+ break;
+ } else {
+ total_bytes ++;
+ buf[total_bytes] = 0;
+ }
+ }
+ TRACE("input_vdr: control_read_cmd: %d bytes ... %s\n",
+ total_bytes, buf);
+ }
+
+ TRACE("control_read_cmd: %d bytes (max %d)\n", total_bytes, maxlen);
+
+ return total_bytes;
+}
+
+
+static int control_read_data(vdr_input_plugin_t *this,
+ unsigned char *buf, int len)
+{
+ int num_bytes, total_bytes = 0;
+#if 0
+ int timeouts = 0;
+#endif
+
+ while(total_bytes < len) {
+ /*int err = _x_io_select(NULL, fd, XIO_READ_READY, 1000);*/
+ int err = io_select_rd(this->fd_control);
+
+ if(err == XIO_TIMEOUT)
+ continue;
+#if 0
+ if(err == XIO_TIMEOUT) {
+ LOGMSG("control_read_data timeout");
+ if(++timeouts >= 2)
+ return -1;
+ continue;
+ }
+#endif
+ if(err == XIO_ABORTED) {
+ LOGERR("control_read_data XIO_ABORTED");
+ continue;
+ }
+ if(err == XIO_ERROR) {
+ LOGERR("control_read_data poll error");
+ return -1;
+ }
+
+ /* num_bytes = _x_io_tcp_read (NULL, fd, buf + total_bytes, len - total_bytes); */
+ num_bytes = read (this->fd_control, buf + total_bytes, len - total_bytes);
+ if (num_bytes <= 0) {
+ LOGERR("control_read_data read() error");
+ return -1;
+ }
+ total_bytes += num_bytes;
+ }
+
+ return total_bytes;
+}
+
+
+static int vdr_plugin_flush(vdr_input_plugin_t *this, int timeout_ms);
+static int vdr_plugin_poll(vdr_input_plugin_t *this, int timeout_ms);
+
+static int vdr_plugin_flush_remote(vdr_input_plugin_t *this, int timeout_ms, uint64_t offset)
+{
+ int r;
+ char buf[64];
+ buf_element_t *bufelem;
+
+ pthread_mutex_lock(&this->lock);
+ this->live_mode = 0; /* --> 1 again when data arrives ... */
+ if(this->scr_tunning) {
+ /*LOGMSG("reset scr tunning by flush");*/
+ reset_scr_tunning(this, this->speed_before_pause);
+ }
+ pthread_mutex_unlock(&this->lock);
+
+ while(this->curpos < offset && timeout_ms > 0) {
+ LOGDBG("FLUSH: wait position (%lld ; need %lld)",
+ this->curpos, offset);
+ xine_usec_sleep(3*1000);
+ timeout_ms -= 3;
+ }
+
+ pthread_mutex_lock(&this->lock);
+ this->live_mode = 0;
+ if(this->scr_tunning) {
+ /*LOGMSG("reset scr tunning by flush");*/
+ reset_scr_tunning(this, this->speed_before_pause);
+ }
+ pthread_mutex_unlock(&this->lock);
+
+ bufelem = this->buffer_pool->buffer_pool_try_alloc(this->buffer_pool);
+ if(bufelem) {
+ bufelem->type = BUF_CONTROL_FLUSH_DECODER;
+ this->block_buffer->put(this->block_buffer, bufelem);
+ }
+
+ r = vdr_plugin_flush(this, timeout_ms);
+ sprintf(buf, "RESULT %d %d\r\n", this->token, r);
+ write(this->fd_control, buf, strlen(buf));
+
+ xine_usec_sleep(20*1000);
+ /*#warning test sleep*/
+
+ this->live_mode = 1;
+
+ this->stream->metronom->set_option(this->stream->metronom,
+ METRONOM_PREBUFFER,
+ METRONOM_PREBUFFER_VAL);
+ pthread_mutex_lock(&this->lock);
+ /*#warning no pause*/
+ /* scr_tunning_set_paused(this);*/
+ this->guard_index = offset;
+ pthread_mutex_unlock(&this->lock);
+
+ return CONTROL_OK;
+}
+
+static int vdr_plugin_parse_control(input_plugin_t *this_gen, char *cmd)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ int err = CONTROL_OK, i, j;
+ int32_t tmp32 = 0;
+ int64_t tmp64 = 0LL;
+ xine_stream_t *stream = this->stream;
+ static const char *str_poll = "POLL";
+ char buf[128];
+
+ pthread_mutex_lock(&this->vdr_entry_lock);
+
+ LOGCMD("vdr_plugin_parse_control: %s", cmd);
+
+ if(this->slave_stream)
+ stream = this->slave_stream;
+
+ if( *((uint32_t*)cmd) == *((uint32_t*)str_poll) ||
+ !strncasecmp(cmd, "POLL ", 5)) {
+ tmp32 = atoi(cmd+5);
+ if(tmp32 >= 0 && tmp32 < 1000) {
+ if(this->fd_control >= 0) {
+ sprintf(buf, "POLL %d\r\n", vdr_plugin_poll(this, tmp32));
+ write(this->fd_control, buf, strlen(buf));
+ } else {
+ err = vdr_plugin_poll(this, tmp32);
+ }
+ } else {
+ err = CONTROL_PARAM_ERROR;
+ }
+
+ } else if(!strncasecmp(cmd, "OSDSCALING", 10)) {
+ err = handle_control_osdscaling(this, cmd);
+
+ } else if(!strncasecmp(cmd, "OSDCMD", 6)) {
+ err = handle_control_osdcmd(this, cmd);
+
+ } else if(!strncasecmp(cmd, "VIDEO_PROPERTIES ", 17)) {
+ int hue, saturation, brightness, contrast;
+ if(4 == sscanf(cmd, "VIDEO_PROPERTIES %d %d %d %d",
+ &hue, &saturation, &brightness, &contrast))
+ err = set_video_properties(this, hue, saturation, brightness, contrast);
+ else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "DEINTERLACE ", 12)) {
+ if(this->fd_control < 0)
+ err = set_deinterlace_method(this, cmd+12);
+
+ } else if(!strncasecmp(cmd, "NOVIDEO ", 8)) {
+ if(1 == sscanf(cmd, "NOVIDEO %d", &tmp32)) {
+ pthread_mutex_lock(&this->lock);
+ this->no_video = tmp32;
+ if(this->no_video) {
+ this->max_buffers = 6;
+ } else {
+ this->max_buffers = this->buffer_pool->buffer_pool_capacity;
+ if(!this->live_mode) this->max_buffers >>= 1;
+ this->max_buffers -= 10;
+ }
+ pthread_mutex_unlock(&this->lock);
+ } else
+ err = CONTROL_PARAM_ERROR;
+
+ signal_buffer_pool_not_empty(this);
+
+ } else if(!strncasecmp(cmd, "DISCARD ", 8)) {
+ if(1 == sscanf(cmd, "DISCARD %lld", &tmp64)) {
+ pthread_mutex_lock(&this->lock);
+ this->discard_index = tmp64;
+ vdr_flush_engine(this);
+ this->I_frames = 0;
+ pthread_mutex_unlock(&this->lock);
+ } else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "STREAMPOS ", 10)) {
+ if(1 == sscanf(cmd, "STREAMPOS %lld", &tmp64)) {
+ pthread_mutex_lock(&this->lock);
+ vdr_flush_engine(this);
+ this->curpos = tmp64;
+ this->discard_index = this->curpos;
+ this->guard_index = 0;
+ pthread_mutex_unlock(&this->lock);
+ } else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "TRICKSPEED ", 11)) {
+ err = (1 == sscanf(cmd, "TRICKSPEED %d", &tmp32)) ?
+ set_playback_speed(this, tmp32) :
+ CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "STILL ", 6)) {
+ if(this->fd_control >= 0) {
+ /*set_live_mode(this, 1);*/
+ if(cmd[6] == '0')
+ this->still_mode = 0;
+ if(cmd[6] == '1') {
+ this->still_mode = 1;
+ reset_scr_tunning(this, this->speed_before_pause);
+ }
+ this->stream_start = 1;
+ }
+
+ } else if(!strncasecmp(cmd, "LIVE ", 5)) {
+ this->still_mode = 0;
+#if 1
+ if(this->fd_control >= 0) {
+ set_live_mode(this, 1);
+ if(cmd[5] == '0') {
+ LOGSCR("reset scr tunning by LIVE 0");
+ reset_scr_tunning(this, this->speed_before_pause);
+ } else {
+ /* */
+ }
+ } else
+#endif
+ err = (1 == sscanf(cmd, "LIVE %d", &tmp32)) ?
+ set_live_mode(this, tmp32) : -2 ;
+
+ } else if(!strncasecmp(cmd, "VOLUME ", 7)) {
+ if(1 == sscanf(cmd, "VOLUME %d", &tmp32)) {
+ xine_set_param(stream, XINE_PARAM_AUDIO_VOLUME, tmp32);
+ xine_set_param(stream, XINE_PARAM_AUDIO_MUTE, tmp32<=0 ? 1 : 0);
+ } else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "AUDIOCOMPRESSION ",17)) {
+ if(1 == sscanf(cmd, "AUDIOCOMPRESSION %d", &tmp32)) {
+ xine_set_param(stream, XINE_PARAM_AUDIO_COMPR_LEVEL, tmp32);
+ } else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "AUDIOSURROUND ",14)) {
+ if(1 == sscanf(cmd, "AUDIOSURROUND %d", &tmp32)) {
+ stream->xine->config->update_num(stream->xine->config,
+ "audio.a52.surround_downmix", tmp32?1:0);
+ } else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "EQUALIZER ", 10)) {
+ int eqs[XINE_PARAM_EQ_16000HZ - XINE_PARAM_EQ_30HZ + 2] = {0};
+ sscanf(cmd,"EQUALIZER %d %d %d %d %d %d %d %d %d %d",
+ eqs,eqs+1,eqs+2,eqs+3,eqs+4,eqs+5,eqs+6,eqs+7,eqs+8,eqs+9);
+ for(i=XINE_PARAM_EQ_30HZ,j=0; i<=XINE_PARAM_EQ_16000HZ; i++,j++)
+ xine_set_param(stream, i, eqs[j]);
+
+ } else if(!strncasecmp(cmd, "NEWAUDIOSTREAM AC3", 18)) {
+#if 0
+ vdr_flush_engine(this);
+ vdr_x_demux_flush_engine(stream,this);
+ _x_demux_control_start(stream);
+#endif
+
+ } else if(!strncasecmp(cmd, "NEWAUDIOSTREAM ", 15)) {
+ if(!this->slave_stream) {
+ if(1 == sscanf(cmd, "NEWAUDIOSTREAM %d", &tmp32)) {
+ buf_element_t *buf_elem =
+ stream->audio_fifo->buffer_pool_try_alloc (stream->audio_fifo);
+ tmp32 &= 0x1f;
+ if(buf_elem) {
+ /* syslog(LOG_INFO, "xineliboutput: %d",tmp32); */
+ buf_elem->type = BUF_CONTROL_AUDIO_CHANNEL;
+ buf_elem->decoder_info[0] = tmp32;
+ this->block_buffer->put(this->block_buffer, buf_elem);
+ }
+ } else {
+ err = CONTROL_PARAM_ERROR;
+ }
+ }
+
+ } else if(!strncasecmp(cmd, "NEWSPUSTREAM ", 13)) {
+ if(1 == sscanf(cmd, "NEWSPUSTREAM %d", &tmp32)) {
+ buf_element_t *buf_elem =
+ stream->video_fifo->buffer_pool_try_alloc (stream->video_fifo);
+ tmp32 &= 0x1f;
+ if(buf_elem) {
+LOGDBG("SPU channel selected: %d", tmp32);
+ buf_elem->type = BUF_CONTROL_SPU_CHANNEL;
+ buf_elem->decoder_info[0] = tmp32; /* widescreen / auto stream id */
+ buf_elem->decoder_info[1] = tmp32; /* letterbox stream id */
+ buf_elem->decoder_info[2] = tmp32; /* pan&scan stream id */
+ this->block_buffer->put(this->block_buffer, buf_elem);
+ }
+ } else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "AUDIODELAY ", 11)) {
+ if(1 == sscanf(cmd, "AUDIODELAY %d", &tmp32))
+ xine_set_param(stream, XINE_PARAM_AV_OFFSET, tmp32*90000/1000);
+ else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "SYNC ", 5)) {
+ if(this->fd_control >= 0)
+ write(this->fd_control, cmd, strlen(cmd));
+
+ } else if(!strncasecmp(cmd, "GETSTC", 6)) {
+ int64_t pts = xine_get_current_vpts(stream) -
+ stream->metronom->get_option(stream->metronom,
+ METRONOM_VPTS_OFFSET);
+ if(this->fd_control >= 0) {
+ sprintf(buf, "STC %lld\r\n", pts);
+ write(this->fd_control, buf, strlen(buf));
+ } else {
+ *((int64_t *)cmd) = pts;
+ }
+
+ } else if(!strncasecmp(cmd, "FLUSH ", 6)) {
+ if(1 == sscanf(cmd, "FLUSH %d", &tmp32)) {
+ if(this->fd_control >= 0) {
+ tmp64 = 0ULL;
+ tmp32 = 0;
+ sscanf(cmd, "FLUSH %d %lld", &tmp32, &tmp64);
+ err = vdr_plugin_flush_remote(this, tmp32, tmp64);
+ } else {
+ err = vdr_plugin_flush(this, tmp32);
+ }
+ } else
+ err = CONTROL_PARAM_ERROR;
+
+ } else if(!strncasecmp(cmd, "TOKEN ", 6)) {
+ this->token = atoi(cmd+6);
+
+ } else if(!strncasecmp(cmd, "SUBSTREAM ", 9)) {
+ err = handle_control_substream(this, cmd);
+
+ } else if(!strncasecmp(cmd, "POST ", 5)) {
+ if(!this->funcs.fe_control)
+ LOGERR("ERROR - no fe_control set ! (%s failed)", cmd);
+ else
+ err = (int) (*(this->funcs.fe_control))(this->funcs.fe_handle, cmd);
+
+ } else if(!strncasecmp(cmd, "PLAYFILE ", 9)) {
+ err = handle_control_playfile(this, cmd);
+ if(this->fd_control >= 0) {
+ sprintf(buf, "RESULT %d %d\r\n", this->token, err);
+ write(this->fd_control, buf, strlen(buf));
+ err = CONTROL_OK;
+ }
+
+ } else if(!strncasecmp(cmd, "SEEK ", 5)) {
+ if(this->slave_stream) {
+ int pos_stream=0, pos_time=0, length_time=0;
+ xine_get_pos_length(this->slave_stream,
+ &pos_stream, &pos_time, &length_time);
+ if(cmd[5]=='+')
+ pos_time += atoi(cmd+6) * 1000;
+ else if(cmd[5]=='-')
+ pos_time -= atoi(cmd+6) * 1000;
+ else
+ pos_time = atoi(cmd+5) * 1000;
+ err = xine_play (this->slave_stream, 0, pos_time);
+ if(this->fd_control >= 0)
+ err = CONTROL_OK;
+ }
+
+ } else if(!strncasecmp(cmd, "GETLENGTH", 9)) {
+ int pos_stream=0, pos_time=0, length_time=0;
+ xine_get_pos_length(stream, &pos_stream, &pos_time, &length_time);
+ err = length_time/1000;
+ if(this->fd_control >= 0) {
+ sprintf(buf, "RESULT %d %d\r\n", this->token, err);
+ write(this->fd_control, buf, strlen(buf));
+ err = CONTROL_OK;
+ }
+
+ } else if(!strncasecmp(cmd, "GETPOS", 6)) {
+ int pos_stream=0, pos_time=0, length_time=0;
+ xine_get_pos_length(stream, &pos_stream, &pos_time, &length_time);
+ err = pos_time/1000;
+ if(this->fd_control >= 0) {
+ sprintf(buf, "RESULT %d %d\r\n", this->token, err);
+ write(this->fd_control, buf, strlen(buf));
+ err = CONTROL_OK;
+ }
+
+ } else if(!strncasecmp(cmd, "SUBTITLES ", 10)) {
+ if(this->slave_stream) {
+ int vpos = 0;
+ if(1 == sscanf(cmd, "SUBTITLES %d", &vpos))
+ stream->xine->config->update_num(stream->xine->config,
+ "subtitles.separate.vertical_offset",vpos);
+ else
+ err = CONTROL_PARAM_ERROR;
+ }
+
+ } else {
+ LOGMSG("unknown control %s", cmd);
+ err = CONTROL_UNKNOWN;
+ }
+
+ LOGCMD("vdr_plugin_parse_control: DONE (%d): %s", err, cmd);
+
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+
+ return err;
+}
+
+static void *vdr_control_thread(void *this_gen)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ char line[1024];
+ int err;
+ int counter = 100;
+
+ LOGDBG("Control thread started\n");
+
+ /* nice(-1); */
+
+ /* wait until state changes from open to play */
+ while(bSymbolsFound && counter>0 && ! this->funcs.fe_control) {
+ xine_usec_sleep(50*1000);
+ counter--;
+ }
+
+ write(this->fd_control, "CONFIG\r\n", 8);
+
+ while(this->control_running) {
+
+ /* read next command */
+ line[0] = 0;
+ pthread_testcancel();
+ if((err=control_read_cmd(this, line+strlen(line), sizeof(line)-1)) <= 0) {
+ if(err < 0) {
+ LOGERR("control stream read error");
+ break;
+ }
+ /*LOGERR("control stream read timeout %s", strerror(errno));*/
+ continue;
+ }
+ LOGCMD("Received command %s\n",line);
+ pthread_testcancel();
+
+ if(!this->control_running)
+ break;
+
+ /* parse */
+ switch(err = vdr_plugin_parse_control(this_gen, line)) {
+ case CONTROL_OK:
+ break;
+ case CONTROL_UNKNOWN:
+ LOGMSG("unknown control message %s", line);
+ break;
+ case CONTROL_PARAM_ERROR:
+ LOGMSG("invalid parameter in control message %s", line);
+ break;
+ case CONTROL_DISCONNECTED:
+ LOGERR("control stream read error - disconnected ?");
+ this->control_running = 0;
+ break;
+ default:
+ LOGMSG("parse_control failed with result: %d", err);
+ break;
+ }
+ }
+
+ this->control_running = 0;
+ LOGDBG("Control thread terminating...");
+
+ pthread_mutex_lock(&this->lock);
+ if(this->fd_control >= 0)
+ close(this->fd_control);
+ if(this->fd_data >= 0)
+ close(this->fd_data);
+ this->fd_data = this->fd_control = -1;
+ pthread_mutex_unlock(&this->lock);
+
+ LOGDBG("Control thread terminated");
+
+ pthread_exit(NULL);
+}
+
+/**************************** Control to VDR ********************************/
+
+/* Map some xine input events to vdr input (remote key names) */
+struct {
+ int event;
+ char *name;
+} vdr_keymap[] = {
+ {XINE_EVENT_INPUT_NEXT, "Next"},
+ {XINE_EVENT_INPUT_PREVIOUS, "Previous"},
+
+ {XINE_EVENT_INPUT_DOWN, "Down"},
+ {XINE_EVENT_INPUT_UP, "Up"},
+ {XINE_EVENT_INPUT_LEFT, "Left"},
+ {XINE_EVENT_INPUT_RIGHT, "Right"},
+ {XINE_EVENT_INPUT_SELECT, "Ok"},
+
+ {XINE_EVENT_INPUT_MENU1, "Menu"},
+ {XINE_EVENT_INPUT_MENU2, "Red"},
+ {XINE_EVENT_INPUT_MENU3, "Green"},
+ {XINE_EVENT_INPUT_MENU4, "Yellow"},
+ {XINE_EVENT_INPUT_MENU5, "Blue"},
+ {XINE_EVENT_INPUT_NUMBER_0, "0"},
+ {XINE_EVENT_INPUT_NUMBER_1, "1"},
+ {XINE_EVENT_INPUT_NUMBER_2, "2"},
+ {XINE_EVENT_INPUT_NUMBER_3, "3"},
+ {XINE_EVENT_INPUT_NUMBER_4, "4"},
+ {XINE_EVENT_INPUT_NUMBER_5, "5"},
+ {XINE_EVENT_INPUT_NUMBER_6, "6"},
+ {XINE_EVENT_INPUT_NUMBER_7, "7"},
+ {XINE_EVENT_INPUT_NUMBER_8, "8"},
+ {XINE_EVENT_INPUT_NUMBER_9, "9"},
+
+ {XINE_EVENT_VDR_BACK, "Back"},
+ {XINE_EVENT_VDR_CHANNELPLUS, "Channel+"},
+ {XINE_EVENT_VDR_CHANNELMINUS, "Channel-"},
+ {XINE_EVENT_VDR_RED, "Red"},
+ {XINE_EVENT_VDR_GREEN, "Green"},
+ {XINE_EVENT_VDR_YELLOW, "Yellow"},
+ {XINE_EVENT_VDR_BLUE, "Blue"},
+ {XINE_EVENT_VDR_PLAY, "Play"},
+ {XINE_EVENT_VDR_PAUSE, "Pause"},
+ {XINE_EVENT_VDR_STOP, "Stop"},
+ {XINE_EVENT_VDR_RECORD, "Record"},
+ {XINE_EVENT_VDR_FASTFWD, "FastFwd"},
+ {XINE_EVENT_VDR_FASTREW, "FastRew"},
+ {XINE_EVENT_VDR_POWER, "Power"},
+ {XINE_EVENT_VDR_SCHEDULE, "Schedule"},
+ {XINE_EVENT_VDR_CHANNELS, "Channels"},
+ {XINE_EVENT_VDR_TIMERS, "Timers"},
+ {XINE_EVENT_VDR_RECORDINGS, "Recordings"},
+ {XINE_EVENT_VDR_SETUP, "Setup"},
+ {XINE_EVENT_VDR_COMMANDS, "Commands"},
+ {XINE_EVENT_VDR_USER1, "User1"},
+ {XINE_EVENT_VDR_USER2, "User2"},
+ {XINE_EVENT_VDR_USER3, "User3"},
+ {XINE_EVENT_VDR_USER4, "User4"},
+ {XINE_EVENT_VDR_USER5, "User5"},
+ {XINE_EVENT_VDR_USER6, "User6"},
+ {XINE_EVENT_VDR_USER7, "User7"},
+ {XINE_EVENT_VDR_USER8, "User8"},
+ {XINE_EVENT_VDR_USER9, "User9"},
+ {XINE_EVENT_VDR_VOLPLUS, "Volume+"},
+ {XINE_EVENT_VDR_VOLMINUS, "Volume-"},
+ {XINE_EVENT_VDR_MUTE, "Mute"},
+ {XINE_EVENT_VDR_AUDIO, "Audio"},
+#if XINE_VERSION_CODE > 10101
+ {XINE_EVENT_VDR_INFO, "Info"},
+#endif
+ {-1, NULL}
+};
+
+static void vdr_event_cb (void *user_data, const xine_event_t *event)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *)user_data;
+ int i = 0;
+
+ /*LOGCMD("Got xine event %08x\n", event->type);*/
+
+ while(vdr_keymap[i].name) {
+ if(event->type == vdr_keymap[i].event) {
+ LOGDBG("XINE_EVENT (input) %d --> %s", event->type, vdr_keymap[i].name);
+ /*pthread_mutex_lock(&this->lock);*/
+ if(this->funcs.input_control) {
+ this->funcs.input_control((input_plugin_t *)this,
+ NULL, vdr_keymap[i].name, 0, 0);
+ }
+ if(this->funcs.xine_input_event) {
+ this->funcs.xine_input_event(NULL, vdr_keymap[i].name);
+ }
+ /*pthread_mutex_unlock(&this->lock);*/
+ return;
+ }
+ i++;
+ }
+
+ switch (event->type) {
+ case XINE_EVENT_FRAME_FORMAT_CHANGE:
+ {
+ xine_format_change_data_t *frame_change =
+ (xine_format_change_data_t *)event->data;
+ LOGOSD("XINE_EVENT_FRAME_FORMAT_CHANGE (%dx%d, aspect=%d)",
+ frame_change->width, frame_change->height,
+ frame_change->aspect);
+ if(this->rescale_osd)
+ vdr_scale_osds(this, frame_change->width, frame_change->height);
+ }
+ break;
+ case XINE_EVENT_UI_PLAYBACK_FINISHED:
+ if(event->stream == this->stream) {
+ LOGMSG("XINE_EVENT_UI_PLAYBACK_FINISHED");
+ pthread_mutex_lock(&this->lock);
+ this->playback_finished = 1;
+ this->control_running = 0;
+ if(this->fd_control >= 0)
+ close(this->fd_control);
+ if(this->fd_data >= 0)
+ close(this->fd_data);
+ this->fd_data = this->fd_control = -1;
+ pthread_mutex_unlock(&this->lock);
+#if 1
+ if(iSysLogLevel > 2) {
+ /* dump whole xine log as we should not be here ... */
+ xine_t *xine = this->stream->xine;
+ int i, j;
+ int logs = xine_get_log_section_count(xine);
+ const char * const * names = xine_get_log_names(xine);
+ for(i=0; i<logs; i++) {
+ const char * const * lines = xine_get_log(xine, i);
+ if(lines[0]) {
+ printf("\nLOG: %s\n",names[i]);
+ j=-1;
+ while(lines[++j] && *lines[++j] )
+ printf(" %2d: %s", j, lines[j]);
+ }
+ }
+ }
+#endif
+ } else if(event->stream == this->slave_stream) {
+ LOGMSG("XINE_EVENT_UI_PLAYBACK_FINISHED (slave stream)");
+ if(this->fd_control >= 0) {
+ write(this->fd_control, "ENDOFSTREAM\r\n", 13);
+ } else {
+ /* forward to vdr-fe (listening only VDR stream events) */
+ xine_event_t event;
+ event.data_length = 0;
+ event.type = XINE_EVENT_UI_PLAYBACK_FINISHED;
+ xine_event_send (this->stream, &event);
+ }
+ }
+ break;
+
+ default:
+ LOGCMD("Got an xine event, type 0x%08x", event->type);
+ break;
+ }
+}
+
+/**************************** Data Stream *********************************/
+
+static int vdr_plugin_poll(vdr_input_plugin_t *this, int timeout_ms)
+{
+ static int timeouts = 0;
+ struct timespec abstime;
+ int result = 0;
+
+ /* Caller must have locked this->vdr_entry_lock ! */
+
+ if(this->slave_stream) {
+ LOGDBG("vdr_plugin_poll: called while playing slave stream !");
+ return 1;
+ }
+
+ TRACE("vdr_plugin_poll (%d ms), buffer_pool: blocks=%d, bytes=%d",
+ timeout_ms, this->buffer_pool->size(this->buffer_pool),
+ this->buffer_pool->data_size(this->buffer_pool));
+
+ if(this->is_paused) {
+ if(pthread_mutex_unlock(&this->vdr_entry_lock))
+ LOGERR("poll: mutex_unlock(vdr_entry_lock) failed !");
+ create_timeout_time(&abstime, timeout_ms);
+ pthread_mutex_lock (&this->buffer_pool->buffer_pool_mutex);
+ while(pthread_cond_timedwait (&this->buffer_pool->buffer_pool_cond_not_empty,
+ &this->buffer_pool->buffer_pool_mutex,
+ &abstime) != ETIMEDOUT)
+ ;
+ pthread_mutex_unlock (&this->buffer_pool->buffer_pool_mutex);
+ timeout_ms = 0;
+ pthread_mutex_lock(&this->vdr_entry_lock);
+ }
+
+ pthread_mutex_lock (&this->buffer_pool->buffer_pool_mutex);
+ result = this->buffer_pool->buffer_pool_num_free -
+ (this->buffer_pool->buffer_pool_capacity - this->max_buffers);
+ pthread_mutex_unlock (&this->buffer_pool->buffer_pool_mutex);
+
+ if(timeout_ms > 0 && result <= 0) {
+ create_timeout_time(&abstime, timeout_ms);
+
+ pthread_mutex_lock(&this->lock);
+ if(this->scr_tunning == SCR_TUNNING_PAUSED) {
+ LOGSCR("scr tunning reset by POLL");
+ reset_scr_tunning(this,this->speed_before_pause);
+ }
+ pthread_mutex_unlock(&this->lock);
+
+ signal_buffer_not_empty(this);
+
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+ pthread_mutex_lock (&this->buffer_pool->buffer_pool_mutex);
+ while(result <= 5) {
+ TRACE("vdr_plugin_poll waiting (max %d ms), %d bufs free (rd pos=%lld)",
+ timeout_ms, this->buffer_pool->buffer_pool_num_free, this->curpos);
+ if(pthread_cond_timedwait (&this->buffer_pool->buffer_pool_cond_not_empty,
+ &this->buffer_pool->buffer_pool_mutex,
+ &abstime) == ETIMEDOUT) {
+
+ if(this->live_mode) {
+ if(timeouts>2 && timeouts<6) {
+ timeout_ms=200;
+ create_timeout_time(&abstime, timeout_ms);
+ continue;
+ }
+ }
+
+ break;
+ }
+ result = this->buffer_pool->buffer_pool_num_free -
+ (this->buffer_pool->buffer_pool_capacity - this->max_buffers);
+ }
+ pthread_mutex_unlock (&this->buffer_pool->buffer_pool_mutex);
+ pthread_mutex_lock(&this->vdr_entry_lock);
+ }
+ if(result>0) timeouts=0;
+
+ TRACE("vdr_plugin_poll returns, %d free (%d used, %d bytes)\n", result,
+ this->buffer_pool->size(this->buffer_pool),
+ this->buffer_pool->data_size(this->buffer_pool));
+
+ /* handle priority problem in paused mode when
+ data source has higher priority than control source */
+ if(result <= 0) {
+ result = 0;
+ xine_usec_sleep(3*1000);
+ }
+
+ return result;
+}
+
+
+/*
+ * Flush returns 0 if there is no data or frames in stream buffers
+ */
+static int vdr_plugin_flush(vdr_input_plugin_t *this, int timeout_ms)
+{
+ struct timespec abstime;
+ buf_element_t *buf;
+ fifo_buffer_t *pool = this->buffer_pool;
+ int result = 0, waitresult=0;
+
+ /* Caller must have locked this->vdr_entry_lock ! */
+
+ if(this->slave_stream) {
+ LOGDBG("vdr_plugin_flush: called while playing slave stream !");
+ return 0;
+ }
+
+ TRACE("vdr_plugin_flush (%d ms) blocks=%d+%d, frames=%d", timeout_ms,
+ this->block_buffer->size(this->block_buffer),
+ pool->size(pool),
+ this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_BUFS_IN_FIFO));
+
+ if(this->live_mode && this->fd_control < 0) {
+ sched_yield();
+ return 1;
+ }
+
+ result = MAX(0, pool->size(pool)) +
+ MAX(0, this->block_buffer->size(this->block_buffer)) +
+ this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_BUFS_IN_FIFO);
+ if(result>0) {
+ buf = pool->buffer_pool_try_alloc(pool);
+ if(buf) {
+ buf->type = BUF_CONTROL_FLUSH_DECODER;
+ this->block_buffer->put(this->block_buffer, buf);
+ }
+ buf = pool->buffer_pool_try_alloc(pool);
+ if(buf) {
+ buf->type = BUF_CONTROL_NOP;
+ this->block_buffer->put(this->block_buffer, buf);
+ }
+ }
+
+ create_timeout_time(&abstime, timeout_ms);
+
+ pthread_mutex_lock(&pool->buffer_pool_mutex);
+ while(result > 0 && waitresult != ETIMEDOUT) {
+ TRACE("vdr_plugin_flush waiting (max %d ms), %d+%d buffers used, "
+ "%d frames (rd pos=%lld)\n", timeout_ms,
+ pool->fifo_size, this->block_buffer->fifo_size,
+ (int)this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_BUFS_IN_FIFO),
+ this->curpos);
+
+ waitresult = pthread_cond_timedwait (&pool->buffer_pool_cond_not_empty,
+ &pool->buffer_pool_mutex, &abstime);
+ result = MAX(0, pool->fifo_size) +
+ MAX(0, this->block_buffer->fifo_size) +
+ this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_BUFS_IN_FIFO);
+ }
+ pthread_mutex_unlock(&pool->buffer_pool_mutex);
+
+ TRACE("vdr_plugin_flush returns %d (%d+%d used, %d frames)\n", result,
+ pool->size(pool),
+ this->block_buffer->size(this->block_buffer),
+ (int)this->stream->video_out->get_property(this->stream->video_out,
+ VO_PROP_BUFS_IN_FIFO));
+
+ return result;
+}
+
+static int vdr_plugin_read_net_tcp(vdr_input_plugin_t *this)
+{
+ buf_element_t *read_buffer = NULL;
+ int cnt = 0, todo = 0, n, result, num_free;
+
+ while(XIO_READY == (result = io_select_rd(this->fd_data))) {
+
+ /* Allocate buffer */
+ if(!read_buffer) {
+
+ pthread_testcancel();
+
+ num_free = this->buffer_pool->num_free(this->buffer_pool) +
+ this->block_buffer->num_free(this->block_buffer);
+
+ if(num_free < 5) {
+ pthread_mutex_lock(&this->vdr_entry_lock);
+ if(!vdr_plugin_poll(this, 100)) {
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+ if(!this->is_paused)
+ LOGDBG("TCP: fifo buffer full");
+ xine_usec_sleep(3*1000);
+ continue; /* must call select to check fd for errors / closing */
+ }
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+ }
+
+ read_buffer =
+ this->buffer_pool->buffer_pool_try_alloc(this->buffer_pool);
+
+ if(!read_buffer) {
+ LOGMSG("TCP: fifo buffer alloc failed");
+ continue; /* must call select to check errors / closing */
+ }
+ read_buffer->content = read_buffer->mem;
+ read_buffer->size = 0;
+ todo = sizeof(stream_tcp_header_t);
+ cnt = 0;
+ }
+
+ /* Read data */
+ errno = 0;
+ n = read(this->fd_data, &read_buffer->mem[cnt], todo-cnt);
+ if(n <= 0) {
+ if(!n || (errno != EINTR && errno != EAGAIN)) {
+ LOGERR("TCP read error (data stream %d : %d)", this->fd_data, n);
+ result = XIO_ERROR;
+ break;
+ }
+ continue;
+ }
+
+ cnt += n;
+ if(cnt == sizeof(stream_tcp_header_t)) {
+ stream_tcp_header_t *hdr = ((stream_tcp_header_t *)read_buffer->content);
+ hdr->len = ntohl(hdr->len);
+ hdr->pos = ntohull(hdr->pos);
+
+ todo = cnt + hdr->len;
+ if(todo+cnt >= read_buffer->max_size) {
+ LOGMSG("TCP: Buffer too small (%d ; incoming frame %d bytes)",
+ read_buffer->max_size, todo + cnt);
+ todo = read_buffer->max_size - cnt - 1;
+ }
+ }
+ if(cnt >= todo) {
+ /* frame ready */
+ read_buffer->size = cnt;
+ read_buffer->type = BUF_MAJOR_MASK;
+ this->block_buffer->put(this->block_buffer, read_buffer);
+ read_buffer = NULL;
+ }
+ }
+
+ if(read_buffer) {
+ read_buffer->free_buffer(read_buffer);
+ if(cnt && this->fd_data >= 0 && result == XIO_TIMEOUT) {
+ LOGMSG("TCP: Delay too long, disconnecting");
+ close(this->fd_data);
+ close(this->fd_control);
+ this->fd_data = this->fd_control = -1;
+ this->control_running = 0;
+ }
+ }
+
+ return result;
+}
+
+static int vdr_plugin_read_net_udp(vdr_input_plugin_t *this)
+{
+ struct sockaddr_in server_address;
+ socklen_t address_len = sizeof(server_address);
+ udp_data_t *udp = this->udp_data;
+ stream_udp_header_t *pkt;
+ uint8_t *pkt_data;
+ int result=XIO_ERROR, n, current_seq, num_free, timeouts = 0;
+ buf_element_t *read_buffer = NULL;
+
+ while(this->fd_data >= 0) {
+
+ result = _x_io_select(this->stream, this->fd_data,
+ XIO_READ_READY, 20);
+ if(result == XIO_TIMEOUT) {
+ if(timeouts++ > 25)
+ return XIO_TIMEOUT;
+ /*#warning fall thru for missing frame detection ... ?*/
+ /* -- use resend wait timer (monotonic_time_ms) --- max wait 40 ms ? */
+ continue;
+ }
+ if(result != XIO_READY)
+ return result;
+
+ timeouts = 0;
+
+ /*
+ * allocate buffer and read incoming UDP packet from socket
+ */
+
+ if(!read_buffer) {
+
+ pthread_testcancel();
+
+ num_free = this->buffer_pool->num_free(this->buffer_pool) +
+ this->block_buffer->num_free(this->block_buffer);
+
+ /* signal queue status to server */
+ if(!udp->queue_full_signaled &&
+ num_free < UDP_SIGNAL_FULL_TRESHOLD) {
+ LOGUDP("send fifo buffer almost full signal ON");
+ write(this->fd_control, "UDP FULL 1\r\n", 12);
+ udp->queue_full_signaled = 1;
+ } else if(udp->queue_full_signaled &&
+ num_free > UDP_SIGNAL_NOT_FULL_TRESHOLD) {
+ LOGUDP("send fifo buffer almost full signal OFF");
+ write(this->fd_control, "UDP FULL 0\r\n", 12);
+ udp->queue_full_signaled = 0;
+ }
+
+ /* if queue is full, skip frame.
+ Waiting for free buffers just makes things worse ... */
+ if(num_free < 5) {
+ if(!this->is_paused) {
+ LOGMSG("UDP Fifo buffer full !");
+
+ if(this->scr && !udp->scr_jump_done) {
+ pvrscr_skip_frame (this->scr);
+ LOGMSG("SCR jump: +40 ms" );
+ udp->scr_jump_done=1;
+ }
+ }
+
+ pthread_mutex_lock(&this->vdr_entry_lock);
+ if(!vdr_plugin_poll(this, 100)) {
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+ if(!this->is_paused)
+ LOGMSG("Fifo buffer still full after poll !");
+ xine_usec_sleep(5*1000);
+ return result;
+ }
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+ }
+
+ if(num_free>30)
+ udp->scr_jump_done=0;
+
+ /* allocate new buffer */
+ read_buffer =
+ this->buffer_pool->buffer_pool_try_alloc(this->buffer_pool);
+
+ if(!read_buffer) {
+ LOGERR("UDP Fifo buffer alloc failed !");
+ break;
+ }
+ read_buffer->content = read_buffer->mem;
+ read_buffer->size = 0;
+ }
+
+ /* Receive frame from socket and check for errors */
+ n = recvfrom(this->fd_data, read_buffer->mem,
+ read_buffer->max_size, MSG_TRUNC,
+ &server_address, &address_len);
+ if(n<=0) {
+ LOGERR("read_net_udp recv() error");
+ if(!n || errno != EINTR)
+ result = XIO_ERROR;
+ break;
+ }
+
+ /* check source address */
+ if((server_address.sin_addr.s_addr !=
+ udp->server_address.sin_addr.s_addr) ||
+ server_address.sin_port != udp->server_address.sin_port) {
+#ifdef LOG_UDP
+ uint32_t tmp_ip = ntohl(server_address.sin_addr.s_addr);
+ LOGUDP("Received data from unknown sender: %d.%d.%d.%d:%d\n",
+ ((tmp_ip>>24)&0xff), ((tmp_ip>>16)&0xff),
+ ((tmp_ip>>8)&0xff), ((tmp_ip)&0xff),
+ server_address.sin_port);
+#endif
+ continue;
+ }
+
+ /* Check if frame size is valid */
+ if(n < sizeof(stream_udp_header_t)) {
+ LOGMSG("received invalid UDP packet (too short)");
+ continue;
+ }
+ if(n > read_buffer->max_size) {
+ LOGMSG("received too large UDP packet ; part of data was discarded");
+ n = read_buffer->max_size;
+ }
+ read_buffer->size = n;
+ read_buffer->type = BUF_MAJOR_MASK;
+
+ pkt = (stream_udp_header_t*)read_buffer->mem;
+ pkt_data = read_buffer->mem + sizeof(stream_udp_header_t);
+ pkt->seq = ntohs(pkt->seq);
+ pkt->pos = ntohull(pkt->pos);
+
+ /* Check for control messages */
+ if(pkt->seq == (uint16_t)(-1) /*0xffff*/) {
+ if(pkt->pos == (uint64_t)(-1ULL) /*0xffffffff*/) {
+ pkt_data[64] = 0;
+ if(!strncmp(pkt_data, "UDP MISSING", 11)) {
+ /* Re-send failed */
+ int seq1 = 0, seq2 = 0;
+ uint64_t rpos = 0ULL;
+ sscanf((char*)pkt_data, "UDP MISSING %d-%d %llu",
+ &seq1, &seq2, &rpos);
+ read_buffer->size = sizeof(stream_udp_header_t);
+ read_buffer->type = BUF_MAJOR_MASK;
+ pkt->seq = seq1;
+ pkt->pos = rpos;
+ udp->missed_frames++;
+ /* -> drop frame thru as empty ; it will trigger queue to continue */
+ } else {
+ /* unknown control message */
+ continue;
+ }
+ } else {
+ /* invalid header or dummy keep-alive frame */
+ continue;
+ }
+ continue;
+ }
+
+ /* Check if header is valid */
+ if(pkt->seq > UDP_SEQ_MASK) {
+ LOGMSG("received invalid UDP packet (sequence number too big)");
+ continue;
+ }
+ if(pkt_data[0] || pkt_data[1] || pkt_data[2] != 1) {
+ LOGMSG("received invalid UDP packet (PES header 0x000001 missing)");
+ continue;
+ }
+
+
+ /*
+ * handle re-ordering and retransmissios
+ */
+
+ current_seq = pkt->seq & UDP_SEQ_MASK;
+ /* first received frame initializes sequence counter */
+ if(udp->received_frames == -1) {
+ udp->next_seq = current_seq;
+ udp->received_frames = 0;
+ }
+
+ /* check if received sequence number is inside allowed window
+ (half of whole range) */
+ /*if(((current_seq + (UDP_SEQ_MASK+1) - udp->next_seq) & UDP_SEQ_MASK) > */
+ if(ADDSEQ(current_seq, -udp->next_seq) > ((UDP_SEQ_MASK+1) >> 1)/*0x80*/) {
+ struct sockaddr_in sin;
+ LOGUDP("Received SeqNo out of window (%d ; [%d..%d])",
+ current_seq, udp->next_seq,
+ (udp->next_seq+((UDP_SEQ_MASK+1) >> 1)/*0x80*/) & UDP_SEQ_MASK);
+ /* reset link */
+ memcpy(&sin, &udp->server_address, sizeof(sin));
+ free_udp_data(udp);
+ udp = this->udp_data = init_udp_data();
+ memcpy(&udp->server_address, &sin, sizeof(sin));
+ continue;
+ }
+
+ /* Add received frame to incoming queue */
+ if(udp->queue[current_seq]) {
+ /* Duplicate packet or lot of dropped packets */
+ LOGUDP("Got duplicate or window exceeded ? (queue slot %d in use) !",
+ current_seq);
+ udp->queue[current_seq]->free_buffer(udp->queue[current_seq]);
+ udp->queue[current_seq] = NULL;
+ if(!udp->queued)
+ LOGERR("UDP queue corrupt !!!");
+ else
+ udp->queued--;
+ }
+ udp->queue[current_seq] = read_buffer;
+ read_buffer = NULL;
+ udp->queued ++;
+
+
+ /* stay inside receiving window:
+ If window exceeded, skip missing frames */
+ if(udp->queued > ((UDP_SEQ_MASK+1)>>2)) {
+#ifdef LOG_UDP
+ int start = udp->next_seq;
+#endif
+ while(!udp->queue[udp->next_seq]) {
+ INCSEQ(udp->next_seq);
+ udp->missed_frames++;
+ }
+ udp->resend_requested = 0;
+ LOGUDP("Re-ordering window exceeded, skipped missed frames %d-%d",
+ start, udp->next_seq-1);
+ }
+
+ /* flush continous part of queue to demuxer queue */
+ while(udp->queued > 0 && udp->queue[udp->next_seq]) {
+ this->block_buffer->put(this->block_buffer, udp->queue[udp->next_seq]);
+ pkt = (stream_udp_header_t*)udp->queue[udp->next_seq]->content;
+ udp->queue_input_pos = pkt->pos + udp->queue[udp->next_seq]->size -
+ sizeof(stream_udp_header_t);
+ udp->queue[udp->next_seq] = NULL;
+ udp->queued --;
+ INCSEQ(udp->next_seq);
+
+ if(udp->resend_requested)
+ udp->resend_requested --;
+ }
+
+ /* no new resend requests until previous has been completed or failed */
+ if(udp->resend_requested)
+ continue;
+
+ /* If frames are missing, request re-send */
+ if(NEXTSEQ(current_seq) != udp->next_seq && udp->queued) {
+
+ if(!udp->resend_requested) {
+ char msg[128];
+ int max_req = 20;
+
+ while(!udp->queue[current_seq] && --max_req > 0)
+ INCSEQ(current_seq);
+
+ sprintf(msg, "UDP RESEND %d-%d %lld\r\n",
+ udp->next_seq, PREVSEQ(current_seq), udp->queue_input_pos);
+ write(this->fd_control, msg, strlen(msg));
+ udp->resend_requested =
+ (current_seq + (UDP_SEQ_MASK+1) - udp->next_seq) & UDP_SEQ_MASK;
+
+ LOGUDP("%d-%d missing, requested re-send for %d frames",
+ udp->next_seq, PREVSEQ(current_seq), udp->resend_requested);
+ }
+ }
+
+ /* Link quality statistics */
+#ifdef LOG_UDP
+ udp->received_frames++;
+ if(udp->received_frames >= 1000) {
+ if(udp->missed_frames)
+ LOGUDP("packet loss %d.%d%% (%4d/%4d)",
+ udp->missed_frames*100/udp->received_frames,
+ (udp->missed_frames*1000/udp->received_frames)%10,
+ udp->missed_frames, udp->received_frames);
+ udp->missed_frames = udp->received_frames = 0;
+ }
+#endif
+ }
+
+ if(read_buffer)
+ read_buffer->free_buffer(read_buffer);
+
+ return result;
+}
+
+
+static void *vdr_data_thread(void *this_gen)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+
+ LOGDBG("Data thread started");
+
+ nice(-1);
+
+ if(this->udp||this->rtp) {
+ while(this->control_running) {
+ if(vdr_plugin_read_net_udp(this)==XIO_ERROR)
+ break;
+ pthread_testcancel();
+ }
+ } else {
+ while(this->control_running) {
+ if(vdr_plugin_read_net_tcp(this)==XIO_ERROR)
+ break;
+ pthread_testcancel();
+ }
+ }
+
+ this->control_running = 0;
+ this->playback_finished = 1;
+
+ pthread_mutex_lock(&this->lock);
+ if(this->fd_control >= 0)
+ close(this->fd_control);
+ if(this->fd_data >= 0)
+ close(this->fd_data);
+ this->fd_data = this->fd_control = -1;
+ pthread_mutex_unlock(&this->lock);
+
+ LOGDBG("Data thread terminated");
+
+ pthread_exit(NULL);
+}
+
+
+static int vdr_plugin_write(input_plugin_t *this_gen, char *data, int len)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ buf_element_t *buf = NULL;
+ int buffer_limit;
+#ifdef BLOCKING_VDR_PLUGIN_WRITE
+ struct timespec abstime;
+ int trycount = 10;
+#endif
+
+ if(this->slave_stream)
+ return len;
+
+ /* slave */
+ if(((uint8_t*)data)[3] > 0xe0 && ((uint8_t*)data)[3] <= 0xef) {
+ if(!this->pip_stream) {
+ LOGMSG("Detected new video stream");
+ LOGMSG(" no xine stream yet, trying to create ...");
+ vdr_plugin_parse_control(this_gen, "SUBSTREAM 0xE1 50 50 288 196");
+ LOGMSG(" substream up and running ?");
+ }
+ if(this->pip_stream) {
+ fifo_input_plugin_t *slave = (fifo_input_plugin_t*)this->pip_stream->input_plugin;
+ /* LOGMSG(" input already open, queuing data"); */
+ if(slave) {
+ buf = slave->buffer_pool->buffer_pool_try_alloc(slave->buffer_pool);
+ if(buf) {
+ if(len < buf->max_size) {
+ /* buf->free_buffer = buffer_pool_free; */
+ buf->content = buf->mem;
+ buf->size = len;
+ buf->type = BUF_DEMUX_BLOCK;
+ xine_fast_memcpy(buf->content, data, len);
+ slave->buffer->put(slave->buffer, buf);
+ } else {
+LOGMSG(" pip substream: buf too small");
+ buf->free_buffer(buf);
+ }
+ } else {
+LOGMSG(" pip substream: fifo full !");
+ }
+ return len;
+ }
+ } else {
+LOGMSG(" pip substream: no stream !");
+ }
+ return len;
+ }
+
+ TRACE("vdr_plugin_write (%d bytes)", len);
+
+ pthread_mutex_lock(&this->vdr_entry_lock);
+
+ buffer_limit = this->buffer_pool->buffer_pool_capacity - this->max_buffers;
+#ifdef BLOCKING_VDR_PLUGIN_WRITE
+ pthread_mutex_lock(&this->buffer_pool->buffer_pool_mutex);
+ while( (this->buffer_pool->buffer_pool_num_free <= buffer_limit)
+ && trycount>0 /*&& !this->is_paused*/) {
+ create_timeout_time(&abstime, 100); /* no infinite waits if player is stopped and fifo full ... */
+ if(pthread_cond_timedwait (&this->buffer_pool->buffer_pool_cond_not_empty,
+ &this->buffer_pool->buffer_pool_mutex, &abstime) == ETIMEDOUT) {
+ trycount--;
+ if(this->is_paused)
+ trycount=0;
+ }
+ buffer_limit = this->buffer_pool->buffer_pool_capacity - this->max_buffers;
+ }
+ pthread_mutex_unlock(&this->buffer_pool->buffer_pool_mutex);
+#else
+ if(this->buffer_pool->buffer_pool_num_free <= buffer_limit) {
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+ return 0/*EAGAIN*/;
+ }
+#endif
+
+ if(len<8000)
+ buf = this->buffer_pool->buffer_pool_try_alloc(this->buffer_pool);
+ else if(len<0xffff) {
+ buf = this->block_buffer->buffer_pool_try_alloc(this->block_buffer);
+ LOGDBG("vdr_plugin_write: big PES (%d bytes) !", len);
+ } else { /* len>64k */
+ if(!this->big_buffer)
+ this->big_buffer = _x_fifo_buffer_new(4,512*1024);
+ buf = this->big_buffer->buffer_pool_try_alloc(this->big_buffer);
+ LOGDBG("vdr_plugin_write: jumbo PES (%d bytes) !", len);
+ }
+
+ if(!buf) {
+ int cnt;
+ if(len>=8000)
+ LOGMSG("vdr_plugin_write: jumbo PES (%d bytes), "
+ "not enough free buffers !", len);
+ LOGMSG("vdr_plugin_write: buffer overflow ! "
+ "(rd pos=%lld) block_buffer=%du/%df,buffer_pool=%du/%df",
+ this->curpos, this->block_buffer->size(this->block_buffer),
+ this->block_buffer->num_free(this->block_buffer),
+ this->buffer_pool->size(this->buffer_pool),
+ this->buffer_pool->num_free(this->buffer_pool));
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+ for(cnt=0; cnt<10; cnt++)
+ pthread_yield();
+ return 0;
+ }
+
+ if(len > buf->max_size) {
+ LOGMSG("vdr_plugin_write: PES too long (%d bytes, max size "
+ "%d bytes), data ignored !\n", len, buf->max_size);
+ buf->free_buffer(buf);
+/* curr_pos will be invalid when this point is reached ! */
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+ return len;
+ }
+
+ buf->free_buffer = buffer_pool_free;
+ buf->content = buf->mem;
+ buf->size = len;
+ buf->type = BUF_DEMUX_BLOCK;
+ xine_fast_memcpy(buf->content, data, len);
+
+ this->block_buffer->put(this->block_buffer, buf);
+
+ pthread_mutex_unlock(&this->vdr_entry_lock);
+
+ TRACE("vdr_plugin_write returns %d", len);
+
+ return len;
+}
+
+static int vdr_plugin_keypress(input_plugin_t *this_gen, char *map, char *key,
+ int repeat, int release)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ pthread_mutex_lock(&this->lock);
+ if(key && this->fd_control >= 0) {
+ char buf[128];
+ if(map)
+ sprintf(buf, "KEY %s %s %s %s\r\n", map, key,
+ repeat?"Repeat":"", release?"Release":"");
+ else
+ sprintf(buf, "KEY %s\r\n", key);
+ if(strlen(buf) != write(this->fd_control, buf, strlen(buf)))
+ return -1;
+ }
+ pthread_mutex_unlock(&this->lock);
+ return 0;
+}
+
+
+/******************************* Plugin **********************************/
+
+static off_t vdr_plugin_read (input_plugin_t *this_gen,
+ char *buf, off_t len)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ off_t n, total=0;
+
+ if(this->slave_stream) {
+ LOGERR("vdr_plugin_read with slave stream !!!");
+ return 0;
+ }
+
+ TRACE("vdr_plugin_read: reading %lld bytes...", len);
+
+ while (total<len) {
+
+ if(!this->curr_buffer) {
+ buf_element_t *buf = this->input_plugin.read_block(this_gen, this->stream->video_fifo, len);
+ if(!buf)
+ return -1 /*total*/;
+ pthread_mutex_lock(&this->lock);
+ this->curr_buffer = buf;
+ this->curpos -= (uint64_t)this->curr_buffer->size;
+ } else
+ pthread_mutex_lock(&this->lock);
+
+ n = MIN(this->curr_buffer->size, len-total);
+
+ xine_fast_memcpy(&buf[total], this->curr_buffer->content, n);
+
+ this->curr_buffer->size -= n;
+ this->curr_buffer->content += n;
+ this->curpos += (uint64_t)n;
+ total += n;
+
+ if(this->curr_buffer->size <= 0) {
+ this->curr_buffer->free_buffer(this->curr_buffer);
+ this->curr_buffer = NULL;
+ }
+ pthread_mutex_unlock(&this->lock);
+
+ TRACE("vdr_plugin_read: got %lld bytes (%lld/%lld bytes read)",
+ n, total, len);
+ }
+
+ TRACE("vdr_plugin_read returns %lld bytes",total);
+
+ return total;
+}
+
+static buf_element_t *vdr_plugin_read_block (input_plugin_t *this_gen,
+ fifo_buffer_t *fifo, off_t todo)
+{
+ static unsigned char padding[] = {0x00,0x00,0x01,0xBE,0x00,0x02,0xff,0xff};
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ buf_element_t *buf = NULL;
+
+ TRACE("vdr_plugin_read_block");
+
+ if(!this->funcs.push_input_write /* reading from socket */ &&
+ !this->control_running) {
+ LOGMSG("read_block: no data source, returning NULL");
+ return NULL; /* disconnected ? */
+ }
+
+#ifdef ADJUST_SCR_SPEED
+ /* pthread_mutex_lock(&this->lock); */
+ LOCKED (
+ if( !this->live_mode ) {
+ if(this->scr_tunning)
+ reset_scr_tunning(this, this->speed_before_pause);
+ } else {
+ vdr_adjust_realtime_speed(this);
+ }
+ );
+ /* pthread_mutex_unlock(&this->lock); */
+#endif
+
+ do {
+ int loops=0;
+ pthread_mutex_lock(&this->block_buffer->mutex);
+ if(this->block_buffer->fifo_size <= 0) {
+ struct timespec abstime;
+ create_timeout_time(&abstime, 250); /* no infinite waits if player is stopped and fifo full ... */
+ while(this->block_buffer->fifo_size <= 0) {
+ if(loops>0)
+ TRACE("vdr_plugin_read_block - wait ... %d",loops);
+ if(pthread_cond_timedwait (&this->block_buffer->not_empty,
+ &this->block_buffer->mutex, &abstime)
+ == ETIMEDOUT) {
+ loops++;
+ create_timeout_time(&abstime, 250);
+
+ }
+ if(loops>1)
+ TRACE("vdr_plugin_read_block - wait end %d",loops);
+
+ if((loops>1 && this->block_buffer->fifo_size <= 0) || /*&&*/
+ this->stream->demux_action_pending) {
+ pthread_mutex_unlock(&this->block_buffer->mutex);
+ buf = this->buffer_pool->buffer_pool_try_alloc(this->buffer_pool);
+
+ if(!buf)
+ buf = this->block_buffer->buffer_pool_try_alloc(this->block_buffer);
+ if(buf) {
+ buf->content = buf->mem;
+ buf->size = 8;
+ buf->type = BUF_DEMUX_BLOCK;
+ memcpy(buf->content,padding,8);
+ pthread_yield();
+ return buf;
+ }
+ /* no free buffers ... */
+ pthread_mutex_lock(&this->block_buffer->mutex);
+ }
+ } /* while(this->block_buffer->fifo_size <= 0) */
+ } /* if(this->block_buffer->fifo_size <= 0) */
+ pthread_mutex_unlock(&this->block_buffer->mutex);
+
+ if(!(buf = fifo_buffer_try_get(this->block_buffer)))
+ continue;
+ loops=0;
+
+ if(buf->type == BUF_MAJOR_MASK) {
+ /* Strip network headers */
+ if(this->udp||this->rtp) {
+ stream_udp_header_t *header = (stream_udp_header_t *)buf->content;
+ this->curpos = header->pos;
+ buf->content += sizeof(stream_udp_header_t);
+ buf->size -= sizeof(stream_udp_header_t);
+ } else {
+ stream_tcp_header_t *header = (stream_tcp_header_t *)buf->content;
+ this->curpos = header->pos;
+ buf->content += sizeof(stream_tcp_header_t);
+ buf->size -= sizeof(stream_tcp_header_t);
+ }
+ }
+
+ /* control buffers go always to demuxer */
+ if ((buf->type & BUF_MAJOR_MASK) == BUF_CONTROL_BASE) {
+ return buf;
+ }
+
+ pthread_mutex_lock(&this->lock);
+ this->curpos += buf->size;
+ if(this->discard_index > this->curpos && this->guard_index < this->curpos) {
+ pthread_mutex_unlock(&this->lock);
+ TRACE("DISCARD: curpos=%lld, discard_index=%lld",
+ this->curpos,this->discard_index);
+ buf->free_buffer(buf);
+ buf = NULL;
+ } else {
+#if 0
+if(this->guard_index >= this->curpos)
+ LOGMSG("guard index: %lld, discard index: %lld, pos: %lld, diff: %d",
+ this->discard_index, this->guard_index, this->curpos, this->discard_index-this->guard_index);
+#endif
+ if(this->stream_start)
+ this->send_pts = 1;
+ if(this->send_pts)
+ if((buf->size>14) && (buf->content[7] & 0x80)) { /* pts avail */
+ int64_t pts;
+ pts = ((int64_t)(buf->content[ 9] & 0x0E)) << 29 ;
+ pts |= buf->content[10] << 22 ;
+ pts |= (buf->content[11] & 0xFE) << 14 ;
+ pts |= buf->content[12] << 7 ;
+ pts |= (buf->content[13] & 0xFE) >> 1 ;
+ if(pts > 0LL) {
+ vdr_x_demux_control_newpts(this->stream,pts,0);
+ this->send_pts=0;
+ }
+ }
+
+ this->stream_start = 0;
+ pthread_mutex_unlock(&this->lock);
+ }
+ } while(!buf);
+
+#if 0
+ /* ei toimi ? */
+ if(buf->content[3] == 0xe0 && buf->size > 32) {
+ uint8_t *Data = buf->content;
+ int i = 8; /* the minimum length of the video packet header */
+ i += Data[i] + 1; /* possible additional header bytes */
+ if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) {
+ if(Data[i + 3] == 0x00) {
+ uint8_t PictureType = (Data[i + 5] >> 3) & 0x07;
+ switch(PictureType) {
+ case 1:
+ this->I_frames++;
+ printf("I");
+ break;
+ case 2:
+ printf("B");
+ break;
+ case 3:
+ printf("P");
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+ TRACE("vdr_plugin_read_block: return data, pos end = %lld", this->curpos);
+ buf->type = BUF_DEMUX_BLOCK;
+ return buf;
+}
+
+static off_t vdr_plugin_seek (input_plugin_t *this_gen, off_t offset,
+ int origin)
+{
+#if 0
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+
+ TRACE("Seek %lld bytes, origin %d", offset, origin);
+
+ /* only relative forward-seeking is implemented */
+ pthread_mutex_lock(&this->lock);
+ if (!this->live_mode && (origin == SEEK_CUR) && (offset >= 0)) {
+ this->discard_index = offset;
+ } else {
+ offset = this->curpos;
+ }
+ pthread_mutex_unlock(&this->lock);
+ return offset;
+#else
+ return -1;
+#endif
+}
+
+static off_t vdr_plugin_get_length (input_plugin_t *this_gen)
+{
+ return -1;
+}
+
+static uint32_t vdr_plugin_get_capabilities (input_plugin_t *this_gen)
+{
+ return /*INPUT_CAP_PREVIEW |*/ INPUT_CAP_BLOCK;
+}
+
+static uint32_t vdr_plugin_get_blocksize (input_plugin_t *this_gen)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ int ret = 2048;
+
+ if(this->block_buffer) {
+ pthread_mutex_lock(&this->block_buffer->buffer_pool_mutex);
+ if(this->block_buffer->first && this->block_buffer->first->size > 0)
+ ret = this->block_buffer->first->size;
+ pthread_mutex_unlock(&this->block_buffer->buffer_pool_mutex);
+ }
+
+ return ret;
+}
+
+static off_t vdr_plugin_get_current_pos (input_plugin_t *this_gen)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ return this->discard_index > this->curpos ? this->discard_index : this->curpos;
+}
+
+static void vdr_plugin_dispose (input_plugin_t *this_gen)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ int i;
+
+ if(!this_gen)
+ return;
+
+ LOGMSG("vdr_plugin_dispose");
+
+ if (this->slave_event_queue)
+ xine_event_dispose_queue (this->slave_event_queue);
+ this->slave_event_queue = NULL;
+ if (this->event_queue)
+ xine_event_dispose_queue (this->event_queue);
+ this->event_queue = NULL;
+
+ pthread_mutex_lock(&this->vdr_entry_lock);
+
+ if(this->video_properties_saved)
+ set_video_properties(this, -1,-1,-1,-1); /* restore defaults */
+
+ pthread_mutex_lock(&this->lock);
+
+ if (this->slave_stream) {
+ if(this->funcs.fe_control)
+ (*(this->funcs.fe_control))(this->funcs.fe_handle, "SLAVE 0x0\r\n");
+ xine_stop(this->slave_stream);
+ xine_close(this->slave_stream);
+ xine_dispose(this->slave_stream);
+ this->slave_stream = NULL;
+ }
+
+ for(i=0; i<MAX_OSD_OBJECT; i++) {
+ if(this->osdhandle[i] != -1) {
+ osd_command_t cmd;
+ memset(&cmd,0,sizeof(cmd));
+ cmd.cmd = OSD_Close;
+ cmd.wnd = i;
+ vdr_plugin_exec_osd_command(this_gen, &cmd);
+ }
+ }
+ pthread_mutex_destroy (&this->osd_lock);
+
+ shutdown(this->fd_control, SHUT_RDWR);
+ shutdown(this->fd_data, SHUT_RDWR);
+ if(this->fd_data >= 0)
+ if(close(this->fd_data))
+ LOGERR("close(fd_data) failed");
+ if(this->fd_control >= 0)
+ if(close(this->fd_control))
+ LOGERR("close(fd_control) failed");
+ this->fd_data = this->fd_control = -1;
+
+ signal_buffer_pool_not_empty(this);
+ signal_buffer_not_empty(this);
+ pthread_mutex_unlock(&this->lock);
+
+ if(this->control_running) {
+ void *p;
+ this->control_running = 0;
+ pthread_cancel(this->control_thread);
+ pthread_join (this->control_thread, &p);
+ pthread_cancel(this->data_thread);
+ pthread_join (this->data_thread, &p);
+ }
+
+ if (this->scr) {
+ this->stream->xine->clock->unregister_scr(this->stream->xine->clock,
+ &this->scr->scr);
+ this->scr->scr.exit(&this->scr->scr);
+ }
+
+ free (this->mrl);
+
+ if(this->udp_data)
+ free_udp_data(this->udp_data);
+
+ if(this->stream && this->stream->audio_fifo)
+ this->stream->audio_fifo->clear(this->stream->audio_fifo); /* need to get all buf elements back before disposing own bufs ... */
+ if(this->stream && this->stream->video_fifo)
+ this->stream->video_fifo->clear(this->stream->video_fifo);
+ if(this->block_buffer)
+ this->block_buffer->clear(this->block_buffer);
+ if(this->big_buffer) {
+ this->big_buffer->clear(this->big_buffer);
+ this->big_buffer->dispose(this->big_buffer);
+ }
+ if(this->block_buffer)
+ this->block_buffer->dispose(this->block_buffer);
+
+ pthread_mutex_destroy(&this->vdr_entry_lock);
+ pthread_mutex_destroy (&this->lock);
+
+ free (this);
+}
+
+static char* vdr_plugin_get_mrl (input_plugin_t *this_gen)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ return this->mrl;
+}
+
+static int vdr_plugin_get_optional_data (input_plugin_t *this_gen,
+ void *data, int data_type)
+{
+ return INPUT_OPTIONAL_UNSUPPORTED;
+}
+
+static int vdr_plugin_open(input_plugin_t *this_gen)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ xine_t *xine = this->stream->xine;
+
+ this->event_queue = xine_event_new_queue (this->stream);
+ xine_event_create_listener_thread (this->event_queue, vdr_event_cb, this);
+
+ this->buffer_pool = this->stream->video_fifo;
+
+#ifdef ADJUST_SCR_SPEED
+ /* enable resample method */
+ xine->config->update_num(xine->config,
+ "audio.synchronization.av_sync_method",1);
+
+ /* register our own scr provider */
+ {
+ uint64_t time;
+ time = xine->clock->get_current_time(xine->clock);
+ this->scr = pvrscr_init();
+ this->scr->scr.start(&this->scr->scr, time);
+ xine->clock->register_scr(this->stream->xine->clock, &this->scr->scr);
+ }
+#endif
+ this->scr_tunning = SCR_TUNNING_OFF;
+ this->curpos = 0;
+ return 1;
+}
+
+static int vdr_plugin_open_local (input_plugin_t *this_gen)
+{
+ LOGDBG("vdr_plugin_open_local");
+ return vdr_plugin_open(this_gen);
+}
+
+static void set_recv_buffer_size(int fd, int max_buf)
+{
+#if 1
+ /* try to have larger receiving buffer */
+
+ /*while(max_buf) {*/
+ if(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &max_buf, sizeof(int)) < 0) {
+ LOGERR("setsockopt(SO_RCVBUF,%d) failed", max_buf);
+ /*max_buf >>= 1;*/
+ } else {
+ unsigned int tmp = 0, len = sizeof(int);;
+ if(getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &tmp, &len) < 0) {
+ LOGERR("getsockopt(SO_RCVBUF,%d) failed", max_buf);
+ /*max_buf >>= 1;*/
+ } else if(tmp != 2*max_buf) {
+ LOGDBG("setsockopt(SO_RCVBUF): got %d bytes", tmp);
+ /*max_buf >>= 1;*/
+ }
+ }
+ /*}*/
+ max_buf = 256;
+ /* not going to send anything, so shrink send buffer ... */
+ setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(int));
+#endif
+}
+
+static int alloc_udp_data_socket(int firstport, int trycount, int *port)
+{
+ int fd;
+ struct sockaddr_in name;
+
+ name.sin_family = AF_INET;
+ name.sin_port = htons(firstport);
+ name.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ fd = socket(PF_INET, SOCK_DGRAM, 0/*IPPROTO_UDP*/);
+
+ set_recv_buffer_size(fd, KILOBYTE(512));
+
+ while(bind(fd, (struct sockaddr *)&name, sizeof(name)) < 0) {
+ if(!--trycount) {
+ LOGMSG("UDP Data stream: bind error, no free port found");
+ close(fd);
+ return -1;
+ }
+ LOGERR("UDP Data stream: bind error, port %d: %s",
+ name.sin_port, strerror(errno));
+ name.sin_port = htons(++firstport);
+ }
+
+ *port = ntohs(name.sin_port);
+ return fd;
+}
+
+static int connect_control_stream(vdr_input_plugin_t *this, char *host,
+ int port, int *client_id)
+{
+ char tmpbuf[256];
+ int fd_control;
+ int saved_fd = this->fd_control;
+
+ this->fd_control = fd_control = _x_io_tcp_connect(this->stream, host, port);
+
+ if(fd_control < 0 ||
+ XIO_READY != _x_io_tcp_connect_finish(this->stream, this->fd_control,
+ 3000)) {
+ LOGERR("Can't connect to tcp://%s:%d", host, port);
+ close(fd_control);
+ this->fd_control = saved_fd;
+ return -1;
+ }
+
+ if(control_read_cmd(this, tmpbuf, 256) > 0) {
+ LOGMSG("Server greeting: %s", tmpbuf);
+ } else {
+ LOGMSG("Server not replying");
+ close(fd_control);
+ this->fd_control = saved_fd;
+ return -1;
+ }
+
+ if(control_read_cmd(this, tmpbuf, 256) > 0 &&
+ !strncmp(tmpbuf, "CLIENT-ID ", 10)) {
+ LOGMSG("Got Client-ID: %s", tmpbuf+10);
+ if(client_id)
+ if(1 != sscanf(tmpbuf+10, "%d", client_id))
+ *client_id = -1;
+ } else {
+ LOGMSG("Warning: No Client-ID !");
+ if(*client_id)
+ *client_id = -1;
+ }
+
+ /* set to non-blocking mode */
+ fcntl (fd_control, F_SETFL, fcntl (fd_control, F_GETFL) | O_NONBLOCK);
+
+ this->fd_control = saved_fd;
+ return fd_control;
+}
+
+
+static int connect_rtp_data_stream(vdr_input_plugin_t *this)
+{
+ char cmd[64];
+ unsigned int ip0, ip1, ip2, ip3, port;
+ int fd=-1, one = 1, retries = 0, n;
+ struct sockaddr_in multicastAddress;
+ struct ip_mreq mreq;
+ struct sockaddr_in server_address, sin;
+ socklen_t len = sizeof(sin);
+ stream_udp_header_t tmp_udp;
+
+ /* get server IP address */
+ if(getpeername(this->fd_control, (struct sockaddr *)&server_address, &len)) {
+ LOGERR("getpeername(fd_control) failed");
+ /* close(fd); */
+ return -1;
+ }
+
+ /* request RTP data transport from server */
+
+ LOGDBG("Requesting RTP transport");
+ sprintf(cmd, "RTP\r\n");
+ if(_x_io_tcp_write(this->stream, this->fd_control, cmd, strlen(cmd)) < 0) {
+ LOGERR("Control stream write error");
+ return -1;
+ }
+
+ cmd[0] = 0;
+ if(control_read_cmd(this, cmd, 256) < 8 ||
+ strncmp(cmd,"RTP ", 4)) {
+ LOGMSG("Server does not support RTP ? (%s)", cmd);
+ return -1;
+ }
+
+ LOGDBG("Got: %s", cmd);
+ if(5 != sscanf(cmd, "RTP %u.%u.%u.%u:%u", &ip0, &ip1, &ip2, &ip3, &port) ||
+ ip0>0xff || ip1>0xff || ip2>0xff || ip3>0xff || port>0xffff) {
+ LOGMSG("Server does not support RTP ? (%s)", cmd);
+ return -1;
+ }
+
+ multicastAddress.sin_family = AF_INET;
+ multicastAddress.sin_port = htons(port);
+ multicastAddress.sin_addr.s_addr = htonl((ip0<<24)|(ip1<<16)|(ip2<<8)|ip3);
+ LOGDBG("got address: %s int=0x%x net=0x%x translated=0x%x port=%d",
+ cmd+4, (ip0<<24)|(ip1<<16)|(ip2<<8)|ip3,
+ htonl((ip0<<24)|(ip1<<16)|(ip2<<8)|ip3),
+ inet_addr("224.0.1.9"), port);
+
+ if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ LOGERR("socket() failed");
+ return -1;
+ }
+ set_recv_buffer_size(fd, KILOBYTE(512));
+
+ if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0) {
+ LOGERR("setsockopt(SO_REUSEADDR) failed");
+ close(fd);
+ return -1;
+ }
+
+ if(bind(fd, (struct sockaddr *)&multicastAddress,
+ sizeof(multicastAddress)) < 0) {
+ LOGERR("bind() to multicast address failed");
+ close(fd);
+ return -1;
+ }
+
+ /* Join to multicast group */
+
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_multiaddr.s_addr = multicastAddress.sin_addr.s_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ /*mreq.imr_ifindex = 0;*/
+ if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) {
+ LOGERR("setsockopt(IP_ADD_MEMBERSHIP) failed. No multicast in kernel?");
+ close(fd);
+ return -1;
+ }
+
+retry_select:
+
+ /* wait until server sends first RTP packet */
+
+ if( XIO_READY != _x_io_select(this->stream, fd, XIO_READ_READY, 500)) {
+ LOGDBG("Requesting RTP transport: RTP poll timeout");
+ if(++retries < 10) {
+ LOGDBG("Requesting RTP transport");
+ write(this->fd_control, "RTP\r\n", 5);
+ goto retry_select;
+ }
+ LOGMSG("Data stream connection timed out (RTP)");
+ close(fd);
+ return -1;
+ }
+
+retry_recvfrom:
+
+ /* check sender address */
+
+ n = recvfrom(fd, &tmp_udp, sizeof(tmp_udp), 0, &sin, &len);
+ if(sin.sin_addr.s_addr != server_address.sin_addr.s_addr) {
+ uint32_t tmp_ip = ntohl(sin.sin_addr.s_addr);
+ LOGMSG("Received UDP/RTP multicast from unknown sender: %d.%d.%d.%d:%d",
+ ((tmp_ip>>24)&0xff), ((tmp_ip>>16)&0xff),
+ ((tmp_ip>>8)&0xff), ((tmp_ip)&0xff),
+ sin.sin_port);
+
+ if(XIO_READY == _x_io_select(this->stream, fd, XIO_READ_READY, 0))
+ goto retry_recvfrom;
+ if(++retries < 4)
+ goto retry_select;
+ close(fd);
+ return -1;
+ }
+
+ /* Succeed */
+
+ this->udp_data = init_udp_data();
+
+ /* store server address */
+ memcpy(&this->udp_data->server_address, &sin, sizeof(sin));
+
+ return fd;
+}
+
+
+static int connect_udp_data_stream(vdr_input_plugin_t *this)
+{
+ char cmd[64];
+ struct sockaddr_in server_address, sin;
+ socklen_t len = sizeof(sin);
+ uint32_t tmp_ip;
+ stream_udp_header_t tmp_udp;
+ int n, retries = 0, port = -1, fd = -1;
+
+ /* get server IP address */
+ if(getpeername(this->fd_control, (struct sockaddr *)&server_address, &len)) {
+ LOGERR("getpeername(fd_control) failed");
+ /* close(fd); */
+ return -1;
+ }
+ tmp_ip = ntohl(server_address.sin_addr.s_addr);
+
+ LOGDBG("VDR server address: %d.%d.%d.%d\n",
+ ((tmp_ip>>24)&0xff), ((tmp_ip>>16)&0xff),
+ ((tmp_ip>>8)&0xff), ((tmp_ip)&0xff));
+
+ /* allocate UDP socket */
+ if((fd = alloc_udp_data_socket(DEFAULT_VDR_PORT, 20, &port)) < 0)
+ return -1;
+
+ LOGDBG("my UDP port is: %d", port);
+
+
+retry_request:
+
+ /* request UDP data transport from server */
+
+ LOGDBG("Requesting UDP transport");
+ sprintf(cmd, "UDP %d\r\n", port);
+ if(_x_io_tcp_write(this->stream, this->fd_control, cmd, strlen(cmd)) < 0) {
+ LOGERR("Control stream write error");
+ close(fd);
+ return -1;
+ }
+
+retry_select:
+
+ /* wait until server sends first UDP packet */
+
+ if( XIO_READY != _x_io_select(this->stream, fd, XIO_READ_READY, 500)) {
+ LOGDBG("Requesting UDP transport: UDP poll timeout");
+ if(++retries < 4)
+ goto retry_request;
+ LOGERR("Data stream connection timed out (UDP)");
+ close(fd);
+ return -1;
+ }
+
+retry_recvfrom:
+
+ /* check sender address */
+
+ n = recvfrom(fd, &tmp_udp, sizeof(tmp_udp), 0, &sin, &len);
+ if(sin.sin_addr.s_addr != server_address.sin_addr.s_addr) {
+ tmp_ip = ntohl(sin.sin_addr.s_addr);
+ LOGMSG("Received UDP packet from unknown sender: %d.%d.%d.%d:%d",
+ ((tmp_ip>>24)&0xff), ((tmp_ip>>16)&0xff),
+ ((tmp_ip>>8)&0xff), ((tmp_ip)&0xff),
+ sin.sin_port);
+
+ if(XIO_READY == _x_io_select(this->stream, fd, XIO_READ_READY, 0))
+ goto retry_recvfrom;
+ if(++retries < 4)
+ goto retry_select;
+ close(fd);
+ return -1;
+ }
+
+ /* Succeed */
+
+ this->udp_data = init_udp_data();
+
+ /* store server address */
+ memcpy(&this->udp_data->server_address, &sin, sizeof(sin));
+
+ return fd;
+}
+
+static int vdr_plugin_open_net (input_plugin_t *this_gen)
+{
+ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen;
+ int err;
+ char tmpbuf[256];
+
+ LOGDBG("vdr_plugin_open_net %s", this->mrl);
+
+ if((!strncasecmp(this->mrl, "xvdr:tcp://", 11) && 1==(this->tcp=1)) ||
+ (!strncasecmp(this->mrl, "xvdr:udp://", 11) && 1==(this->udp=1)) ||
+ (!strncasecmp(this->mrl, "xvdr:rtp://", 11) && 1==(this->rtp=1)) ||
+ (!strncasecmp(this->mrl, "xvdr://", 7))) {
+ char *phost = strdup(strstr(this->mrl, "//") + 2);
+ char host[256];
+ char *port = strchr(phost, ':');
+ int iport;
+ int one = 1;
+ if(port) *port++ = 0;
+ iport = port ? atoi(port) : DEFAULT_VDR_PORT;
+ strncpy(host,phost,254);
+ free(phost);
+ /* TODO: use multiple input plugins - tcp/udp/file */
+
+ /* connect control stream */
+
+ LOGMSG("Connecting (control) to tcp://%s:%d ...", host, iport);
+ this->fd_control = connect_control_stream(this, host, iport,
+ &this->client_id);
+ if (this->fd_control < 0) {
+ LOGERR("Can't connect to tcp://%s:%d", host, iport);
+ return 0;
+ }
+ setsockopt(this->fd_control, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(int));
+
+ LOGMSG("Connected (control) to tcp://%s:%d", host, iport);
+
+ /* connect data stream */
+
+ /* try pipe ? */
+
+ if(!this->tcp && !this->udp && !this->rtp) {
+ LOGMSG("Trying pipe (data) ...");
+ _x_io_tcp_write(this->stream, this->fd_control, "PIPE\r\n", 6);
+ *tmpbuf=0;
+ if(control_read_cmd(this, tmpbuf, 256) >5 &&
+ !strncmp(tmpbuf,"PIPE ", 5) &&
+ strncmp(tmpbuf,"PIPE NONE", 9)) {
+ LOGMSG("PIPE: %s", tmpbuf);
+ if((this->fd_data = open(tmpbuf+5, O_RDONLY|O_NONBLOCK)) >= 0) {
+ _x_io_tcp_write(this->stream, this->fd_control, "PIPE OPEN\r\n", 11);
+ if(control_read_cmd(this, tmpbuf, 256) >6 &&
+ !strncmp(tmpbuf,"PIPE OK",7)) {
+ fcntl (this->fd_data, F_SETFL,
+ fcntl (this->fd_data, F_GETFL) | O_NONBLOCK);
+ this->tcp = this->udp = this->tcp = 0;
+ LOGMSG("Pipe connected (data)");
+ } else {
+ close(this->fd_data);
+ this->fd_data = -1;
+ LOGMSG("Pipe connection failed.");
+ }
+ } else {
+ LOGERR("Pipe opening failed");
+ }
+ } else {
+ LOGMSG("Server does not support pipes.");
+ }
+ }
+
+ /* try RTP ? */
+
+ if(this->fd_data < 0 && !this->udp && !this->tcp) {
+ LOGMSG("Connecting (data) to rtp://%s ...", host);
+ /* flush control buffer (if PIPE was tried first) */
+ while(0 < read(this->fd_control, tmpbuf, 255)) ;
+ if((this->fd_data = connect_rtp_data_stream(this)) < 0) {
+ LOGMSG("connect_rtp_data_stream failed");
+ this->rtp = 0;
+ } else {
+ this->rtp = 1;
+ this->tcp = this->udp = 0;
+ LOGMSG("Data stream connected (RTP)");
+ }
+ }
+
+ /* try UDP ? */
+
+ if(this->fd_data < 0 && !this->tcp) {
+ LOGMSG("Connecting (data) to udp://%s ...", host);
+ /* flush control buffer (if RTP was tried first) */
+ while(0 < read(this->fd_control, tmpbuf, 255)) ;
+ if((this->fd_data = connect_udp_data_stream(this)) < 0) {
+ LOGMSG("connect_udp_data_stream failed");
+ this->udp = 0;
+ } else {
+ this->udp = 1;
+ this->tcp = this->rtp = 0;
+ LOGMSG("Data stream connected (UDP)");
+ }
+ }
+
+ /* fall back to TCP ? */
+
+ if(this->fd_data < 0) {
+ LOGMSG("Connecting (data) to tcp://%s:%d ...", host, iport);
+ this->tcp = 0;
+ this->fd_data = connect_control_stream(this, host, iport, NULL);
+ if(this->fd_data < 0) {
+ LOGMSG("Data stream connection failed (TCP)");
+ } else {
+ set_recv_buffer_size(this->fd_data, KILOBYTE(64));
+ fcntl (this->fd_data, F_SETFL,
+ fcntl (this->fd_data, F_GETFL) | O_NONBLOCK);
+ /* flush control buffer (if UDP/RTP was tried first) */
+ while(0 < read(this->fd_control, tmpbuf, 255)) ;
+
+ sprintf(tmpbuf, "DATA %d\r\n", this->client_id);
+ write(this->fd_data, tmpbuf, strlen(tmpbuf));
+
+ if( XIO_READY != _x_io_select(this->stream, this->fd_data,
+ XIO_READ_READY, 1000)) {
+ LOGERR("Data stream connection failed (TCP, select)");
+ } else if(read(this->fd_data, tmpbuf, 6) != 6) {
+ LOGERR("Data stream connection failed (TCP, read)");
+ } else if(strncmp(tmpbuf, "DATA\r\n", 6)) {
+ LOGMSG("Data stream connection failed (TCP, token)");
+ } else {
+ this->tcp = 1;
+ }
+ }
+ if(this->tcp) {
+ /* succeed */
+ this->rtp = this->udp = 0;
+ LOGMSG("Data stream connected (TCP)");
+ } else {
+ /* failed */
+ close(this->fd_data);
+ close(this->fd_control);
+ this->fd_control = this->fd_data = -1;
+ return 0;
+ }
+ }
+
+ } else {
+ LOGMSG("Unknown mrl (%s)", this->mrl);
+ return 0;
+ }
+
+ if(!vdr_plugin_open(this_gen))
+ return 0;
+
+ this->control_running = 1;
+ if ((err = pthread_create (&this->control_thread,
+ NULL, vdr_control_thread, (void*)this)) != 0) {
+ LOGERR("Can't create new thread");
+ abort();
+ }
+ if ((err = pthread_create (&this->data_thread,
+ NULL, vdr_data_thread, (void*)this)) != 0) {
+ LOGERR("Can't create new thread");
+ abort();
+ }
+
+ return 1;
+}
+
+/**************************** Plugin class *******************************/
+
+static input_plugin_t *vdr_class_get_instance (input_class_t *cls_gen,
+ xine_stream_t *stream,
+ const char *data)
+{
+ vdr_input_class_t *cls = (vdr_input_class_t *) cls_gen;
+ vdr_input_plugin_t *this;
+ char *mrl = (char *) data;
+ int local_mode, i;
+
+ LOGDBG("vdr_class_get_instance");
+
+ if (strncasecmp (mrl, "xvdr:",5))
+ return NULL;
+
+ if(!strncasecmp(mrl, "xvdr:slave:0x", 13))
+ return fifo_class_get_instance(cls_gen, stream, data);
+
+ this = (vdr_input_plugin_t *) xine_xmalloc (sizeof(vdr_input_plugin_t));
+ memset(this, 0, sizeof(vdr_input_plugin_t));
+
+ this->stream = stream;
+ this->mrl = NULL;
+ this->cls = cls;
+ this->event_queue = NULL;
+
+ this->fd_data = -1;
+ this->fd_control = -1;
+ this->udp = 0;
+ this->control_running = 0;
+ this->read_buffer = NULL;
+
+ this->block_buffer = NULL;
+ this->curr_buffer = NULL;
+ this->discard_index= 0;
+ this->guard_index = 0;
+ this->curpos = 0;
+ this->read_delay = 0;
+ this->max_buffers = 10;
+
+ this->scr = NULL;
+
+ this->I_frames = 0;
+
+ this->no_video = 0;
+ this->live_mode = 0;
+ this->still_mode = 0;
+ this->playback_finished = 0;
+ this->stream_start = 1;
+ this->send_pts = 1;
+
+ this->rescale_osd = 0;
+ this->unscaled_osd = 0;
+ for(i=0; i<MAX_OSD_OBJECT; i++)
+ this->osdhandle[i] = -1;;
+ this->video_width = this->vdr_osd_width = 720;
+ this->video_height = this->vdr_osd_height = 576;
+
+ this->video_properties_saved = 0;
+ this->orig_hue = 0;
+ this->orig_brightness = 0;
+ this->orig_saturation = 0;
+ this->orig_contrast = 0;
+ local_mode = ( (!strncasecmp(mrl, "xvdr://", 7)) &&
+ (strlen(mrl)==7))
+ || (!strncasecmp(mrl, "xvdr:///", 8));
+
+ this->input_plugin.open = local_mode ? vdr_plugin_open_local
+ : vdr_plugin_open_net;
+ this->input_plugin.get_mrl = vdr_plugin_get_mrl;
+ this->input_plugin.dispose = vdr_plugin_dispose;
+ this->input_plugin.input_class = cls_gen;
+
+ this->input_plugin.get_capabilities = vdr_plugin_get_capabilities;
+ this->input_plugin.read = vdr_plugin_read;
+ this->input_plugin.read_block = vdr_plugin_read_block;
+ this->input_plugin.seek = vdr_plugin_seek;
+ this->input_plugin.get_current_pos = vdr_plugin_get_current_pos;
+ this->input_plugin.get_length = vdr_plugin_get_length;
+ this->input_plugin.get_blocksize = vdr_plugin_get_blocksize;
+ this->input_plugin.get_optional_data = vdr_plugin_get_optional_data;
+
+ if(local_mode) {
+ this->funcs.push_input_write = vdr_plugin_write;
+ this->funcs.push_input_control= vdr_plugin_parse_control;
+ this->funcs.push_input_osd = vdr_plugin_exec_osd_command;
+ /*this->funcs.xine_input_event= NULL; -- frontend sets this */
+ } else {
+ this->funcs.input_control = vdr_plugin_keypress;
+ }
+
+ this->mrl = strdup(mrl);
+
+ /* buffer */
+ this->block_buffer = _x_fifo_buffer_new(4,0xffff+0xff); /* dummy buf to be used before first read and for big PES frames */
+ this->big_buffer = NULL; /* dummy buf to be used for jumbo PES frames */
+ pthread_mutex_init (&this->lock, NULL);
+ pthread_mutex_init (&this->osd_lock, NULL);
+ pthread_mutex_init (&this->vdr_entry_lock, NULL);
+
+ this->udp_data = NULL;
+
+ LOGDBG("vdr_class_get_instance done.");
+
+ return &this->input_plugin;
+}
+
+/*
+ * vdr input plugin class stuff
+ */
+
+static char *vdr_class_get_description (input_class_t *this_gen)
+{
+ return _("VDR (Video Disk Recorder) input plugin");
+}
+
+static const char *vdr_class_get_identifier (input_class_t *this_gen)
+{
+ return "xvdr";
+}
+
+static void vdr_class_dispose (input_class_t *this_gen)
+{
+ vdr_input_class_t *cls = (vdr_input_class_t *) this_gen;
+ free (cls);
+}
+
+static void *init_class (xine_t *xine, void *data)
+{
+ vdr_input_class_t *this;
+
+ this = (vdr_input_class_t *) xine_xmalloc (sizeof (vdr_input_class_t));
+
+ this->xine = xine;
+
+ this->input_class.get_instance = vdr_class_get_instance;
+ this->input_class.get_identifier = vdr_class_get_identifier;
+ this->input_class.get_description = vdr_class_get_description;
+ this->input_class.get_dir = NULL;
+ this->input_class.get_autoplay_list = NULL;
+ this->input_class.dispose = vdr_class_dispose;
+ this->input_class.eject_media = NULL;
+
+ SetupLogLevel();
+
+ LOGDBG("init class succeeded");
+
+ return this;
+}
+
+
+/*
+ * exported plugin catalog entry
+ */
+
+const plugin_info_t xine_plugin_info[] __attribute__((visibility("default"))) = {
+ /* type, API, "name", version, special_info, init_function */
+ { PLUGIN_INPUT, INPUT_PLUGIN_IFACE_VERSION, "XVDR", XINE_VERSION_CODE, NULL, init_class },
+ { PLUGIN_NONE, 0, "", 0, NULL, NULL }
+};
+
+const plugin_info_t *xine_plugin_info_xvdr = xine_plugin_info;
+
+