diff options
author | phintuka <phintuka> | 2006-06-03 10:04:49 +0000 |
---|---|---|
committer | phintuka <phintuka> | 2006-06-03 10:04:49 +0000 |
commit | 0e345486181ef82b681dd6047f3b6ccb44c77146 (patch) | |
tree | a5401c7f97ab047a0afa890e6806d8537564102a /xine_input_vdr.c | |
parent | 321eb114c9fe9abd954ce4270595d53df6cccbae (diff) | |
download | xineliboutput-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.c | 4570 |
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; + + |