summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config.c323
-rw-r--r--device.c1364
-rw-r--r--dummy_player.c110
-rw-r--r--equalizer.c149
-rw-r--r--frontend.c614
-rw-r--r--frontend_local.c452
-rw-r--r--frontend_svr.c1276
-rw-r--r--i18n.c1829
-rw-r--r--media_player.c545
-rw-r--r--menu.c730
-rw-r--r--menuitems.c137
-rw-r--r--mpg2c.c50
-rw-r--r--osd.c349
-rw-r--r--setup_menu.c1119
-rw-r--r--tools/backgroundwriter.c215
-rw-r--r--tools/backgroundwriter.h62
-rw-r--r--tools/cxsocket.c3
-rw-r--r--tools/cxsocket.h192
-rw-r--r--tools/future.h84
-rw-r--r--tools/general_remote.h27
-rw-r--r--tools/listiter.h83
-rw-r--r--tools/pes.h258
-rw-r--r--tools/timer.c292
-rw-r--r--tools/timer.h296
-rw-r--r--tools/udp_buffer.h115
-rw-r--r--tools/udp_pes_scheduler.c595
-rw-r--r--tools/udp_pes_scheduler.h103
-rw-r--r--xine/post.c868
-rw-r--r--xine/post.h99
-rw-r--r--xine_fbfe_frontend.c269
-rw-r--r--xine_frontend.c1207
-rw-r--r--xine_frontend_lirc.c157
-rw-r--r--xine_frontend_main.c379
-rw-r--r--xine_frontend_vdrdiscovery.c115
-rw-r--r--xine_input_vdr.c4570
-rw-r--r--xine_sxfe_frontend.c601
36 files changed, 19637 insertions, 0 deletions
diff --git a/config.c b/config.c
new file mode 100644
index 00000000..5cee08fc
--- /dev/null
+++ b/config.c
@@ -0,0 +1,323 @@
+/*
+ * config.c: User settings
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: config.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include <vdr/config.h>
+
+#include "config.h"
+
+#define DEFAULT_DEINTERLACE_OPTS "method=Linear,cheap_mode=1,pulldown=0,use_progressive_frame_flag=1"
+
+const char *config_t::s_bufferSize[] =
+ {"custom","tiny","small","medium","large","huge",NULL};
+const int config_t::i_pesBufferSize[] =
+ {0,50,250,500,1000,2000,500};
+const char *config_t::s_aspects[] =
+ {"automatic", "default", "4:3", "16:9", "Pan&Scan", "CenterCutOut", 0};
+const char *config_t::s_deinterlaceMethods[] =
+ {"none", "bob", "weave", "greedy", "onefield", "onefield_xv",
+ "linearblend", "tvtime", 0};
+const char *config_t::s_deinterlaceMethodNames[] =
+ {"off", "Bob", "Weave", "Greedy", "One Field", "One Field XV",
+ "Linear Blend", "TvTime", NULL};
+const char *config_t::s_decoderPriority[] =
+ {"low", "normal", "high", 0};
+const char *config_t::s_fieldOrder[] =
+ {"normal", "inverted", NULL};
+const char *config_t::s_audioDriverNames[] =
+ {"automatic","Alsa","OSS","no audio","Arts","ESound",NULL};
+const char *config_t::s_audioDrivers[] =
+ {"auto","alsa","oss","none","arts","esound",NULL};
+const char *config_t::s_videoDriverNamesX11[] =
+ {"automatic","XShm","Xv","XvMC","XvMC+VLD","no video",NULL};
+const char *config_t::s_videoDriversX11[] =
+ {"auto","X11","xv","xvmc","xxmc","none",NULL};
+const char *config_t::s_videoDriverNamesFB[] =
+ {"automatic","Framebuffer","DirectFB","No Video",NULL};
+const char *config_t::s_videoDriversFB[] =
+ {"auto","fb","DirectFB","none",NULL};
+const char *config_t::s_frontendNames[] =
+ {"X11 (sxfe)", "Framebuffer (fbfe)", "Off", NULL};
+const char *config_t::s_frontends[] =
+ {"sxfe", "fbfe", "", NULL};
+const char *config_t::s_frontend_files[] =
+ {"lib" PLUGIN_NAME_I18N "-sxfe.so." XINELIBOUTPUT_VERSION,
+ "lib" PLUGIN_NAME_I18N "-fbfe.so." XINELIBOUTPUT_VERSION,
+ // example: xineliboutput-sxfe.so.0.4.0
+ "",
+ NULL};
+
+const char *config_t::s_audioEqNames[] =
+ {"30 Hz", "60 Hz", "125 Hz", "250 Hz", "500 Hz",
+ "1 kHz", "2 kHz", "4 kHz", "8 kHz", "16 kHz", NULL};
+const char *config_t::s_audioVisualizationNames[] =
+ {"Off", "Goom", "Oscilloscope", "FFT Scope", "FFT Graph", NULL};
+const char *config_t::s_audioVisualizations[] =
+ {"none", "goom", "oscope", "fftscope", "fftgraph", NULL};
+
+static char *strcatrealloc(char *dest, const char *src)
+{
+ if (!src || !*src)
+ return dest;
+
+ int l = (dest ? strlen(dest) : 0) + strlen(src) + 1;
+ if(dest) {
+ dest = (char *)realloc(dest, l);
+ strcat(dest, src);
+ } else {
+ dest = (char*)malloc(l);
+ strcpy(dest, src);
+ }
+ return dest;
+}
+
+config_t::config_t() {
+ memset(this, 0, sizeof(config_t));
+
+ strcpy(local_frontend, s_frontends[FRONTEND_X11]);
+ strcpy(video_driver , s_videoDriversX11[X11_DRIVER_XV]);
+ strcpy(video_port , "127.0.0.1:0.0");
+ strcpy(modeline , "");
+
+ strcpy(audio_driver , s_audioDrivers[AUDIO_DRIVER_ALSA]);
+ strcpy(audio_port , "default");
+
+ post_plugins = NULL;
+
+ audio_delay = 0;
+ audio_compression = 0;
+ memset(audio_equalizer,0,sizeof(audio_equalizer));
+ strcpy(audio_visualization, "goom");
+ //strcpy(audio_vis_goom_opts, "fps:25,width:720,height:576");
+ headphone = 0;
+ audio_upmix = 0;
+ audio_surround = 0;
+
+ inactivity_timer = 0;
+ decoder_priority = DECODER_PRIORITY_NORMAL;
+ pes_buffers = i_pesBufferSize[PES_BUFFERS_SMALL_250];
+ strcpy(deinterlace_method, s_deinterlaceMethods[DEINTERLACE_NONE]);
+ strcpy(deinterlace_opts, DEFAULT_DEINTERLACE_OPTS);
+ display_aspect = 0; /* auto */
+
+ hide_main_menu = 0;
+ prescale_osd = 1;
+ prescale_osd_downscale = 0;
+ unscaled_osd = 0;
+ unscaled_osd_opaque = 0;
+ unscaled_osd_lowresvideo = 1;
+
+ alpha_correction = 0;
+ alpha_correction_abs = 0;
+
+ fullscreen = 0;
+ modeswitch = 1;
+ width = 720;
+ height = 576;
+ scale_video = 0;
+ field_order = 0;
+ autocrop = 0;
+
+ remote_mode = 0;
+ listen_port = LISTEN_PORT;
+ use_remote_keyboard = 1;
+ remote_usetcp = 1;
+ remote_useudp = 1;
+ remote_usertp = 1;
+ remote_usepipe = 1;
+ remote_usebcast = 1;
+
+ strcpy(remote_rtp_addr, "224.0.1.9");
+ remote_rtp_port = LISTEN_PORT;
+ remote_rtp_ttl = 1;
+ remote_rtp_always_on = 0;
+
+ use_x_keyboard = 1;
+
+ hue = -1;
+ saturation = -1;
+ contrast = -1;
+ brightness = -1;
+
+ strcpy(browse_files_dir, "/video");
+ strcpy(browse_images_dir, "/video");
+
+ main_menu_mode = ShowMenu;
+ force_primary_device = 0;
+
+ m_ProcessedArgs = NULL;
+};
+
+bool config_t::ProcessArg(const char *Name, const char *Value)
+{
+ char *s = m_ProcessedArgs;
+ m_ProcessedArgs = NULL;
+ if(SetupParse(Name, Value)) {
+ m_ProcessedArgs = s ? s : strcpy(new char[4096], " ");
+ strcat(strcat(m_ProcessedArgs, Name), " ");
+ return true;
+ }
+ m_ProcessedArgs = s;
+ return false;
+}
+
+char *m_ProcessedArgs;
+
+bool config_t::ProcessArgs(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ { "display", required_argument, NULL, 'd' },
+ { "fullscreen", no_argument, NULL, 'f' },
+ { "xkeyboard", no_argument, NULL, 'k' },
+ //{ "noxkeyboard",no_argument, NULL, 'K' },
+ { "local", required_argument, NULL, 'l' },
+ { "nolocal", no_argument, NULL, 'L' },
+ { "modeline", required_argument, NULL, 'm' },
+ { "remote", required_argument, NULL, 'r' },
+ { "noremote", no_argument, NULL, 'R' },
+ { "window", no_argument, NULL, 'w' },
+ { "video", required_argument, NULL, 'V' },
+ { "audio", required_argument, NULL, 'A' },
+ { "post", required_argument, NULL, 'P' },
+ { "primary", no_argument, NULL, 'p' },
+ { NULL }
+ };
+
+ int c;
+ while ((c = getopt_long(argc, argv, "d:fkKl:Lm:r:RW", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'd': ProcessArg("Video.Port", optarg);
+ break;
+ case 'f': ProcessArg("Fullscreen", "1");
+ break;
+ case 'k': ProcessArg("X11.UseKeyboard", "1");
+ break;
+ //case 'K': ProcessArg("X11.UseKeyboard", "0");
+ //break;
+ case 'l': ProcessArg("Frontend", optarg);
+ break;
+ case 'L': ProcessArg("Frontend", "none");
+ break;
+ case 'm': ProcessArg("Modeline", optarg);
+ break;
+ case 'r': ProcessArg("Remote.ListenPort", optarg);
+ ProcessArg("RemoteMode", "1");
+ break;
+ case 'R': ProcessArg("RemoteMode", "0");
+ break;
+ case 'w': ProcessArg("Fullscreen", "0");
+ break;
+ case 'V': ProcessArg("Video.Driver", optarg);
+ break;
+ case 'A': ProcessArg("Audio.Driver", optarg);
+ break;
+ case 'P': if(post_plugins)
+ post_plugins = strcatrealloc(post_plugins, ";");
+ post_plugins = strcatrealloc(post_plugins, optarg);
+ break;
+ case 'p': ProcessArg("ForcePrimaryDevice", "1");
+ break;
+ default: return false;
+ }
+ }
+ return true;
+}
+
+bool config_t::SetupParse(const char *Name, const char *Value)
+{
+ char *pt;
+ if(m_ProcessedArgs && NULL != (pt=strstr(m_ProcessedArgs+1, Name)) &&
+ *(pt-1) == ' ' && *(pt+strlen(Name)) == ' ')
+ return true;
+
+ if (!strcasecmp(Name, "Frontend")) strcpy(local_frontend, Value);
+ else if (!strcasecmp(Name, "Modeline")) strcpy(modeline, Value);
+ else if (!strcasecmp(Name, "VideoModeSwitching")) modeswitch = atoi(Value);
+ else if (!strcasecmp(Name, "Fullscreen")) fullscreen = atoi(Value);
+ else if (!strcasecmp(Name, "DisplayAspect")) display_aspect = strstra(Value, s_aspects, 0);
+ else if (!strcasecmp(Name, "ForcePrimaryDevice")) force_primary_device = atoi(Value);
+
+ else if (!strcasecmp(Name, "X11.WindowWidth")) width = atoi(Value);
+ else if (!strcasecmp(Name, "X11.WindowHeight")) height = atoi(Value);
+ else if (!strcasecmp(Name, "X11.UseKeyboard")) use_x_keyboard = atoi(Value);
+
+ else if (!strcasecmp(Name, "Audio.Driver")) strcpy(audio_driver, Value);
+ else if (!strcasecmp(Name, "Audio.Port")) strcpy(audio_port, Value);
+ else if (!strcasecmp(Name, "Audio.Delay")) audio_delay = atoi(Value);
+ else if (!strcasecmp(Name, "Audio.Compression")) audio_compression = atoi(Value);
+ else if (!strcasecmp(Name, "Audio.Visualization")) strcpy(audio_visualization, Value);
+ else if (!strcasecmp(Name, "Audio.Surround")) audio_surround = atoi(Value);
+ else if (!strcasecmp(Name, "Audio.Upmix")) audio_upmix = atoi(Value);
+ else if (!strcasecmp(Name, "Audio.Headphone")) headphone = atoi(Value);
+
+ else if (!strcasecmp(Name, "OSD.HideMainMenu")) hide_main_menu = atoi(Value);
+ else if (!strcasecmp(Name, "OSD.Prescale")) prescale_osd = atoi(Value);
+ else if (!strcasecmp(Name, "OSD.Downscale")) prescale_osd_downscale = atoi(Value);
+ else if (!strcasecmp(Name, "OSD.UnscaledAlways")) unscaled_osd = atoi(Value);
+ else if (!strcasecmp(Name, "OSD.UnscaledOpaque")) unscaled_osd_opaque = atoi(Value);
+ else if (!strcasecmp(Name, "OSD.UnscaledLowRes")) unscaled_osd_lowresvideo = atoi(Value);
+
+ else if (!strcasecmp(Name, "OSD.AlphaCorrection")) alpha_correction = atoi(Value);
+ else if (!strcasecmp(Name, "OSD.AlphaCorrectionAbs")) alpha_correction_abs = atoi(Value);
+
+ else if (!strcasecmp(Name, "RemoteMode")) remote_mode = atoi(Value);
+ else if (!strcasecmp(Name, "Remote.ListenPort")) listen_port = atoi(Value);
+ else if (!strcasecmp(Name, "Remote.Keyboard")) use_remote_keyboard = atoi(Value);
+ else if (!strcasecmp(Name, "Remote.UseTcp")) remote_usetcp = atoi(Value);
+ else if (!strcasecmp(Name, "Remote.UseUdp")) remote_useudp = atoi(Value);
+ else if (!strcasecmp(Name, "Remote.UseRtp")) remote_usertp = atoi(Value);
+ else if (!strcasecmp(Name, "Remote.UsePipe")) remote_usepipe= atoi(Value);
+ else if (!strcasecmp(Name, "Remote.UseBroadcast")) remote_usebcast = atoi(Value);
+
+ else if (!strcasecmp(Name, "Remote.Rtp.Address")) strncpy(remote_rtp_addr, Value, 20);
+ else if (!strcasecmp(Name, "Remote.Rtp.Port")) remote_rtp_port = atoi(Value);
+ else if (!strcasecmp(Name, "Remote.Rtp.TTL")) remote_rtp_ttl = atoi(Value);
+ else if (!strcasecmp(Name, "Remote.Rtp.AlwaysOn")) remote_rtp_always_on = atoi(Value);
+
+ else if (!strcasecmp(Name, "Decoder.InactivityTimer")) inactivity_timer=atoi(Value);
+ else if (!strcasecmp(Name, "Decoder.Priority")) decoder_priority=strstra(Value,s_decoderPriority,1);
+ else if (!strcasecmp(Name, "Decoder.PesBuffers")) pes_buffers=atoi(Value);
+
+ else if (!strcasecmp(Name, "Video.Driver")) strcpy(video_driver, Value);
+ else if (!strcasecmp(Name, "Video.Port")) strcpy(video_port, Value);
+ else if (!strcasecmp(Name, "Video.Scale")) scale_video = atoi(Value);
+ else if (!strcasecmp(Name, "Video.DeinterlaceOptions")) strcpy(deinterlace_opts, Value);
+ else if (!strcasecmp(Name, "Video.Deinterlace")) strcpy(deinterlace_method, Value);
+ else if (!strcasecmp(Name, "Video.FieldOrder")) field_order=atoi(Value)?1:0;
+ else if (!strcasecmp(Name, "Video.AutoCrop")) autocrop = atoi(Value);
+ else if (!strcasecmp(Name, "Video.HUE")) hue = atoi(Value);
+ else if (!strcasecmp(Name, "Video.Saturation")) saturation = atoi(Value);
+ else if (!strcasecmp(Name, "Video.Contrast")) contrast = atoi(Value);
+ else if (!strcasecmp(Name, "Video.Brightness")) brightness = atoi(Value);
+
+ else if (!strcasecmp(Name, "BrowseFilesDir")) strcpy(browse_files_dir, Value);
+ else if (!strcasecmp(Name, "BrowseImagesDir")) strcpy(browse_images_dir, Value);
+
+ else if (!strcasecmp(Name, "Audio.Equalizer"))
+ sscanf(Value,"%d %d %d %d %d %d %d %d %d %d",
+ audio_equalizer ,audio_equalizer+1,
+ audio_equalizer+2,audio_equalizer+3,
+ audio_equalizer+4,audio_equalizer+5,
+ audio_equalizer+6,audio_equalizer+7,
+ audio_equalizer+8,audio_equalizer+9);
+
+ else return false;
+
+ return true;
+}
+
+/* Global instance */
+config_t xc;
+
+
diff --git a/device.c b/device.c
new file mode 100644
index 00000000..01a25f6e
--- /dev/null
+++ b/device.c
@@ -0,0 +1,1364 @@
+/*
+ * device.c: xine-lib output device for the Video Disk Recorder
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: device.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <vdr/config.h>
+#include <vdr/thread.h>
+#include <vdr/dvbspu.h>
+#include <vdr/channels.h>
+#include <vdr/skins.h>
+#include <vdr/status.h>
+#include <vdr/remote.h>
+
+//#define XINELIBOUTPUT_DEBUG
+//#define XINELIBOUTPUT_DEBUG_STDOUT
+//#define XINELIBOUTPUT_DEBUG_STDERR
+
+#include "logdefs.h"
+#include "config.h"
+#include "osd.h"
+
+#ifdef ENABLE_SUSPEND
+# include "tools/timer.h"
+# include "tools/timer.c"
+# ifdef SUSPEND_BY_PLAYER
+# include "dummy_player.h"
+# include "dummy_player.c"
+# endif
+# define ACTIVITY m_inactivityTimer = 0;
+#else
+# define ACTIVITY
+#endif
+
+#include "tools/listiter.h"
+#include "tools/pes.h"
+
+#include "frontend_local.h"
+#include "frontend_svr.h"
+
+#include "device.h"
+
+#define STILLPICTURE_REPEAT_COUNT 3
+
+//---------------------------- status monitor -------------------------------
+
+#define DEBUG_SWITCHING_TIME
+
+#ifdef DEBUG_SWITCHING_TIME
+int64_t switchtimeOff = 0LL;
+int64_t switchtimeOn = 0LL;
+bool switchingIframe;
+#endif
+
+class cXinelibStatusMonitor : public cStatus
+{
+ private:
+ cXinelibStatusMonitor();
+ cXinelibStatusMonitor(cXinelibStatusMonitor&);
+
+ public:
+ cXinelibStatusMonitor(cXinelibDevice& device, int cardIndex) :
+ m_Device(device), m_cardIndex(cardIndex) {};
+
+ protected:
+ virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
+#if VDRVERSNUM < 10338
+ virtual void Replaying(const cControl *Control, const char *Name);
+#else
+ virtual void Replaying(const cControl *Control, const char *Name,
+ const char *FileName, bool On);
+#endif
+
+ cXinelibDevice& m_Device;
+ int m_cardIndex;
+};
+
+void cXinelibStatusMonitor::ChannelSwitch(const cDevice *Device,
+ int ChannelNumber)
+{
+ TRACEF("cXinelibStatusMonitor::ChannelSwitch");
+
+ if (ChannelNumber) {
+ if (Device->CardIndex() == m_cardIndex) {
+#ifdef DEBUG_SWITCHING_TIME
+ switchtimeOn = cTimeMs::Now();
+#endif
+ m_Device.SetTvMode(Channels.GetByNumber(ChannelNumber));
+ TRACE("cXinelibStatusMonitor: Set to TvMode");
+ }
+ } else {
+ if (Device->CardIndex() == m_cardIndex) {
+#ifdef DEBUG_SWITCHING_TIME
+ switchtimeOff = cTimeMs::Now();
+#endif
+ m_Device.StopOutput();
+ TRACE("cXinelibStatusMonitor: received stop");
+ }
+ }
+}
+
+#if VDRVERSNUM < 10338
+void cXinelibStatusMonitor::Replaying(const cControl *Control,
+ const char *Name)
+{
+ TRACEF("cXinelibStatusMonitor::Replaying");
+
+ if (Name != NULL) {
+ TRACE("cXinelibStatusMonitor: Replaying " << Name);
+ m_Device.SetReplayMode();
+ }
+}
+#else
+void cXinelibStatusMonitor::Replaying(const cControl *Control,
+ const char *Name,
+ const char *FileName, bool On)
+{
+ TRACEF("cXinelibStatusMonitor::Replaying");
+
+ if (On /*&& Name != NULL*/) {
+ TRACE("cXinelibStatusMonitor: Replaying " << Name << "("<<FileName")");
+ m_Device.SetReplayMode();
+ }
+}
+#endif
+
+//----------------------------- device ----------------------------------------
+
+#ifdef USENOPPLAYER
+class cNopControl : public cControl {
+public:
+ cNopControl() : cControl(new cPlayer())
+ { LOGMSG("cNopControl created"); }
+ ~cNopControl()
+ { LOGMSG("cNopControl destroyed"); }
+ virtual void Hide(void) {};
+
+ static int NopPlayerActivated;
+};
+
+int cNopControl::NopPlayerActivated = 0;
+#endif
+
+//
+// Singleton
+//
+
+cXinelibDevice* cXinelibDevice::m_pInstance = NULL;
+
+cXinelibDevice& cXinelibDevice::Instance(void)
+{
+ TRACEF("cXinelibDevice::Instance");
+ if (!m_pInstance) {
+ m_pInstance = new cXinelibDevice();
+ TRACE("cXinelibDevice::Instance(): create, cardindex = "
+ << m_pInstance->CardIndex());
+ }
+
+ return *m_pInstance;
+}
+
+void cXinelibDevice::Dispose(void)
+{
+ TRACEF("cXinelibDevice::Dispose");
+ delete m_pInstance;
+ m_pInstance = NULL;
+}
+
+//
+// init and shutdown
+//
+
+cXinelibDevice::cXinelibDevice()
+{
+ TRACEF("cXinelibDevice::cXinelibDevice");
+
+ m_statusMonitor = NULL;
+ m_spuDecoder = NULL;
+
+ m_local = NULL;
+ m_server = NULL;
+
+ if(*xc.local_frontend && strncmp(xc.local_frontend, "none", 4))
+ m_clients.Add(m_local = new cXinelibLocal(xc.local_frontend));
+ if(xc.remote_mode && xc.listen_port>0)
+ m_clients.Add(m_server = new cXinelibServer(xc.listen_port));
+
+ m_ac3Present = false;
+ m_spuPresent = false;
+#ifdef ENABLE_SUSPEND
+ m_suspended = false;
+ ACTIVITY
+#endif
+ m_liveMode = false;
+ m_TrickSpeed = -1;
+ m_SkipAudio = false;
+ m_PlayingFile = false;
+ m_StreamStart = true;
+ m_RadioStream = false;
+ m_AudioCount = 0;
+}
+
+cXinelibDevice::~cXinelibDevice()
+{
+ TRACEF("cXinelibDevice::~cXinelibDevice");
+
+ StopDevice();
+
+ m_pInstance = NULL;
+}
+
+bool cXinelibDevice::StartDevice()
+{
+ TRACEF("cXinelibDevice::StartDevice");
+
+ // if(dynamic_cast<cXinelibLocal*>(it))
+ if(m_local) {
+ m_local->Start();
+ while(!m_local->IsReady()) {
+ cCondWait::SleepMs(100);
+ if(m_local->IsFinished()) {
+ LOGMSG("cXinelibDevice::Start(): Local frontend init failed");
+ return false;
+ }
+ }
+ if(xc.force_primary_device)
+ ForcePrimaryDevice(true);
+ }
+
+ if(m_server) {
+ m_server->Start();
+ while(!m_server->IsReady()) {
+ cCondWait::SleepMs(100);
+ if(m_server->IsFinished()) {
+ LOGMSG("cXinelibDevice::Start(): Server init failed");
+ return false;
+ }
+ }
+ }
+
+#ifdef ENABLE_SUSPEND
+ m_suspended = false;
+ ACTIVITY
+#endif
+
+ m_statusMonitor = new cXinelibStatusMonitor(*this, CardIndex());
+
+#ifdef ENABLE_SUSPEND
+ CreateTimerEvent(this, &cXinelibDevice::CheckInactivityTimer, 60*1000, false);
+#endif
+
+#ifdef USENOPPLAYER
+ if(!m_local) {
+ LOGMSG("No clients, activating cNopControl");
+ cControl::Launch(new cNopControl);
+ cNopControl::NopPlayerActivated = true;
+ }
+#endif
+
+ LOGDBG("cXinelibDevice::StartDevice(): Device started");
+ return true;
+}
+
+void cXinelibDevice::StopDevice(void)
+{
+ TRACEF("cXinelibDevice::StopDevice");
+ LOGDBG("cXinelibDevice::StopDevice(): Stopping device ...");
+#ifdef ENABLE_SUSPEND
+ CancelTimerEvents(this);
+#endif
+ if(m_statusMonitor) {
+ delete m_statusMonitor;
+ m_statusMonitor = NULL;
+ }
+ if (m_spuDecoder) {
+ delete m_spuDecoder;
+ m_spuDecoder = NULL;
+ }
+
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
+ TrickSpeed(-1);
+ ForEach(m_clients, &cXinelibThread::Stop);
+
+ m_local = m_server = NULL;
+ m_clients.Clear();
+}
+
+void cXinelibDevice::MakePrimaryDevice(bool On)
+{
+ TRACEF("cXinelibDevice::MakePrimaryDevice");
+ if(On)
+ new cXinelibOsdProvider(this);
+}
+
+void cXinelibDevice::ForcePrimaryDevice(bool On)
+{
+ static int Original = 0;
+ static int Counter = 0;
+
+ TRACEF("cXinelibDevice::ForcePrimaryDevice");
+
+ if(On) {
+ Counter++;
+#ifdef USENOPPLAYER
+ if(cNopControl::NopPlayerActivated) {
+ LOGMSG("First client, stopping cNopControl");
+ cControl::Shutdown();
+ cNopControl::NopPlayerActivated = false;
+ }
+#endif
+ if(xc.force_primary_device) {
+ if(cDevice::PrimaryDevice() && this != cDevice::PrimaryDevice()) {
+ /* TODO: may need to use vdr main thread for this */
+ Original = cDevice::PrimaryDevice()->DeviceNumber() + 1;
+ cControl::Shutdown();
+ LOGMSG("Forcing primary device, original index = %d", Original);
+ if(cOsd::IsOpen()) {
+ LOGMSG("Forcing primary device, old OSD still open !");
+#if VDRVERSNUM >= 10400
+ xc.main_menu_mode = CloseOsd;
+ cRemote::CallPlugin("xineliboutput");
+#endif
+ }
+ SetPrimaryDevice(DeviceNumber() + 1);
+ }
+ }
+
+ } else /* Off */ {
+ Counter--;
+ if(Counter<0)
+ LOGMSG("Internal error (ForcePrimaryDevice < 0)");
+ if(!Counter) {
+ if(Original) {
+ LOGMSG("Restoring original primary device %d", Original);
+ cControl::Shutdown();
+ if(cOsd::IsOpen()) {
+ LOGMSG("Restoring primary device, xineliboutput OSD still open !");
+#if VDRVERSNUM >= 10400
+ xc.main_menu_mode = CloseOsd;
+ cRemote::CallPlugin("xineliboutput");
+#endif
+ }
+ cDevice::SetPrimaryDevice(Original);
+ Original = 0;
+ }
+#ifdef USENOPPLAYER
+ if(!m_local && !cControl::Control()) {
+ LOGMSG("No clients, activating cNopControl");
+ cControl::Launch(new cNopControl);
+ cNopControl::NopPlayerActivated = true;
+ }
+#endif
+ }
+ }
+}
+
+
+//
+// Configuration
+//
+
+void cXinelibDevice::ConfigureOSD(bool prescale_osd, bool unscaled_osd)
+{
+ TRACEF("cXinelibDevice::ConfigureOSD");
+
+ ACTIVITY
+ if(m_local)
+ m_local->ConfigureOSD(prescale_osd, unscaled_osd);
+ if(m_server)
+ m_server->ConfigureOSD(prescale_osd, unscaled_osd);
+}
+
+void cXinelibDevice::ConfigurePostprocessing(char *deinterlace_method, int audio_delay, int audio_compression, int *audio_equalizer, int audio_surround)
+{
+ TRACEF("cXinelibDevice::ConfigurePostprocessing");
+
+ ACTIVITY
+ if(m_local)
+ m_local->ConfigurePostprocessing(deinterlace_method, audio_delay,
+ audio_compression, audio_equalizer,
+ audio_surround);
+ if(m_server)
+ m_server->ConfigurePostprocessing(deinterlace_method, audio_delay,
+ audio_compression, audio_equalizer,
+ audio_surround);
+}
+
+void cXinelibDevice::ConfigurePostprocessing(char *name, bool on, char *args)
+{
+ TRACEF("cXinelibDevice::ConfigurePostprocessing");
+
+ ACTIVITY
+ if(m_local)
+ m_local->ConfigurePostprocessing(name, on, args);
+ if(m_server)
+ m_server->ConfigurePostprocessing(name, on, args);
+}
+
+void cXinelibDevice::ConfigureVideo(int hue, int saturation, int brightness, int contrast)
+{
+ TRACEF("cXinelibDevice::ConfigureVideo");
+
+ ACTIVITY
+ if(m_local)
+ m_local->ConfigureVideo(hue, saturation, brightness, contrast);
+ if(m_server)
+ m_server->ConfigureVideo(hue, saturation, brightness, contrast);
+}
+
+void cXinelibDevice::ConfigureDecoder(int pes_buffers, int priority)
+{
+ TRACEF("cXinelibDevice::ConfigureDecoder");
+
+ ACTIVITY
+ if(m_local)
+ m_local->ConfigureDecoder(pes_buffers, priority);
+ //if(m_server)
+ // m_server->ConfigureDecoder(pes_buffers, priority);
+
+ cXinelibOsdProvider::RefreshOsd();
+}
+
+void cXinelibDevice::ConfigureWindow(int fullscreen, int width, int height,
+ int modeswitch, char *modeline,
+ int aspect, int scale_video, int field_order)
+{
+ TRACEF("cXinelibDevice::ConfigureWindow");
+
+ ACTIVITY
+ if((!*xc.local_frontend || !strncmp(xc.local_frontend, "none", 4)) && m_local) {
+ cXinelibThread *tmp = m_local;
+ m_clients.Del(tmp, false);
+ m_local = NULL;
+ cCondWait::SleepMs(5);
+ tmp->Stop();
+ cCondWait::SleepMs(5);
+ delete tmp;
+ if(xc.force_primary_device)
+ ForcePrimaryDevice(false);
+ }
+ if(m_local)
+ m_local->ConfigureWindow(fullscreen, width, height, modeswitch, modeline,
+ aspect, scale_video, field_order);
+ else if(*xc.local_frontend && strncmp(xc.local_frontend, "none", 4)) {
+ cXinelibThread *tmp = new cXinelibLocal(xc.local_frontend);
+ tmp->Start();
+ m_clients.Add(m_local = tmp);
+
+ cCondWait::SleepMs(25);
+ while(!m_local->IsReady() && !m_local->IsFinished())
+ cCondWait::SleepMs(25);
+
+ if(m_local->IsFinished()) {
+ m_local = NULL;
+ m_clients.Del(tmp, true);
+ Skins.QueueMessage(mtError, tr("Frontend initialization failed"), 10);
+ } else {
+ if(xc.force_primary_device)
+ ForcePrimaryDevice(true);
+
+ m_local->ConfigureWindow(fullscreen, width, height, modeswitch, modeline,
+ aspect, scale_video, field_order);
+ }
+ }
+}
+
+void cXinelibDevice::Listen(bool activate, int port)
+{
+ TRACEF("cXinelibDevice::Listen");
+
+ ACTIVITY
+ if(activate && port>0) {
+ if(!m_server) {
+ cXinelibThread *tmp = new cXinelibServer(port);
+ tmp->Start();
+ m_clients.Add(m_server = tmp);
+
+ cCondWait::SleepMs(10);
+ while(!m_server->IsReady() && !m_server->IsFinished())
+ cCondWait::SleepMs(10);
+
+ if(m_server->IsFinished()) {
+ Skins.QueueMessage(mtError, tr("Server initialization failed"), 10);
+ m_server = NULL;
+ m_clients.Del(tmp, true);
+ }
+
+ } else {
+ if(! m_server->Listen(port))
+ Skins.QueueMessage(mtError, tr("Server initialization failed"), 10);
+ }
+ } else if( /*((!activate) || port<=0) && */ m_server) {
+ cXinelibThread *tmp = m_server;
+ m_clients.Del(tmp, false);
+ m_server = NULL;
+ cCondWait::SleepMs(5);
+ tmp->Stop();
+ cCondWait::SleepMs(5);
+ delete tmp;
+ }
+}
+
+//
+// OSD
+//
+
+void cXinelibDevice::OsdCmd(void *cmd)
+{
+ TRACEF("cXinelibDevice::OsdCmd");
+
+ ACTIVITY
+ if(m_server) // call first server, local frontend modifies contents of the message ...
+ m_server->OsdCmd(cmd);
+ if(m_local)
+ m_local->OsdCmd(cmd);
+}
+
+//
+// Play mode control
+//
+
+void cXinelibDevice::StopOutput(void)
+{
+ TRACEF("cXinelibDevice::StopOutput");
+
+ ACTIVITY
+ m_RadioStream = false;
+ m_AudioCount = 0;
+
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
+ Clear();
+ ForEach(m_clients, &cXinelibThread::QueueBlankDisplay);
+ ForEach(m_clients, &cXinelibThread::SetNoVideo, false);
+}
+
+void cXinelibDevice::SetTvMode(cChannel *Channel)
+{
+ TRACEF("cXinelibDevice::SetTvMode");
+
+ m_RadioStream = false;
+ if (Channel && !Channel->Vpid() && (Channel->Apid(0) || Channel->Apid(1)))
+ m_RadioStream = true;
+ if(/*playMode==pmAudioOnly||*/playMode==pmAudioOnlyBlack)
+ m_RadioStream = true;
+ TRACE("cXinelibDevice::SetTvMode - isRadio = "<<m_RadioStream);
+
+ m_StreamStart = true;
+ m_liveMode = true;
+ ACTIVITY
+ m_TrickSpeed = -1;
+ m_SkipAudio = false;
+ m_AudioCount = 0;
+
+ Clear();
+ ForEach(m_clients, &cXinelibThread::SetNoVideo, m_RadioStream);
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, true);
+ ForEach(m_clients, &cXinelibThread::QueueBlankDisplay);
+ ForEach(m_clients, &cXinelibThread::ResumeOutput);
+}
+
+void cXinelibDevice::SetReplayMode(void)
+{
+ TRACEF("cXinelibDevice::SetReplayMode");
+
+ //m_RadioStream = false;
+#if 1
+ //m_RadioStream = (playMode==pmAudioOnly || playMode==pmAudioOnlyBlack);
+ //TRACE("cXinelibDevice::SetReplayMode - isRadio = "<<m_RadioStream);
+ m_RadioStream = true; // first seen replayed video packet resets this
+ m_AudioCount = 15;
+#endif
+
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
+ TrickSpeed(-1);
+ ForEach(m_clients, &cXinelibThread::Clear);
+ ForEach(m_clients, &cXinelibThread::SetNoVideo, false /*m_RadioStream*/);
+ if(m_RadioStream && !m_liveMode)
+ ForEach(m_clients, &cXinelibThread::BlankDisplay);
+ ForEach(m_clients, &cXinelibThread::ResumeOutput);
+
+ m_liveMode = false;
+ ACTIVITY
+}
+
+bool cXinelibDevice::SetPlayMode(ePlayMode PlayMode)
+{
+ TRACEF("cXinelibDevice::SetPlayMode");
+
+ ACTIVITY
+
+#ifdef XINELIBOUTPUT_DEBUG
+ switch (PlayMode) {
+ case pmNone:
+ TRACE("cXinelibDevice::SetPlayMode audio/video from decoder"); break;
+ case pmAudioVideo:
+ TRACE("cXinelibDevice::SetPlayMode audio/video from player"); break;
+ case pmVideoOnly:
+ TRACE("cXinelibDevice::SetPlayMode video from player, audio from decoder"); break;
+ case pmAudioOnly:
+ TRACE("cXinelibDevice::SetPlayMode audio from player, video from decoder"); break;
+ case pmAudioOnlyBlack:
+ TRACE("cXinelibDevice::SetPlayMode audio only from player, no video (black screen)"); break;
+ case pmExtern_THIS_SHOULD_BE_AVOIDED:
+ TRACE("cXinelibDevice::SetPlayMode this should be avoided"); break;
+ }
+#endif
+
+ m_ac3Present = false;
+ m_spuPresent = false;
+ playMode = PlayMode;
+
+ TrickSpeed(-1);
+ if (playMode == pmAudioOnlyBlack /*|| playMode == pmNone*/) {
+ TRACE("pmAudioOnlyBlack --> BlankDisplay, NoVideo");
+ ForEach(m_clients, &cXinelibThread::BlankDisplay);
+ ForEach(m_clients, &cXinelibThread::SetNoVideo, true);
+ }
+
+ return true;
+}
+
+//
+// Playback control
+//
+
+void cXinelibDevice::TrickSpeed(int Speed)
+{
+ TRACEF("cXinelibDevice::TrickSpeed");
+
+ int RealSpeed = abs(Speed);
+ ACTIVITY
+ m_TrickSpeed = Speed;
+ m_TrickSpeedPts = 0;
+
+ ForEach(m_clients, &cXinelibThread::TrickSpeed, RealSpeed);
+}
+
+void cXinelibDevice::Clear(void)
+{
+ TRACEF("cXinelibDevice::Clear");
+ m_StreamStart = 1;
+ TrickSpeed(-1);
+ ForEach(m_clients, &cXinelibThread::Clear);
+}
+
+void cXinelibDevice::Play(void)
+{
+ TRACEF("cXinelibDevice::Play");
+
+ ACTIVITY
+ m_SkipAudio = false;
+
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
+ TrickSpeed(-1);
+}
+
+void cXinelibDevice::Freeze(void)
+{
+ TRACEF("cXinelibDevice::Freeze");
+
+ ACTIVITY
+
+ TrickSpeed(0);
+}
+
+//
+// Suspend device, inactivity timer
+//
+#ifdef ENABLE_SUSPEND
+void cXinelibDevice::CheckInactivityTimer()
+{
+ TRACEF("cXinelibDevice::CheckInactivityTimer");
+
+ Lock();
+
+ if(xc.inactivity_timer>0) {
+ int old_Timer = m_inactivityTimer++;
+ TRACE("cXinelibDevice::CheckInactivityTimer @" << time(NULL));
+ TRACE("cXinelibDevice::CheckInactivityTimer: m_inactivityTimer = " << m_inactivityTimer);
+
+ if(old_Timer<=xc.inactivity_timer && m_inactivityTimer>xc.inactivity_timer) {
+ SuspendedAction();
+ Unlock();
+# ifndef SUSPEND_BY_PLAYER
+ CreateTimerEvent(this, &cXinelibDevice::SuspendedAction, 5000);
+# endif
+ return;
+ }
+ }
+ Unlock();
+}
+
+bool cXinelibDevice::SuspendedAction(void)
+{
+ TRACEF("cXinelibDevice::SuspendedAction");
+
+ LOCK_THREAD;
+ if(m_suspended || (xc.inactivity_timer>0 && m_inactivityTimer>xc.inactivity_timer)) {
+ if(m_liveMode) {
+# ifndef SUSPEND_BY_PLAYER
+ TRACE("cXinelibDevice::SuspendedAction - DECODER SUSPENDED");
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
+ ForEach(m_clients, &cXinelibThread::LogoDisplay);
+# else
+ if(!cDummyPlayerControl::IsOpen())
+ cControl::Launch(new cDummyPlayerControl);
+# endif
+ }
+ return true;
+ }
+
+# ifndef SUSPEND_BY_PLAYER
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, m_liveMode);
+# else
+ if(cDummyPlayerControl::IsOpen())
+ cDummyPlayerControl::Close();
+# endif
+
+ return false;
+}
+
+void cXinelibDevice::Suspend(bool onoff)
+{
+ TRACEF("cXinelibDevice::Suspend");
+ TRACE("cXinelibDevice::Suspend = " << onoff);
+
+ Lock();
+ ACTIVITY
+
+ if(!m_suspended && onoff) {
+ m_suspended = onoff;
+ SuspendedAction();
+ Unlock();
+#ifndef SUSPEND_BY_PLAYER
+ CreateTimerEvent(this, &cXinelibDevice::SuspendedAction, 5000);
+#endif
+ return;
+ }
+ m_suspended = onoff;
+ Unlock();
+}
+#endif // ENABLE_SUSPEND
+
+//
+// Playback of files and images
+//
+
+int cXinelibDevice::PlayFileCtrl(const char *Cmd)
+{
+ TRACEF("cXinelibDevice::PlayFile");
+ int result = -1;
+
+ if(m_PlayingFile) {
+ if(m_server)
+ result = m_server->PlayFileCtrl(Cmd);
+ if(m_local)
+ result = m_local->PlayFileCtrl(Cmd);
+ }
+ return result;
+}
+
+bool cXinelibDevice::EndOfStreamReached(void)
+{
+ return (((!m_server) || m_server->EndOfStreamReached()) &&
+ ((!m_local) || m_local->EndOfStreamReached()));
+}
+
+bool cXinelibDevice::PlayFile(const char *FileName, int Position, bool LoopPlay)
+{
+ TRACEF("cXinelibDevice::PlayFile");
+ TRACE("cXinelibDevice::PlayFile(\"" << FileName << "\")");
+
+ bool result = true;
+
+ if(FileName) {
+ if(!m_PlayingFile) {
+ m_PlayingFile = true;
+ StopOutput();
+ }
+ result = (((!m_server) ||
+ m_server->PlayFile(FileName, Position, LoopPlay)) &&
+ ((!m_local) ||
+ m_local->PlayFile(FileName, Position, LoopPlay)));
+ } else if(/*!FileName &&*/m_PlayingFile) {
+ result = (((!m_server) || m_server->PlayFile(NULL, 0)) &&
+ ((!m_local) || m_local->PlayFile(NULL, 0)));
+ if(!m_liveMode)
+ SetReplayMode();
+ else
+ SetTvMode(Channels.GetByNumber(cDevice::CurrentChannel()));
+ m_PlayingFile = false;
+ }
+
+ return result;
+}
+
+//
+// Data stream handling
+//
+
+int cXinelibDevice::PlayAny(const uchar *buf, int length)
+{
+ TRACEF("cXinelibDevice::PlayAny");
+
+ if(m_PlayingFile)
+ return length;
+
+#ifdef ENABLE_SUSPEND
+ if(m_suspended || (xc.inactivity_timer > 0 &&
+ m_inactivityTimer > xc.inactivity_timer)) {
+ if(m_liveMode) {
+ return length;
+ }
+ }
+#endif
+
+ bool isMpeg1 = false;
+
+ int len = pes_packet_len(buf, length, isMpeg1);
+ if(len>0 && len != length)
+ LOGMSG("cXinelibDevice::PlayAny: invalid data !");
+
+ // strip timestamps in trick speed modes
+ if(m_SkipAudio || m_TrickSpeed > 0) {
+ if(!m_SkipAudio) {
+
+#ifdef TEST_TRICKSPEEDS
+#warning Experimental trickspeed mode handling included !
+ // TODO: re-gen pts or signal pts+trickspeed for udp scheduler
+ bool Video = false, Audio = false;
+ uchar PictureType = NO_PICTURE;
+ int64_t pts = pes_extract_pts(buf, length, Audio, Video);
+ if(m_TrickSpeedPts <= 0LL) {
+ if(pts>0 && Video) {
+ m_TrickSpeedPts = pts;
+ if(ScanVideoPacket(buf, length, PictureType) > 0)
+ ;
+ LOGMSG("TrickSpeed: VIDEO PTS %lld (%s)", pts,
+ PictureTypeStr(PictureType));
+ }
+ } else if(Audio) {
+ LOGMSG("TrickSpeed: AUDIO PTS %lld", pts);
+ } else if(pts > 0LL) {
+ if(ScanVideoPacket(buf, length, PictureType) > 0)
+ ;
+ LOGMSG("TrickSpeed: VIDEO PTS DIFF %lld (%s)", pts - m_TrickSpeedPts,
+ PictureTypeStr(PictureType));
+ //m_TrickSpeedPts += (int64_t)(40*90 * m_TrickSpeed); /* 40ms * 90kHz */
+ //pes_change_pts((uchar *)buf, length);
+ }
+ pes_strip_pts((uchar*)buf, length);
+
+#else
+ pes_strip_pts((uchar*)buf, length);
+#endif
+ } else {
+ pes_strip_pts((uchar*)buf, length);
+ }
+ }
+
+ if(m_local) {
+ length = (isMpeg1 ? m_local->Play_Mpeg1_PES(buf,length) :
+ m_local->Play_PES(buf,length));
+ }
+ if(m_server && length > 0) {
+ int length2 = isMpeg1 ? m_server->Play_Mpeg1_PES(buf, length) :
+ m_server->Play_PES(buf, length);
+ if(!m_local)
+ return length2;
+ }
+
+ return length;
+}
+
+int cXinelibDevice::PlayVideo(const uchar *buf, int length)
+{
+ TRACEF("cXinelibDevice::PlayVideo");
+
+ if(m_RadioStream) {
+ m_RadioStream = false;
+ m_AudioCount = 0;
+ ForEach(m_clients, &cXinelibThread::SetNoVideo, m_RadioStream);
+ }
+
+#ifdef START_IFRAME
+ // Start with I-frame if stream has video
+ if(m_StreamStart) {
+ // wait for first I-frame
+ uchar pictureType;
+ if( ScanVideoPacket(buf, length, /*0,*/pictureType) > 0 &&
+ pictureType == I_FRAME) {
+ m_StreamStart = false;
+ } else {
+ return length;
+ }
+ }
+#else
+ m_StreamStart = false;
+#endif
+
+#ifdef DEBUG_SWITCHING_TIME
+ if(switchtimeOff && switchtimeOn) {
+ uchar pictureType;
+ if( ScanVideoPacket(buf, length, /*0,*/pictureType) > 0 &&
+ pictureType == I_FRAME) {
+ if(!switchingIframe) {
+ int64_t now = cTimeMs::Now();
+ switchingIframe = true;
+ LOGMSG("Channel switch: off -> on %lld ms, on -> 1. I-frame %lld ms",
+ switchtimeOn-switchtimeOff, now-switchtimeOn);
+ } else {
+ int64_t now = cTimeMs::Now();
+ LOGMSG("Channel switch: on -> 2. I-frame %lld ms, Total %lld ms",
+ now-switchtimeOn, now-switchtimeOff);
+ switchtimeOff = 0LL;
+ switchtimeOn = 0LL;
+ switchingIframe = false;
+ }
+ }
+ }
+#endif
+
+ return PlayAny(buf, length);
+}
+
+void cXinelibDevice::StillPicture(const uchar *Data, int Length)
+{
+ TRACEF("cXinelibDevice::StillPicture");
+
+ bool isPes = (!Data[0] && !Data[1] && Data[2] == 0x01 &&
+ (Data[3] & 0xF0) == 0xE0);
+ bool isMpeg1 = isPes && ((Data[6] & 0xC0) != 0x80);
+ int i;
+
+ if(m_PlayingFile)
+ return;
+
+ TRACE("cXinelibDevice::StillPicture: isPes = "<<isPes
+ <<", isMpeg1 = "<<isMpeg1);
+
+ ForEach(m_clients, &cXinelibThread::Clear);
+ ForEach(m_clients, &cXinelibThread::SetNoVideo, false);
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
+ ForEach(m_clients, &cXinelibThread::SetStillMode, true);
+ ForEach(m_clients, &cXinelibThread::TrickSpeed, 1);
+
+ m_TrickSpeed = -1; // to make Poll work ...
+ m_SkipAudio = 1; // enables audio and pts stripping
+
+ for(i=0; i<STILLPICTURE_REPEAT_COUNT; i++)
+ if(isMpeg1) {
+ ForEach(m_clients, &cXinelibThread::Play_Mpeg1_PES, Data, Length,
+ &mmin<int>, Length);
+ } else if(isPes) {
+ /*cDevice::*/PlayPes(Data, Length, m_SkipAudio);
+ } else {
+ ForEach(m_clients, &cXinelibThread::Play_Mpeg2_ES,
+ Data, Length, VIDEO_STREAM,
+ &mand<bool>, true);
+ }
+
+ ForEach(m_clients, &cXinelibThread::Play_Mpeg2_ES,
+ Data, 0, VIDEO_STREAM,
+ &mand<bool>, true);
+
+#if 0
+ if(m_server)
+ for(i=0; i<5; i++)
+ if(m_server->Flush(50))
+ break;
+ else
+ LOGMSG("cXinelibDevice::StillPicture: retry server flush (%d)", i+1);
+#endif
+ m_TrickSpeed = 0;
+ m_SkipAudio = 0;
+}
+
+int cXinelibDevice::PlayAudio(const uchar *buf, int length, uchar Id)
+{
+ TRACEF("cXinelibDevice::PlayAudio");
+
+#ifdef SKIP_AC3_AUDIO
+ // skip AC3 audio
+ if(((unsigned char *)buf)[3] == PRIVATE_STREAM1) {
+ TRACE("cXinelibDevice::PlayVideo: PRIVATE_STREAM1 discarded");
+ return length;
+ }
+#endif
+
+ // strip audio in trick speed modes
+ if(m_SkipAudio || m_TrickSpeed > 0)
+ return length;
+
+ if(m_RadioStream) {
+ if(m_AudioCount) {
+ m_AudioCount--;
+ if(!m_AudioCount)
+ ForEach(m_clients, &cXinelibThread::SetNoVideo, m_RadioStream);
+ }
+ }
+
+ return PlayAny(buf, length);
+}
+
+int cXinelibDevice::PlaySpu(const uchar *buf, int length, uchar Id)
+{
+ TRACEF("cXinelibDevice::PlaySpu");
+
+#ifdef SKIP_DVDSPU
+ return length;
+#else
+ if(((unsigned char *)buf)[3] == PRIVATE_STREAM1) {
+
+ if(!m_spuPresent) {
+ TRACE("cXinelibDevice::PlaySpu first DVD SPU frame");
+ Skins.QueueMessage(mtInfo,"DVD SPU");
+ m_spuPresent = true;
+ }
+ }
+
+ //
+ // TODO: channel must be selectable
+ //
+ // use: cXinelibThread::SpuStreamChanged(int StreamId);
+ //
+
+ return PlayAny(buf, length);
+#endif
+}
+
+void cXinelibDevice::SetVolumeDevice(int Volume)
+{
+ TRACEF("cXinelibDevice::SetVolumeDevice");
+
+ ACTIVITY
+ ForEach(m_clients, &cXinelibThread::SetVolume, Volume);
+}
+
+void cXinelibDevice::SetAudioTrackDevice(eTrackType Type)
+{
+ TRACEF("cXinelibDevice::SetAudioTrackDevice");
+
+ LOGDBG("SetAudioTrackDevice(%d)", (int)Type);
+ if(IS_DOLBY_TRACK(Type))
+ ForEach(m_clients, &cXinelibThread::AudioStreamChanged,
+ true, (int)(Type - ttDolbyFirst));
+ if(IS_AUDIO_TRACK(Type))
+ ForEach(m_clients, &cXinelibThread::AudioStreamChanged,
+ false, AUDIO_STREAM + (int)(Type - ttAudioFirst));
+}
+
+void cXinelibDevice::SetAudioChannelDevice(int AudioChannel)
+{
+ TRACEF("cXinelibDevice::SetAudioChannelDevice");
+
+ LOGDBG("SetAudioChannelDevice(%d)", (int)AudioChannel);
+ m_AudioChannel = AudioChannel;
+ //
+ // TODO
+ //
+ // - stereo, left only, right only
+ //
+}
+
+void cXinelibDevice::SetDigitalAudioDevice(bool On)
+{
+ TRACEF("cXinelibDevice::SetDigitalAudioDevice");
+
+ LOGDBG("SeDigitalAudioDevice(%s)", On ? "on" : "off");
+ //
+ // should we do something here ???
+ //
+}
+
+void cXinelibDevice::SetVideoFormat(bool VideoFormat16_9)
+{
+ TRACEF("cXinelibDevice::SetVideoFormat");
+
+ LOGDBG("SetVideoFormat(%s)", VideoFormat16_9 ? "16:9" : "4:3");
+ ACTIVITY
+ cDevice::SetVideoFormat(VideoFormat16_9);
+
+ //
+ // TODO
+ //
+#if 0
+ if(xc.aspect != ASPECT_AUTO &&
+ xc.aspect != ASPECT_DEFAULT) {
+ if(VideoFormat16_9)
+ xc.aspect = ASPECT_16_9;
+ else if(xc.aspect == ASPECT_16_9)
+ xc.aspect = ASPECT_4_3;
+ ConfigureDecoder(,,,xc.aspect,,,);
+ }
+#endif
+}
+
+void cXinelibDevice::SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat)
+{
+ TRACEF("cXinelibDevice::SetVideoDisplayFormat");
+
+ LOGDBG("SetVideoDisplayFormat(%d)", VideoDisplayFormat);
+ cDevice::SetVideoDisplayFormat(VideoDisplayFormat);
+ //
+ // TODO
+ //
+ // - set normal, pan&scan, letterbox (only for 4:3?)
+ //
+#if 0
+ if(xc.aspect != ASPECT_AUTO &&
+ xc.aspect != ASPECT_DEFAULT) {
+ switch(VideoDisplayFormat) {
+ case vdfPanAndScan:
+ xc.aspect = ASPECT_PAN_SCAN;
+ break;
+ case vdfLetterBox:
+ xc.aspect = ASPECT_4_3; /* borders are added automatically if needed */
+ break;
+ case vdfCenterCutOut:
+ xc.aspect = ASPECT_CENTER_CUT_OUT;
+ break;
+ }
+ ConfigureDecoder(,,,xc.aspect,,,);
+ }
+#endif
+}
+
+eVideoSystem cXinelibDevice::GetVideoSystem(void)
+{
+ TRACEF("cXinelibDevice::GetVideoSystem");
+ return cDevice::GetVideoSystem();
+}
+
+bool cXinelibDevice::Poll(cPoller &Poller, int TimeoutMs)
+{
+ TRACEF("cXinelibDevice::Poll");
+
+ if(m_PlayingFile)
+ return true;
+
+ if(m_TrickSpeed == 0) {
+ cCondWait::SleepMs(TimeoutMs);
+ return Poller.Poll(0);
+ }
+
+ if(!m_local && !m_server) {
+ /* nothing to do... why do I exist ... ? */
+ cCondWait::SleepMs(TimeoutMs);
+ return Poller.Poll(0);
+ }
+
+ bool result = true;
+
+ if(m_local)
+ result = result && m_local->Poll(Poller, TimeoutMs);
+ if(m_server)
+ result = result && m_server->Poll(Poller, TimeoutMs);
+
+ return result /*|| Poller.Poll(0)*/;
+}
+
+bool cXinelibDevice::Flush(int TimeoutMs)
+{
+ TRACEF("cXinelibDevice::Flush");
+
+ if(m_TrickSpeed == 0) {
+ ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
+ TrickSpeed(-1);
+ }
+
+ bool r = ForEach(m_clients, &cXinelibThread::Flush, TimeoutMs,
+ &mand<bool>, true);
+
+ return r;
+}
+
+#if 0
+//
+// TODO
+// - forward spu's directly to Xine
+//
+class cXineSpuDecoder : public cDvbSpuDecoder
+{
+ private:
+ cSpuDecoder::eScaleMode scaleMode;
+ cXinelibDevice *m_Device;
+
+ public:
+ cXineSpuDecoder(cXinelibDevice *dev) {
+ scaleMode = eSpuNormal;
+ m_Device = dev;
+ }
+ virtual ~cXineSpuDecoder() {};
+
+ virtual int setTime(uint32_t pts) { return 1; }
+
+ cSpuDecoder::eScaleMode getScaleMode(void) { return scaleMode; }
+ virtual void setScaleMode(cSpuDecoder::eScaleMode ScaleMode)
+ { scaleMode = ScaleMode; }
+ virtual void setPalette(uint32_t * pal) {};
+ virtual void setHighlight(uint16_t sx, uint16_t sy,
+ uint16_t ex, uint16_t ey,
+ uint32_t palette) {};
+ virtual void clearHighlight(void) {};
+ virtual void Empty(void) {};
+ virtual void Hide(void) {};
+ virtual void Draw(void) {};
+ virtual bool IsVisible(void) { return true; }
+ virtual void processSPU(uint32_t pts, uint8_t * buf,
+ bool AllowedShow = true);
+};
+
+#define CMD_SPU_MENU 0x00
+#define CMD_SPU_SHOW 0x01
+#define CMD_SPU_HIDE 0x02
+#define CMD_SPU_SET_PALETTE 0x03
+#define CMD_SPU_SET_ALPHA 0x04
+#define CMD_SPU_SET_SIZE 0x05
+#define CMD_SPU_SET_PXD_OFFSET 0x06
+#define CMD_SPU_CHG_COLCON 0x07
+#define CMD_SPU_EOF 0xff
+
+#define spuU32(i) ((spu[i] << 8) + spu[i+1])
+
+void cXineSpuDecoder::processSPU(uint32_t pts, uint8_t * buf, bool AllowedShow)
+{
+ uchar buf2[65536+8] = {0, 0, 1, PRIVATE_STREAM1, 0, 0, 0x80, 0x80, 5};
+ int len = ((buf[0] << 8) | buf[1]);
+
+ if(len+8 < 0xffff) {
+ buf2[4] = ((len+8)<<8) & 0xFF;
+ buf2[5] = ((len+8)) & 0xFF;
+ } else {
+ // should be able to handle this (but only internally ...)
+ LOGMSG("cXineSpuDecoder: SPU bigger than PES packet !");
+ buf2[4] = 0xff;
+ buf2[5] = 0xff;
+ }
+ buf2[9] = ((pts>>29) & 0x0E) | 0x21;
+ buf2[10] = (pts>>22) & 0xFF;
+ buf2[11] = (pts>>14) & 0xFE;
+ buf2[12] = (pts>>7) & 0xFF;
+ buf2[13] = (pts<<1) & 0xFE;
+
+ memcpy(buf2+14, buf, len);
+
+ m_Device->PlaySpu(buf, len+14, 0);
+}
+#endif
+
+cSpuDecoder *cXinelibDevice::GetSpuDecoder(void)
+{
+ TRACEF("cXinelibDevice::GetSpuDecoder");
+ if (!m_spuDecoder && IsPrimaryDevice())
+ //
+ // TODO
+ //
+ // - use own derived SpuDecoder with special cXinelibOsd
+ // -> always visible
+ //
+
+#if 1
+ m_spuDecoder = new cDvbSpuDecoder();
+#else
+#warning NON-FUNCTIONAL SPU DECODER SELECTED !!!
+ m_spuDecoder = new cXineSpuDecoder(this);
+#endif
+ return m_spuDecoder;
+}
+
+int64_t cXinelibDevice::GetSTC(void)
+{
+ TRACEF("cXinelibDevice::GetSTC");
+
+ if(m_local)
+ return m_local->GetSTC();
+ if(m_server)
+ return m_server->GetSTC();
+ return cDevice::GetSTC();
+}
+
+
+#if VDRVERSNUM < 10338
+
+bool cXinelibDevice::GrabImage(const char *FileName, bool Jpeg,
+ int Quality, int SizeX, int SizeY)
+{
+ uchar *Data = NULL;
+ int Size = 0;
+ TRACEF("cXinelibDevice::GrabImage");
+
+ ACTIVITY
+ if(m_local)
+ Data = m_local->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);
+ if(!Data && m_server)
+ Data = m_local->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);
+
+ if(Data) {
+ FILE *fp = fopen(FileName, "wb");
+ if(fp) {
+ fwrite(Data, Size, 1, fp);
+ fclose(fp);
+ free(Data);
+ return true;
+ }
+ LOGERR("Grab: Can't open %s", FileName);
+ free(Data);
+ } else {
+ LOGMSG("Grab to %s failed", FileName);
+ }
+ return false;
+}
+
+#else
+
+uchar *cXinelibDevice::GrabImage(int &Size, bool Jpeg,
+ int Quality, int SizeX, int SizeY)
+{
+ TRACEF("cXinelibDevice::GrabImage");
+
+ ACTIVITY
+ if(m_local)
+ return m_local->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);
+ if(m_server)
+ return m_local->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);
+
+ return NULL;
+}
+
+#endif
+
+
+#if 0
+// override cDevice to get DVD SPUs
+int cXinelibDevice::PlayPesPacket(const uchar *Data, int Length,
+ bool VideoOnly)
+{
+#ifndef SKIP_DVDSPU
+ switch (Data[3]) {
+ case 0xBD: { // private stream 1
+ int PayloadOffset = Data[8] + 9;
+ uchar SubStreamId = Data[PayloadOffset];
+ uchar SubStreamType = SubStreamId & 0xF0;
+ uchar SubStreamIndex = SubStreamId & 0x1F;
+ switch (SubStreamType) {
+ case 0x20: // SPU
+ case 0x30: // SPU
+ return PlaySpu(Data, Length, SubStreamIndex);
+ break;
+ default:
+ ;
+ }
+ }
+ default:
+ ;
+ }
+#endif
+ return cDevice::PlayPesPacket(Data, Length, VideoOnly);
+}
+#endif
diff --git a/dummy_player.c b/dummy_player.c
new file mode 100644
index 00000000..eb235539
--- /dev/null
+++ b/dummy_player.c
@@ -0,0 +1,110 @@
+/*
+ * dummy_player.c: Player that does nothing (saves CPU time)
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: dummy_player.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <vdr/status.h>
+
+#include "dummy_player.h"
+#include "tools/timer.h"
+
+#define STILLPICTURE_INTERVAL (5*1000) // 5 sec
+
+//
+// cDummyPlayerControl
+//
+
+extern unsigned char v_mpg_vdrlogo[]; // vdrlogo_720x576.mpg.c
+extern int v_mpg_vdrlogo_length; // vdrlogo_720x576.mpg.c
+//extern unsigned char v_mpg_nosignal[];// nosignal_720x576.mpg.c
+//extern int v_mpg_nosignal_length; // nosignal_720x576.mpg.c
+//extern unsigned char v_mpg_black[]; // black_720x576.mpg.c
+//extern int v_mpg_black_length; // black_720x576.mpg.c
+
+class cDummyPlayer : public cPlayer {
+ protected:
+ virtual void Activate(bool On)
+ {
+ if(On) {
+ TimerHandler();
+ CreateTimerEvent(this, &cDummyPlayer::TimerHandler, STILLPICTURE_INTERVAL);
+ } else {
+ CancelTimerEvents(this);
+ }
+ }
+ bool TimerHandler(void)
+ {
+ DeviceStillPicture(v_mpg_vdrlogo, v_mpg_vdrlogo_length);
+ //DeviceStillPicture(v_mpg_nosignal, v_mpg_nosignal_length);
+ //DeviceStillPicture(v_mpg_black, v_mpg_black_length);
+ return true;
+ }
+
+ public:
+ cDummyPlayer(void) {};
+ virtual ~cDummyPlayer()
+ {
+ Activate(false);
+ Detach();
+ }
+};
+
+//
+// cDummyPlayerControl
+//
+
+cDummyPlayer *cDummyPlayerControl::m_Player = NULL;
+cMutex cDummyPlayerControl::m_Lock;
+
+cDummyPlayerControl::cDummyPlayerControl(void) :
+ cControl(OpenPlayer())
+{
+#if VDRVERSNUM < 10338
+ cStatus::MsgReplaying(this, "none");
+#else
+ cStatus::MsgReplaying(this, "none", NULL, true);
+#endif
+}
+
+cDummyPlayerControl::~cDummyPlayerControl()
+{
+#if VDRVERSNUM < 10338
+ cStatus::MsgReplaying(this, NULL);
+#else
+ cStatus::MsgReplaying(this, NULL, NULL, false);
+#endif
+ Close();
+}
+
+cDummyPlayer *cDummyPlayerControl::OpenPlayer(void)
+{
+ m_Lock.Lock();
+ if(!m_Player)
+ m_Player = new cDummyPlayer;
+ m_Lock.Unlock();
+ return m_Player;
+}
+
+void cDummyPlayerControl::Close(void)
+{
+ m_Lock.Lock();
+ if(m_Player)
+ delete m_Player;
+ m_Player = NULL;
+ m_Lock.Unlock();
+}
+
+eOSState cDummyPlayerControl::ProcessKey(eKeys Key)
+{
+ if(!ISMODELESSKEY(Key) || Key == kBack || Key == kStop) {
+ Close();
+ return osEnd;
+ }
+ return osContinue;
+}
+
diff --git a/equalizer.c b/equalizer.c
new file mode 100644
index 00000000..ebadd34e
--- /dev/null
+++ b/equalizer.c
@@ -0,0 +1,149 @@
+/*
+ * equalizer.c: audio equalizer OSD control
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: equalizer.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <vdr/config.h>
+
+#if VDRVERSNUM < 10307
+# error VDR < 1.3.7 not supported
+#endif
+
+#include "config.h"
+#include "device.h"
+#include "equalizer.h"
+
+cEqualizer::cEqualizer() : cOsdObject()
+{
+ m_Values = new int[AUDIO_EQ_count];
+ memcpy(m_Values, xc.audio_equalizer, sizeof(xc.audio_equalizer));
+ m_Osd = NULL;
+ m_Current = 0;
+}
+
+cEqualizer::~cEqualizer()
+{
+ delete m_Osd;
+ delete m_Values;
+}
+
+#define OSD_W (220)
+#define OSD_H (220)
+#define OSD_X (720-50-OSD_W)
+#define OSD_Y (576-50-OSD_H)
+/* dvbdevice requires bpp*width to be n*8 */
+
+#define ADJUST_MIN (-100)
+#define ADJUST_MAX (100)
+#define ADJUST_STEP (5)
+
+void cEqualizer::Show()
+{
+ tArea areas [] = { {0, 0, OSD_W - 1, OSD_H - 1, 4} };
+
+ m_Osd = cOsdProvider::NewOsd(OSD_X, OSD_Y);
+
+ if(m_Osd) {
+ if (m_Osd->CanHandleAreas(areas, sizeof(areas) / sizeof(tArea) ) == oeOk) {
+ m_Osd->SetAreas(areas, sizeof(areas) / sizeof(tArea));
+ m_Osd->Flush();
+ DrawBackground();
+ DrawBar(0,true);
+ for(int i=1; i<AUDIO_EQ_count; i++)
+ DrawBar(i);
+ }
+ }
+}
+
+eOSState cEqualizer::ProcessKey(eKeys key)
+{
+ eOSState state = cOsdObject::ProcessKey(key);
+ if (state == osUnknown) {
+ switch (key & ~k_Repeat) {
+ case kDown:
+ m_Values[m_Current] -= ADJUST_STEP;
+ if(m_Values[m_Current] < ADJUST_MIN)
+ m_Values[m_Current] = ADJUST_MIN;
+ DrawBar(m_Current,true);
+ cXinelibDevice::Instance().ConfigurePostprocessing(xc.deinterlace_method, xc.audio_delay, xc.audio_compression, m_Values, xc.audio_surround);
+ break;
+ case kUp:
+ m_Values[m_Current] += ADJUST_STEP;
+ if(m_Values[m_Current] > ADJUST_MAX)
+ m_Values[m_Current] = ADJUST_MAX;
+ DrawBar(m_Current,true);
+ cXinelibDevice::Instance().ConfigurePostprocessing(xc.deinterlace_method, xc.audio_delay, xc.audio_compression, m_Values, xc.audio_surround);
+ break;
+ case kLeft:
+ if(m_Current>0) {
+ DrawBar(m_Current);
+ m_Current--;
+ DrawBar(m_Current, true);
+ }
+ break;
+ case kRight:
+ if(m_Current+1 < AUDIO_EQ_count) {
+ DrawBar(m_Current);
+ m_Current++;
+ DrawBar(m_Current, true);
+ }
+ break;
+ case kBack:
+ cXinelibDevice::Instance().ConfigurePostprocessing(xc.deinterlace_method, xc.audio_delay, xc.audio_compression, xc.audio_equalizer, xc.audio_surround);
+ return osEnd;
+ case kOk:
+ memcpy(xc.audio_equalizer, m_Values, sizeof(xc.audio_equalizer));
+ return osEnd;
+ }
+ }
+
+ return state;
+}
+
+#define COL_BORDER 0xffb0b0b0
+#define COL_BG 0x7f7f7f7f
+#define COL_BAR 0xff000000
+#define COL_BAR_SEL 0xffff0000
+#define COL_BAR_ON 0xff00FF00
+#define COL_BAR_OFF 0xff000000
+#define COL_BAR_BORDER 0xff7f7f7f
+
+void cEqualizer::DrawBackground()
+{
+ // border
+ m_Osd->DrawRectangle(0, 0, OSD_W - 1, OSD_H - 1, COL_BORDER);
+ m_Osd->DrawRectangle(1, 1, OSD_W - 2, OSD_H - 2, COL_BORDER);
+ // background
+ m_Osd->DrawRectangle(2, 2, OSD_W - 3, OSD_H - 3, COL_BG);
+ // line
+ m_Osd->DrawRectangle(5, 10+100-1, OSD_W-6, 10+100, COL_BAR);
+ // commit
+ m_Osd->Flush();
+}
+
+void cEqualizer::DrawBar(int Index, bool Selected)
+{
+ // bar
+ if(Selected)
+ m_Osd->DrawRectangle(10+20*Index, 10, 10+20*Index+7, OSD_H - 10, COL_BAR_SEL);
+ else
+ m_Osd->DrawRectangle(10+20*Index, 10, 10+20*Index+7, OSD_H - 10, COL_BAR);
+ // off
+ m_Osd->DrawRectangle(12+20*Index, 10, 10+20*Index+5, OSD_H - 10, COL_BAR_OFF);
+ // on
+ if(m_Values[Index]>0)
+ m_Osd->DrawRectangle(12+20*Index, 10+100-m_Values[Index], 10+20*Index+5, 10+100, COL_BAR_ON);
+ else
+ m_Osd->DrawRectangle(12+20*Index, 10+100, 10+20*Index+5, 10+100-m_Values[Index], COL_BAR_ON);
+ // line
+ m_Osd->DrawRectangle(12+20*Index, 10+100-1, 10+20*Index+5, 10+100, COL_BAR_ON);
+
+ m_Osd->Flush();
+}
+
+
diff --git a/frontend.c b/frontend.c
new file mode 100644
index 00000000..0dc42273
--- /dev/null
+++ b/frontend.c
@@ -0,0 +1,614 @@
+/*
+ * frontend.c:
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: frontend.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+#include <pthread.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <vdr/config.h>
+#include <vdr/tools.h>
+#include <vdr/plugin.h>
+
+#include "logdefs.h"
+#include "config.h"
+#include "frontend.h"
+
+#include "tools/pes.h"
+#include "tools/general_remote.h"
+
+//#define LOG_CONTROL_MESSAGES
+//#define XINELIBOUTPUT_LOG_KEYS
+
+//----------------------------- cXinelibThread --------------------------------
+
+//
+// keyboard control handler
+//
+
+/*static*/
+void cXinelibThread::KeypressHandler(char *keymap, char *key,
+ bool repeat, bool release)
+{
+#ifdef XINELIBOUTPUT_LOG_KEYS
+ static FILE *flog = fopen("/video/keys.log","w");
+ fprintf(flog,"KEY %s %s %d %d\n",keymap,key,repeat,release);fflush(flog);
+#endif
+
+ TRACE("keypress_handler: " << (keymap?keymap:"") << " " << key);
+
+ if(!key)
+ return;
+
+ if(keymap) {
+ cRemote *item = Remotes.First();
+ while(item) {
+ if(!strcmp(item->Name(), keymap)) {
+ // dirty... but only way to support learning ...
+ ((cGeneralRemote*)item)->Put(key, repeat, release);
+ return;
+ }
+ item = Remotes.Next(item);
+ }
+ cGeneralRemote *r = new cGeneralRemote(keymap);
+ if(*key)
+ r->Put(key, repeat, release);
+ } else {
+ cRemote::Put(cKey::FromString(key));
+ }
+}
+
+cXinelibThread::cXinelibThread(const char *Description) : cThread(Description)
+{
+ TRACEF("cXinelibThread::cXinelibThread");
+
+ m_bStopThread = false;
+ m_bReady = false;
+ m_bIsFinished = false;
+ m_bNoVideo = true;
+ m_bLiveMode = false;
+ m_StreamPos = 0;
+ m_bEndOfStreamReached = false;
+ m_bPlayingFile = false;
+ m_FileName = NULL;
+}
+
+cXinelibThread::~cXinelibThread()
+{
+ TRACEF("cXinelibThread::~cXinelibThread");
+
+ if(Active())
+ Cancel();
+ if(m_FileName)
+ free(m_FileName);
+}
+
+//
+// Thread control
+//
+
+void cXinelibThread::Start(void)
+{
+ TRACEF("cXinelibThread::Start");
+
+ cThread::Start();
+}
+
+void cXinelibThread::Stop(void)
+{
+ TRACEF("cXinelibThread::Stop");
+
+ SetStopSignal();
+
+ //if(Active())
+ Cancel(5);
+}
+
+void cXinelibThread::SetStopSignal(void)
+{
+ TRACEF("cXinelibThread::SetStopSignal");
+
+ LOCK_THREAD;
+ m_bStopThread = true;
+}
+
+bool cXinelibThread::GetStopSignal(void)
+{
+ TRACEF("cXinelibThread::GetStopSignal");
+
+ LOCK_THREAD;
+ return m_bStopThread;
+}
+
+bool cXinelibThread::IsReady(void)
+{
+ LOCK_THREAD;
+ return m_bReady;
+}
+
+bool cXinelibThread::IsFinished(void)
+{
+ LOCK_THREAD;
+ return m_bIsFinished;
+}
+
+//
+// Playback control
+//
+
+void cXinelibThread::SetVolume(int NewVolume)
+{
+ Xine_Control("VOLUME", NewVolume * 100 / 255);
+}
+
+void cXinelibThread::TrickSpeed(int Speed)
+{
+ TRACEF("cXinelibThread::TrickSpeed");
+
+ Xine_Control("TRICKSPEED", Speed);
+}
+
+void cXinelibThread::SetLiveMode(bool LiveModeOn)
+{
+ TRACEF("cXinelibThread::SetLiveMode");
+
+ Lock();
+ if(m_bLiveMode == LiveModeOn) {
+ Unlock();
+ return;
+ }
+ m_bLiveMode = LiveModeOn;
+ Unlock();
+
+ Xine_Control("LIVE", m_bLiveMode ? 1 : 0);
+ Xine_Sync();
+}
+
+void cXinelibThread::SetStillMode(bool StillModeOn)
+{
+ TRACEF("cXinelibThread::SetStillMode");
+ Xine_Control("STILL", StillModeOn ? 1 : 0);
+ Xine_Sync();
+}
+
+void cXinelibThread::SetNoVideo(bool bVal)
+{
+ TRACEF("cXinelibThread::SetNoVideo");
+
+ Lock();
+ if(m_bNoVideo == bVal) {
+ Unlock();
+ return;
+ }
+ m_bNoVideo = bVal;
+ Unlock();
+
+ Xine_Control("NOVIDEO", m_bNoVideo ? 1 : 0);
+
+ if(m_bNoVideo && strcmp(xc.audio_visualization, "none")) {
+ ConfigurePostprocessing(xc.audio_visualization, true, NULL);
+ } else {
+ ConfigurePostprocessing("AudioVisualization", false, NULL);
+ }
+}
+
+void cXinelibThread::AudioStreamChanged(bool ac3, int StreamId)
+{
+ TRACEF("cXinelibThread::AudioStreamChanged");
+ if(ac3)
+ Xine_Control("NEWAUDIOSTREAM AC3");
+ else
+ Xine_Control("NEWAUDIOSTREAM", StreamId);
+}
+
+void cXinelibThread::SpuStreamChanged(int StreamId)
+{
+ TRACEF("cXinelibThread::SpuStreamChanged");
+ Xine_Control("NEWSPUSTREAM", StreamId);
+}
+
+void cXinelibThread::Clear(void)
+{
+ TRACEF("cXinelibThread::Clear");
+
+ Lock();
+ int64_t tmp = m_StreamPos;
+ Unlock();
+ Xine_Control("DISCARD", tmp);
+}
+
+bool cXinelibThread::Flush(int TimeoutMs)
+{
+ TRACEF("cXinelibThread::Flush");
+
+ return Xine_Control("FLUSH", TimeoutMs) <= 0;
+}
+
+bool cXinelibThread::Poll(cPoller& Poller, int TimeoutMs)
+{
+ TRACEF("cXinelibThread::Poll");
+
+ int n = Xine_Control("POLL", TimeoutMs);
+
+ if(n>0)
+ return true;
+
+ return false; // Poller.Poll(TimeoutMs);
+}
+
+//
+// Data transfer
+//
+
+int cXinelibThread::Play_PES(const uchar *data, int len)
+{
+ Lock();
+ m_StreamPos += len;
+ /*m_bEndOfStreamReached = false;*/
+ Unlock();
+ return len;
+}
+
+//
+// Stream conversions
+//
+
+// Convert MPEG1 PES headers to MPEG2 PES headers
+
+int cXinelibThread::Play_Mpeg1_PES(const uchar *data1, int len)
+{
+ if(!data1[0] && !data1[1] && data1[2] == 0x01 && len>7 && /* header sync bytes */
+ ( VIDEO_STREAM == (data1[3] & ~VIDEO_STREAM_MASK) || /* video stream */
+ AUDIO_STREAM == (data1[3] & ~AUDIO_STREAM_MASK) || /* audio stream */
+ PRIVATE_STREAM1 == data1[3]) && /* private stream 1 */
+ ((data1[6] & 0xC0) != 0x80) && /* really mpeg1 pes */
+ (len == ((data1[4]<<8) | data1[5]) + 6)) { /* whole PES packet and nothing else */
+ uchar *data2 = new uchar[len+64];
+ int i1=0, i2=0, r=0;
+
+ data2[i2++]=data1[i1++]; // 00 (sync)
+ data2[i2++]=data1[i1++]; // 00 (sync)
+ data2[i2++]=data1[i1++]; // 01 (sync)
+ data2[i2++]=data1[i1++]; // stream ID
+ data2[i2++]=data1[i1++]; // len hi
+ data2[i2++]=data1[i1++]; // len lo
+
+ // skip stuffing
+ while ((data1[i1] & 0x80) == 0x80)
+ i1++;
+
+ if ((data1[i1] & 0xc0) == 0x40) {
+ // skip STD_buffer_scale, STD_buffer_size
+ i1 += 2;
+ }
+
+ if(len<i1+5) return len;
+
+ data2[i2++] = 0x80;
+
+ if ((data1[i1] & 0xf0) == 0x20) {
+ /* PTS */
+ data2[i2++] = 0x80;
+ data2[i2++] = 5;
+ data2[i2++] = data1[i1++] & 0x0E;
+ data2[i2++] = data1[i1++] & 0xFF;
+ data2[i2++] = data1[i1++] & 0xFE;
+ data2[i2++] = data1[i1++] & 0xFF;
+ data2[i2++] = data1[i1++] & 0xFE;
+ }
+ else if ((data1[i1] & 0xf0) == 0x30) {
+ /* PTS & DTS */
+ data2[i2++] = 0x80|0x40;
+ data2[i2++] = 10;
+ data2[i2++] = data1[i1++] & 0x0E;
+ data2[i2++] = data1[i1++] & 0xFF;
+ data2[i2++] = data1[i1++] & 0xFE;
+ data2[i2++] = data1[i1++] & 0xFF;
+ data2[i2++] = data1[i1++] & 0xFE;
+
+ data2[i2++] = data1[i1++] & 0x0E;
+ data2[i2++] = data1[i1++] & 0xFF;
+ data2[i2++] = data1[i1++] & 0xFE;
+ data2[i2++] = data1[i1++] & 0xFF;
+ data2[i2++] = data1[i1++] & 0xFE;
+ } else {
+ i1++;
+ data2[i2++] = 0; /* no pts, no dts */
+ data2[i2++] = 0; /* header len */
+ }
+
+ int newlen = ((data1[4]<<8) | data1[5]) + (i2-i1), loops=0;
+ data2[4] = ((newlen)&0xff00)>>8;
+ data2[5] = ((newlen)&0xff);
+ if(len-i1 > 0) {
+ memcpy(data2+i2, data1+i1, len-i1);
+ cPoller p;
+ while(!Poll(p,100) && loops++ < 10) {
+ LOGDBG("Play_Mpeg1_PES: poll failed");
+ }
+ r = Play_PES(data2,newlen+6);
+ }
+
+ delete data2;
+ return r==newlen+6 ? ((data1[4]<<8)|data1[5])+6 : 0;
+ }
+ return len; // nothing useful found ...
+}
+
+// Pack elementary MPEG stream to PES
+
+bool cXinelibThread::Play_Mpeg2_ES(const uchar *data, int len, int streamID)
+{
+ static uchar hdr[] = {0x00,0x00,0x01,0xe0, 0x00,0x00,0x80,0,0}; /* mpeg2 */
+ int todo = len, done = 0, hdrlen = 9/*sizeof(hdr)*/;
+ uchar *frame = new uchar[PES_CHUNK_SIZE+32];
+
+ hdr[3] = (uchar)streamID;
+ while(todo) {
+ int blocklen = todo;
+ if(blocklen > ((PES_CHUNK_SIZE - hdrlen) & 0xfffc))
+ blocklen = (PES_CHUNK_SIZE - hdrlen) & 0xfffc;
+ hdr[4] = ((blocklen+3)&0xff00)>>8;
+ hdr[5] = ((blocklen+3)&0xff);
+
+ memcpy(frame, hdr, hdrlen);
+ memcpy(frame+hdrlen, data+done, blocklen);
+
+ done += blocklen;
+ todo -= blocklen;
+
+ cPoller p;
+#if 1
+ Poll(p, 100);
+#else
+ int loops=0;
+ while(!Poll(p,100) && loops++ < 10) {
+ LOGDBG("Play_ES: Poll Failed");
+ }
+#endif
+
+ if(blocklen+hdrlen != Play_PES(frame,blocklen+hdrlen)) {
+ delete frame;
+ return false;
+ }
+ }
+
+ // append sequence end code to video
+ if((streamID & 0xF0) == 0xE0) {
+ static uchar seqend[] = {0x00,0x00,0x01,0xe0, 0x00,0x07,0x80,0x00,
+ 0x00,
+ 0x00,0x00,0x01,0xB7}; /* mpeg2 */
+#if 0
+ frame[0] = 0x00;
+ frame[1] = 0x00;
+ frame[2] = 0x01;
+ frame[3] = (uchar)streamID;
+ frame[4] = 0x00;
+ frame[5] = 0x07;
+ frame[6] = 0x80;
+ frame[7] = 0x00;
+ frame[8] = 0x00;
+ frame[9] = 0x00;
+ frame[10] = 0x00;
+ frame[11] = 0x01;
+ frame[12] = 0xB7;
+ Play_PES(frame, 13);
+#else
+ seqend[3] = (uchar)streamID;
+ Play_PES(seqend, 13);
+#endif
+ }
+
+ delete frame;
+ return true;
+}
+
+//
+// Built-in still images
+//
+
+bool cXinelibThread::QueueBlankDisplay(void)
+{
+ TRACEF("cXinelibThread::BlankDisplay");
+
+ extern unsigned char v_mpg_black[]; // black_720x576.c
+ extern int v_mpg_black_length;
+
+ return Play_Mpeg2_ES(v_mpg_black, v_mpg_black_length, VIDEO_STREAM);
+}
+
+bool cXinelibThread::BlankDisplay(void)
+{
+ TRACEF("cXinelibThread::BlankDisplay");
+
+ bool r = QueueBlankDisplay();
+ for(int i=0; i<5 && !Flush(100); i++)
+ ;
+ return r;
+}
+
+bool cXinelibThread::LogoDisplay(void)
+{
+ TRACEF("cXinelibThread::LogoDisplay");
+
+ extern unsigned char v_mpg_vdrlogo[]; // vdrlogo_720x576.c
+ extern int v_mpg_vdrlogo_length;
+
+ bool r = Play_Mpeg2_ES(v_mpg_vdrlogo, v_mpg_vdrlogo_length, VIDEO_STREAM);
+ for(int i=0; i<5 && !Flush(100); i++)
+ ;
+ return r;
+}
+
+bool cXinelibThread::NoSignalDisplay(void)
+{
+ TRACEF("cXinelibThread::NoSignalDisplay");
+
+ extern unsigned char v_mpg_nosignal[]; // nosignal_720x576.c
+ extern int v_mpg_nosignal_length;
+
+ bool r = Play_Mpeg2_ES(v_mpg_nosignal, v_mpg_nosignal_length, VIDEO_STREAM);
+ for(int i=0; i<5 && !Flush(100); i++)
+ ;
+ return r;
+}
+
+//
+// Xine Control
+//
+
+int cXinelibThread::Xine_Control(const char *cmd, int p1)
+{
+ char buf[128];
+ sprintf(buf, "%s %d", cmd, p1);
+ return Xine_Control(buf);
+}
+
+int cXinelibThread::Xine_Control(const char *cmd, int64_t p1)
+{
+ char buf[128];
+ sprintf(buf, "%s %lld", cmd, p1);
+ return Xine_Control(buf);
+}
+
+int cXinelibThread::Xine_Control(const char *cmd, char *p1)
+{
+ char buf[128];
+ sprintf(buf, "%s %s", cmd, p1);
+ return Xine_Control(buf);
+}
+
+bool cXinelibThread::PlayFile(const char *FileName, int Position,
+ bool LoopPlay)
+{
+ TRACEF("cXinelibThread::PlayFile");
+ char buf[2048];
+ m_bEndOfStreamReached = false;
+ sprintf(buf, "PLAYFILE %s %d %s\r\n",
+ LoopPlay ? "Loop" : "", Position, FileName ? FileName : "");
+ int result = PlayFileCtrl(buf);
+
+ if(!FileName || result != 0) {
+ m_bPlayingFile = false;
+ if(m_FileName)
+ free(m_FileName);
+ } else {
+ m_FileName = strdup(FileName);
+ m_bPlayingFile = true;
+ }
+
+ return (!GetStopSignal()) && (result==0);
+}
+
+
+//
+// Configuration
+//
+
+int cXinelibThread::ConfigureOSD(bool prescale_osd, bool unscaled_osd)
+{
+ char s[256];
+ sprintf(s, "OSDSCALING %d", prescale_osd);
+ if(!xc.prescale_osd_downscale)
+ strcat(s, " NoDownscale");
+ if(unscaled_osd)
+ strcat(s, " UnscaledAlways");
+ if(xc.unscaled_osd_opaque)
+ strcat(s, " UnscaledOpaque");
+ if(xc.unscaled_osd_lowresvideo)
+ strcat(s, " UnscaledLowRes");
+
+ return Xine_Control(s);
+}
+
+int cXinelibThread::ConfigurePostprocessing(char *deinterlace_method, int audio_delay,
+ int audio_compression, int *audio_equalizer,
+ int audio_surround)
+{
+ char tmp[255];
+ int r = true;
+
+ if(strcmp(deinterlace_method, "tvtime"))
+ r = ConfigurePostprocessing("tvtime", false, NULL) && r;
+
+ r = Xine_Control("DEINTERLACE", deinterlace_method) && r;
+ r = Xine_Control("AUDIODELAY", audio_delay) && r;
+ r = Xine_Control("AUDIOCOMPRESSION", audio_compression) && r;
+ r = Xine_Control("AUDIOSURROUND", audio_compression) && r;
+ sprintf(tmp,"EQUALIZER %d %d %d %d %d %d %d %d %d %d",
+ audio_equalizer[0],audio_equalizer[1],
+ audio_equalizer[2],audio_equalizer[3],
+ audio_equalizer[4],audio_equalizer[5],
+ audio_equalizer[6],audio_equalizer[7],
+ audio_equalizer[8],audio_equalizer[9]);
+ r = Xine_Control(tmp) && r;
+
+ if(m_bNoVideo && strcmp(xc.audio_visualization, "none")) {
+ //fe->post_open(fe, xc.audio_visualization, NULL);
+ r = ConfigurePostprocessing(xc.audio_visualization, true, NULL) && r;
+ } else {
+ //fe->post_close(fe, NULL, 0);
+ r = ConfigurePostprocessing("AudioVisualization", false, NULL) && r;
+ }
+
+ if(!strcmp(deinterlace_method, "tvtime"))
+ r = ConfigurePostprocessing("tvtime", true, xc.deinterlace_opts) && r;
+
+ return r;
+}
+
+int cXinelibThread::ConfigurePostprocessing(char *name, bool on, char *args)
+{
+ char tmp[1024];
+ if(!name) name = "0";
+ if(!args) args = "";
+ if(on) {
+ sprintf(tmp, "POST %s On %s", name, args);
+ return Xine_Control(tmp);
+ } else {
+ // 0 - audio vis.
+ // 1 - audio post
+ // 2 - video post
+ //return fe->post_close(fe, name, -1);
+ sprintf(tmp, "POST %s Off", name);
+ return Xine_Control(tmp);
+ }
+ return -1;
+}
+
+int cXinelibThread::ConfigureVideo(int hue, int saturation,
+ int brightness, int contrast)
+{
+ char cmd[128];
+ sprintf(cmd, "VIDEO_PROPERTIES %d %d %d %d",
+ hue, saturation, brightness, contrast);
+ return Xine_Control(cmd);
+}
+
+//
+// Playback files
+//
+
+bool cXinelibThread::EndOfStreamReached(void)
+{
+ LOCK_THREAD;
+ bool r = m_bEndOfStreamReached;
+ return r;
+}
+
+
diff --git a/frontend_local.c b/frontend_local.c
new file mode 100644
index 00000000..6c7b40e0
--- /dev/null
+++ b/frontend_local.c
@@ -0,0 +1,452 @@
+/*
+ * frontend_local.c:
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: frontend_local.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+#include <vdr/config.h>
+#include <vdr/tools.h>
+
+#include "logdefs.h"
+#include "config.h"
+
+#include "xine_frontend.h"
+
+#include "frontend_local.h"
+
+//------------------------------ cRwLockBlock ---------------------------------
+
+class cRwLockBlock
+{
+ private:
+ cRwLock& m_Lock;
+
+ public:
+ cRwLockBlock(cRwLock& lock, bool write) : m_Lock(lock)
+ { m_Lock.Lock(write);}
+
+ ~cRwLockBlock()
+ { m_Lock.Unlock(); }
+};
+
+#define LOCK_FE cRwLockBlock(m_feLock, false)
+#define LOCK_FE_WR cRwLockBlock(m_feLock, true)
+
+//----------------------------- cXinelibLocal --------------------------------
+
+cXinelibLocal::cXinelibLocal(const char *frontend_name) :
+ cXinelibThread("Local decoder/display (cXinelibThread)"), m_feLock(true)
+{
+ fe = NULL;
+ h_fe_lib = NULL;
+ m_bReconfigRequest = true;
+}
+
+cXinelibLocal::~cXinelibLocal()
+{
+ TRACEF("cXinelibLocal::~cXinelibLocal");
+
+ Stop();
+ if(fe) {
+ fe->fe_free(fe);
+ fe = NULL;
+ }
+ if(h_fe_lib) {
+ dlclose(h_fe_lib);
+ }
+}
+
+void cXinelibLocal::Stop(void)
+{
+ TRACEF("cXinelibLocal::Stop");
+
+ SetStopSignal();
+
+ if(1) {
+ LOCK_FE;
+ m_bReady=false;
+ if(fe)
+ fe->fe_interrupt(fe);
+ }
+
+ cXinelibThread::Stop();
+}
+
+//
+// Data transfer
+//
+
+int cXinelibLocal::Play_PES(const uchar *data, int len)
+{
+ TRACEF("cXinelibLocal::Play_PES");
+ LOCK_FE;
+ if(fe) {
+ //m_bLoopPlay = false;
+ int done = fe->xine_queue_pes_packet(fe, (char*)data, len);
+ if(done>0) {
+ Lock();
+ m_StreamPos += done;
+ Unlock();
+ } else {
+ cCondWait::SleepMs(5);
+ }
+ return done;
+ }
+
+ return 0;
+}
+
+void cXinelibLocal::OsdCmd(void *cmd)
+{
+ TRACEF("cXinelibLocal::OsdCmd");
+ if(cmd) {
+ LOCK_FE;
+ if(fe)
+ fe->xine_osd_command(fe, (struct osd_command_s*)cmd);
+ }
+}
+
+uchar *cXinelibLocal::GrabImage(int &Size, bool Jpeg,
+ int Quality, int SizeX,
+ int SizeY)
+{
+ uchar *data;
+ LOCK_FE;
+ if(fe && fe->grab)
+ if((data = (uchar*)fe->grab(fe, &Size, Jpeg, Quality, SizeX, SizeY)))
+ return data;
+ return NULL;
+}
+
+int64_t cXinelibLocal::GetSTC()
+{
+ TRACEF("cXinelibLocal::GetSTC");
+
+ int64_t pts = -1;
+ char buf[32] = {0};
+ strcpy(buf, "GETSTC\r\n");
+
+ LOCK_FE;
+ if(fe)
+ if(0 == fe->xine_control(fe, (char*)buf))
+ //if(*((int64_t *)buf) < MAX_SCR)
+ // if(*((int64_t *)buf) >= 0LL)
+ pts = *((int64_t *)buf);
+ return pts;
+}
+
+//
+// Configuration
+//
+
+void cXinelibLocal::ConfigureWindow(int fullscreen, int width, int height,
+ int modeswitch, char *modeline,
+ int aspect, int scale_video,
+ int field_order)
+{
+ LOCK_FE;
+ if(fe)
+ fe->fe_display_config(fe, width, height, fullscreen, modeswitch, modeline,
+ aspect, scale_video, field_order);
+}
+
+void cXinelibLocal::ConfigureDecoder(int pes_buffers, int priority)
+{
+ // needs xine restart
+ {
+ LOCK_FE;
+ xc.pes_buffers = pes_buffers;
+ xc.decoder_priority = priority;
+ if(!fe)
+ return;
+ m_bReady=false;
+ m_bReconfigRequest=true;
+ fe->fe_interrupt(fe);
+ }
+
+ while(!m_bReady && !GetStopSignal()) {
+ cCondWait::SleepMs(100);
+ pthread_yield();
+ }
+
+ cCondWait::SleepMs(100);
+}
+
+//
+// Xine control
+//
+
+int cXinelibLocal::Xine_Control(const char *cmd)
+{
+ TRACEF("cXinelibLocal::Xine_Control");
+ char buf[256];
+ sprintf(buf, "%s\r\n", cmd);
+ LOCK_FE;
+ if(fe)
+ return fe->xine_control(fe, (char*)buf);
+ return 0;
+}
+
+//
+// keyboard control handler (C callback)
+//
+
+extern "C" {
+ static void keypress_handler(char *keymap, char *key)
+ {
+ if(!xc.use_x_keyboard || !key) {
+ /* Only X11 key events came this way in local mode.
+ Keyboard is handled by vdr. */
+ LOGMSG("keypress_handler(%s): X11 Keyboard disabled in config", key);
+ return;
+ }
+ cXinelibThread::KeypressHandler(keymap, key, false, false);
+ }
+};
+
+//
+// Frontend loader
+//
+
+frontend_t *cXinelibLocal::load_frontend(const char *fe_name)
+{
+ Dl_info info;
+ struct stat statbuffer;
+ char libname[4096]="";
+ void *lib = NULL;
+ fe_creator_f *fe_creator = NULL;
+ static int my_marker = 0;
+
+ if(!dladdr((void *)&my_marker, &info)) {
+ LOGERR("Error searching plugin: dladdr() returned false (%s)",dlerror());
+ return NULL;
+ }
+ LOGDBG("xineliboutput: plugin file is %s", info.dli_fname);
+
+ int fe_ind = strstra(fe_name, xc.s_frontends, FRONTEND_NONE);
+ bool fe_try = false;
+ if(fe_ind == FRONTEND_NONE) {
+ LOGMSG("Front-end %s unknown!", fe_name);
+ fe_ind = 0;
+ fe_try = true;
+ }
+
+ strcpy(libname, info.dli_fname);
+ if(strrchr(libname, '/'))
+ *(strrchr(libname, '/')+1) = 0;
+
+ LOGMSG("Searching frontend %s from %s", xc.s_frontends[fe_ind], libname);
+
+ do {
+ strcat(libname, xc.s_frontend_files[fe_ind]);
+ LOGMSG("Probing %s", libname);
+
+ if (stat(libname, &statbuffer)) {
+ LOGERR("load_frontend: can't stat %s",libname);
+ } else if((statbuffer.st_mode & S_IFMT) != S_IFREG) {
+ LOGMSG("load_frontend: %s not regular file ! trying to load anyway ...",
+ libname);
+ }
+
+ if( !(lib = dlopen (libname, RTLD_LAZY | RTLD_GLOBAL))) {
+ char *dl_error_msg = dlerror();
+ LOGERR("load_frontend: cannot dlopen file %s: %s",
+ libname, dl_error_msg);
+ } else if ( (fe_creator = (fe_creator_f*)dlsym(lib, "fe_creator"))) {
+ LOGDBG("load_frontend: entry at %p", fe_creator);
+ frontend_t *fe = (**fe_creator)();
+
+ if(fe) {
+ if(h_fe_lib)
+ dlclose(h_fe_lib);
+ h_fe_lib = lib;
+
+ LOGMSG("Using frontend %s (%s) from %s",
+ xc.s_frontends[fe_ind], xc.s_frontendNames[fe_ind],
+ xc.s_frontend_files[fe_ind]);
+
+ return fe;
+ } else {
+ LOGMSG("Frontend %s (%s) creation failed",
+ xc.s_frontends[fe_ind], xc.s_frontendNames[fe_ind]);
+ }
+ } else {
+ LOGERR("Frontend entry point not found");
+ dlclose(lib);
+ }
+
+ fe_ind++; // try next frontend ...
+
+ } while(fe_try && fe_ind < FRONTEND_count);
+
+ LOGMSG("No usable frontends found, giving up !");
+ return NULL;
+}
+
+//
+// Thread main loop
+//
+
+void cXinelibLocal::Action(void)
+{
+ frontend_t *curr_fe = NULL;
+
+ TRACEF("cXinelibLocal::Action");
+
+ SetPriority(2); /* lower priority */
+#if 0
+ // set priority
+ sched_param temp;
+ temp.sched_priority = 2;
+
+ if (!pthread_setschedparam(pthread_self(), SCHED_RR, &temp)) {
+ TRACE("cXinelibLocal("<<getpid()<<"): priority set successful to "
+ << temp.sched_priority <<
+ " [" << sched_get_priority_min(SCHED_RR) << ","
+ << sched_get_priority_max(SCHED_RR) << "]");
+ } else {
+ TRACE("cXinelibLocal(" << getpid() << "): Error: can't set priority to "
+ << temp.sched_priority << " ["
+ << sched_get_priority_min(SCHED_RR)
+ << "," << sched_get_priority_max(SCHED_RR) << "]");
+ }
+ errno = 0;
+#endif
+
+ // init frontend
+ if(!curr_fe) {
+ curr_fe = load_frontend(xc.local_frontend);
+ if(!curr_fe) {
+ LOGMSG("cXinelibLocal: Error initializing frontend");
+ SetStopSignal();
+ } else {
+ LOGDBG("cXinelibLocal::Action - fe created");
+ if(!curr_fe->fe_display_open(curr_fe, xc.width, xc.height, xc.fullscreen,
+ xc.modeswitch, xc.modeline,
+ xc.display_aspect, keypress_handler,
+ xc.video_port, xc.scale_video,
+ xc.field_order)) {
+ LOGMSG("cXinelibLocal: Error initializing display");
+ SetStopSignal();
+ } else {
+ LOGDBG("cXinelibLocal::Action - fe->fe_display_open ok");
+ }
+ }
+ }
+
+ // main loop
+ while (!GetStopSignal()) {
+
+ {
+ // init and start xine engine
+ LOCK_FE_WR;
+ LOGDBG("cXinelibLocal::Action - xine_init");
+
+ fe = curr_fe;
+ if(m_bReconfigRequest) {
+ if(!fe->xine_init(fe, xc.audio_driver, xc.audio_port,
+ xc.video_driver,
+ xc.pes_buffers, xc.decoder_priority,
+ xc.post_plugins)) {
+ LOGMSG("cXinelibLocal: Error initializing frontend");
+ break;
+ }
+ LOGDBG("cXinelibLocal::Action - fe->xine_init ok");
+ m_bReconfigRequest = false;
+ }
+
+ // open (xine) stream
+ LOGDBG("cXinelibLocal::Action - xine_open");
+ if(!fe->xine_open(fe, NULL)) {
+ LOGMSG("cXinelibLocal: Error opening xvdr://");
+ break;
+ }
+ LOGDBG("cXinelibLocal::Action - fe->xine_open ok");
+
+ // start playing (xine) stream
+ if(!fe->xine_play(fe)) {
+ LOGMSG("cXinelibLocal: Error playing xvdr://");
+ break;
+ }
+ LOGDBG("cXinelibLocal::Action - fe->xine_play ok");
+
+ m_StreamPos = 0;
+ Xine_Control("STREAMPOS 0");
+ }
+
+ // configure frontend and xine
+ m_bNoVideo = false;
+ ConfigureOSD(xc.prescale_osd, xc.unscaled_osd);
+ ConfigurePostprocessing(xc.deinterlace_method, xc.audio_delay,
+ xc.audio_compression, xc.audio_equalizer,
+ xc.audio_surround);
+ ConfigureVideo(xc.hue, xc.saturation, xc.brightness, xc.contrast);
+ ConfigurePostprocessing("upmix", xc.audio_upmix ? true : false, NULL);
+#ifdef ENABLE_TEST_POSTPLUGINS
+ ConfigurePostprocessing("autocrop", xc.autocrop ? true : false, NULL);
+ ConfigurePostprocessing("headphone", xc.headphone ? true : false, NULL);
+#endif
+ LOGDBG("cXinelibLocal::Action - fe config OK");
+
+ LogoDisplay();
+ LOGDBG("cXinelibLocal::Action - logo sent");
+
+ {
+ LOCK_THREAD;
+ Xine_Control("NOVIDEO 0");
+ Xine_Control("LIVE 0");
+ m_bNoVideo = false;
+ m_bLiveMode = false;
+ m_bReady = true;
+ }
+
+ // main event loop
+ LOGDBG("cXinelibLocal:Action - Starting event loop");
+ {
+ LOCK_FE;
+ while(!GetStopSignal() && m_bReady &&
+ (/*m_bLoopPlay ||*/ !fe->xine_is_finished(fe)) &&
+ fe->fe_run(fe))
+ /*cCondWait::SleepMs(50)*/ ;
+ }
+
+ LOGDBG("cXinelibLocal::Action - event loop terminated, "
+ "xine_is_finished=%d", fe->xine_is_finished(fe));
+
+ {
+ LOCK_THREAD;
+ m_bReady = false;
+ m_bEndOfStreamReached = true;
+ }
+
+ {
+ LOCK_FE_WR;
+ if(fe)
+ fe->xine_close(fe);
+ fe = NULL;
+ }
+
+ LOGMSG("cXinelibLocal::Action - Xine closed");
+ }
+
+ if(curr_fe) {
+ curr_fe->xine_exit(fe);
+ curr_fe->fe_display_close(curr_fe);
+ curr_fe->fe_free(curr_fe);
+ }
+
+ m_bIsFinished = true;
+ LOGMSG("cXinelibLocal::Action - finished");
+}
+
diff --git a/frontend_svr.c b/frontend_svr.c
new file mode 100644
index 00000000..9d67d267
--- /dev/null
+++ b/frontend_svr.c
@@ -0,0 +1,1276 @@
+/*
+ * frontend_svr.c: server for remote frontends
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: frontend_svr.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+
+#include <vdr/config.h>
+#include <vdr/tools.h>
+#include <vdr/plugin.h>
+
+#include "logdefs.h"
+#include "config.h"
+
+#include "xine_input_vdr_net.h" // stream header(s)
+#include "xine_osd_command.h" // osd commands
+
+#include "tools/cxsocket.h"
+#include "tools/future.h"
+#include "tools/backgroundwriter.h"
+#include "tools/udp_pes_scheduler.h"
+
+#include "frontend_svr.h"
+#include "device.h"
+
+#define LOG_OSD_BANDWIDTH (128*1024)
+
+class cStcFuture : public cFuture<int64_t> {};
+class cReplyFuture : public cFuture<int>, public cListObject {};
+class cCmdFutures : public cHash<cReplyFuture> {};
+
+#define POLLING_INTERVAL (10*1000)
+
+//----------------------------- cXinelibServer --------------------------------
+
+cXinelibServer::cXinelibServer(int listen_port) :
+ cXinelibThread("Remote decoder/display server (cXinelibServer)")
+{
+ int i;
+ for(i=0; i<MAXCLIENTS; i++) {
+ fd_data[i] = -1;
+ fd_control[i] = -1;
+ m_Writer[i] = NULL;
+ m_bMulticast[i] = 0;
+ m_bConfigOk[i] = false;
+ m_bUdp[i] = 0;
+ }
+
+ m_Port = listen_port;
+
+ fd_listen = -1;
+ fd_multicast = -1;
+ fd_discovery = -1;
+
+ m_iMulticastMask = 0;
+ m_iUdpFlowMask = 0;
+
+ m_Master = false;
+
+ m_Scheduler = new cUdpScheduler;
+ m_StcFuture = new cStcFuture;
+ m_Futures = new cCmdFutures;
+}
+
+cXinelibServer::~cXinelibServer()
+{
+ int i;
+
+ CLOSESOCKET(fd_listen);
+ CLOSESOCKET(fd_discovery);
+ CLOSESOCKET(fd_multicast);
+
+ for(i=0; i<MAXCLIENTS; i++)
+ CloseConnection(i);
+
+ delete m_StcFuture;
+ delete m_Futures;
+ delete m_Scheduler;
+
+ RemoveFileOrDir(cPlugin::ConfigDirectory("xineliboutput/pipes"), false);
+}
+
+void cXinelibServer::Stop(void)
+{
+ int i;
+
+ TRACEF("cXinelibServer::Stop");
+
+ SetStopSignal();
+
+ CLOSESOCKET(fd_listen);
+ CLOSESOCKET(fd_discovery);
+ CLOSESOCKET(fd_multicast);
+
+ for(i=0; i<MAXCLIENTS; i++)
+ CloseConnection(i);
+
+ cXinelibThread::Stop();
+}
+
+void cXinelibServer::Clear(void)
+{
+ TRACEF("cXinelibServer::Clear");
+
+ LOCK_THREAD;
+
+ cXinelibThread::Clear();
+
+ for(int i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i] >= 0 && fd_data >= 0 && m_Writer[i])
+ m_Writer[i]->Clear();
+
+ if(m_Scheduler)
+ m_Scheduler->Clear();
+}
+
+#define CloseDataConnection(cli) \
+ do { \
+ if(m_bUdp[cli] && fd_data[cli]>=0) \
+ m_Scheduler->RemoveHandle(fd_data[cli]); \
+ CLOSESOCKET(fd_data[cli]); \
+ if(m_Writer[cli]) { \
+ delete m_Writer[cli]; \
+ m_Writer[cli] = NULL; \
+ } \
+ m_iUdpFlowMask &= ~(1<<cli); \
+ m_iMulticastMask &= ~(1<<cli); \
+ if(!m_iMulticastMask && !xc.remote_rtp_always_on) \
+ m_Scheduler->RemoveHandle(fd_multicast); \
+ m_bUdp[cli] = false; \
+ m_bMulticast[cli] = false; \
+ m_bConfigOk[cli] = false; \
+ } while(0)
+
+void cXinelibServer::CloseConnection(int cli)
+{
+ CloseDataConnection(cli);
+ if(fd_control[cli]>=0) {
+ LOGMSG("Closing connection %d", cli);
+ CLOSESOCKET(fd_control[cli]);
+ cXinelibDevice::Instance().ForcePrimaryDevice(false);
+ }
+}
+
+void cXinelibServer::OsdCmd(void *cmd_gen)
+{
+ TRACEF("cXinelibServer::OsdCmd");
+ int i;
+
+ LOCK_THREAD;
+
+ // check if there are any clients
+ for(i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i]>=0)
+ break;
+ if(i == MAXCLIENTS)
+ return;
+
+ if(cmd_gen) {
+ osd_command_t *cmd = (osd_command_t*)cmd_gen;
+#ifdef LOG_OSD_BANDWIDTH
+ {
+ static int64_t timer = 0LL;
+ static int bytes = 0;
+ int64_t now = cTimeMs::Now();
+
+ if(timer + 5000LL < now) {
+ timer = now;
+ bytes = 0;
+ } else if(timer + 1000LL < now) {
+ bytes = bytes / (((int)(now - timer)) / 1000);
+ if(bytes > LOG_OSD_BANDWIDTH)
+ LOGMSG("OSD bandwidth: %d bytes/s (%d kbit/s)", bytes, bytes*8/1024);
+ timer = now;
+ bytes = 0;
+ }
+ bytes += cmd->datalen;
+ }
+#endif
+
+ /* -> network order */
+ if(0x12345678 != htonl(0x12345678)) {
+ cmd->cmd = htonl(cmd->cmd);
+ cmd->wnd = htonl(cmd->wnd);
+ cmd->pts = htonll(cmd->pts);
+ cmd->delay_ms = htonl(cmd->delay_ms);
+ cmd->x = htons(cmd->x);
+ cmd->y = htons(cmd->y);
+ cmd->w = htons(cmd->w);
+ cmd->h = htons(cmd->h);
+ if(cmd->data) {
+ for(unsigned int i=0; i<cmd->datalen/4; i++) {
+ cmd->data[i].len = htons(cmd->data[i].len);
+ cmd->data[i].color = htons(cmd->data[i].color);
+ }
+ }
+ cmd->datalen = htonl(cmd->datalen);
+ cmd->colors = htonl(cmd->colors);
+ }
+
+ for(i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i] >= 0 &&
+ !write_osd_command(fd_control[i], (osd_command_t*)cmd_gen)) {
+ LOGMSG("Send OSD command failed");
+ CloseConnection(i);
+ }
+ }
+}
+
+
+int64_t cXinelibServer::GetSTC(void)
+{
+ int i;
+ //cTimeMs delay;
+
+ Lock();
+
+ // check if there are any clients
+ for(i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i]>=0)
+ break;
+ if(i == MAXCLIENTS) {
+ Unlock();
+ return -1ULL;
+ }
+
+ // Query client(s)
+ m_StcFuture->Reset();
+ Xine_Control("GETSTC");
+
+ Unlock();
+
+ if(! m_StcFuture->Wait(100)) {
+ LOGMSG("cXinelibServer::GetSTC timeout (100ms)");
+ return -1ULL;
+ }
+
+ //if(delay.Elapsed() > 0 && !is_Paused)
+ // LOGMSG("GetSTC: compensating network delay by %s ticks (ms)\n",
+ // delay.Elapsed()*90000/2, delay.Elapsed()/2);
+
+ return m_StcFuture->Value() /*+ (delay.Elapsed()*90000/2*/;
+}
+
+int cXinelibServer::Play_PES(const uchar *data, int len)
+{
+ int TcpClients = 0, UdpClients = 0, RtpClients = 0;
+
+ if(!m_bLiveMode && m_Master) {
+ // dvbplayer feeds multiple pes packets after each poll
+ // (all frames between two pictures).
+ // So, we must poll here again to avoid overflows ...
+ static cPoller dummy;
+ if(!Poll(dummy, 3))
+ return 0;
+ }
+
+ LOCK_THREAD; // Lock control thread out
+
+ for(int i=0; i<MAXCLIENTS; i++) {
+ if(fd_control[i] >= 0 && m_bConfigOk[i]) {
+ if(fd_data[i] >= 0) {
+ if(m_bUdp[i]) {
+# if 0
+ if(m_iUdpFlowMask & (1<<i)) {
+ LOGDBG("UDP full signal in Play_PES, sleeping 5ms");
+ cCondWait::SleepMs(5);
+ }
+# endif
+ UdpClients++;
+
+ } else if(m_Writer[i]) {
+ int result = m_Writer[i]->Put(m_StreamPos, data, len);
+ if(!result) {
+ LOGMSG("cXinelibServer::Play_PES Write/Queue error (TCP/PIPE)");
+ CloseConnection(i);
+ } else if(result<0) {
+ LOGMSG("cXinelibServer::Play_PES Buffer overflow (TCP/PIPE)");
+ }
+
+ TcpClients++;
+ }
+ }
+ }
+ }
+
+ RtpClients = (m_iMulticastMask && fd_multicast >= 0);
+
+ if(UdpClients || RtpClients)
+ if(! m_Scheduler->Queue(m_StreamPos, data, len)) {
+ LOGMSG("cXinelibServer::Play_PES Buffer overflow (UDP/RTP)");
+ }
+
+ if(TcpClients || UdpClients || RtpClients)
+ cXinelibThread::Play_PES(data, len);
+
+ return len;
+}
+
+
+void cXinelibServer::Xine_Sync(void)
+{
+#if 0
+ TRACEF("cXinelibServer::Xine_Sync");
+
+ bool foundReceivers=false;
+ SyncLock.Lock();
+ cXinelibThread::Xine_Control((const char *)"SYNC",m_StreamPos);
+ for(int i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i] >= 0) {
+ wait_sync[i]=true;
+ foundReceivers=true;
+ }
+ Unlock();
+ if(foundReceivers) {
+ TRACE("cXinelibServer::Xine_Sync --> WAIT...");
+ SyncDone.Wait(SyncLock);
+ TRACE("cXinelibServer::Xine_Sync --> WAIT DONE.");
+ }
+ SyncLock.Unlock();
+ Lock();
+#endif
+}
+
+
+bool cXinelibServer::Poll(cPoller &Poller, int TimeoutMs)
+{
+ // in live mode transponder clock is the master ...
+ if((*xc.local_frontend && strncmp(xc.local_frontend, "none", 4)) || m_bLiveMode)
+ return m_Scheduler->Poll(TimeoutMs, m_Master=false);
+
+ // replay mode:
+ do {
+ Lock();
+ m_Master = true;
+ int Free = 0xffff, Clients = 0;
+ for(int i=0; i<MAXCLIENTS; i++) {
+ if(fd_control[i]>=0 && m_bConfigOk[i])
+ if(fd_data[i]>=0 || m_bMulticast[i]) {
+ if(m_Writer[i])
+ Free = min(Free, m_Writer[i]->Free());
+ Clients++;
+ }
+ }
+ Unlock();
+
+ // replay is paused when no clients
+ if(!Clients) {
+ if(TimeoutMs>0)
+ cCondWait::SleepMs(TimeoutMs);
+ return false;
+ }
+
+ // in replay mode cUdpScheduler is master timing source
+ if(Free < 8128 || !m_Scheduler->Poll(TimeoutMs, true)) {
+ if(TimeoutMs > 0)
+ cCondWait::SleepMs(min(TimeoutMs, 5));
+ TimeoutMs -= 5;
+ } else {
+ return true;
+ }
+
+ } while(TimeoutMs > 0);
+
+ return false;
+}
+
+bool cXinelibServer::Flush(int TimeoutMs)
+{
+ int result = true;
+
+ if(m_Scheduler)
+ result = m_Scheduler->Flush(TimeoutMs) && result;
+
+ for(int i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i]>=0 && fd_data[i]>=0 && m_Writer[i])
+ result = m_Writer[i]->Flush(TimeoutMs) && result;
+
+ if(TimeoutMs > 50)
+ TimeoutMs = 50;
+
+ if(result) {
+ char tmp[64];
+ sprintf(tmp, "FLUSH %d %lld", TimeoutMs, m_StreamPos);
+ result = (PlayFileCtrl(tmp)) <= 0 && result;
+ }
+ return result;
+}
+
+int cXinelibServer::Xine_Control(const char *cmd)
+{
+ TRACEF("cXinelibServer::Xine_Control");
+
+ char buf[128];
+ sprintf(buf, "%s\r\n", cmd);
+ int len = strlen(buf);
+ LOCK_THREAD;
+ for(int i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i]>=0 && (fd_data[i]>=0 || m_bMulticast[i]) && m_bConfigOk[i])
+ if(len != timed_write(fd_control[i], buf, len, 100)) {
+ LOGMSG("Control send failed, dropping client");
+ CloseConnection(i);
+ }
+
+ return 1;
+}
+
+bool cXinelibServer::EndOfStreamReached(void)
+{
+ LOCK_THREAD;
+
+ /* Check if there are any clients */
+ int i;
+ for(i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i]>=0)
+ break;
+ if(i == MAXCLIENTS) {
+ return true;
+ }
+
+ return cXinelibThread::EndOfStreamReached();
+}
+
+int cXinelibServer::PlayFileCtrl(const char *Cmd)
+{
+ if( 0 /*(*xc.local_frontend && strncmp(xc.local_frontend, "none", 4))*/ ) {
+ // Don't wait reply if local frontend is running
+ //
+ // return cXinelibThread::PlayFileCtrl(Cmd);
+ //
+ } else {
+ if(!strncmp(Cmd, "FLUSH", 5) ||
+ !strncmp(Cmd, "PLAYFILE", 8) ||
+ !strncmp(Cmd, "GET", 3)) { // GETPOS, GETLENGTH, ...
+
+ cReplyFuture future;
+ static int myToken = 0;
+ int i;
+
+ Lock();
+
+ /* Check if there are any clients */
+ for(i=0; i<MAXCLIENTS; i++)
+ if(fd_control[i]>=0 && m_bConfigOk[i])
+ break;
+ if(i == MAXCLIENTS) {
+ Unlock();
+ return -1;
+ }
+
+ /* Next token */
+ cXinelibThread::Xine_Control((const char *)"TOKEN", myToken);
+ int token = myToken++;
+ m_Futures->Add(&future, token);
+
+ // When server get REPLY %d %d (first %d == myToken, second returned value)
+ // it sets corresponding future (by token; if found) in list
+ // and removes it from list.
+
+ Unlock();
+
+ cXinelibThread::PlayFileCtrl(Cmd);
+
+ if(! future.Wait(250)) {
+ Lock();
+ m_Futures->Del(&future, token);
+ Unlock();
+ LOGMSG("cXinelibServer::PlayFileCtrl: Timeout (%s , 250ms)", Cmd);
+ return -1;
+ }
+
+ return future.Value();
+ }
+ }
+
+ return cXinelibThread::PlayFileCtrl(Cmd);
+}
+
+
+bool cXinelibServer::Listen(int listen_port)
+{
+ LOCK_THREAD;
+
+ bool result = false;
+ TRACEF("cXinelibServer::Listen");
+
+ if(listen_port <= 0 || listen_port > 0xffff) {
+ CLOSESOCKET(fd_listen);
+ CLOSESOCKET(fd_discovery);
+ if(fd_multicast >= 0 && m_Scheduler)
+ m_Scheduler->RemoveHandle(fd_multicast);
+ CLOSESOCKET(fd_multicast);
+ LOGMSG("Not listening for remote connections");
+ return false;
+ }
+
+ if(fd_listen<0 || listen_port != m_Port) {
+ m_Port = listen_port;
+ CLOSESOCKET(fd_listen);
+ if(m_Port>0) {
+ int iReuse = 1;
+ struct sockaddr_in name;
+ name.sin_family = AF_INET;
+ name.sin_addr.s_addr = htonl(INADDR_ANY);
+ name.sin_port = htons(m_Port);
+
+ fd_listen = socket(PF_INET,SOCK_STREAM,0);
+ setsockopt(fd_listen, SOL_SOCKET, SO_REUSEADDR, &iReuse, sizeof(int));
+
+ if (bind(fd_listen, (struct sockaddr *)&name, sizeof(name)) < 0) {
+ LOGERR("cXinelibServer: bind error (port %d): %s",
+ m_Port, strerror(errno));
+ CLOSESOCKET(fd_listen);
+ } else if(listen(fd_listen, MAXCLIENTS)) {
+ LOGERR("cXinelibServer: listen error (port %d): %s",
+ m_Port, strerror(errno));
+ CLOSESOCKET(fd_listen);
+ } else {
+ LOGMSG("Listening on port %d", m_Port);
+ result = true;
+ }
+ }
+ }
+
+ // set listen for discovery messages
+ CLOSESOCKET(fd_discovery);
+ if(xc.remote_usebcast && fd_discovery<0) {
+ struct sockaddr_in sin;
+ if ((fd_discovery = socket(PF_INET, SOCK_DGRAM, 0/*IPPROTO_TCP*/)) < 0) {
+ LOGERR("socket() failed (UDP discovery)");
+ } else {
+ int iBroadcast = 1, iReuse = 1;
+ setsockopt(fd_discovery, SOL_SOCKET, SO_BROADCAST, &iBroadcast, sizeof(int));
+ setsockopt(fd_discovery, SOL_SOCKET, SO_REUSEADDR, &iReuse, sizeof(int));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(DISCOVERY_PORT);
+ sin.sin_addr.s_addr = INADDR_ANY;//BROADCAST;
+
+ if (bind(fd_discovery, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+ LOGERR("bind() failed (UDP discovery)");
+ CLOSESOCKET(fd_discovery);
+ } else {
+ if(udp_discovery_broadcast(fd_discovery, m_Port) < 0)
+ CLOSESOCKET(fd_discovery);
+ else
+ LOGMSG("Listening for UDP broadcasts on port %d", m_Port);
+ }
+ }
+ }
+
+ // set up multicast socket
+
+ //if(!xc.remote_usertp)
+ if(fd_multicast >= 0 && m_Scheduler)
+ m_Scheduler->RemoveHandle(fd_multicast);
+ CLOSESOCKET(fd_multicast);
+
+ if(xc.remote_usertp && fd_multicast < 0) {
+ int iReuse = 1, iLoop = 1, iTtl = xc.remote_rtp_ttl;
+
+ if(xc.remote_rtp_always_on)
+ LOGMSG("WARNING: RTP Configuration: transmission is always on !");
+
+ fd_multicast = socket(AF_INET, SOCK_DGRAM, 0);
+ if(fd_multicast < 0) {
+ LOGERR("socket() failed (UDP/RTP multicast)");
+ } else {
+
+ // Set buffer sizes
+ int max_buf = KILOBYTE(128);
+ if(setsockopt(fd_multicast, SOL_SOCKET, SO_SNDBUF,
+ &max_buf, sizeof(int))) {
+ LOGERR("setsockopt(fd_multicast, SO_SNDBUF,%d) failed", max_buf);
+ } else {
+ int tmp = 0;
+ int len = sizeof(int);
+ if(getsockopt(fd_multicast, SOL_SOCKET, SO_SNDBUF,
+ &tmp, (socklen_t*)&len)) {
+ LOGERR("getsockopt(fd_multicast, SO_SNDBUF,%d) failed", max_buf);
+ } else if(tmp != max_buf) {
+ LOGDBG("setsockopt(fd_multicast, SO_SNDBUF): got %d bytes", tmp);
+ }
+ }
+ max_buf = 1024;
+ setsockopt(fd_multicast, SOL_SOCKET, SO_RCVBUF, &max_buf, sizeof(int));
+
+ // Set multicast socket options
+ if(setsockopt(fd_multicast, SOL_SOCKET, SO_REUSEADDR,
+ &iReuse, sizeof(int)) < 0)
+ LOGERR("setsockopt(SO_REUSEADDR) failed");
+
+ if(setsockopt(fd_multicast, IPPROTO_IP, IP_MULTICAST_TTL,
+ &iTtl, sizeof(int))) {
+ LOGERR("setsockopt(IP_MULTICAST_TTL) failed");
+ CLOSESOCKET(fd_multicast);
+ }
+
+ if(setsockopt(fd_multicast, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &iLoop, sizeof(int))) {
+ LOGERR("setsockopt(IP_MULTICAST_LOOP) failed");
+ CLOSESOCKET(fd_multicast);
+ }
+
+ // Connect to multicast address
+ struct sockaddr_in sin;
+ sin.sin_family = sin.sin_family = AF_INET;
+ sin.sin_port = sin.sin_port = htons(xc.remote_rtp_port);
+ //sin.sin_addr.s_addr = htonl(0xe0000109); //inet_addr(sAddr);
+ sin.sin_addr.s_addr = inet_addr(xc.remote_rtp_addr);
+
+ if(connect(fd_multicast, (struct sockaddr *)&sin, sizeof(sin))==-1 &&
+ errno != EINPROGRESS)
+ LOGERR("connect(fd_multicast) failed. Address=%s, port=%d",
+ xc.remote_rtp_addr, xc.remote_rtp_port);
+
+ // Set to non-blocking mode
+ if(fcntl (fd_multicast, F_SETFL,
+ fcntl (fd_multicast, F_GETFL) | O_NONBLOCK) == -1)
+ LOGERR("can't put multicast socket in non-blocking mode");
+
+ if(fd_multicast >= 0 && xc.remote_rtp_always_on)
+ m_Scheduler->AddHandle(fd_multicast);
+
+ // Finished
+ }
+ }
+
+ return result;
+}
+
+
+uchar *cXinelibServer::GrabImage(int &Size, bool Jpeg,
+ int Quality, int SizeX, int SizeY)
+{
+ //
+ // TODO
+ //
+ return NULL;
+}
+
+
+//
+// (Client) Control message handling
+//
+
+#define CREATE_NEW_WRITER \
+ if(m_Writer[cli]) \
+ delete m_Writer[cli]; \
+ m_Writer[cli] = new cBackgroundWriter(fd);
+
+void cXinelibServer::Handle_Control_PIPE(int cli, char *arg)
+{
+ char buf[256];
+ LOGDBG("Trying PIPE connection ...");
+
+ CloseDataConnection(cli);
+
+ //
+ // TODO: client should create pipe; waiting here is not good thing ...
+ //
+
+ if(!xc.remote_usepipe) {
+ LOGMSG("PIPE transport disabled in configuration");
+ write_cmd(fd_control[cli], "PIPE NONE\r\n");
+ return;
+ }
+
+ char pipeName[1024];
+ MakeDirs(cPlugin::ConfigDirectory("xineliboutput/pipes"), true);
+ int i;
+ for(i=0; i<10; i++) {
+ sprintf(pipeName,"%s/pipe.%d",
+ cPlugin::ConfigDirectory("xineliboutput/pipes"),i);
+ if(mknod(pipeName, 0644|S_IFIFO, 0) < 0) {
+ unlink(pipeName);
+ continue;
+ }
+ else
+ break;
+ }
+ if(i>=10) {
+ LOGERR("Pipe creation failed (%s)", pipeName);
+ write_cmd(fd_control[cli], "PIPE NONE\r\n");
+ return;
+ }
+
+ sprintf(buf, "PIPE %s\r\n", pipeName);
+ write_cmd(fd_control[cli], buf);
+
+ cPoller poller(fd_control[cli],false);
+ poller.Poll(500); /* quite short time ... */
+
+ int fd;
+ if((fd = open(pipeName, O_WRONLY|O_NONBLOCK)) < 0) {
+ LOGDBG("Pipe not opened by client");
+ /*write_cmd(fd_control[cli], "PIPE NONE\r\n");*/
+ unlink(pipeName);
+ return;
+ }
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)|O_NONBLOCK);
+
+ LOGDBG("cXinelibServer::Handle_Control: pipe %s open", pipeName);
+
+ unlink(pipeName); /* safe to remove now, both ends are open or closed. */
+ write_cmd(fd_control[cli], "PIPE OK\r\n");
+
+ CREATE_NEW_WRITER;
+
+ fd_data[cli] = fd;
+}
+
+
+void cXinelibServer::Handle_Control_DATA(int cli, char *arg)
+{
+ LOGDBG("Data connection (TCP) requested");
+
+ CloseDataConnection(cli);
+
+ if(!xc.remote_usetcp) {
+ LOGMSG("TCP transports disabled in configuration");
+ CloseConnection(cli); /* actually closes the new data connection */
+ return;
+ }
+
+ int clientId = -1, oldId = cli, fd = fd_control[cli];
+ if(1 == sscanf(arg, "%d", &clientId) &&
+ clientId >= 0 && clientId < MAXCLIENTS &&
+ fd_control[clientId] >= 0) {
+ fd_control[oldId] = -1;
+ m_bUdp[clientId] = false;
+ m_bMulticast[clientId] = false;
+ m_iUdpFlowMask &= ~(1<<clientId);
+ m_iMulticastMask &= ~(1<<clientId);
+ cli = clientId;
+
+ write(fd, "DATA\r\n", 6);
+
+ CREATE_NEW_WRITER;
+
+ fd_data[cli] = fd;
+
+ return;
+ }
+
+ LOGDBG("Invalid data connection (TCP) request"); /* closes new data conn., no ctrl conn. */
+ CloseConnection(cli);
+}
+
+void cXinelibServer::Handle_Control_RTP(int cli, char *arg)
+{
+ if(xc.remote_usertp && fd_multicast>=0) {
+ char buf[256];
+ LOGDBG("Trying RTP connection ...");
+
+ CloseDataConnection(cli);
+
+ // TODO: rtp address & port -> config
+ //sprintf(buf, "RTP 224.0.1.9:%d\r\n", m_Port);
+ sprintf(buf, "RTP %s:%d\r\n", xc.remote_rtp_addr, xc.remote_rtp_port);
+ write(fd_control[cli], buf, strlen(buf));
+
+
+ stream_udp_header_t nullhdr;
+ nullhdr.pos = m_StreamPos;
+ nullhdr.seq = 0xffff;//-1;//m_UdpSeqNo;
+ //strcpy(buf, "RTP 224.0.1.9:" );
+ struct sockaddr_in sin;
+ sin.sin_family = sin.sin_family = AF_INET;
+ sin.sin_port = sin.sin_port = htons(m_Port);
+ sin.sin_addr.s_addr = htonl(0xe0000109); //inet_addr(sAddr);
+
+ if(sizeof(nullhdr) !=
+ sendto(fd_multicast, &nullhdr, sizeof(nullhdr), 0,
+ (struct sockaddr *)&sin, sizeof(sin))) {
+ LOGERR("UDP/RTP multicast send() failed");
+ //CloseConnection(cli);
+ return;
+ }
+
+ if(!m_iMulticastMask)
+ m_Scheduler->AddHandle(fd_multicast);
+
+ m_bMulticast[cli] = true;
+ m_iMulticastMask |= (1<<cli);
+
+ } else {
+ write(fd_control[cli], "RTP NONE\r\n", 10);
+ LOGMSG("RTP transports disabled");
+ }
+}
+
+void cXinelibServer::Handle_Control_UDP(int cli, char *arg)
+{
+ LOGDBG("Trying UDP connection ...");
+
+ CloseDataConnection(cli);
+
+ if(!xc.remote_useudp) {
+ LOGMSG("UDP transports disabled in configuration");
+ //CloseConnection(cli);
+ return;
+ }
+
+ int fd = sock_connect(fd_control[cli], atoi(arg), SOCK_DGRAM);
+ if(fd < 0) {
+ LOGERR("socket() for UDP failed");
+ //CloseConnection(cli);
+ return;
+ }
+
+ stream_udp_header_t nullhdr;
+ nullhdr.pos = m_StreamPos;
+ nullhdr.seq = 0xffff;//-1;//m_UdpSeqNo;
+ if(sizeof(nullhdr) != send(fd, &nullhdr, sizeof(nullhdr), 0)) {
+ LOGERR("UDP send() failed");
+ CloseConnection(cli);
+ return;
+ }
+
+ m_bUdp[cli] = true;
+ fd_data[cli] = fd;
+ m_Scheduler->AddHandle(fd);
+}
+
+void cXinelibServer::Handle_Control_KEY(int cli, char *arg)
+{
+ char buf[256], buf2[256];
+ bool repeat=false, release=false;
+
+ if(!xc.use_remote_keyboard) {
+ LOGMSG("Handle_Control_KEY(%s): Remote keyboard disabled in config", arg);
+ return;
+ }
+
+ strcpy(buf, arg);
+ //*strstr(buf, "\r\n") = 0;
+ TRACE("cXinelibServer received KEY " << buf);
+
+ int n = strlen(buf)-1;
+ while(buf[n]==' ') buf[n--]=0;
+
+ if(strchr(buf, ' ')) {
+ strcpy(buf2,strchr(buf, ' ')+1);
+ *strchr(buf, ' ') = 0;
+
+ char *pt = strchr(buf, ' ');
+ if(pt) {
+ *(pt++) = 0;
+ if(strstr(pt, "Repeat"))
+ repeat = true;
+ if(strstr(pt, "Release"))
+ release = true;
+ }
+ cXinelibThread::KeypressHandler(buf, buf2, repeat, release);
+ } else {
+ cXinelibThread::KeypressHandler(NULL, buf, repeat, release);
+ }
+}
+
+void cXinelibServer::Handle_Control_CONFIG(int cli)
+{
+ char buf[256];
+
+ m_bConfigOk[cli] = true;
+
+ int one = 1;
+ setsockopt(fd_control[cli], IPPROTO_TCP, TCP_NODELAY, &one, sizeof(int));
+
+ sprintf(buf, "NOVIDEO %d\r\nLIVE %d\r\n", m_bNoVideo?1:0, m_bLiveMode?1:0);
+ write_cmd(fd_control[cli], buf);
+
+ ConfigureOSD(xc.prescale_osd, xc.unscaled_osd);
+ ConfigurePostprocessing(xc.deinterlace_method, xc.audio_delay,
+ xc.audio_compression, xc.audio_equalizer,
+ xc.audio_surround);
+ ConfigureVideo(xc.hue, xc.saturation, xc.brightness, xc.contrast);
+
+ ConfigurePostprocessing("upmix", xc.audio_upmix ? true : false, NULL);
+ ConfigurePostprocessing("autocrop", xc.autocrop ? true : false, NULL);
+ ConfigurePostprocessing("headphone", xc.headphone ? true : false, NULL);
+
+ if(m_bPlayingFile) {
+ char buf[2048];
+ Unlock();
+ sprintf(buf, "PLAYFILE %d ", cXinelibDevice::Instance().PlayFileCtrl("GETPOS"));
+ Lock();
+ if(m_bPlayingFile) {
+ strcat(buf, m_FileName ? m_FileName : "");
+ strcat(buf, "\r\n");
+ write_cmd(fd_control[cli], buf);
+ }
+ }
+}
+
+void cXinelibServer::Handle_Control_UDP_RESEND(int cli, char *arg)
+{
+ unsigned int seq1, seq2;
+ uint64_t pos;
+
+ if( (!fd_data[cli] || !m_bUdp[cli]) &&
+ (!m_bMulticast[cli])) {
+ LOGMSG("Got invalid re-send request: no udp/rtp in use");
+ return;
+ }
+
+ if(3 == sscanf(arg, "%d-%d %lld", &seq1, &seq2, &pos)) {
+
+ if(seq1 <= UDP_SEQ_MASK && seq2 <= UDP_SEQ_MASK && pos <= m_StreamPos) {
+
+ if(fd_data[cli] >= 0)
+ m_Scheduler->ReSend(fd_data[cli], pos, seq1, seq2);
+ else
+ m_Scheduler->ReSend(fd_multicast, pos, seq1, seq2);
+ } else {
+ LOGMSG("Invalid re-send request: %s (send pos=%lld)", arg, m_StreamPos);
+ }
+ } else {
+ LOGMSG("Invalid re-send request: %s (send pos=%lld)", arg, m_StreamPos);
+ }
+}
+
+
+void cXinelibServer::Handle_Control(int cli, char *cmd)
+{
+ TRACEF("cXinelibServer::Handle_Control");
+
+#ifdef LOG_CONTROL_MESSAGES
+ static FILE *flog = fopen("/video/control.log","w");
+ fprintf(flog,"CTRL (%d): %s\n",cli,cmd); fflush(flog);
+#endif
+
+ //LOGDBG("Server received %s", cmd);
+
+ /* Order of tests is significant !!!
+ (example: UDP 2\r\n or UDP FULL 1\r\n) */
+
+ if(!strncasecmp(cmd, "PIPE OPEN", 9)) {
+ LOGDBG("Pipe open");
+
+ } else if(!strncasecmp(cmd, "PIPE", 4)) {
+ Handle_Control_PIPE(cli, cmd+4);
+
+ } else if(!strncasecmp(cmd, "RTP", 3)) {
+ Handle_Control_RTP(cli, cmd+4);
+
+ } else if(!strncasecmp(cmd, "UDP FULL 1", 10)) {
+ m_iUdpFlowMask |= (1<<cli);
+
+ } else if(!strncasecmp(cmd, "UDP FULL 0", 10)) {
+ m_iUdpFlowMask &= ~(1<<cli);
+
+ } else if(!strncasecmp(cmd, "UDP RESEND ", 11)) {
+ Handle_Control_UDP_RESEND(cli, cmd+11);
+
+ } else if(!strncasecmp(cmd, "UDP ", 4)) {
+ Handle_Control_UDP(cli, cmd+4);
+
+ } else if(!strncasecmp(cmd, "DATA ", 5)) {
+ Handle_Control_DATA(cli, cmd+5);
+
+ } else if(!strncasecmp(cmd, "KEY ", 4)) {
+ Handle_Control_KEY(cli, cmd+4);
+
+ } else if(!strncasecmp(cmd, "CONFIG", 6)) {
+ Handle_Control_CONFIG(cli);
+
+ } else if(!strncasecmp(cmd, "STC ", 4)) {
+ int64_t pts = -1;
+ if(1 == sscanf(cmd, "STC %lld", &pts))
+ m_StcFuture->Set(pts);
+
+ } else if(!strncasecmp(cmd, "ENDOFSTREAM", 11)) {
+ m_bEndOfStreamReached = true;
+
+ } else if(!strncasecmp(cmd, "RESULT ", 7)) {
+ int token = -1, result = -1;
+ if(2 == sscanf(cmd, "RESULT %d %d", &token, &result)) {
+ cReplyFuture *f = m_Futures->Get(token);
+ if(f) {
+ f->Set(result);
+ m_Futures->Del(f, token);
+ }
+ }
+
+ } else if(!strncasecmp(cmd, "CLOSE", 5)) {
+ CloseConnection(cli);
+ }
+}
+
+void cXinelibServer::Read_Control(int cli)
+{
+ int n = read(fd_control[cli],
+ &m_CtrlBuf[ cli ][ m_CtrlBufPos[cli] ],
+ 90 - m_CtrlBufPos[cli]);
+ if(n<=0) {
+ LOGMSG("Client connection %d closed", cli);
+ CloseConnection(cli);
+ return;
+ }
+
+ char *pt;
+ m_CtrlBufPos[cli] += n;
+ m_CtrlBuf[cli][m_CtrlBufPos[cli]] = 0;
+ while(NULL != (pt=strstr(m_CtrlBuf[cli], "\r\n"))) {
+ *pt = 0;
+ Handle_Control(cli, m_CtrlBuf[cli]);
+ strcpy(m_CtrlBuf[cli], pt + 2);
+ }
+ m_CtrlBufPos[cli] = strlen(m_CtrlBuf[cli]);
+
+ if(m_CtrlBufPos[cli]>=80) {
+ LOGMSG("Received too long control message from client %d", cli);
+ CloseConnection(cli);
+ }
+}
+
+void cXinelibServer::Handle_ClientConnected(int fd)
+{
+ struct sockaddr_in sin;
+ socklen_t len = sizeof(sin);
+ char str[1024];
+ int cli;
+
+ for(cli=0; cli<MAXCLIENTS; cli++)
+ if(fd_control[cli]<0)
+ break;
+
+ if(getpeername(fd, (struct sockaddr *)&sin, &len)) {
+ LOGERR("getpeername() failed, dropping new incoming connection %d", cli);
+ CLOSESOCKET(fd);
+ return;
+ }
+
+ uint32_t tmp = ntohl(sin.sin_addr.s_addr);
+ LOGMSG("Client %d connected: %d.%d.%d.%d:%d", cli,
+ ((tmp>>24)&0xff), ((tmp>>16)&0xff),
+ ((tmp>>8)&0xff), ((tmp)&0xff),
+ ntohs(sin.sin_port));
+
+ bool accepted = SVDRPhosts.Acceptable(sin.sin_addr.s_addr);
+ if(!accepted) {
+ LOGMSG("Address not allowed to connect (svdrphosts.conf).");
+ write_cmd(fd, "Access denied.\r\n");
+ CLOSESOCKET(fd);
+ return;
+ }
+
+ if(cli>=MAXCLIENTS) {
+ // too many clients
+ LOGMSG("Too mant clients, connection refused");
+ CLOSESOCKET(fd);
+ return;
+ }
+
+ if (fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK) == -1) {
+ LOGERR("Error setting control socket to nonblocking mode");
+ CLOSESOCKET(fd);
+ }
+
+ CloseDataConnection(cli);
+
+ m_CtrlBufPos[cli] = 0;
+ m_CtrlBuf[cli][0] = 0;
+
+ sprintf(str,
+ "VDR-" VDRVERSION " "
+ "xineliboutput-" XINELIBOUTPUT_VERSION " "
+ "READY\r\nCLIENT-ID %d\r\n", cli);
+ write_cmd(fd, str);
+ fd_control[cli] = fd;
+
+ cXinelibDevice::Instance().ForcePrimaryDevice(true);
+}
+
+void cXinelibServer::Handle_Discovery_Broadcast()
+{
+ char buf[1024];
+ struct sockaddr_in from;
+ socklen_t fromlen = sizeof(from);
+ memset(&from, 0, sizeof(from));
+ memset(buf, 0, sizeof(buf));
+ errno=0;
+
+ int n = recvfrom(fd_discovery, buf, 1023, 0,
+ (struct sockaddr *)&from, &fromlen);
+
+ if(!xc.remote_usebcast) {
+ LOGDBG("BROADCASTS disabled in configuration");
+ return;
+ }
+
+ if(n==0) {
+ LOGDBG("fd_discovery recv() 0 bytes");
+ return;
+ } else if(n<0) {
+ LOGERR("fd_discovery recv() error");
+ //CLOSESOCKET(fd_discovery);
+ return;
+ }
+
+ uint32_t tmp = ntohl(from.sin_addr.s_addr);
+ LOGDBG("BROADCAST: (%d bytes from %d.%d.%d.%d): %s", n,
+ ((tmp>>24)&0xff), ((tmp>>16)&0xff),
+ ((tmp>>8)&0xff), ((tmp)&0xff),
+ buf);
+
+ char *id_string = "VDR xineliboutput DISCOVERY 1.0\r\nClient:";
+
+ if(!strncmp(id_string, buf, strlen(id_string))) {
+ LOGMSG("Received valid discovery message from %d.%d.%d.%d",
+ ((tmp>>24)&0xff), ((tmp>>16)&0xff),
+ ((tmp>>8)&0xff), ((tmp)&0xff));
+ if(udp_discovery_broadcast(fd_discovery, m_Port) < 0) {
+ //LOGERR("Discovery broadcast send error");
+ } else {
+ //LOGMSG("Discovery broadcast (announce) sent");
+ }
+ }
+}
+
+void cXinelibServer::Action(void)
+{
+
+ TRACEF("cXinelibServer::Action");
+
+ int i, fds=0;
+ pollfd pfd[MAXCLIENTS];
+
+ /* higher priority */
+ SetPriority(-1);
+ SetPriority(-2);
+ SetPriority(-3);
+
+ sched_param temp;
+ temp.sched_priority = 2;
+
+ /* request real-time scheduling */
+ if (!pthread_setschedparam(pthread_self(), SCHED_RR, &temp)) {
+ LOGMSG("cXinelibServer priority set successful SCHED_RR %d [%d,%d]",
+ temp.sched_priority,
+ sched_get_priority_min(SCHED_RR),
+ sched_get_priority_max(SCHED_RR));
+ } else {
+ LOGMSG("cXinelibServer: Can't set priority to SCHED_RR %d [%d,%d]",
+ temp.sched_priority,
+ sched_get_priority_min(SCHED_RR),
+ sched_get_priority_max(SCHED_RR));
+ }
+ errno = 0;
+
+ Lock();
+ Listen(m_Port);
+ m_bReady=true;
+
+ if(fd_listen>=0)
+ while (!GetStopSignal() && fds>=0) {
+
+ fds = 0;
+ if(fd_listen>=0) {
+ pfd[fds].fd = fd_listen;
+ pfd[fds++].events = POLLIN;
+ }
+ if(fd_discovery >= 0) {
+ pfd[fds].fd = fd_discovery;
+ pfd[fds++].events = POLLIN;
+ }
+
+ for(i=0; i<MAXCLIENTS; i++) {
+ if(fd_control[i]>=0) {
+ pfd[fds].fd = fd_control[i];
+ pfd[fds++].events = POLLIN;
+ }
+ if(fd_data[i]>=0) {
+ pfd[fds].fd = fd_data[i];
+ pfd[fds++].events = 0;
+ }
+ }
+ Unlock();
+
+ int err = poll(pfd,fds,1000);
+
+ if(err < 0) {
+ LOGERR("cXinelibServer: poll failed");
+ if(!GetStopSignal())
+ cCondWait::SleepMs(100);
+
+ } else if(err == 0) {
+ // poll timeout
+
+ } else {
+ TRACE("cXinelibServer::Action --> select READY " << err);
+ Lock();
+ for(int f=0; f<fds; f++) {
+
+ // Check errors (closed connections etc.)
+ if(pfd[f].revents & (POLLERR|POLLHUP|POLLNVAL)) {
+
+ if(pfd[f].fd == fd_listen) {
+ LOGERR("cXinelibServer: listen socket error");
+ CLOSESOCKET(fd_listen);
+ cCondWait::SleepMs(100);
+ Listen(m_Port);
+ } /* fd_listen */
+
+ else if(pfd[f].fd == fd_discovery) {
+ LOGERR("cXinelibServer: discovery socket error");
+ CLOSESOCKET(fd_discovery);
+ } /* fd_discovery */
+
+ else /* fd_data[] / fd_control[] */ {
+ for(i=0; i<MAXCLIENTS; i++) {
+ if(pfd[f].fd == fd_data[i] || pfd[f].fd == fd_control[i]) {
+ LOGMSG("Client %d disconnected", i);
+ CloseConnection(i);
+ }
+ }
+ } /* fd_data / fd_control */
+
+ } /* Check ERRORS */
+
+ // Check ready for reading
+ else if(pfd[f].revents & POLLIN) {
+
+ // New connection
+ if(pfd[f].fd == fd_listen) {
+ int fd = accept(fd_listen, 0, 0);
+ if(fd>=0)
+ Handle_ClientConnected(fd);
+ } /* fd_listen */
+
+ // VDR Discovery
+ else if(pfd[f].fd == fd_discovery) {
+ Handle_Discovery_Broadcast();
+ } /* fd_discovery */
+
+ // Control data
+ else {
+ for(i=0; i<MAXCLIENTS; i++) {
+ if(pfd[f].fd == fd_control[i]) {
+ Read_Control(i);
+ break;
+ }
+ }
+ } /* fd_control */
+
+ } /* Check ready for reading */
+
+ } /* for(fds) */
+
+ Unlock();
+ } /* Check poll result */
+
+ Lock();
+ } /* while running */
+
+ m_bReady = false;
+ m_bIsFinished = true;
+ Unlock();
+}
diff --git a/i18n.c b/i18n.c
new file mode 100644
index 00000000..cba8eca8
--- /dev/null
+++ b/i18n.c
@@ -0,0 +1,1829 @@
+/*
+ * i18n.c: Internationalization
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: i18n.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ * Translations provided by:
+ *
+ * Finnish Petri Hintukainen, Rolf Ahrenberg
+ * Russian Vladimir Monchenko
+ *
+ */
+
+#include <vdr/config.h>
+#include "i18n.h"
+
+const tI18nPhrase Phrases[] = {
+ {
+ "X11/xine-lib output plugin", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "X11/xine-lib näyttölaite", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "X11/xine-lib ÒØÔÕÞ ÜÞÔãÛì", // Russian
+ "", // Croatian
+ },
+ {
+ "Xine-lib", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Xine-lib", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "Xine-lib", // Russian
+ "", // Croatian
+ },
+ {
+ "high", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "korkea", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "²ëáÞÚØÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "low", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "matala", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "½Ø×ÚØÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "normal", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "normaali", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "½ÞàÜÐÛìÝëÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "inverted", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "käänteinen", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¸ÝÒÕàâØàÞÒÐÝÞ", // Russian
+ "", // Croatian
+ },
+ {
+ "paused", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "pysäytetty", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿àØÞáâÐÝÞÒÛÕÝ", // Russian
+ "", // Croatian
+ },
+ {
+ "running", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "toiminnassa", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "·ÐßãéÕÝ", // Russian
+ "", // Croatian
+ },
+ {
+ "Interlaced Field Order", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Lomitettujen kenttien järjestys", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÇÕàÕ×áâàÞçÝëÙ ßÞàïÔÞÚ ßÞÛÕÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "Decoder state", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Dekooderin tila", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÁÞáâÞïÝØÕ ÔÕÚÞÔÕàÐ", // Russian
+ "", // Croatian
+ },
+ {
+ "Stop after inactivity", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Pysäytä käytön jälkeen", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¾áâÐÝÞÒØâì ßÞáÛÕ ÝÕÐÚâØÒÝÞáâØ", // Russian
+ "", // Croatian
+ },
+ {
+ "Brightness", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Kirkkaus", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÏàÚÞáâì", // Russian
+ "", // Croatian
+ },
+ {
+ "Decoder", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Dekooderi", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "´ÕÚÞÔÕà", // Russian
+ "", // Croatian
+ },
+ {
+ "Audio", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Ääni", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "°ãÔØÞ", // Russian
+ "", // Croatian
+ },
+ {
+ "On-Screen Display", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Kuvaruutunäyttö", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÍÚàÐÝÝÞÕ ÜÕÝî", // Russian
+ "", // Croatian
+ },
+ {
+ "Hide main menu", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Piilota valinta päävalikossa", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÁÚàëâì ÞáÝÞÒÝÞÕ ÜÕÝî", // Russian
+ "", // Croatian
+ },
+ {
+ "Window aspect", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Ikkunan kuvasuhde", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÁÞÞâÝÞèÕÝØÕ áâÞàÞÝ", // Russian
+ "", // Croatian
+ },
+ {
+ "Scale to window size", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Skaalaa ikkunan kokoiseksi", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¼ÐáèâÐÑØàÞÒÐâì Ò àÐ×ÜÕà ÞÚÝÐ", // Russian
+ "", // Croatian
+ },
+ {
+ "Scale OSD to video size", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Skaalaa videon kokoiseksi", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¼ÐáèâÐÑØàÞÒÐâì Ò àÐ×ÜÕà ÒØÔÕÞ", // Russian
+ "", // Croatian
+ },
+ {
+ "Unscaled OSD (no transparency)", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Skaalaamaton (ei läpinäkyvyyttä)", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "½Õ ÜÐáèâÐÑØàÞÒÐâì (ÝÕ ßàÞ×àÐçÝÞ)", // Russian
+ "", // Croatian
+ },
+ {
+ "Dynamic transparency correction", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Dynaaminen läpinäkyvyyden korjaus", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "´ØÝÐÜØçÕáÚÐï ÚÞààÕÚæØï ßàÞ×àÐçÝÞáâØ", // Russian
+ "", // Croatian
+ },
+ {
+ "Static transparency correction", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Läpinäkyvyyden korjaus", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÁâÐâØçÕáÚÐï ÚÞààÕÚæØï ßàÞ×àÐçÝÞáâØ", // Russian
+ "", // Croatian
+ },
+ {
+ "Video", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Kuva", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "²ØÔÕÞ", // Russian
+ "", // Croatian
+ },
+ {
+ "Deinterlacing", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Lomituksen poisto", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "´ÕØÝâÕàÛÕÙáØÝÓ", // Russian
+ "", // Croatian
+ },
+ {
+ "Remote Clients", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Etäkäyttö", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÃÔÐÛÕÝÝëÕ ÚÛØÕÝâë", // Russian
+ "", // Croatian
+ },
+ {
+ "Allow remote clients", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Salli etäkäyttö", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÀÐ×àÕèØâì ãÔÐÛÕÝÝëå ÚÛØÕÝâÞÒ", // Russian
+ "", // Croatian
+ },
+ {
+ " Listen port (TCP and broadcast)", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ " Kuuntele TCP-porttia", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " ¿Þàâ (TCP Ø èØàÞÚÞÒÕèÐâÕÛìÝëÙ)", // Russian
+ "", // Croatian
+ },
+ {
+ " Remote keyboard", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ " Käytä etänäppäimistöä", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " ÃÔÐÛÕÝÝÐï ÚÛÐÒØÐâãàÐ", // Russian
+ "", // Croatian
+ },
+ {
+ "Buffer size", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Puskurin koko", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÀÐ×ÜÕà ÑãäÕàÐ", // Russian
+ "", // Croatian
+ },
+ {
+ " Number of PES packets", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ " PES-pakettien lukumäärä", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " PES ßÐÚÕâÞÒ", // Russian
+ "", // Croatian
+ },
+ {
+ "Priority", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Prioriteetti", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿àØÞàØâÕâ", // Russian
+ "", // Croatian
+ },
+ {
+ "custom", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "oma", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿ÞÛì×ÞÒÐâÕÛì", // Russian
+ "", // Croatian
+ },
+ {
+ "tiny", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "olematon", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¾çÕÝì ÜÐÛÕÝìÚØÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "small", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "pieni", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¼ÐÛÕÝìÚØÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "medium", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "keskikokoinen", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÁàÕÔÝØÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "large", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "suuri", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "±ÞÛìèÞÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "huge", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "valtava", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¾çÕÝì ÑÞÛìÝÞÙ", // Russian
+ "", // Croatian
+ },
+ {
+ "Display address", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Näytön osoite", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "°ÔàÕá ÔØáßÛÕï", // Russian
+ "", // Croatian
+ },
+ {
+ "Use keyboard", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Käytä näppäimistöä", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¸áßÞÛì×ÞÒÐâì ÚÛÐÒØÐâãàã", // Russian
+ "", // Croatian
+ },
+ {
+ "Driver", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Ohjain", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "´àÐÙÒÕà", // Russian
+ "", // Croatian
+ },
+ {
+ "Port", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Portti", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿Þàâ", // Russian
+ "", // Croatian
+ },
+ {
+ "Delay", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Viive", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "·ÐÔÕàÖÚÐ", // Russian
+ "", // Croatian
+ },
+ { // min - minutes
+ "min", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "min", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "min", // Russian
+ "", // Croatian
+ },
+ { // ms -- milliseconds
+ "ms", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "ms", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ms", // Russian
+ "", // Croatian
+ },
+ { // px - pixels
+ "px", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "px", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ߨÚáÕÛÕÙ", // Russian
+ "", // Croatian
+ },
+ {
+ " Window width", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ " Ikkunan leveys", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " ÈØàØÝÐ ÞÚÝÐ", // Russian
+ "", // Croatian
+ },
+ {
+ " Window height", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ " Ikkunan korkeus", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " ²ëáÞâÐ ÞÚÝÐ", // Russian
+ "", // Croatian
+ },
+ {
+ "automatic", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "automaattinen", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "°ÒâÞÜÐâØçÕáÚØ", // Russian
+ "", // Croatian
+ },
+ {
+ "default", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "oletus", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿Þ ãÜÞÛçÐÝØî", // Russian
+ "", // Croatian
+ },
+ {
+ "4:3", // English
+ "4:3", // Deutsch
+ "4:3", // Slovenski
+ "4:3", // Italiano
+ "4:3", // Nederlands
+ "4:3", // Português
+ "4:3", // Français
+ "4:3", // Norsk
+ "4:3", // Suomi
+ "4:3", // Polski
+ "4:3", // Español
+ "4:3", // Ellinika
+ "4:3", // Svenska
+ "4:3", // Romaneste
+ "4:3", // Magyar
+ "4:3", // Catala
+ "4:3", // Russian
+ "4:3", // Croatian
+ },
+ {
+ "16:9", // English
+ "16:9", // Deutsch
+ "16:9", // Slovenski
+ "16:9", // Italiano
+ "16:9", // Nederlands
+ "16:9", // Português
+ "16:9", // Français
+ "16:9", // Norsk
+ "16:9", // Suomi
+ "16:9", // Polski
+ "16:9", // Español
+ "16:9", // Ellinika
+ "16:9", // Svenska
+ "16:9", // Romaneste
+ "16:9", // Magyar
+ "16:9", // Catala
+ "16:9", // Russian
+ "16:9", // Croatian
+ },
+ {
+ "Pan&Scan", // English
+ "Pan&Scan", // Deutsch
+ "Pan&Scan", // Slovenski
+ "Pan&Scan", // Italiano
+ "Pan&Scan", // Nederlands
+ "Pan&Scan", // Português
+ "Pan&Scan", // Français
+ "Pan&Scan", // Norsk
+ "Pan&Scan", // Suomi
+ "Pan&Scan", // Polski
+ "Pan&Scan", // Español
+ "Pan&Scan", // Ellinika
+ "Pan&Scan", // Svenska
+ "Pan&Scan", // Romaneste
+ "Pan&Scan", // Magyar
+ "Pan&Scan", // Catala
+ "Pan&Scan", // Russian
+ "Pan&Scan", // Croatian
+ },
+ {
+ "HUE", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Värisävy", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "HUE", // Russian
+ "", // Croatian
+ },
+ {
+ "Saturation", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Saturaatio", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "½ÐáëéÕÝÝÞáâì", // Russian
+ "", // Croatian
+ },
+ {
+ "Contrast", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Kontrasti", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ºÞÝâàÐáâÝÞáâì", // Russian
+ "", // Croatian
+ },
+ {
+ "off", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "ei käytössä", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "²ëÚÛ.", // Russian
+ "", // Croatian
+ },
+ {
+ "no audio", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "ei ääntä", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "½Õâ ÐãÔØÞ", // Russian
+ "", // Croatian
+ },
+ {
+ "no video", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "ei kuvaa", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "½Õâ ÒØÔÕÞ", // Russian
+ "", // Croatian
+ },
+ {
+ "Fullscreen mode", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Kokoruututila", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿ÞÛÝÞíÚàÐÝÝëÙ àÕÖØÜ", // Russian
+ "", // Croatian
+ },
+ {
+ "Local Frontend", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Paikallinen näyttö", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "»ÞÚÐÛìÝëÙ äàÞÝâÕÝÔ", // Russian
+ "", // Croatian
+ },
+ {
+ "Local Display Frontend", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Português
+ "", // Français
+ "", // Norsk
+ "Paikallinen näyttö", // Suomi
+ "", // Polski
+ "", // Español
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÄàÞÝâÕÝÔ ÛÞÚÐÛìÝÞÓÞ íÚàÐÝÐ", // Russian
+ "", // Croatian
+ },
+ {
+ "Delete image ?", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Poistetaanko kuva ?", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÃÔÐÛØâì ÚÐàâØÝÚã ?", // Russian
+ "", // Croatian
+ },
+ {
+ " TCP transport", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ " TCP-siirto", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "TCP âàÐÝáßÞàâ", // Russian
+ "", // Croatian
+ },
+ {
+ " UDP transport", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ " UDP-siirto", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "UDP âàÐÝáßÞàâ", // Russian
+ "", // Croatian
+ },
+ {
+ " RTP (multicast) transport", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ " RTP (multicast) -siirto", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " RTP (èØàÞÚÞÒÕéÐâÕÛìÝëÙ) âàÐÝáßÞàâ", // Russian
+ "", // Croatian
+ },
+ {
+ " PIPE transport", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ " PIPE-siirto", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " PIPE âàÐÝáßÞàâ", // Russian
+ "", // Croatian
+ },
+ {
+ " Server announce broadcasts", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ " Palvelimen broadcast-ilmoitukset", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " ÁÕàÒÕà ØáßÞÛì×ãÕâ èØàÞÚÞÒÕéÐÝØÕ", // Russian
+ "", // Croatian
+ },
+ {
+ "Audio Equalizer >>", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Taajuuskorjain >>", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "°ãÔØÞ íÚÒÐÛÐÙ×Õà >>", // Russian
+ "", // Croatian
+ },
+ {
+ "Audio Equalizer", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Taajuuskorjain", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "°ãÔØÞ íÚÒÐÛÐÙ×Õà", // Russian
+ "", // Croatian
+ },
+ {
+ "Grayscale", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Harmaasävy", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¾ââÕÝÚØ áÕàÞÓÞ", // Russian
+ "", // Croatian
+ },
+ {
+ "Bitmap", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Bittikartta", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "±ØâÞÒÐï ÚÐàâÐ", // Russian
+ "", // Croatian
+ },
+ {
+ "Button$Up", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Edellinen", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "", // Russian
+ "", // Croatian
+ },
+ {
+ "Button$Select", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Valitse", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "", // Russian
+ "", // Croatian
+ },
+ {
+ "Button$Info", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Tiedot", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "", // Russian
+ "", // Croatian
+ },
+ {
+ "Audio Compression", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Voimista hiljaisia ääniä", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "°ãÔØÞ ÚÞÜßàÕááØï", // Russian
+ "", // Croatian
+ },
+ {
+ "Remove letterbox (4:3 -> 16:9)", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Poista letterbox (4:3 -> 16:9)", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÃÔÐÛØâì letterbox (4:3 -> 16:9)", // Russian
+ "", // Croatian
+ },
+ {
+ "Play file >>", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Toista tiedosto >>", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿àÞØÓàÐâì äÐÙÛ >>", // Russian
+ "", // Croatian
+ },
+ {
+ "View images >>", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Katsele kuvia >>", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿àÞáÜÞâàÕâì Ø×ÞÑàÐÖÕÝØï >>", // Russian
+ "", // Croatian
+ },
+ {
+ "Play file", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Toista tiedosto", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿àÞØÓàÐâì äÐÙÛ", // Russian
+ "", // Croatian
+ },
+ {
+ "Images", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Kuvat", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¸×ÞÑàÐÖÕÝØï", // Russian
+ "", // Croatian
+ },
+ {
+ "CenterCutOut", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "CenterCutOut", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "", // Russian
+ "", // Croatian
+ },
+ {
+ "Test Images", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Testikuvat", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "ÂÕáâÞÒëÕ Ø×ÞÑàÐÖÕÝØï", // Russian
+ "", // Croatian
+ },
+ {
+ "Visualization", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Visualisointi", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "²Ø×ãÐÛØ×ÐæØï", // Russian
+ "", // Croatian
+ },
+ {
+ "Upmix stereo to 5.1", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Miksaa stereo 5.1-kanavaiseksi", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "¿àÕÞÑàÐ×ÞÒÐâì áâÕàÕÞ Ò 5.1", // Russian
+ "", // Croatian
+ },
+ {
+ "Downmix AC3 to surround", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Miksaa AC3 surroundiksi", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "", // Russian
+ "", // Croatian
+ },
+ {
+ "Framebuffer device", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "Framebuffer-laite", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "Framebuffer ãáâàÞÙáâÒÞ", // Russian
+ "", // Croatian
+ },
+ {
+ " Allow downscaling", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ " Salli skaalaus pienemmäksi", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " ¼ÐáèâÐÑØàÞÒÐâì á ßÐÔÕÝØÕÜ ÚÐçÕáâÒÐ", // Russian
+ "", // Croatian
+ },
+ {
+ " When opaque OSD", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ " Kun ei läpinäkyvä", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " ºÞÓÔÐ ÝÕßàÞ×àÐçÝÞ OSD", // Russian
+ "", // Croatian
+ },
+ {
+ " When low-res video", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ " Kun matalaresoluutioinen video", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ " ºÞÓÔÐ ÒØÔÕÞ ÝØ×ÚÞÓÞ àÐ×àÕèÕÝØï", // Russian
+ "", // Croatian
+ },
+
+#if 0
+ {
+ "", // English
+ "", // Deutsch
+ "", // Slovenski
+ "", // Italiano
+ "", // Nederlands
+ "", // Portugu<EA>s
+ "", // Fran<E7>ais
+ "", // Norsk
+ "", // Suomi
+ "", // Polski
+ "", // Espa<F1>ol
+ "", // Ellinika
+ "", // Svenska
+ "", // Romaneste
+ "", // Magyar
+ "", // Catala
+ "", // Russian
+ "", // Croatian
+ },
+#endif
+
+
+ { NULL }
+};
+
+
+
diff --git a/media_player.c b/media_player.c
new file mode 100644
index 00000000..0466915e
--- /dev/null
+++ b/media_player.c
@@ -0,0 +1,545 @@
+/*
+ * media_player.c:
+ *
+ * See the main source file '.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: media_player.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <unistd.h>
+
+#include <vdr/config.h>
+#include <vdr/status.h>
+#include <vdr/interface.h>
+
+#include "config.h"
+#include "media_player.h"
+#include "device.h"
+
+
+#if VDRVERSNUM < 10400
+// Dirty hack to bring menu back ...
+#include <vdr/remote.h>
+static void BackToMenu(void)
+{
+ static bool MagicKeyAdded = false;
+
+ if(!MagicKeyAdded) {
+ MagicKeyAdded = true;
+ cKeyMacro *m = new cKeyMacro();
+ char *tmp = strdup("User1\t@xineliboutput");
+ m->Parse(tmp);
+ free(tmp);
+ eKeys *keys = (eKeys*)m->Macro();
+ keys[0] = (eKeys)(k_Plugin|0x1000); /* replace kUser1 if it is used to something else */
+ keys[1] = k_Plugin;
+
+ KeyMacros.Add(m);
+ }
+
+ cRemote::PutMacro((eKeys)(k_Plugin|0x1000));
+}
+#else
+static void BackToMenu(void)
+{
+ cRemote::CallPlugin("xineliboutput");
+}
+#endif
+
+
+//
+// cXinelibPlayer
+//
+
+class cXinelibPlayer : public cPlayer {
+ private:
+ char *m_File;
+ char *m_ResumeFile;
+
+ protected:
+ virtual void Activate(bool On);
+
+ public:
+ cXinelibPlayer(const char *file);
+ virtual ~cXinelibPlayer();
+};
+
+cXinelibPlayer::cXinelibPlayer(const char *file)
+{
+ m_File = strdup(file);
+ m_ResumeFile = NULL;
+ asprintf(&m_ResumeFile, "%s.resume", m_File);
+}
+
+cXinelibPlayer::~cXinelibPlayer()
+{
+ Activate(false);
+ Detach();
+
+ free(m_File);
+ m_File = NULL;
+ free(m_ResumeFile);
+ m_ResumeFile = NULL;
+}
+
+void cXinelibPlayer::Activate(bool On)
+{
+ int pos = 0, fd=-1;
+ if(On) {
+ if(0 <= (fd = open(m_ResumeFile,O_RDONLY))) {
+ read(fd, &pos, sizeof(int));
+ close(fd);
+ }
+ cXinelibDevice::Instance().PlayFile(m_File, pos);
+ } else {
+ pos = cXinelibDevice::Instance().PlayFileCtrl("GETPOS");
+ if(pos>=0 && strcasecmp(m_File+strlen(m_File)-4,".ram")) {
+ if(0 <= (fd = open(m_ResumeFile, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) {
+ write(fd, &pos, sizeof(int));
+ close(fd);
+ } else {
+ Skins.QueueMessage(mtInfo, "Error writing resume position !", 5, 30);
+ }
+ }
+ cXinelibDevice::Instance().PlayFile(NULL,0);
+ }
+}
+
+//
+// cXinelibPlayerControl
+//
+
+#include <vdr/skins.h>
+
+cXinelibPlayer *cXinelibPlayerControl::m_Player = NULL;
+cMutex cXinelibPlayerControl::m_Lock;
+int cXinelibPlayerControl::m_SubtitlePos = 0;
+
+cXinelibPlayerControl::cXinelibPlayerControl(const char *File) :
+ cControl(OpenPlayer(File))
+{
+ char *pt;
+ m_DisplayReplay = NULL;
+ m_ShowModeOnly = true;
+ m_Speed = 1;
+ m_File = strdup(File);
+ if(NULL != (pt=strrchr(m_File,'/')))
+ strcpy(m_File, pt+1);
+ if(NULL != (pt=strrchr(m_File,'.')))
+ *pt = 0;
+
+#if VDRVERSNUM < 10338
+ cStatus::MsgReplaying(this, m_File);
+#else
+ cStatus::MsgReplaying(this, m_File, File, true);
+#endif
+}
+
+cXinelibPlayerControl::~cXinelibPlayerControl()
+{
+ if(m_DisplayReplay)
+ delete m_DisplayReplay;
+ m_DisplayReplay = NULL;
+
+#if VDRVERSNUM < 10338
+ cStatus::MsgReplaying(this, NULL);
+#else
+ cStatus::MsgReplaying(this, NULL, NULL, false);
+#endif
+ Close();
+ free(m_File);
+}
+
+cXinelibPlayer *cXinelibPlayerControl::OpenPlayer(const char *File)
+{
+ m_Lock.Lock();
+ if(!m_Player)
+ m_Player = new cXinelibPlayer(File);
+ m_Lock.Unlock();
+ return m_Player;
+}
+
+void cXinelibPlayerControl::Close(void)
+{
+ m_Lock.Lock();
+ if(m_Player)
+ delete m_Player;
+ m_Player = NULL;
+ m_Lock.Unlock();
+}
+
+void cXinelibPlayerControl::Show()
+{
+ bool Play = (m_Speed!=0), Forward = true;
+ int Speed = -1;
+
+ if(!m_DisplayReplay) {
+ m_DisplayReplay = Skins.Current()->DisplayReplay(m_ShowModeOnly);
+ }
+
+ if(!m_ShowModeOnly) {
+ char t[128] = "";
+ int Current, Total;
+ Current = cXinelibDevice::Instance().PlayFileCtrl("GETPOS");
+ Total = cXinelibDevice::Instance().PlayFileCtrl("GETLENGTH");
+ m_DisplayReplay->SetTitle(m_File);
+ m_DisplayReplay->SetProgress(Current, Total);
+ sprintf(t, "%d:%02d:%02d", Total/3600, (Total%3600)/60, Total%60);
+ m_DisplayReplay->SetTotal( t );
+ sprintf(t, "%d:%02d:%02d", Current/3600, (Current%3600)/60, Current%60);
+ m_DisplayReplay->SetCurrent( t );
+ }
+
+ m_DisplayReplay->SetMode(Play, Forward, Speed);
+ m_DisplayReplay->Flush();
+}
+
+void cXinelibPlayerControl::Hide()
+{
+ if(m_DisplayReplay) {
+ delete m_DisplayReplay;
+ m_DisplayReplay = NULL;
+ }
+}
+
+eOSState cXinelibPlayerControl::ProcessKey(eKeys Key)
+{
+ if (cXinelibDevice::Instance().EndOfStreamReached()) {
+ Hide();
+ return osEnd;
+ }
+
+ if (m_DisplayReplay)
+ Show();
+
+ int r;
+ char *tmp = NULL;
+ switch(Key) {
+ case kBack: xc.main_menu_mode = ShowFiles;
+ Hide();
+ Close();
+ BackToMenu();
+ return osEnd;
+ case kStop:
+ case kBlue: Hide();
+ Close();
+ return osEnd;
+ case kRed: r = cXinelibDevice::Instance().PlayFileCtrl("SEEK 0"); break;
+ case kGreen: r = cXinelibDevice::Instance().PlayFileCtrl("SEEK -60"); break;
+ case kYellow: r = cXinelibDevice::Instance().PlayFileCtrl("SEEK +60"); break;
+ //case k1: r = cXinelibDevice::Instance().PlayFileCtrl("SEEK -600"); break;
+ //case k4: r = cXinelibDevice::Instance().PlayFileCtrl("SEEK +600"); break;
+ case k1:
+ case kUser8: r = cXinelibDevice::Instance().PlayFileCtrl("SEEK -20"); break;
+ case k3:
+ case kUser9: r = cXinelibDevice::Instance().PlayFileCtrl("SEEK +20"); break;
+ case k2: m_SubtitlePos -= 10;
+ case k5: m_SubtitlePos += 5;
+ asprintf(&tmp,"SUBTITLES %d",m_SubtitlePos);
+ r = cXinelibDevice::Instance().PlayFileCtrl(tmp);
+ free(tmp);
+ break;
+ case kDown:
+ case kPause: if(m_Speed != 0) {
+ r = cXinelibDevice::Instance().PlayFileCtrl("TRICKSPEED 0");
+ if(!m_DisplayReplay)
+ m_ShowModeOnly = true;
+ m_Speed = 0;
+ Show();
+ break;
+ }
+ // fall thru
+ /*case kUp:*/
+ case kPlay: r = cXinelibDevice::Instance().PlayFileCtrl("TRICKSPEED 1");
+ m_Speed = 1;
+ if(m_ShowModeOnly && m_DisplayReplay)
+ Hide();
+ else if(m_DisplayReplay)
+ Show();
+ m_ShowModeOnly = false;
+ break;
+ case kOk: if(m_DisplayReplay) {
+ if(m_Speed==0) {
+ Hide();
+ m_ShowModeOnly = !m_ShowModeOnly;
+ Show();
+ } else if(m_ShowModeOnly) {
+ Hide();
+ } else {
+ Hide();
+ m_ShowModeOnly = true;
+ Show();
+ }
+ } else {
+ m_ShowModeOnly = false;
+ Show();
+ }
+ break;
+ default: break;
+ }
+
+ return osContinue;
+}
+
+//
+// cXinelibImagePlayer
+//
+
+class cXinelibImagePlayer : public cPlayer {
+ private:
+ char *m_File;
+ bool m_Active;
+
+ protected:
+ virtual void Activate(bool On);
+
+ public:
+ cXinelibImagePlayer(const char *file);
+ virtual ~cXinelibImagePlayer();
+
+ bool ShowImage(char *file);
+};
+
+cXinelibImagePlayer::cXinelibImagePlayer(const char *file)
+{
+ m_File = strdup(file);
+ m_Active = false;
+}
+
+cXinelibImagePlayer::~cXinelibImagePlayer()
+{
+ Activate(false);
+ Detach();
+
+ free(m_File);
+ m_File = NULL;
+}
+
+void cXinelibImagePlayer::Activate(bool On)
+{
+ if(On) {
+ m_Active = true;
+ cXinelibDevice::Instance().PlayFile(m_File, 0, true);
+ } else {
+ m_Active = false;
+ cXinelibDevice::Instance().PlayFile(NULL,0);
+ }
+}
+
+bool cXinelibImagePlayer::ShowImage(char *file)
+{
+ free(m_File);
+ m_File = strdup(file);
+ if(m_Active)
+ return cXinelibDevice::Instance().PlayFile(m_File, 0, true);
+ return true;
+}
+
+
+//
+// cXinelibImagesControl
+//
+
+cXinelibImagePlayer *cXinelibImagesControl::m_Player = NULL;
+cMutex cXinelibImagesControl::m_Lock;
+
+cXinelibImagesControl::cXinelibImagesControl(char **Files, int Index, int Count) :
+ cControl(OpenPlayer(Files[Index]))
+{
+ m_DisplayReplay = NULL;
+ m_Files = Files;
+ m_File = NULL;
+ m_Index = Index;
+ m_Count = Count;
+ m_Speed = 0;
+ m_ShowModeOnly = false;
+
+ Seek(0);
+}
+
+cXinelibImagesControl::~cXinelibImagesControl()
+{
+ if(m_DisplayReplay)
+ delete m_DisplayReplay;
+ m_DisplayReplay = NULL;
+
+#if VDRVERSNUM < 10338
+ cStatus::MsgReplaying(this, NULL);
+#else
+ cStatus::MsgReplaying(this, NULL, NULL, false);
+#endif
+ Close();
+
+ if(m_Files) {
+ int i=0;
+ while(m_Files[i]) {
+ free(m_Files[i]);
+ m_Files[i] = NULL;
+ i++;
+ }
+ delete [] m_Files;
+ m_Files = NULL;
+ }
+}
+
+cXinelibImagePlayer *cXinelibImagesControl::OpenPlayer(const char *File)
+{
+ m_Lock.Lock();
+ if(!m_Player)
+ m_Player = new cXinelibImagePlayer(File);
+ m_Lock.Unlock();
+ return m_Player;
+}
+
+void cXinelibImagesControl::Close(void)
+{
+ m_Lock.Lock();
+ if(m_Player)
+ delete m_Player;
+ m_Player = NULL;
+ m_Lock.Unlock();
+}
+
+void cXinelibImagesControl::Delete(void)
+{
+ if(Interface->Confirm(tr("Delete image ?"))) {
+ if(!unlink(m_Files[m_Index])) {
+ free(m_Files[m_Index]);
+ for(int i=m_Index; i<m_Count; i++)
+ m_Files[i] = m_Files[i+1];
+ m_Count--;
+ m_Files[m_Count] = NULL;
+ Seek(0);
+ }
+ }
+}
+
+void cXinelibImagesControl::Seek(int Rel)
+{
+ if(m_Index == m_Count-1 && Rel>0)
+ m_Index = 0;
+ else if(m_Index == 0 && Rel<0)
+ m_Index = m_Count-1;
+ else
+ m_Index += Rel;
+
+ if(m_Index < 0)
+ m_Index = 0;
+ else if(m_Index >= m_Count)
+ m_Index = m_Count;
+
+ char *pt;
+ free(m_File);
+ m_File = strdup(m_Files[m_Index]);
+ if(NULL != (pt=strrchr(m_File,'/')))
+ strcpy(m_File, pt+1);
+ if(NULL != (pt=strrchr(m_File,'.')))
+ *pt = 0;
+
+#if VDRVERSNUM < 10338
+ cStatus::MsgReplaying(this, m_Files[m_Index]);
+#else
+ cStatus::MsgReplaying(this, m_File, m_Files[m_Index], true);
+#endif
+
+ m_Player->ShowImage(m_Files[m_Index]);
+ m_LastShowTime = time(NULL);
+ strcpy(xc.browse_images_dir, m_Files[m_Index]);
+}
+
+void cXinelibImagesControl::Show(void)
+{
+ bool Play = (m_Speed!=0), Forward = m_Speed>=0;
+ int Speed = abs(m_Speed);
+
+ if(!m_DisplayReplay) {
+ m_DisplayReplay = Skins.Current()->DisplayReplay(m_ShowModeOnly);
+ }
+
+ if(!m_ShowModeOnly) {
+ char t[128] = "";
+ m_DisplayReplay->SetTitle(m_File);
+ m_DisplayReplay->SetProgress(m_Index, m_Count);
+ sprintf(t, "%d", m_Count);
+ m_DisplayReplay->SetTotal( t );
+ sprintf(t, "%d", m_Index+1);
+ m_DisplayReplay->SetCurrent( t );
+ }
+
+ m_DisplayReplay->SetMode(Play, Forward, Speed);
+ m_DisplayReplay->Flush();
+}
+
+void cXinelibImagesControl::Hide(void)
+{
+ if(m_DisplayReplay) {
+ delete m_DisplayReplay;
+ m_DisplayReplay = NULL;
+ }
+}
+
+eOSState cXinelibImagesControl::ProcessKey(eKeys Key)
+{
+ switch(Key) {
+ case kBack: xc.main_menu_mode = ShowImages;
+ Hide();
+ Close();
+ BackToMenu();
+ //return osPlugin;
+ return osEnd;
+ case kYellow: Delete();
+ break;
+ case kStop:
+ case kBlue: Hide();
+ Close();
+ return osEnd;
+ case kLeft: Seek(-5);
+ break;
+ case kRight: Seek(5);
+ break;
+ case kUp: Seek(1);
+ break;
+ case kDown: Seek(-1);
+ break;
+ case kPause: m_Speed = 0;
+ break;
+ case kPlay: m_Speed = 2;
+ break;
+ case kFastFwd: m_Speed++;
+ break;
+ case kFastRew: m_Speed--;
+ break;
+ case kOk: if(m_DisplayReplay) {
+ if(m_ShowModeOnly) {
+ Hide();
+ m_ShowModeOnly = false;
+ Show();
+ } else {
+ Hide();
+ }
+ } else {
+ m_ShowModeOnly = true;
+ Show();
+ }
+ break;
+ default: break;
+ }
+
+ static const int Speed2Time[] = {0,5,3,1};
+ if(m_Speed > 3)
+ m_Speed = 3;
+ if(m_Speed < -3)
+ m_Speed = -3;
+
+ if(Key == kNone && m_Speed != 0) {
+ if(m_LastShowTime + Speed2Time[m_Speed<0 ? -m_Speed : m_Speed] <= time(NULL))
+ Seek(sgn(m_Speed));
+ }
+
+ if (m_DisplayReplay)
+ Show();
+
+ return osContinue;
+}
diff --git a/menu.c b/menu.c
new file mode 100644
index 00000000..991d68e8
--- /dev/null
+++ b/menu.c
@@ -0,0 +1,730 @@
+/*
+ * menu.c: Main Menu
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: menu.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <dirent.h>
+
+#include <vdr/config.h>
+#include <vdr/i18n.h>
+#include <vdr/interface.h>
+#include <vdr/menu.h>
+#include <vdr/plugin.h>
+
+#include "config.h"
+#include "menu.h"
+#include "menuitems.h"
+#include "device.h"
+#ifdef ENABLE_SUSPEND
+# ifdef SUSPEND_BY_PLAYER
+# include "dummy_player.h"
+# endif
+#endif
+#include "media_player.h"
+#include "equalizer.h"
+
+//--------------------------- cMenuBrowseFiles -------------------------------
+
+class cMenuBrowseFiles : public cOsdMenu
+{
+ protected:
+ char *m_CurrentDir;
+ bool m_Images, m_Preview;
+ char *m_ConfigLastDir;
+
+ virtual bool ScanDir(const char *DirName);
+ virtual eOSState Open(bool Parent=false);
+ virtual eOSState Delete(void);
+ virtual eOSState Info(void);
+ virtual void Set(void);
+ virtual void SetHelpButtons(void);
+ cFileListItem *GetCurrent() { return (cFileListItem *)Get(Current()); }
+ void StoreConfig(void);
+
+ public:
+ cMenuBrowseFiles(const char *title, bool images = false, bool preview = false);
+ ~cMenuBrowseFiles();
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+static char *ParentDir(const char *dir)
+{
+ char *result = strdup(dir);
+ char *pt = strrchr(result, '/');
+ if(pt)
+ *(pt+1)=0;
+ if(pt != result)
+ *pt = 0;
+ return result;
+}
+
+static char *LastDir(const char *dir)
+{
+ char *pt = strrchr(dir, '/');
+ if(pt && pt[0] && pt[1])
+ return strdup(pt+1);
+ return NULL;
+}
+
+cMenuBrowseFiles::cMenuBrowseFiles(const char *title, bool images, bool preview) :
+ cOsdMenu(title, 2, 4)
+{
+ m_CurrentDir = NULL;
+ m_Images = images;
+ m_Preview = preview;
+ m_ConfigLastDir = (!m_Images) ? xc.browse_files_dir : xc.browse_images_dir;
+ Set();
+}
+
+cMenuBrowseFiles::~cMenuBrowseFiles()
+{
+ Setup.Save();
+ free(m_CurrentDir);
+}
+
+void cMenuBrowseFiles::Set(void)
+{
+ Clear();
+
+ if(!m_CurrentDir)
+ m_CurrentDir = strdup(m_ConfigLastDir);
+
+ // find deepest accessible directory from path
+ while(!ScanDir(m_CurrentDir) && strlen(m_CurrentDir) > 1) {
+ char *n = ParentDir(m_CurrentDir);
+ free(m_CurrentDir);
+ m_CurrentDir = n;
+ }
+
+ // add link to parent folder
+ if(strlen(m_CurrentDir) > 1)
+ Add(new cFileListItem("..",true));
+
+ Sort();
+
+ SetCurrent(Get(Count()>1 && strlen(m_CurrentDir)>1 ? 1 : 0));
+
+ // select last selected item
+
+ char *lastParent = ParentDir(m_ConfigLastDir);
+ if(!strncmp(m_CurrentDir,lastParent,strlen(m_CurrentDir))) {
+ char *item = LastDir(m_ConfigLastDir);
+ if(item) {
+ for(cFileListItem *it = (cFileListItem*)First(); it; it = (cFileListItem*)Next(it))
+ if(!strcmp(it->Name(),item))
+ SetCurrent(it);
+ free(item);
+ }
+ }
+ free(lastParent);
+
+ strcpy(m_ConfigLastDir, m_CurrentDir);
+ StoreConfig();
+
+ SetHelpButtons();
+}
+
+void cMenuBrowseFiles::StoreConfig(void)
+{
+ if(!m_Images)
+ cPluginManager::GetPlugin(PLUGIN_NAME_I18N)->SetupStore("BrowseFilesDir",
+ xc.browse_files_dir);
+ else
+ cPluginManager::GetPlugin(PLUGIN_NAME_I18N)->SetupStore("BrowseImagesDir",
+ xc.browse_images_dir);
+}
+
+void cMenuBrowseFiles::SetHelpButtons(void)
+{
+ bool isDir = !GetCurrent() || GetCurrent()->IsDir();
+ SetHelp(tr("Button$Select"), strlen(m_CurrentDir) > 1 ? "[..]" : NULL,
+ isDir ? NULL : tr("Button$Delete"), isDir ? NULL : tr("Button$Info"));
+ Display();
+}
+
+eOSState cMenuBrowseFiles::Delete(void)
+{
+ cFileListItem *it = GetCurrent();
+ if(!it->IsDir()) {
+ if (Interface->Confirm(tr("Delete recording?"))) {
+ char *name = NULL;
+ asprintf(&name, "%s/%s", m_CurrentDir, it->Name());
+ if(!unlink(name)) {
+ isyslog("file %s deleted", name);
+ if(!m_Images) {
+ free(name);
+ name=NULL;
+ asprintf(&name, "%s/%s.resume", m_CurrentDir, it->Name());
+ unlink(name);
+ }
+ cOsdMenu::Del(Current());
+ SetHelpButtons();
+ Display();
+ } else {
+ Skins.Message(mtError, tr("Error while deleting recording!"));
+ isyslog("Error deleting file %s", name);
+ }
+ free(name);
+ }
+ }
+ return osContinue;
+}
+
+eOSState cMenuBrowseFiles::Open(bool Parent)
+{
+ if(!GetCurrent()) {
+ return osContinue;
+ }
+
+ /* parent directory */
+ if(Parent || !strcmp("..", GetCurrent()->Name())) {
+ char *n = ParentDir(m_CurrentDir);
+ free(m_CurrentDir);
+ m_CurrentDir = n;
+ Set();
+ return osContinue;
+
+ /* directory */
+ } else if (GetCurrent()->IsDir()) {
+ const char *d = GetCurrent()->Name();
+ char *buffer = NULL;
+ asprintf(&buffer, "%s/%s", m_CurrentDir, d);
+ free(m_CurrentDir);
+ m_CurrentDir = buffer;
+ Set();
+ return osContinue;
+
+ /* regular file */
+ } else {
+ char *f = NULL;
+ asprintf(&f, "%s/%s", m_CurrentDir, GetCurrent()->Name());
+ strcpy(m_ConfigLastDir, f);
+ StoreConfig();
+ if(!m_Images) {
+ /* video/audio */
+ cControl::Launch(new cXinelibPlayerControl(f));
+ } else {
+ /* image */
+ char **files = new char*[Count()+1];
+ int i=0, index = 0;
+ memset(files, 0, sizeof(char*)*(Count()+1));
+ for(cFileListItem *it = (cFileListItem*)First(); it; it=(cFileListItem*)Next(it)) {
+ if(it==Get(Current()))
+ index = i;
+ if(!it->IsDir())
+ asprintf(&files[i++], "%s/%s", m_CurrentDir, it->Name());
+ }
+ cControl::Launch(new cXinelibImagesControl(files, index, i));
+ }
+ free(f);
+ return osEnd;
+ }
+ return osContinue;
+}
+
+eOSState cMenuBrowseFiles::Info(void)
+{
+ if(GetCurrent() && !GetCurrent()->IsDir()) {
+ char *cmd=NULL, buf[4096];
+ asprintf(&cmd,"file '%s/%s'", m_CurrentDir, GetCurrent()->Name());
+ FILE *f = popen(cmd, "r");
+ free(cmd);
+ if(f) {
+ int n=0, ch;
+ while((ch = fgetc(f)) != EOF && n<4000)
+ buf[n++] = ch;
+ buf[n] = 0;
+ if(n>0) {
+ buf[n] = 0;
+ strreplace(buf, ',', '\n');
+ fclose(f);
+ return AddSubMenu(new cMenuText(GetCurrent()->Name(), buf));
+ }
+ fclose(f);
+ }
+ }
+
+ return osContinue;
+}
+
+static bool IsVideoFile(const char *fname)
+{
+ char *pos = strrchr(fname,'.');
+ if(pos) {
+ if(!strcasecmp(pos, ".avi") ||
+ !strcasecmp(pos, ".mpv") ||
+ !strcasecmp(pos, ".vob") ||
+ !strcasecmp(pos, ".vdr") ||
+ !strcasecmp(pos, ".mpg") ||
+ !strcasecmp(pos, ".mpeg")||
+ !strcasecmp(pos, ".mpa") ||
+ !strcasecmp(pos, ".mp2") ||
+ !strcasecmp(pos, ".mp3") ||
+ !strcasecmp(pos, ".mp4") ||
+ !strcasecmp(pos, ".asf") ||
+ !strcasecmp(pos, ".ram"))
+ return true;
+ }
+ return false;
+}
+
+static bool IsImageFile(const char *fname)
+{
+ char *pos = strrchr(fname,'.');
+ if(pos) {
+ if(!strcasecmp(pos, ".jpg") ||
+ !strcasecmp(pos, ".jpeg") ||
+ !strcasecmp(pos, ".gif") ||
+ !strcasecmp(pos, ".tiff") ||
+ !strcasecmp(pos, ".bmp") ||
+ !strcasecmp(pos, ".png"))
+ return true;
+ }
+ return false;
+}
+
+bool cMenuBrowseFiles::ScanDir(const char *DirName)
+{
+ DIR *d = opendir(DirName);
+ if (d) {
+ struct dirent *e;
+ while ((e = readdir(d)) != NULL) {
+ if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) {
+ char *buffer;
+ asprintf(&buffer, "%s/%s", DirName, e->d_name);
+ struct stat st;
+ if (stat(buffer, &st) == 0) {
+
+ // check symlink destination
+ if (S_ISLNK(st.st_mode)) {
+ free(buffer);
+ buffer = ReadLink(buffer);
+ if (!buffer)
+ continue;
+ if (stat(buffer, &st) != 0) {
+ free(buffer);
+ continue;
+ }
+ }
+
+ // folders
+ if (S_ISDIR(st.st_mode)) {
+ if(m_Images)
+ Add(new cFileListItem(e->d_name,true));
+ else
+ Add(new cFileListItem(e->d_name,true,false,false));
+
+ // regular files
+ } else {
+ // video/audio
+ if (!m_Images && IsVideoFile(buffer)) {
+ bool resume=false, subs=false;
+ free(buffer);
+ buffer=NULL;
+ asprintf(&buffer, "%s/%s.resume", DirName, e->d_name);
+ if (stat(buffer, &st) == 0)
+ resume=true;
+ *strrchr(buffer,'.')=0;
+ strcpy(strrchr(buffer,'.'), ".sub");
+ if (stat(buffer, &st) == 0)
+ subs=true;
+ strcpy(strrchr(buffer,'.'), ".srt");
+ if (stat(buffer, &st) == 0)
+ subs=true;
+ Add(new cFileListItem(e->d_name,false,resume,subs));
+
+ // images
+ } else if(m_Images && IsImageFile(buffer)) {
+ Add(new cFileListItem(e->d_name,false));
+ }
+ }
+ }
+ free(buffer);
+ }
+ }
+ closedir(d);
+ return true;
+ }
+ return false;
+}
+
+eOSState cMenuBrowseFiles::ProcessKey(eKeys Key)
+{
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kPlay: if(!GetCurrent()->IsDir()) return Open();
+ case kOk:
+ case kRed: return Open();
+ case kGreen: return Open(true);
+ case kYellow: return Delete();
+ case kBlue: return Info();
+ default: break;
+ }
+ }
+
+ if(!HasSubMenu())
+ SetHelpButtons();
+
+ return state;
+}
+
+//--------------------------- cMenuTestImages-------------------------------
+
+#include <vdr/osdbase.h>
+
+#define OSD_W (720-2)
+#define OSD_H (576-2)
+#define OSD_X (1)
+#define OSD_Y (1)
+
+//
+// cTestGrayscale
+//
+
+class cTestGrayscale : public cOsdObject
+{
+ private:
+ cOsd *m_Osd;
+
+ public:
+ cTestGrayscale() { m_Osd = NULL; }
+ virtual ~cTestGrayscale() { delete m_Osd; }
+
+ virtual void Show();
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+void cTestGrayscale::Show()
+{
+ tArea areas [] = { { 0, 0, OSD_W/2 - 1, OSD_H - 1, 8},
+ {OSD_W/2, 0, OSD_W - 1, OSD_H - 1, 8}};
+ int i;
+
+ if(!m_Osd)
+ m_Osd = cOsdProvider::NewOsd(OSD_X, OSD_Y);
+
+ if(m_Osd) {
+ if (m_Osd->CanHandleAreas(areas, sizeof(areas) / sizeof(tArea) ) == oeOk) {
+ m_Osd->SetAreas(areas, sizeof(areas) / sizeof(tArea));
+ m_Osd->Flush();
+
+ // border
+ m_Osd->DrawRectangle(0, 0, OSD_W - 1, OSD_H - 1, 0xff000000);
+ m_Osd->DrawRectangle(1, 1, OSD_W - 2, OSD_H - 2, 0xff000000);
+
+ // background
+ m_Osd->DrawRectangle(2, 2, 2+103, OSD_H - 3, 0xffffffff);
+ m_Osd->DrawRectangle(OSD_W-2-103, 2, OSD_W-2, OSD_H - 3, 0xff000000);
+
+ for(i=0; i<0xff; i++)
+ m_Osd->DrawRectangle(2+103+2*i, 2, 2+103+2*(i+1), OSD_H - 3,
+ 0xff000000|(i*0x00010101)/*=(i<<16)|(i<<8)|(i)*/);
+ // line
+ m_Osd->DrawRectangle(1, OSD_H/2-20, OSD_W - 2, OSD_H/2, 0xffffffff);
+ m_Osd->DrawRectangle(1, OSD_H/2+1, OSD_W - 2, OSD_H/2+21, 0xff000000);
+
+ // Cross
+ for(int x=0; x<OSD_W;x++) {
+ m_Osd->DrawPixel(x, x*OSD_H/OSD_W, 0x00000000);
+ m_Osd->DrawPixel(x, OSD_H - 1 - x*OSD_H/OSD_W, 0x00000000);
+ }
+
+ // commit
+ m_Osd->Flush();
+ }
+
+ }
+}
+
+eOSState cTestGrayscale::ProcessKey(eKeys key)
+{
+ char s[32];
+ static int br = xc.brightness;
+ static int co = xc.contrast;
+ eOSState state = cOsdObject::ProcessKey(key);
+ if (state == osUnknown) {
+ switch (key & ~k_Repeat) {
+ case kOk:
+ case kBack:
+ return osEnd;
+ case kRight:
+ br += 0xffff/1024*2;
+ case kLeft:
+ br -= 0xffff/1024;
+ sprintf(s, "b %d", br);
+ m_Osd->DrawText(400, 100, s, 0xff000000, 0xffffffff, cFont::GetFont(fontSml));
+ cXinelibDevice::Instance().ConfigureVideo(xc.hue, xc.saturation, br, co);
+ m_Osd->Flush();
+ return osContinue;
+ case kUp:
+ co += 0xffff/1024*2;
+ case kDown:
+ co -= 0xffff/1024;
+ sprintf(s, "c %d", co);
+ m_Osd->DrawText(400, 130, s, 0xff000000, 0xffffffff, cFont::GetFont(fontSml));
+ cXinelibDevice::Instance().ConfigureVideo(xc.hue, xc.saturation, br, co);
+ m_Osd->Flush();
+ return osContinue;
+ }
+ }
+ return state;
+}
+
+
+//
+// cTestBitmap
+//
+
+class cTestBitmap : public cOsdObject
+{
+ private:
+ cOsd *m_Osd;
+ int bpp;
+
+ public:
+ cTestBitmap(int _bpp = 1) {
+ m_Osd = NULL;
+ if(_bpp<1) _bpp = 1;
+ if(_bpp>6) _bpp = 6;
+ bpp = 1<<_bpp;
+ }
+ virtual ~cTestBitmap() { delete m_Osd; }
+
+ virtual void Show();
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+void cTestBitmap::Show()
+{
+ tArea areas [] = {{ 0, 0, OSD_W - 1, OSD_H - 1, 8}};
+ int x, y, bit = 0;
+
+ if(!m_Osd) {
+ m_Osd = cOsdProvider::NewOsd(OSD_X, OSD_Y);
+
+ if(m_Osd) {
+ if (m_Osd->CanHandleAreas(areas, sizeof(areas) / sizeof(tArea) ) == oeOk) {
+ m_Osd->SetAreas(areas, sizeof(areas) / sizeof(tArea));
+ m_Osd->Flush();
+ }
+ }
+ }
+
+ if(m_Osd) {
+ for(x=0; x<OSD_W; x+=bpp) {
+ bit = (x/bpp) & 1;
+ for(y=0; y<OSD_H; y+=bpp) {
+ m_Osd->DrawRectangle(x, y, x+bpp, y+bpp, bit?0xffffffff:0xff000000);
+ bit = !bit;
+ }
+ }
+ // commit
+ m_Osd->Flush();
+ }
+}
+
+eOSState cTestBitmap::ProcessKey(eKeys key)
+{
+ eOSState state = cOsdObject::ProcessKey(key);
+
+ if (state == osUnknown) {
+ switch (key & ~k_Repeat) {
+ case kOk:
+ case kBack:
+ return osEnd;
+ case kRight:
+ bpp = (bpp<64) ? (bpp<<1) : 1;
+ Show();
+ return osContinue;
+ case kLeft:
+ bpp = (bpp>1) ? (bpp>>1) : 64;
+ Show();
+ return osContinue;
+ default:
+ break;
+ }
+ }
+ return state;
+}
+
+//----------------------------- cMenuXinelib ---------------------------------
+
+static cOsdItem *NewTitle(const char *s)
+{
+ char str[128];
+ cOsdItem *tmp;
+ sprintf(str,"----- %s -----", tr(s));
+ tmp = new cOsdItem(str);
+ tmp->SetSelectable(false);
+ return tmp;
+}
+
+
+const char *decoderState[] = {"running", "paused", NULL};
+extern cOsdObject *g_PendingMenuAction;
+
+cMenuXinelib::cMenuXinelib()
+{
+ field_order = xc.field_order;
+#ifdef ENABLE_SUSPEND
+ suspend = cXinelibDevice::Instance().IsSuspended();
+#endif
+ compression = xc.audio_compression;
+ headphone = xc.headphone;
+ autocrop = xc.autocrop;
+
+ Add(new cOsdItem(tr("Play file >>"), osUser1));
+ Add(new cOsdItem(tr("View images >>"), osUser2));
+#if 0
+ Add(new cOsdItem(tr("Play remote DVD >>"), osUser4));
+#endif
+#ifdef ENABLE_TEST_POSTPLUGINS
+#warning Experimental post plugins enabled !
+ Add(ctrl_headphone = new cMenuEditBoolItem(tr("Headphone audio mode"),
+ &headphone));
+ Add(ctrl_autocrop = new cMenuEditBoolItem(tr("Remove letterbox (4:3 -> 16:9)"),
+ &autocrop));
+#else
+ ctrl_headphone = NULL;
+ ctrl_autocrop = NULL;
+#endif
+
+#ifdef HAVE_XV_FIELD_ORDER
+ Add(video_ctrl_interlace_order = new cMenuEditStraI18nItem(tr("Interlaced Field Order"), &field_order, 2, xc.s_fieldOrder));
+#endif
+#ifdef ENABLE_SUSPEND
+ Add(decoder_ctrl_suspend = new cMenuEditStraI18nItem(tr("Decoder state"), &suspend, 2, decoderState));
+#endif
+ Add(audio_ctrl_compress = new cMenuEditTypedIntItem(tr("Audio Compression"),"%", &compression, 100, 500, tr("Off")));
+
+ Add(new cOsdItem(tr("Audio Equalizer >>"), osUser3));
+
+ switch(xc.main_menu_mode) {
+ case ShowFiles: AddSubMenu(new cMenuBrowseFiles(tr("Play file"))); break;
+ case ShowImages: AddSubMenu(new cMenuBrowseFiles(tr("Images"),true,true)); break;
+ default: break;
+ }
+
+ /* #warning should be separate plugin ? fbconfig / x11config */
+ Add(NewTitle("Test Images"));
+ char buf[128];
+ Add(new cOsdItem(tr("Grayscale"), osUser8));
+ sprintf(buf, "%s 1bit", tr("Bitmap"));
+ Add(new cOsdItem(buf, osUser9));
+ sprintf(buf, "%s 4bit", tr("Bitmap"));
+ Add(new cOsdItem(buf, osUser10));
+
+ xc.main_menu_mode = ShowMenu;
+}
+
+cMenuXinelib::~cMenuXinelib()
+{
+#ifdef HAVE_XV_FIELD_ORDER
+ if(xc.field_order != field_order )
+ cXinelibDevice::Instance().ConfigureWindow(xc.fullscreen, xc.width, xc.height, xc.modeswitch, xc.modeline, xc.display_aspect, xc.scale_video, xc.field_order);
+#endif
+
+ if(xc.audio_compression != compression)
+ cXinelibDevice::Instance().ConfigurePostprocessing(xc.deinterlace_method, xc.audio_delay, xc.audio_compression, xc.audio_equalizer, xc.audio_surround);
+
+ if(xc.headphone != headphone)
+ cXinelibDevice::Instance().ConfigurePostprocessing("headphone",
+ xc.headphone ? true : false);
+
+ if(xc.autocrop != autocrop)
+ cXinelibDevice::Instance().ConfigurePostprocessing("autocrop",
+ xc.autocrop ? true : false);
+}
+
+eOSState cMenuXinelib::ProcessKey(eKeys Key)
+{
+ cOsdItem *item = Get(Current());
+
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ if(HasSubMenu())
+ return state;
+
+ switch(state) {
+ case osUser1:
+ AddSubMenu(new cMenuBrowseFiles(tr("Play file")));
+ return osUnknown;
+ case osUser2:
+ AddSubMenu(new cMenuBrowseFiles(tr("Images"), true, true));
+ return osContinue;
+ case osUser3:
+ if(!g_PendingMenuAction) {
+ g_PendingMenuAction = new cEqualizer();
+ return osPlugin;
+ }
+ state = osContinue;
+ case osUser4:
+ cControl::Launch(new cXinelibPlayerControl("dvd://"));
+ return osEnd;
+ case osUser8:
+ if(!g_PendingMenuAction) {
+ g_PendingMenuAction = new cTestGrayscale();
+ return osPlugin;
+ }
+ return osContinue;
+ case osUser9:
+ if(!g_PendingMenuAction) {
+ g_PendingMenuAction = new cTestBitmap(1);
+ return osPlugin;
+ }
+ return osContinue;
+ case osUser10:
+ if(!g_PendingMenuAction) {
+ g_PendingMenuAction = new cTestBitmap(4);
+ return osPlugin;
+ }
+ return osContinue;
+ default: ;
+ }
+
+ if(Key==kLeft || Key==kRight) {
+ if(item == audio_ctrl_compress)
+ cXinelibDevice::Instance().ConfigurePostprocessing(xc.deinterlace_method, xc.audio_delay, compression, xc.audio_equalizer, xc.audio_surround);
+ else if(item == ctrl_headphone)
+ cXinelibDevice::Instance().ConfigurePostprocessing("headphone", headphone?true:false);
+ else if(item == ctrl_autocrop)
+ cXinelibDevice::Instance().ConfigurePostprocessing("autocrop", autocrop?true:false);
+#ifdef ENABLE_SUSPEND
+ else if(decoder_ctrl_suspend && item == decoder_ctrl_suspend) {
+ cXinelibDevice::Instance().Suspend(suspend);
+# ifdef SUSPEND_BY_PLAYER
+ if(suspend && !cDummyPlayerControl::IsOpen()) {
+ cControl::Launch(new cDummyPlayerControl);
+ } else {
+ cDummyPlayerControl::Close();
+ }
+# endif
+ }
+#endif
+#ifdef HAVE_XV_FIELD_ORDER
+ else if(video_ctrl_interlace_order && item == video_ctrl_interlace_order)
+ cXinelibDevice::Instance().ConfigureWindow(xc.fullscreen, xc.width, xc.height, xc.modeswitch, xc.modeline, xc.display_aspect, xc.scale_video, field_order);
+#endif
+ }
+
+ return state;
+}
+
+void cMenuXinelib::Store(void)
+{
+#ifdef HAVE_XV_FIELD_ORDER
+ xc.field_order = field_order;
+#endif
+ xc.audio_compression = compression;
+ xc.autocrop = autocrop;
+ xc.headphone = headphone;
+}
+
diff --git a/menuitems.c b/menuitems.c
new file mode 100644
index 00000000..afbc9fc8
--- /dev/null
+++ b/menuitems.c
@@ -0,0 +1,137 @@
+/*
+ * menuitems.c: New menu item types
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: menuitems.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <vdr/i18n.h>
+
+#include "menuitems.h"
+
+// --- cMenuEditTypedIntItem -------------------------------------------------
+
+cMenuEditTypedIntItem::cMenuEditTypedIntItem(const char *Name, const char *Type, int *Value, int Min, int Max, const char *ZeroText)
+:cMenuEditIntItem(Name,Value,Min,Max)
+{
+ type = strdup(Type?Type:"");
+ zeroText = ZeroText ? strdup(ZeroText) : NULL;
+ Set();
+}
+
+cMenuEditTypedIntItem::~cMenuEditTypedIntItem()
+{
+ free(type);
+ if(zeroText)
+ free(zeroText);
+}
+
+void cMenuEditTypedIntItem::Set(void)
+{
+ char buf[16];
+ if(zeroText && *value == 0) {
+ SetValue(zeroText);
+ } else {
+ snprintf(buf, sizeof(buf), "%d %s", *value, type);
+ SetValue(buf);
+ }
+}
+
+// --- cMenuEditStraI18nItem -------------------------------------------------
+
+cMenuEditStraI18nItem::cMenuEditStraI18nItem(const char *Name, int *Value, int NumStrings, const char * const *Strings)
+:cMenuEditIntItem(Name, Value, 0, NumStrings - 1)
+{
+ strings = Strings;
+ Set();
+}
+
+void cMenuEditStraI18nItem::Set(void)
+{
+ SetValue(tr(strings[*value]));
+}
+
+// --- cFileListItem -------------------------------------------------
+
+cFileListItem::cFileListItem(const char *name, bool isDir)
+{
+ m_Name = strdup(name);
+ m_IsDir = isDir;
+ m_HasResume = false;
+ m_HasSubs = false;
+ m_ShowFlags = false;
+ m_Up = m_IsDir && !strcmp(m_Name,"..");
+ Set();
+}
+
+cFileListItem::cFileListItem(const char *name, bool isDir, bool HasResume, bool HasSubs)
+{
+ m_Name = strdup(name);
+ m_IsDir = isDir;
+ m_HasResume = HasResume;
+ m_HasSubs = HasSubs;
+ m_ShowFlags = true;
+ m_Up = m_IsDir && !strcmp(m_Name,"..");
+ Set();
+}
+
+cFileListItem::~cFileListItem()
+{
+ free(m_Name);
+}
+
+void cFileListItem::Set(void)
+{
+ char *txt = NULL,*pt;
+ if(m_ShowFlags) {
+ if(m_IsDir) {
+ asprintf(&txt, "\t\t[%s] ", m_Name); // text2skin requires space at end of string to display item correctly ...
+ } else {
+ asprintf(&txt, "%c\t%c\t%s", m_HasResume?' ':'*', m_HasSubs ? 'S' : ' ', m_Name);
+ if(NULL != (pt = strrchr(txt,'.')))
+ *pt = 0;
+ }
+ } else {
+ if(m_IsDir) {
+ asprintf(&txt, "[%s] ", m_Name); // text2skin requires space at end of string to display item correctly ...
+ } else {
+ asprintf(&txt, "%s", m_Name);
+ if(NULL != (pt = strrchr(txt,'.')))
+ *pt = 0;
+ }
+ }
+ SetText(txt, false);
+}
+
+int cFileListItem::Compare(const cListObject &ListObject) const
+{
+ cFileListItem *other = (cFileListItem *)&ListObject;
+
+ if(m_IsDir && !other->m_IsDir)
+ return -1;
+ if(!m_IsDir && other->m_IsDir)
+ return 1;
+ if(m_Up && !other->m_Up)
+ return -1;
+ if(!m_Up && other->m_Up)
+ return 1;
+ return strcmp(m_Name,other->m_Name);
+}
+
+bool cFileListItem::operator< (const cListObject &ListObject)
+{
+ cFileListItem *other = (cFileListItem *)&ListObject;
+
+ if(m_IsDir && !other->m_IsDir)
+ return true;
+ if(!m_IsDir && other->m_IsDir)
+ return false;
+ if(m_Up && !other->m_Up)
+ return true;
+ if(!m_Up && other->m_Up)
+ return false;
+ return strcmp(m_Name,other->m_Name) < 0;
+}
diff --git a/mpg2c.c b/mpg2c.c
new file mode 100644
index 00000000..830ca7e0
--- /dev/null
+++ b/mpg2c.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2003-2006 Petri Hintukainen <phintuka@cc.hut.fi>
+ *
+ * This code is distributed under the terms and conditions of the
+ * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
+ *
+ * mpg2.c:
+ *
+ * $Id: mpg2c.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <stdio.h>
+
+#define LINELEN 20
+
+int main(int argc, char *argv[])
+{
+ int ch;
+ int pos=1;
+
+ if(argc != 4) {
+ printf("%s - convert binary file to C code\n\n"
+ "usage: %s variable inputfile outputfile\n",
+ argv[0],argv[0]);
+ return -1;
+ }
+
+ FILE *fi = fopen(argv[2],"rb");
+ FILE *fo = fopen(argv[3],"wt");
+ if(!fi ||!fo) {
+ printf("Error opening files\n");
+ return -1;
+ }
+ fprintf(fo, "unsigned char v_mpg_%s[] = \n \"", argv[1]);
+ while(EOF != (ch = fgetc(fi))) {
+ fprintf(fo, "\\x%02x", ch);
+ if(pos++ > LINELEN) {
+ fprintf(fo, "\"\n \"");
+ pos=1;
+ }
+ }
+ fprintf(fo, "\";\n\nint v_mpg_%s_length = sizeof(v_mpg_%s);\n\n",
+ argv[1], argv[1]);
+
+ fclose(fi);
+ fclose(fo);
+
+ return 0;
+}
diff --git a/osd.c b/osd.c
new file mode 100644
index 00000000..8ace6945
--- /dev/null
+++ b/osd.c
@@ -0,0 +1,349 @@
+/*
+ * osd.c: Xinelib On Screen Display control
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: osd.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <vdr/config.h>
+
+#include "logdefs.h"
+#include "device.h"
+#include "osd.h"
+#include "config.h"
+
+#define LIMIT_OSD_REFRESH_RATE
+
+#include "xine_osd_command.h"
+
+//extern "C" {
+//#include "xine_frontend.h"
+//} // extern "C"
+
+static inline void CmdSize(cXinelibDevice *Device, int wnd, int w=0, int h=0)
+{
+ TRACEF("xinelib_osd.c:CmdSize");
+
+ if(Device) {
+ osd_command_t osdcmd;
+ memset(&osdcmd,0,sizeof(osdcmd));
+
+ osdcmd.cmd = OSD_Size;
+ osdcmd.wnd = wnd;
+ osdcmd.w = w;
+ osdcmd.h = h;
+
+ Device->OsdCmd((void*)&osdcmd);
+ }
+}
+
+static inline void CmdClose(cXinelibDevice *Device, int wnd)
+{
+ TRACEF("xinelib_osd.c:CmdClose");
+
+ if(Device) {
+ osd_command_t osdcmd;
+ memset(&osdcmd,0,sizeof(osdcmd));
+
+ osdcmd.cmd = OSD_Close;
+ osdcmd.wnd = wnd;
+
+ Device->OsdCmd((void*)&osdcmd);
+ }
+}
+
+static inline void RleCmd(cXinelibDevice *Device, int wnd,
+ int x0, int y0, int w, int h, unsigned char *data,
+ int colors, unsigned int *palette)
+{
+ TRACEF("xinelib_osd.c:RleCmd");
+
+ if(Device) {
+
+ xine_rle_elem_t rle, *rle_p=0;
+
+ osd_command_t osdcmd;
+ int x, y, num_rle=0, rle_size=0;
+ uint8_t *c;
+ xine_clut_t clut[256];
+
+ memset(&osdcmd, 0, sizeof(osdcmd));
+ osdcmd.cmd = OSD_Set_RLE;
+ osdcmd.wnd = wnd;
+ osdcmd.x = x0;
+ osdcmd.y = y0;
+ osdcmd.w = w;
+ osdcmd.h = h;
+
+ /* apply alpha layer correction and convert ARGB -> AYCrCb */
+ if (colors) {
+ for(int c=0; c<colors; c++) {
+ int alpha = (palette[c] & 0xff000000)>>24;
+ alpha = alpha + xc.alpha_correction*alpha/100 + xc.alpha_correction_abs;
+ float R = (float)((palette[c] & 0x00ff0000)>>16);
+ float G = (float)((palette[c] & 0x0000ff00)>>8);
+ float B = (float)((palette[c] & 0x000000ff));
+ float Y = + (0.2578125 * R) + (0.50390625 * G) + (0.09765625 * B) + 16.0;
+ float CR = + (0.4375000 * R) - (0.36718750 * G) - (0.07031250 * B) + 128.0;
+ float CB = - (0.1484375 * R) - (0.28906250 * G) + (0.43750000 * B) + 128.0;
+ int y = (int)Y;
+ int cr = (int)CR;
+ int cb = (int)CB;
+ clut[c].y = y<0?0 : y>0xff?0xff : y;
+ clut[c].cb = cb<0?0 : cb>0xff?0xff : cb;
+ clut[c].cr = cr<0?0 : cr>0xff?0xff : cr;
+ clut[c].alpha = alpha<0?0 : alpha>0xff?0xff : alpha;
+ }
+ }
+
+ osdcmd.colors = colors;
+ osdcmd.palette = clut;
+
+ /* RLE compression */
+
+ rle_size = 8128;
+ rle_p = (xine_rle_elem_t*)malloc(4*rle_size);
+ osdcmd.data = rle_p;
+
+ for( y = 0; y < h; y++ ) {
+ rle.len = 0;
+ rle.color = 0;
+ c = data + y * w;
+ for( x = 0; x < w; x++, c++ ) {
+ if( rle.color != *c ) {
+ if( rle.len ) {
+ if( (num_rle + h-y+1) > rle_size ) {
+ rle_size *= 2;
+ rle_p = (xine_rle_elem_t*)realloc( osdcmd.data, 4*rle_size);
+ osdcmd.data = rle_p;
+ rle_p += num_rle;
+ }
+ *rle_p++ = rle;
+ num_rle++;
+ }
+ rle.color = *c;
+ rle.len = 1;
+ } else {
+ rle.len++;
+ }
+ }
+ *rle_p++ = rle;
+ num_rle++;
+ }
+ osdcmd.datalen = 4 * num_rle;
+
+ TRACE("xinelib_osd.c:RleCmd uncompressed="<< (w*h) <<", compressed=" << (4*num_rle));
+
+ Device->OsdCmd((void*)&osdcmd);
+
+ if(osdcmd.data)
+ free(osdcmd.data);
+ }
+}
+
+cXinelibOsd::cXinelibOsd(cXinelibDevice *Device, int x, int y)
+ : cOsd(x, y), m_IsVisible(true)
+{
+ TRACEF("cXinelibOsd::cXinelibOsd");
+ m_Device = Device;
+ m_Shown = false;
+ CmdSize(m_Device, 0, 720, 576);
+}
+
+cXinelibOsd::~cXinelibOsd()
+{
+ TRACEF("cXinelibOsd::~cXinelibOsd");
+
+ cXinelibOsdProvider::OsdClosing(this);
+ m_Lock.Lock();
+ if(m_IsVisible)
+ Hide();
+ m_Lock.Unlock();
+ cXinelibOsdProvider::OsdClosed(this);
+}
+
+eOsdError cXinelibOsd::SetAreas(const tArea *Areas, int NumAreas)
+{
+ TRACEF("cXinelibOsd::SetAreas");
+ cMutexLock ml(&m_Lock);
+
+ eOsdError Result = cOsd::SetAreas(Areas, NumAreas);
+ if(Result == oeOk)
+ m_Shown = false;
+
+ return Result;
+}
+
+eOsdError cXinelibOsd::CanHandleAreas(const tArea *Areas, int NumAreas)
+{
+ TRACEF("cXinelibOsd::CanHandleAreas");
+
+ m_Shown = false;
+ eOsdError Result = cOsd::CanHandleAreas(Areas, NumAreas);
+ if (Result == oeOk) {
+ if (NumAreas > MAX_OSD_OBJECT)
+ return oeTooManyAreas;
+ for (int i = 0; i < NumAreas; i++) {
+ if (Areas[i].bpp != 1 && Areas[i].bpp != 2 &&
+ Areas[i].bpp != 4 && Areas[i].bpp != 8)
+ return oeBppNotSupported;
+ }
+ }
+ return Result;
+}
+
+void cXinelibOsd::Flush(void)
+{
+ TRACEF("cXinelibOsd::Flush");
+
+ cMutexLock ml(&m_Lock);
+
+ cBitmap *Bitmap;
+
+ if(!m_IsVisible)
+ return;
+
+ int SendDone = 0;
+ for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++) {
+ int x1 = 0, y1 = 0, x2 = Bitmap->Width()-1, y2 = Bitmap->Height()-1;
+ if (!m_Shown || Bitmap->Dirty(x1, y1, x2, y2)) {
+
+ /* XXX what if only palette has been changed ? */
+ int NumColors;
+ const tColor *Colors = Bitmap->Colors(NumColors);
+ RleCmd(m_Device, i,
+ Left() + Bitmap->X0(), Top() + Bitmap->Y0(),
+ Bitmap->Width(), Bitmap->Height(),
+ (unsigned char *)Bitmap->Data(0,0),
+ NumColors, (unsigned int *)Colors);
+ SendDone++;
+ }
+ Bitmap->Clean();
+ }
+
+#ifdef LIMIT_OSD_REFRESH_RATE
+ if(SendDone) {
+ static int64_t last_refresh = 0LL;
+ int64_t now = cTimeMs::Now();
+ if(now - last_refresh < 100LL) {
+ /* too fast refresh rate, delay ... */
+ cCondWait::SleepMs(100);
+#if 0
+ LOGDBG("cXinelibOsd::Flush: OSD refreshing too fast ! (>10Hz) -> Sleeping 100ms");
+#endif
+ }
+ last_refresh = cTimeMs::Now();
+ }
+#endif
+
+#ifdef YAEPG_PATCH
+ // yaepg
+ if(!m_Shown && vidWin.bpp != 0) {
+ LOGDBG("yaepg vidWin %d %d %d %d\n",
+ vidWin.x1, vidWin.y1, vidWin.x2, vidWin.y2);
+ fflush(stdout);
+ }
+#endif
+
+ m_Shown = true;
+}
+
+void cXinelibOsd::Refresh(void)
+{
+ TRACEF("cXinelibOsd::Refresh");
+
+ cMutexLock ml(&m_Lock);
+
+ m_Shown = false;
+ Flush();
+}
+
+void cXinelibOsd::Show(void)
+{
+ TRACEF("cXinelibOsd::Show");
+
+ cMutexLock ml(&m_Lock);
+
+ m_IsVisible = true;
+ Refresh();
+}
+
+void cXinelibOsd::Hide(void)
+{
+ TRACEF("cXinelibOsd::Hide");
+
+ cMutexLock ml(&m_Lock);
+
+ if(m_IsVisible) {
+ cBitmap *Bitmap;
+ m_IsVisible = false;
+ for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++)
+ CmdClose(m_Device, i);
+ }
+}
+
+
+cList<cXinelibOsd> cXinelibOsdProvider::m_OsdStack;
+cMutex cXinelibOsdProvider::m_Lock;
+
+cXinelibOsdProvider::cXinelibOsdProvider(cXinelibDevice *Device)
+ : m_Device(Device)
+{
+}
+
+cXinelibOsdProvider::~cXinelibOsdProvider()
+{
+ if(m_OsdStack.First())
+ LOGMSG("cXinelibOsdProvider: OSD open while OSD provider shutting down !");
+}
+
+cOsd *cXinelibOsdProvider::CreateOsd(int Left, int Top)
+{
+ TRACEF("cXinelibOsdProvider::CreateOsd");
+
+ cMutexLock ml(&m_Lock);
+
+ if(m_OsdStack.First())
+ LOGDBG("cXinelibOsdProvider::CreateOsd - OSD already open !");
+
+ cXinelibOsd *m_OsdInstance = new cXinelibOsd(m_Device, Left, Top);
+
+ if(m_OsdStack.First())
+ m_OsdStack.First()->Hide();
+
+ m_OsdStack.Ins(m_OsdInstance);
+
+ return m_OsdInstance;
+}
+
+void cXinelibOsdProvider::OsdClosed(cXinelibOsd *Osd)
+{
+ TRACEF("cXinelibOsdProvider::OsdClosed");
+// m_Lock.Lock(); Atomic with OsdClosing
+ if(m_OsdStack.First())
+ m_OsdStack.First()->Show();
+ m_Lock.Unlock();
+}
+
+void cXinelibOsdProvider::OsdClosing(cXinelibOsd *Osd)
+{
+ TRACEF("cXinelibOsdProvider::OsdClosing");
+ m_Lock.Lock();
+ m_OsdStack.Del(Osd,false);
+// m_Lock.Unlock(); Atomic with OsdClosed
+}
+
+void cXinelibOsdProvider::RefreshOsd(void)
+{
+ TRACEF("cXinelibOsdProvider::RefreshOsd");
+ cMutexLock ml(&m_Lock);
+
+ if(m_OsdStack.First())
+ m_OsdStack.First()->Refresh();
+}
+
+
+
diff --git a/setup_menu.c b/setup_menu.c
new file mode 100644
index 00000000..baabdad2
--- /dev/null
+++ b/setup_menu.c
@@ -0,0 +1,1119 @@
+/*
+ * setup_menu.c: Setup Menu
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: setup_menu.c,v 1.1 2006-06-03 10:01:17 phintuka Exp $
+ *
+ */
+
+#include <vdr/config.h>
+#include <vdr/i18n.h>
+#include <vdr/plugin.h>
+
+#include "setup_menu.h"
+#include "device.h"
+#include "menuitems.h"
+#include "config.h"
+
+namespace XinelibOutputSetupMenu {
+
+//#define INTEGER_CONFIG_VIDEO_CONTROLS
+//#define LINEAR_VIDEO_CONTROLS
+
+//--- Setup Menu -------------------------------------------------------------
+
+const char *ModeLineChars =
+ " 0123456789+-hvsync.";
+const char *DriverNameChars =
+ " abcdefghijklmnopqrstuvwxyz0123456789-.,#~:;";
+const char *OptionsChars =
+ "=.,abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+const char *controls[] =
+ { "Off",
+ "[|---------------]","[|---------------]",
+ "[-|--------------]","[-|--------------]",
+ "[--|-------------]","[--|-------------]",
+ "[---|------------]","[---|------------]",
+ "[----|-----------]","[----|-----------]",
+ "[-----|----------]","[-----|----------]",
+ "[------|---------]","[------|---------]",
+ "[-------|--------]","[-------|--------]",
+ "[--------|-------]","[--------|-------]",
+ "[---------|------]","[---------|------]",
+ "[----------|-----]","[----------|-----]",
+ "[-----------|----]","[-----------|----]",
+ "[------------|---]","[------------|---]",
+ "[-------------|--]","[-------------|--]",
+ "[--------------|-]","[--------------|-]",
+ "[---------------|]","[---------------|]",
+ NULL
+ };
+
+#ifdef LINEAR_VIDEO_CONTROLS
+# define CONTROL_TO_INDEX(val) ((val)>=0 ? ((val)>>11)+1 : 0)
+# define INDEX_TO_CONTROL(ind) ((ind)==0 ? -1 : ((ind)-1)<<11)
+#else
+const int ind2ctrl_tbl[33] = {
+ -1, 0, 0x0001, 0x0002, 0x0003, 0x0004, 0x0007, 0x000a,
+ 0x000f, 0x0014, 0x001f, 42, 0x003f, 80, 0x007f, 170,
+ 0x00ff, 336, 0x01ff, 682, 0x03ff, 1630, 0x07ff, 2730,
+ 0x0fff, 5726, 0x1fff, 10858, 0x3fff, 22110, 0x7fff, 43224,
+ 0xffff };
+static int CONTROL_TO_INDEX(int val)
+{
+ for(int i=0; i<33;i++)
+ if(val<=ind2ctrl_tbl[i])
+ return i;
+ return 32;
+}
+static int INDEX_TO_CONTROL(int ind)
+{
+ if(ind<0) ind=0;
+ if(ind>32) ind=32;
+ return ind2ctrl_tbl[ind];
+}
+#endif
+
+static cOsdItem *NewTitle(const char *s)
+{
+ char str[128];
+ cOsdItem *tmp;
+ sprintf(str,"----- %s -----", tr(s));
+ tmp = new cOsdItem(str);
+ tmp->SetSelectable(false);
+ return tmp;
+}
+
+//--- cMenuSetupAudio --------------------------------------------------------
+
+class cMenuSetupAudio : public cMenuSetupPage
+{
+ private:
+ config_t newconfig;
+ int audio_driver;
+ int visualization;
+
+ cOsdItem *audio_driver_item;
+ cOsdItem *audio_ctrl_delay;
+ cOsdItem *audio_ctrl_compression;
+ cOsdItem *audio_ctrl_upmix;
+ cOsdItem *audio_ctrl_surround;
+ cOsdItem *audio_ctrl_headphone;
+ cMenuEditItem *audio_port_item;
+
+ protected:
+ virtual void Store(void);
+ void Set(void);
+
+ public:
+ cMenuSetupAudio(void);
+ ~cMenuSetupAudio(void);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+cMenuSetupAudio::cMenuSetupAudio(void)
+{
+ memcpy(&newconfig, &xc, sizeof(config_t));
+ audio_driver = strstra(xc.audio_driver,
+ xc.s_audioDrivers,
+ 0);
+ visualization = strstra(xc.audio_visualization,
+ xc.s_audioVisualizations,
+ 0);
+ Set();
+}
+
+cMenuSetupAudio::~cMenuSetupAudio(void)
+{
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ xc.deinterlace_method, xc.audio_delay, xc.audio_compression,
+ xc.audio_equalizer, xc.audio_surround);
+#ifdef ENABLE_TEST_POSTPLUGINS
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ "upmix", xc.audio_upmix ? true : false, NULL);
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ "headphone", xc.headphone ? true : false, NULL);
+#endif
+}
+
+void cMenuSetupAudio::Set(void)
+{
+ SetPlugin(cPluginManager::GetPlugin(PLUGIN_NAME_I18N));
+ int current = Current();
+ Clear();
+
+ Add(NewTitle("Audio"));
+ Add(audio_driver_item =
+ new cMenuEditStraI18nItem(tr("Driver"), &audio_driver,
+ AUDIO_DRIVER_count, xc.s_audioDriverNames));
+ if(audio_driver != AUDIO_DRIVER_AUTO && audio_driver != AUDIO_DRIVER_NONE)
+ Add(audio_port_item =
+ new cMenuEditStrItem(tr("Port"), newconfig.audio_port, 31,
+ DriverNameChars));
+ else
+ audio_port_item = NULL;
+ Add(audio_ctrl_delay =
+ new cMenuEditTypedIntItem(tr("Delay"), tr("ms"), &newconfig.audio_delay,
+ -3000, 3000, tr("Off")));
+ Add(audio_ctrl_compression =
+ new cMenuEditTypedIntItem(tr("Audio Compression"), "%",
+ &newconfig.audio_compression,
+ 100, 500, tr("Off")));
+ Add(audio_ctrl_upmix =
+ new cMenuEditBoolItem(tr("Upmix stereo to 5.1"),
+ &newconfig.audio_upmix));
+ Add(audio_ctrl_surround =
+ new cMenuEditBoolItem(tr("Downmix AC3 to surround"),
+ &newconfig.audio_upmix));
+#ifdef ENABLE_TEST_POSTPLUGINS
+ Add(audio_ctrl_headphone =
+ new cMenuEditBoolItem(tr("Mix to headphones"),
+ &newconfig.headphone));
+#else
+ audio_ctrl_headphone = NULL;
+#endif
+ Add(new cMenuEditStraI18nItem(tr("Visualization"), &visualization,
+ AUDIO_VIS_count,
+ xc.s_audioVisualizationNames));
+
+ if(current<1) current=1; /* first item is not selectable */
+ SetCurrent(Get(current));
+ Display();
+}
+
+eOSState cMenuSetupAudio::ProcessKey(eKeys Key)
+{
+ cOsdItem *item = Get(Current());
+ int val = audio_driver;
+
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ if(Key!=kLeft && Key!=kRight)
+ return state;
+
+ if(item == audio_driver_item) {
+ if(val != audio_driver) {
+ if(audio_driver == AUDIO_DRIVER_ALSA) {
+ strcpy(newconfig.audio_port, "default");
+ Set();
+ } else if(audio_driver == AUDIO_DRIVER_OSS) {
+ strcpy(newconfig.audio_port, "/dev/dsp0");
+ Set();
+ } else {
+ strcpy(newconfig.audio_port, "");
+ Set();
+ }
+ }
+ else if((audio_driver != AUDIO_DRIVER_AUTO &&
+ audio_driver != AUDIO_DRIVER_NONE) &&
+ !audio_port_item)
+ Set();
+ else if((audio_driver == AUDIO_DRIVER_AUTO ||
+ audio_driver == AUDIO_DRIVER_NONE) &&
+ audio_port_item)
+ Set();
+ }
+ else if(item == audio_ctrl_delay || item == audio_ctrl_compression ||
+ item == audio_ctrl_surround) {
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ xc.deinterlace_method, newconfig.audio_delay,
+ newconfig.audio_compression, newconfig.audio_equalizer,
+ newconfig.audio_surround);
+ }
+ else if(item == audio_ctrl_upmix) {
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ "upmix", newconfig.audio_upmix ? true : false, NULL);
+ }
+#ifdef ENABLE_TEST_POSTPLUGINS
+ else if(item == audio_ctrl_headphone) {
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ "headphone", newconfig.headphone ? true : false, NULL);
+ }
+#endif
+
+ return state;
+}
+
+
+void cMenuSetupAudio::Store(void)
+{
+ memcpy(&xc, &newconfig, sizeof(config_t));
+ strcpy(xc.audio_driver, xc.s_audioDrivers[audio_driver]);
+ strcpy(xc.audio_visualization, xc.s_audioVisualizations[visualization]);
+
+ SetupStore("Audio.Driver", xc.audio_driver);
+ SetupStore("Audio.Port", xc.audio_port);
+ SetupStore("Audio.Delay", xc.audio_delay);
+ SetupStore("Audio.Compression", xc.audio_compression);
+ SetupStore("Audio.Surround", xc.audio_surround);
+ SetupStore("Audio.Upmix", xc.audio_upmix);
+ SetupStore("Audio.Headphone", xc.headphone);
+ SetupStore("Audio.Visualization",xc.audio_visualization);
+}
+
+//--- cMenuSetupAudioEq ------------------------------------------------------
+
+class cMenuSetupAudioEq : public cMenuSetupPage
+{
+ private:
+ config_t newconfig;
+
+ protected:
+ virtual void Store(void);
+ void Set(void);
+
+ public:
+ cMenuSetupAudioEq(void);
+ ~cMenuSetupAudioEq(void);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+cMenuSetupAudioEq::cMenuSetupAudioEq(void)
+{
+ memcpy(&newconfig, &xc, sizeof(config_t));
+ Set();
+}
+
+cMenuSetupAudioEq::~cMenuSetupAudioEq(void)
+{
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ xc.deinterlace_method, xc.audio_delay, xc.audio_compression,
+ xc.audio_equalizer, xc.audio_surround);
+}
+
+void cMenuSetupAudioEq::Set(void)
+{
+ SetPlugin(cPluginManager::GetPlugin(PLUGIN_NAME_I18N));
+ int current = Current();
+ Clear();
+
+ Add(NewTitle("Audio Equalizer"));
+ for(int i=0; i<AUDIO_EQ_count; i++)
+ Add(new cMenuEditTypedIntItem(config_t::s_audioEqNames[i], "%",
+ &newconfig.audio_equalizer[i],
+ -100, 100, tr("Off")));
+
+ if(current<1) current=1; /* first item is not selectable */
+ SetCurrent(Get(current));
+ Display();
+}
+
+eOSState cMenuSetupAudioEq::ProcessKey(eKeys Key)
+{
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ if(Key == kLeft || Key == kRight) {
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ xc.deinterlace_method, xc.audio_delay, xc.audio_compression,
+ newconfig.audio_equalizer, xc.audio_surround);
+ }
+
+ return state;
+}
+
+void cMenuSetupAudioEq::Store(void)
+{
+ memcpy(&xc, &newconfig, sizeof(config_t));
+
+ char tmp[255];
+ sprintf(tmp,"%d %d %d %d %d %d %d %d %d %d",
+ xc.audio_equalizer[0], xc.audio_equalizer[1],
+ xc.audio_equalizer[2], xc.audio_equalizer[3],
+ xc.audio_equalizer[4], xc.audio_equalizer[5],
+ xc.audio_equalizer[6], xc.audio_equalizer[7],
+ xc.audio_equalizer[8], xc.audio_equalizer[9]);
+ SetupStore("Audio.Equalizer", tmp);
+}
+
+//--- cMenuSetupVideo --------------------------------------------------------
+
+class cMenuSetupVideo : public cMenuSetupPage
+{
+ private:
+ config_t newconfig;
+
+ cOsdItem *ctrl_hue;
+ cOsdItem *ctrl_saturation;
+ cOsdItem *ctrl_contrast;
+ cOsdItem *ctrl_brightness;
+
+ protected:
+ virtual void Store(void);
+ void Set(void);
+
+ public:
+ cMenuSetupVideo(void);
+ ~cMenuSetupVideo(void);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+cMenuSetupVideo::cMenuSetupVideo(void)
+{
+ memcpy(&newconfig, &xc, sizeof(config_t));
+
+ newconfig.hue = CONTROL_TO_INDEX(newconfig.hue);
+ newconfig.saturation = CONTROL_TO_INDEX(newconfig.saturation);
+ newconfig.contrast = CONTROL_TO_INDEX(newconfig.contrast);
+ newconfig.brightness = CONTROL_TO_INDEX(newconfig.brightness);
+
+ Set();
+}
+
+cMenuSetupVideo::~cMenuSetupVideo(void)
+{
+ cXinelibDevice::Instance().ConfigureVideo(xc.hue, xc.saturation,
+ xc.brightness, xc.contrast);
+}
+
+void cMenuSetupVideo::Set(void)
+{
+ SetPlugin(cPluginManager::GetPlugin(PLUGIN_NAME_I18N));
+ //int current = Current();
+ Clear();
+
+ Add(NewTitle("Video"));
+
+#ifdef INTEGER_CONFIG_VIDEO_CONTROLS
+ Add(new cMenuEditIntItem(tr("HUE"), &newconfig.hue, -1, 0xffff));
+ Add(new cMenuEditIntItem(tr("Saturation"), &newconfig.saturation,-1,0xffff));
+ Add(new cMenuEditIntItem(tr("Contrast"), &newconfig.contrast, -1, 0xffff));
+ Add(new cMenuEditIntItem(tr("Brightness"), &newconfig.brightness,-1,0xffff));
+#else
+ Add(ctrl_hue = new cMenuEditStraItem(tr("HUE"), &newconfig.hue, 33,
+ controls));
+ Add(ctrl_saturation =
+ new cMenuEditStraItem(tr("Saturation"), &newconfig.saturation, 33,
+ controls));
+ Add(ctrl_contrast =
+ new cMenuEditStraItem(tr("Contrast"), &newconfig.contrast, 33,
+ controls));
+ Add(ctrl_brightness =
+ new cMenuEditStraItem(tr("Brightness"), &newconfig.brightness, 33,
+ controls));
+#endif
+
+ //if(current<1) current=1; /* first item is not selectable */
+ //SetCurrent(Get(current));
+ SetCurrent(Get(1));
+ Display();
+}
+
+eOSState cMenuSetupVideo::ProcessKey(eKeys Key)
+{
+ cOsdItem *item = Get(Current());
+
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ if(Key!=kLeft && Key!=kRight)
+ return state;
+
+ if(item == ctrl_hue || item == ctrl_saturation ||
+ item == ctrl_contrast || item == ctrl_brightness )
+#ifdef INTEGER_CONFIG_VIDEO_CONTROLS
+ cXinelibDevice::Instance().ConfigureVideo(newconfig.hue,
+ newconfig.saturation,
+ newconfig.brightness,
+ newconfig.contrast);
+#else
+ cXinelibDevice::Instance().ConfigureVideo(
+ INDEX_TO_CONTROL(newconfig.hue),
+ INDEX_TO_CONTROL(newconfig.saturation),
+ INDEX_TO_CONTROL(newconfig.brightness),
+ INDEX_TO_CONTROL(newconfig.contrast));
+#endif
+ return state;
+}
+
+void cMenuSetupVideo::Store(void)
+{
+ memcpy(&xc, &newconfig, sizeof(config_t));
+
+#ifdef INTEGER_CONFIG_VIDEO_CONTROLS
+#else
+ xc.hue = INDEX_TO_CONTROL(xc.hue);
+ xc.saturation = INDEX_TO_CONTROL(xc.saturation);
+ xc.contrast = INDEX_TO_CONTROL(xc.contrast);
+ xc.brightness = INDEX_TO_CONTROL(xc.brightness);
+#endif
+
+ SetupStore("Video.HUE", xc.hue);
+ SetupStore("Video.Saturation", xc.saturation);
+ SetupStore("Video.Contrast", xc.contrast);
+ SetupStore("Video.Brightness", xc.brightness);
+}
+
+
+//--- cMenuSetupOSD ----------------------------------------------------------
+
+class cMenuSetupOSD : public cMenuSetupPage
+{
+ private:
+ config_t newconfig;
+
+ int orig_alpha_correction;
+ int orig_alpha_correction_abs;
+
+ cOsdItem *ctrl_alpha;
+ cOsdItem *ctrl_alpha_abs;
+ cOsdItem *ctrl_unscaled;
+ cOsdItem *ctrl_scale;
+ cOsdItem *ctrl_downscale;
+ cOsdItem *ctrl_lowres;
+
+ protected:
+ virtual void Store(void);
+ void Set(void);
+
+ public:
+ cMenuSetupOSD(void);
+ ~cMenuSetupOSD();
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+cMenuSetupOSD::cMenuSetupOSD(void)
+{
+ memcpy(&newconfig, &xc, sizeof(config_t));
+ orig_alpha_correction = xc.alpha_correction;
+ orig_alpha_correction_abs = xc.alpha_correction_abs;
+
+ Set();
+}
+
+cMenuSetupOSD::~cMenuSetupOSD()
+{
+ xc.alpha_correction = orig_alpha_correction;
+ xc.alpha_correction_abs = orig_alpha_correction_abs;
+
+ cXinelibDevice::Instance().ConfigureOSD(xc.prescale_osd, xc.unscaled_osd);
+}
+
+void cMenuSetupOSD::Set(void)
+{
+ SetPlugin(cPluginManager::GetPlugin(PLUGIN_NAME_I18N));
+ int current = Current();
+ Clear();
+
+ ctrl_scale = NULL;
+ ctrl_downscale = NULL;
+ ctrl_unscaled = NULL;
+ ctrl_lowres = NULL;
+ ctrl_alpha = NULL;
+ ctrl_alpha_abs = NULL;
+
+ Add(NewTitle("On-Screen Display"));
+ Add(new cMenuEditBoolItem(tr("Hide main menu"),
+ &newconfig.hide_main_menu));
+ Add(ctrl_scale =
+ new cMenuEditBoolItem(tr("Scale OSD to video size"),
+ &newconfig.prescale_osd));
+ if(newconfig.prescale_osd)
+ Add(ctrl_downscale =
+ new cMenuEditBoolItem(tr(" Allow downscaling"),
+ &newconfig.prescale_osd_downscale));
+ Add(ctrl_unscaled =
+ new cMenuEditBoolItem(tr("Unscaled OSD (no transparency)"),
+ &newconfig.unscaled_osd));
+ if(!newconfig.unscaled_osd) {
+ Add(new cMenuEditBoolItem(tr(" When opaque OSD"),
+ &newconfig.unscaled_osd_opaque));
+ Add(ctrl_lowres =
+ new cMenuEditBoolItem(tr(" When low-res video"),
+ &newconfig.unscaled_osd_lowresvideo));
+ }
+
+ Add(ctrl_alpha =
+ new cMenuEditTypedIntItem(tr("Dynamic transparency correction"), "%",
+ &newconfig.alpha_correction, -200, 200,
+ tr("Off")));
+ Add(ctrl_alpha_abs =
+ new cMenuEditTypedIntItem(tr("Static transparency correction"), "",
+ &newconfig.alpha_correction_abs, -0xff, 0xff,
+ tr("Off")));
+
+ if(current<1) current=1; /* first item is not selectable */
+ SetCurrent(Get(current));
+ SetCurrent(Get(1));
+ Display();
+}
+
+eOSState cMenuSetupOSD::ProcessKey(eKeys Key)
+{
+ cOsdItem *item = Get(Current());
+
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ if(Key!=kLeft && Key!=kRight)
+ return state;
+
+ if(item == ctrl_alpha)
+ xc.alpha_correction = newconfig.alpha_correction;
+ else if(item == ctrl_alpha_abs)
+ xc.alpha_correction_abs = newconfig.alpha_correction_abs;
+ else if(item == ctrl_unscaled || item == ctrl_scale)
+ cXinelibDevice::Instance().ConfigureOSD(newconfig.prescale_osd,
+ newconfig.unscaled_osd);
+
+ if(newconfig.prescale_osd && !ctrl_downscale)
+ Set();
+ if(!newconfig.prescale_osd && ctrl_downscale)
+ Set();
+ if(!newconfig.unscaled_osd && !ctrl_lowres)
+ Set();
+ if(newconfig.unscaled_osd && ctrl_lowres)
+ Set();
+
+ return state;
+}
+
+void cMenuSetupOSD::Store(void)
+{
+ memcpy(&xc, &newconfig, sizeof(config_t));
+
+ orig_alpha_correction = xc.alpha_correction;
+ orig_alpha_correction_abs = xc.alpha_correction_abs;
+
+ SetupStore("OSD.HideMainMenu", xc.hide_main_menu);
+ SetupStore("OSD.Prescale", xc.prescale_osd);
+ SetupStore("OSD.Downscale", xc.prescale_osd_downscale);
+ SetupStore("OSD.UnscaledAlways", xc.unscaled_osd);
+ SetupStore("OSD.UnscaledOpaque", xc.unscaled_osd_opaque);
+ SetupStore("OSD.UnscaledLowRes", xc.unscaled_osd_lowresvideo);
+ SetupStore("OSD.AlphaCorrection", xc.alpha_correction);
+ SetupStore("OSD.AlphaCorrectionAbs", xc.alpha_correction_abs);
+}
+
+
+//--- cMenuSetupDecoder ------------------------------------------------------
+
+class cMenuSetupDecoder : public cMenuSetupPage
+{
+ private:
+ config_t newconfig;
+
+ int pes_buffers_ind;
+
+ cOsdItem *ctrl_pes_buffers_ind;
+ cOsdItem *ctrl_pes_buffers;
+
+ protected:
+ virtual void Store(void);
+ void Set(void);
+
+ public:
+ cMenuSetupDecoder(void);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+cMenuSetupDecoder::cMenuSetupDecoder(void)
+{
+ int i;
+ memcpy(&newconfig, &xc, sizeof(config_t));
+
+ pes_buffers_ind = PES_BUFFERS_CUSTOM;
+ for(i=0;xc.s_bufferSize[i];i++)
+ if(xc.pes_buffers == xc.i_pesBufferSize[i])
+ pes_buffers_ind = i;
+
+ Set();
+}
+
+void cMenuSetupDecoder::Set(void)
+{
+ SetPlugin(cPluginManager::GetPlugin(PLUGIN_NAME_I18N));
+ int current = Current();
+ Clear();
+
+ Add(NewTitle("Decoder"));
+ Add(new cMenuEditStraI18nItem(tr("Priority"), &xc.decoder_priority,
+ DECODER_PRIORITY_count, xc.s_decoderPriority));
+#ifdef ENABLE_SUSPEND
+ Add(new cMenuEditTypedIntItem(tr("Stop after inactivity"), tr("min"),
+ &newconfig.inactivity_timer, 0, 1440,
+ tr("Off")));
+#endif
+ Add(ctrl_pes_buffers_ind =
+ new cMenuEditStraI18nItem(tr("Buffer size"), &pes_buffers_ind,
+ PES_BUFFERS_count, xc.s_bufferSize));
+ if(pes_buffers_ind == PES_BUFFERS_CUSTOM)
+ Add(ctrl_pes_buffers =
+ new cMenuEditIntItem(tr(" Number of PES packets"), &newconfig.pes_buffers,
+ 10, 10000));
+ else
+ ctrl_pes_buffers = NULL;
+
+ if(current<1) current=1; /* first item is not selectable */
+ SetCurrent(Get(current));
+ Display();
+}
+
+eOSState cMenuSetupDecoder::ProcessKey(eKeys Key)
+{
+ cOsdItem *item = Get(Current());
+
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ if(Key!=kLeft && Key!=kRight)
+ return state;
+
+ if(item == ctrl_pes_buffers_ind) {
+ if(pes_buffers_ind == PES_BUFFERS_CUSTOM && !ctrl_pes_buffers) {
+ Set();
+ } else if(pes_buffers_ind != PES_BUFFERS_CUSTOM && ctrl_pes_buffers) {
+ Set();
+ }
+ }
+
+ return state;
+}
+
+void cMenuSetupDecoder::Store(void)
+{
+ int old_buffers = xc.pes_buffers;
+ int old_priority = xc.decoder_priority;
+
+ memcpy(&xc, &newconfig, sizeof(config_t));
+
+ if(pes_buffers_ind != PES_BUFFERS_CUSTOM)
+ xc.pes_buffers = xc.i_pesBufferSize[pes_buffers_ind];
+
+ SetupStore("Decoder.InactivityTimer", xc.inactivity_timer);
+ SetupStore("Decoder.Priority", xc.s_decoderPriority[xc.decoder_priority]);
+ SetupStore("Decoder.PesBuffers", xc.pes_buffers);
+
+ if(xc.pes_buffers != old_buffers || xc.decoder_priority != old_priority)
+ cXinelibDevice::Instance().ConfigureDecoder(xc.pes_buffers,
+ xc.decoder_priority);
+}
+
+
+//--- cMenuSetupLocal --------------------------------------------------------
+
+class cMenuSetupLocal : public cMenuSetupPage
+{
+ private:
+ config_t newconfig;
+
+ int deinterlace;
+ int video_driver;
+ int local_frontend;
+
+ cOsdItem *ctrl_scale;
+ cOsdItem *ctrl_local_fe;
+ cOsdItem *ctrl_driver;
+ cOsdItem *ctrl_fullscreen;
+ cOsdItem *ctrl_window_width;
+ cOsdItem *ctrl_window_height;
+ cOsdItem *ctrl_deinterlace;
+ cOsdItem *ctrl_deinterlace_opts;
+ cOsdItem *ctrl_interlace_order;
+ cOsdItem *ctrl_aspect;
+ cOsdItem *ctrl_autocrop;
+
+ protected:
+ virtual void Store(void);
+ void Set(void);
+
+ public:
+ cMenuSetupLocal(void);
+ ~cMenuSetupLocal(void);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+cMenuSetupLocal::cMenuSetupLocal(void)
+{
+ SetPlugin(cPluginManager::GetPlugin(PLUGIN_NAME_I18N));
+
+ memcpy(&newconfig, &xc, sizeof(config_t));
+
+ local_frontend = strstra(xc.local_frontend, xc.s_frontends, 0);
+
+ video_driver = 0;
+ if(local_frontend == FRONTEND_X11)
+ video_driver = strstra(xc.video_driver, xc.s_videoDriversX11, 0);
+ if(local_frontend == FRONTEND_FB)
+ video_driver = strstra(xc.video_driver, xc.s_videoDriversFB, 0);
+
+ deinterlace = strstra(xc.deinterlace_method, xc.s_deinterlaceMethods, 0);
+
+ Set();
+}
+
+cMenuSetupLocal::~cMenuSetupLocal(void)
+{
+ cXinelibDevice::Instance().ConfigureWindow(
+ xc.fullscreen, xc.width, xc.height, xc.modeswitch, xc.modeline,
+ xc.display_aspect, xc.scale_video, xc.field_order);
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ xc.deinterlace_method, xc.audio_delay, xc.audio_compression,
+ xc.audio_equalizer, xc.audio_surround);
+#ifdef ENABLE_TEST_POSTPLUGINS
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ "autocrop", xc.autocrop ? true : false, NULL);
+#endif
+}
+
+void cMenuSetupLocal::Set(void)
+{
+ int current = Current();
+ Clear();
+
+ ctrl_autocrop = NULL;
+ ctrl_interlace_order = NULL;
+ ctrl_fullscreen = NULL;
+ ctrl_window_width = NULL;
+ ctrl_window_height = NULL;
+ ctrl_driver = NULL;
+ ctrl_aspect = NULL;
+ ctrl_scale = NULL;
+ ctrl_deinterlace_opts = NULL;
+
+ //Add(NewTitle("Video"));
+
+#ifdef ENABLE_TEST_POSTPLUGINS
+#warning move to Video menu (or enable only for local, for remote --> cmdline)
+ Add(ctrl_autocrop =
+ new cMenuEditBoolItem(tr("Crop letterbox to 16:9"),
+ &newconfig.autocrop));
+#endif
+
+ Add(NewTitle("Local Frontend"));
+ Add(ctrl_local_fe =
+ new cMenuEditStraI18nItem(tr("Local Display Frontend"), &local_frontend,
+ FRONTEND_count, xc.s_frontendNames));
+
+ if(local_frontend == FRONTEND_X11) {
+ Add(ctrl_driver =
+ new cMenuEditStraI18nItem(tr("Driver"), &video_driver,
+ X11_DRIVER_count,
+ xc.s_videoDriverNamesX11));
+ strcpy(newconfig.video_port, "127.0.0.1:0.0");
+ Add(new cMenuEditStrItem(tr("Display address"), newconfig.video_port,
+ 31, DriverNameChars));
+ Add(new cMenuEditBoolItem(tr("Use keyboard"),
+ &newconfig.use_x_keyboard));
+
+ } else if(local_frontend == FRONTEND_FB) {
+ Add(ctrl_driver =
+ new cMenuEditStraI18nItem(tr("Driver"), &video_driver,
+ FB_DRIVER_count,
+ xc.s_videoDriverNamesFB));
+ strcpy(newconfig.video_port, "/dev/fb/0");
+ Add(new cMenuEditStrItem(tr("Framebuffer device"), newconfig.video_port, 31,
+ DriverNameChars));
+ }
+#if 0
+ if(local_frontend == FRONTEND_FB || !newconfig.fullscreen) {
+ Add(new cMenuEditStrItem( "Modeline", newconfig.modeline, 31,
+ ModeLineChars));
+ Add(new cMenuEditBoolItem("Videomode switching", &xc.modeswitch));
+ }
+#endif
+
+ if(local_frontend == FRONTEND_X11) {
+ Add(ctrl_fullscreen = new cMenuEditBoolItem(tr("Fullscreen mode"),
+ &newconfig.fullscreen));
+ if(!newconfig.fullscreen) {
+ Add(ctrl_window_width =
+ new cMenuEditTypedIntItem( tr(" Window width"), tr("px"),
+ &newconfig.width, 1, 2048));
+ Add(ctrl_window_height =
+ new cMenuEditTypedIntItem( tr(" Window height"), tr("px"),
+ &newconfig.height, 1, 2048));
+ }
+ }
+
+ if(local_frontend != FRONTEND_NONE) {
+ Add(ctrl_aspect =
+ new cMenuEditStraI18nItem(tr("Window aspect"), &newconfig.display_aspect,
+ ASPECT_count, xc.s_aspects));
+ Add(ctrl_scale =
+ new cMenuEditBoolItem(tr("Scale to window size"), &newconfig.scale_video));
+
+ Add(ctrl_deinterlace =
+ new cMenuEditStraI18nItem(tr("Deinterlacing"), &deinterlace,
+ DEINTERLACE_count,
+ xc.s_deinterlaceMethodNames));
+
+ if(deinterlace == DEINTERLACE_TVTIME)
+ Add(ctrl_deinterlace_opts = new cMenuEditStrItem(tr(" Options:"),
+ newconfig.deinterlace_opts,
+ 64, OptionsChars));
+
+#ifdef HAVE_XV_FIELD_ORDER
+ if(!deinterlace)
+ Add(ctrl_interlace_order =
+ new cMenuEditStraI18nItem(tr("Interlaced Field Order"),
+ &newconfig.field_order, FIELD_ORDER_count,
+ xc.s_fieldOrder));
+#endif
+ }
+
+ if(current<1) current=1; /* first item is not selectable */
+ SetCurrent(Get(current));
+ Display();
+}
+
+eOSState cMenuSetupLocal::ProcessKey(eKeys Key)
+{
+ int prev_frontend = local_frontend;
+
+ cOsdItem *item = Get(Current());
+
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ if((Key!=kLeft && Key!=kRight) || !item)
+ return state;
+
+ if(item == ctrl_aspect || item == ctrl_scale || item == ctrl_interlace_order)
+ cXinelibDevice::Instance().ConfigureWindow(
+ xc.fullscreen, xc.width, xc.height, xc.modeswitch, xc.modeline,
+ newconfig.display_aspect, newconfig.scale_video,
+ newconfig.field_order);
+ else if(item == ctrl_local_fe && local_frontend != prev_frontend)
+ Set();
+ else if(item == ctrl_fullscreen) {
+ if(!newconfig.fullscreen && !ctrl_window_width) {
+ Set();
+ } else if(newconfig.fullscreen && ctrl_window_width) {
+ Set();
+ }
+#ifdef ENABLE_TEST_POSTPLUGINS
+ } else if(item == ctrl_autocrop) {
+ cXinelibDevice::Instance().ConfigurePostprocessing(
+ "autocrop", xc.autocrop ? true : false, NULL);
+#endif
+ } else if(item == ctrl_deinterlace) {
+ if(deinterlace == DEINTERLACE_TVTIME && !ctrl_deinterlace_opts) {
+ Set();
+ } else if(deinterlace != DEINTERLACE_TVTIME && ctrl_deinterlace_opts) {
+ Set();
+ }
+ }
+
+#ifdef HAVE_XV_FIELD_ORDER
+ else if(item == ctrl_deinterlace) {
+ if(!deinterlace && !ctrl_interlace_order) {
+ Set();
+ } else if(deinterlace && ctrl_interlace_order) {
+ Set();
+ }
+ }
+#endif
+
+ return state;
+}
+
+void cMenuSetupLocal::Store(void)
+{
+ memcpy(&xc, &newconfig, sizeof(config_t));
+
+ strcpy(xc.local_frontend, xc.s_frontends[local_frontend]);
+ if(local_frontend == FRONTEND_X11)
+ strcpy(xc.video_driver, xc.s_videoDriversX11[video_driver]);
+ if(local_frontend == FRONTEND_FB)
+ strcpy(xc.video_driver, xc.s_videoDriversFB[video_driver]);
+
+ strcpy(xc.deinterlace_method, xc.s_deinterlaceMethods[deinterlace]);
+
+ SetupStore("Frontend", xc.local_frontend);
+ SetupStore("Modeline", xc.modeline);
+ SetupStore("VideoModeSwitching", xc.modeswitch);
+ SetupStore("Fullscreen", xc.fullscreen);
+ SetupStore("DisplayAspect", xc.s_aspects[xc.display_aspect]);
+
+ SetupStore("X11.WindowWidth", xc.width);
+ SetupStore("X11.WindowHeight", xc.height);
+ SetupStore("X11.UseKeyboard", xc.use_x_keyboard);
+
+ SetupStore("Video.Driver", xc.video_driver);
+ SetupStore("Video.Port", xc.video_port);
+ SetupStore("Video.Scale", xc.scale_video);
+ SetupStore("Video.Deinterlace", xc.deinterlace_method);
+ SetupStore("Video.DeinterlaceOptions", xc.deinterlace_opts);
+
+ SetupStore("Video.FieldOrder", xc.field_order);
+ SetupStore("Video.AutoCrop", xc.autocrop);
+}
+
+//--- cMenuSetupRemote -------------------------------------------------------
+
+class cMenuSetupRemote : public cMenuSetupPage
+{
+ private:
+ config_t newconfig;
+
+ cOsdItem *ctrl_remote_mode;
+ cOsdItem *ctrl_usertp;
+ cOsdItem *ctrl_rtp_addr;
+
+ protected:
+ virtual void Store(void);
+ void Set(void);
+
+ public:
+ cMenuSetupRemote(void);
+
+ virtual eOSState ProcessKey(eKeys Key);
+};
+
+cMenuSetupRemote::cMenuSetupRemote(void)
+{
+ memcpy(&newconfig, &xc, sizeof(config_t));
+ Set();
+}
+
+void cMenuSetupRemote::Set(void)
+{
+ SetPlugin(cPluginManager::GetPlugin(PLUGIN_NAME_I18N));
+ Clear();
+
+ Add(NewTitle("Remote Clients"));
+ Add(ctrl_remote_mode = new cMenuEditBoolItem(tr("Allow remote clients"),
+ &newconfig.remote_mode));
+ ctrl_usertp = NULL;
+ ctrl_rtp_addr = NULL;
+ if(newconfig.remote_mode) {
+ Add(new cMenuEditIntItem( tr(" Listen port (TCP and broadcast)"),
+ &newconfig.listen_port,
+ 0, 0xffff));
+ Add(new cMenuEditBoolItem(tr(" Remote keyboard"),
+ &newconfig.use_remote_keyboard));
+
+ Add(new cMenuEditBoolItem(tr(" TCP transport"),
+ &newconfig.remote_usetcp));
+ Add(new cMenuEditBoolItem(tr(" UDP transport"),
+ &newconfig.remote_useudp));
+ Add(ctrl_usertp =
+ new cMenuEditBoolItem(tr(" RTP (multicast) transport"),
+ &newconfig.remote_usertp));
+ if(newconfig.remote_usertp) {
+ Add(ctrl_rtp_addr =
+ new cMenuEditStrItem( tr(" Multicast address"),
+ &newconfig.remote_rtp_addr[0], 16, "0123456789."));
+ Add(new cMenuEditIntItem( tr(" Multicast port"),
+ &newconfig.remote_rtp_port, 1000, 0xffff));
+ Add(new cMenuEditIntItem( tr(" Multicast TTL"),
+ &newconfig.remote_rtp_ttl, 1, 10));
+ Add(new cMenuEditBoolItem(tr(" Transmit always on"),
+ &newconfig.remote_rtp_always_on));
+ }
+ Add(new cMenuEditBoolItem(tr(" PIPE transport"),
+ &newconfig.remote_usepipe));
+ Add(new cMenuEditBoolItem(tr(" Server announce broadcasts"),
+ &newconfig.remote_usebcast));
+ }
+
+ Display();
+}
+
+eOSState cMenuSetupRemote::ProcessKey(eKeys Key)
+{
+ cOsdItem *item = Get(Current());
+
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ if(Key!=kLeft && Key!=kRight)
+ return state;
+
+ if(item == ctrl_remote_mode) {
+ if(newconfig.remote_mode && !ctrl_usertp) {
+ Set();
+ } else if(!newconfig.remote_mode && ctrl_usertp) {
+ Set();
+ }
+ }
+ if(item == ctrl_usertp) {
+ if(newconfig.remote_usertp && !ctrl_rtp_addr) {
+ Set();
+ } else if(!newconfig.remote_usertp && ctrl_rtp_addr) {
+ Set();
+ }
+ }
+
+ return state;
+}
+
+void cMenuSetupRemote::Store(void)
+{
+ memcpy(&xc, &newconfig, sizeof(config_t));
+
+ SetupStore("RemoteMode", xc.remote_mode);
+ SetupStore("Remote.ListenPort", xc.listen_port);
+ SetupStore("Remote.Keyboard", xc.use_remote_keyboard);
+
+ SetupStore("Remote.UseTcp", xc.remote_usetcp);
+ SetupStore("Remote.UseUdp", xc.remote_useudp);
+ SetupStore("Remote.UseRtp", xc.remote_usertp);
+ SetupStore("Remote.UsePipe",xc.remote_usepipe);
+ SetupStore("Remote.UseBroadcast", xc.remote_usebcast);
+
+ SetupStore("Remote.Rtp.Address", xc.remote_rtp_addr);
+ SetupStore("Remote.Rtp.Port", xc.remote_rtp_port);
+ SetupStore("Remote.Rtp.TTL", xc.remote_rtp_ttl);
+ SetupStore("Remote.Rtp.AlwaysOn", xc.remote_rtp_always_on);
+
+ cXinelibDevice::Instance().Listen(xc.remote_mode, xc.listen_port);
+}
+
+} // namespace
+
+//--- cMenuSetupXinelib ------------------------------------------------------
+
+cMenuSetupXinelib::cMenuSetupXinelib(void)
+{
+ XinelibOutputSetupMenu::controls[0] = tr("Off");
+ Set();
+}
+
+void cMenuSetupXinelib::Set(void)
+{
+ Clear();
+
+ SetHasHotkeys();
+ Add(new cOsdItem(hk(tr("Audio")), osUser1));
+ Add(new cOsdItem(hk(tr("Audio Equalizer")),osUser2));
+ Add(new cOsdItem(hk(tr("Video")), osUser3));
+ Add(new cOsdItem(hk(tr("OSD")), osUser4));
+ Add(new cOsdItem(hk(tr("Decoder")), osUser5));
+ Add(new cOsdItem(hk(tr("Local Frontend")), osUser6));
+ Add(new cOsdItem(hk(tr("Remote Clients")), osUser7));
+
+ Display();
+}
+
+eOSState cMenuSetupXinelib::ProcessKey(eKeys Key)
+{
+ eOSState state = cMenuSetupPage::ProcessKey(Key);
+
+ switch (state) {
+ case osUser1:
+ return AddSubMenu(new XinelibOutputSetupMenu::cMenuSetupAudio);
+ case osUser2:
+ return AddSubMenu(new XinelibOutputSetupMenu::cMenuSetupAudioEq);
+ case osUser3:
+ return AddSubMenu(new XinelibOutputSetupMenu::cMenuSetupVideo);
+ case osUser4:
+ return AddSubMenu(new XinelibOutputSetupMenu::cMenuSetupOSD);
+ case osUser5:
+ return AddSubMenu(new XinelibOutputSetupMenu::cMenuSetupDecoder);
+ case osUser6:
+ return AddSubMenu(new XinelibOutputSetupMenu::cMenuSetupLocal);
+ case osUser7:
+ return AddSubMenu(new XinelibOutputSetupMenu::cMenuSetupRemote);
+
+ default: ;
+ }
+
+ return state;
+}
+
+
diff --git a/tools/backgroundwriter.c b/tools/backgroundwriter.c
new file mode 100644
index 00000000..26e1f34b
--- /dev/null
+++ b/tools/backgroundwriter.c
@@ -0,0 +1,215 @@
+/*
+ * backgroundwriter.h: Buffered socket/file writing thread
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: backgroundwriter.c,v 1.1 2006-06-03 10:04:27 phintuka Exp $
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <vdr/tools.h>
+
+#include "../logdefs.h"
+#include "../xine_input_vdr_net.h" // stream_tcp_header_t
+
+#include "backgroundwriter.h"
+
+//#define DISABLE_DISCARD
+//#define LOG_DISCARDS
+
+
+cBackgroundWriter::cBackgroundWriter(int fd, int Size)
+ : m_RingBuffer(Size, sizeof(stream_tcp_header_t))
+{
+ m_fd = fd;
+ m_RingBuffer.SetTimeouts(0, 100);
+ m_Active = true;
+
+ m_PutPos = 0;
+ m_DiscardStart = 0;
+ m_DiscardEnd = 0;
+
+ LOGDBG("cBackgroundWriter initialized (buffer %d kb)", Size/1024);
+
+ Start();
+}
+
+cBackgroundWriter::~cBackgroundWriter()
+{
+ m_Active = false;
+ Cancel(3);
+}
+
+int cBackgroundWriter::Free(void)
+{
+ return m_RingBuffer.Free();
+}
+
+void cBackgroundWriter::Action(void)
+{
+ uint64_t NextHeaderPos = 0ULL;
+ uint64_t GetPos = 0ULL;
+ cPoller Poller(m_fd, true);
+
+ while(m_Active) {
+
+ if(Poller.Poll(100)) {
+
+ int Count = 0;
+ uchar *Data = m_RingBuffer.Get(Count);
+
+ if(Data && Count > 0) {
+
+#ifndef DISABLE_DISCARD
+ Lock(); // uint64_t m_DiscardStart can not be read atomically (IA32)
+ if(m_DiscardEnd > GetPos) {
+
+# ifdef LOG_DISCARDS
+ LOGMSG("TCP: queue: discard request: queue %d bytes, "
+ "next point %d bytes forward (Count=%d)",
+ m_RingBuffer.Available(),
+ NextHeaderPos - GetPos,
+ Count);
+# endif
+ if(NextHeaderPos == GetPos) {
+ // we're at frame boundary
+# ifdef LOG_DISCARDS
+ uint8_t *pkt = TCP_PAYLOAD(Data);
+ if(pkt[0] || pkt[1] || pkt[2] != 1 || hdr->len > 2100) {
+ LOGMSG(" -> %x %x %x %x", pkt[0], pkt[1], pkt[2], pkt[3]);
+ }
+# endif
+ Count = min(Count, (int)(m_DiscardEnd - GetPos));
+# ifdef LOG_DISCARDS
+ LOGMSG("Flushing %d bytes", Count);
+#endif
+ Unlock();
+
+ m_RingBuffer.Del(Count);
+ GetPos += Count;
+ NextHeaderPos = GetPos;
+# ifdef LOG_DISCARDS
+ LOGMSG("Queue now %d bytes", m_RingBuffer.Available());
+ pkt = TCP_PAYLOAD(Data);
+ if(pkt[0] || pkt[1] || pkt[2] != 1 || hdr->len > 2100) {
+ LOGMSG(" -> %x %x %x %x", pkt[0], pkt[1], pkt[2], pkt[3]);
+# endif
+ continue;
+ }
+ }
+ Unlock();
+#endif
+
+#ifndef DISABLE_DISCARD
+ if(GetPos == NextHeaderPos) {
+ if(Count < (int)sizeof(stream_tcp_header_t))
+ LOGMSG("cBackgroundWriter @NextHeaderPos: Count < header size !");
+
+ stream_tcp_header_t *header = (stream_tcp_header_t*)Data;
+ if(Count < (int)(ntohl(header->len) + sizeof(stream_tcp_header_t)))
+ ;//LOGMSG("Count = %d < %d", Count,
+ // header->len + sizeof(stream_tcp_header_t));
+ else
+ Count = ntohl(header->len) + sizeof(stream_tcp_header_t);
+ NextHeaderPos = GetPos + ntohl(header->len) + sizeof(stream_tcp_header_t);
+ } else {
+ Count = min(Count, (int)(NextHeaderPos-GetPos));
+ }
+#endif
+
+ errno = 0;
+ int n = write(m_fd, Data, Count);
+
+ if(n == 0) {
+ LOGERR("cBackgroundWriter: Client disconnected data stream ?");
+ break;
+
+ } else if(n < 0) {
+
+ if (errno == EINTR || errno == EWOULDBLOCK) {
+ TRACE("cBackgroundWriter: EINTR while writing to file handle "
+ <<m_fd<<" - retrying");
+ continue;
+
+ } else {
+ LOGERR("cBackgroundWriter: TCP write error");
+ break;
+ }
+ }
+
+ GetPos += n;
+ m_RingBuffer.Del(n);
+ }
+ }
+ }
+
+ m_RingBuffer.Clear();
+ m_Active = false;
+}
+
+void cBackgroundWriter::Clear(void)
+{
+ // Can't just drop buffer contents or PES frames will be broken.
+
+ // Serialize with Put
+ LOCK_THREAD;
+#ifdef LOG_DISCARDS
+ LOGMSG("cBackgroundWriter::Clear() @%lld", m_PutPos);
+#endif
+ m_DiscardEnd = m_PutPos;
+}
+
+bool cBackgroundWriter::Flush(int TimeoutMs)
+{
+ uint64_t WaitEnd = cTimeMs::Now();
+
+ if(TimeoutMs > 0)
+ WaitEnd += (uint64_t)TimeoutMs;
+
+ while(cTimeMs::Now() < WaitEnd &&
+ m_Active &&
+ m_RingBuffer.Available() > 0)
+ cCondWait::SleepMs(3);
+
+ return m_RingBuffer.Available() <= 0;
+}
+
+int cBackgroundWriter::Put(uint64_t StreamPos,
+ const uchar *Data, int DataCount)
+{
+ stream_tcp_header_t header;
+ header.pos = htonull(StreamPos);
+ header.len = htonl(DataCount);
+ return Put((uchar*)&header, sizeof(header), Data, DataCount);
+}
+
+int cBackgroundWriter::Put(const uchar *Header, int HeaderCount,
+ const uchar *Data, int DataCount)
+{
+ if(m_Active) {
+
+ // Serialize Put access to keep Data and Header together
+ LOCK_THREAD;
+
+ if(m_RingBuffer.Free() < HeaderCount+DataCount) {
+ LOGMSG("cXinelibServer: TCP buffer overflow !");
+ return -HeaderCount-DataCount;
+ }
+ int n = m_RingBuffer.Put(Header, HeaderCount) +
+ m_RingBuffer.Put(Data, DataCount);
+ if(n == HeaderCount+DataCount) {
+ m_PutPos += n;
+ return n;
+ }
+
+ LOGMSG("cXinelibServer: TCP buffer internal error ?!?");
+ m_RingBuffer.Clear();
+ m_Active = false;
+ }
+
+ return 0;
+}
diff --git a/tools/backgroundwriter.h b/tools/backgroundwriter.h
new file mode 100644
index 00000000..619b2a29
--- /dev/null
+++ b/tools/backgroundwriter.h
@@ -0,0 +1,62 @@
+/*
+ * backgroundwriter.h: Buffered socket/file writing thread
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: backgroundwriter.h,v 1.1 2006-06-03 10:04:27 phintuka Exp $
+ *
+ */
+
+#ifndef __BACKGROUNDWRITER_H
+#define __BACKGROUNDWRITER_H
+
+#include <stdint.h>
+
+#include <vdr/thread.h>
+#include <vdr/ringbuffer.h>
+
+
+class cBackgroundWriter : public cThread {
+
+ private:
+ cRingBufferLinear m_RingBuffer;
+
+ volatile bool m_Active;
+ int m_fd;
+
+ uint64_t m_PutPos;
+ uint64_t m_DiscardStart;
+ uint64_t m_DiscardEnd;
+
+ protected:
+ virtual void Action(void);
+
+ int Put(const uchar *Header, int HeaderCount,
+ const uchar *Data, int DataCount);
+
+ public:
+ cBackgroundWriter(int fd, int Size = KILOBYTE(512));
+ virtual ~cBackgroundWriter() ;
+
+ // Return largest possible Put size
+ int Free(void);
+
+ // Add PES frame to buffer
+ //
+ // Return value:
+ // Success: Count (all bytes pushed to queue)
+ // Error: 0 (write error ; socket disconnected)
+ // Buffer full: -Count (no bytes will be pushed to queue)
+ //
+ int Put(uint64_t StreamPos, const uchar *Data, int DataCount);
+
+ // Drop all data (only complete frames) from buffer
+ void Clear(void);
+
+ //bool Poll(int TimeoutMs);
+ bool Flush(int TimeoutMs);
+};
+
+
+#endif
diff --git a/tools/cxsocket.c b/tools/cxsocket.c
new file mode 100644
index 00000000..a8a1953e
--- /dev/null
+++ b/tools/cxsocket.c
@@ -0,0 +1,3 @@
+/*static*/ int cxsocket_c_dummy;
+
+/* #warning TODO: socket helper classes / functions */
diff --git a/tools/cxsocket.h b/tools/cxsocket.h
new file mode 100644
index 00000000..f3d823e5
--- /dev/null
+++ b/tools/cxsocket.h
@@ -0,0 +1,192 @@
+/*
+ * cxsocket.h: Socket wrapper classes
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: cxsocket.h,v 1.1 2006-06-03 10:04:27 phintuka Exp $
+ *
+ */
+
+#ifndef __CXSOCKET_H
+#define __CXSOCKET_H
+
+#define CLOSESOCKET(fd) do { if(fd>=0) { close(fd); fd=-1; } } while(0)
+
+//
+// Connect data socket to client (take address from fd_control)
+//
+static inline int sock_connect(int fd_control, int port, int type)
+{
+ struct sockaddr_in sin;
+ socklen_t len = sizeof(sin);
+ int s;
+
+ if(getpeername(fd_control, (struct sockaddr *)&sin, &len)) {
+ LOGERR("sock_connect: getpeername failed");
+ return -1;
+ }
+
+ uint32_t tmp = ntohl(sin.sin_addr.s_addr);
+ LOGMSG("Client address: %d.%d.%d.%d",
+ ((tmp>>24)&0xff), ((tmp>>16)&0xff),
+ ((tmp>>8)&0xff), ((tmp)&0xff));
+
+#if 0
+ if ((h = gethostbyname(tmp)) == NULL) {
+ LOGDBG("sock_connect: unable to resolve host name", tmp);
+ }
+#endif
+
+ if ((s = socket(PF_INET, type,
+ type==SOCK_DGRAM?IPPROTO_UDP:IPPROTO_TCP)) < 0) {
+ LOGERR("sock_connect: failed to create socket");
+ return -1;
+ }
+
+#if 1
+ // Set socket buffers: large send buffer, small receive buffer
+ {
+ int max_buf = KILOBYTE(128);
+ //while(max_buf) {
+ errno = 0;
+ if(setsockopt(s, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(int))) {
+ LOGERR("setsockopt(SO_SNDBUF,%d) failed", max_buf);
+ max_buf >>= 1;
+ } else {
+ int tmp = 0;
+ int len = sizeof(int);
+ errno = 0;
+ if(getsockopt(s, SOL_SOCKET, SO_SNDBUF, &tmp, (socklen_t*)&len)) {
+ LOGERR("getsockopt(SO_SNDBUF,%d) failed", max_buf);
+ max_buf >>= 1;
+ } else if(tmp != max_buf) {
+ LOGDBG("setsockopt(SO_SNDBUF): got %d bytes", tmp);
+ max_buf >>= 1;
+ }
+ }
+ max_buf = 1024;
+ setsockopt(s, SOL_SOCKET, SO_RCVBUF, &max_buf, sizeof(int));
+ }
+#endif
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+
+ if (connect(s, (struct sockaddr *)&sin, sizeof(sin))==-1 &&
+ errno != EINPROGRESS) {
+ LOGERR("connect() failed");
+ CLOSESOCKET(s);
+ }
+
+ if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) | O_NONBLOCK) == -1) {
+ LOGERR("can't put socket in non-blocking mode");
+ CLOSESOCKET(s);
+ return -1;
+ }
+
+ return s;
+}
+
+static inline int timed_write(int fd, const void *buffer, size_t size,
+ int timeout_ms)
+{
+ int written = size;
+ const unsigned char *ptr = (const unsigned char *)buffer;
+ cPoller poller(fd, true);
+
+ while (size > 0) {
+
+ if(!poller.Poll(timeout_ms)) {
+ LOGERR("timed_write: poll() failed");
+ return written-size;
+ }
+
+ errno = 0;
+ int p = write(fd, ptr, size);
+
+ if (p <= 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ LOGDBG("timed_write: EINTR during write(), retrying");
+ continue;
+ }
+ LOGERR("timed_write: write() error");
+ return p;
+ }
+
+ ptr += p;
+ size -= p;
+ }
+
+ return written;
+}
+
+//#include "xine_osd_command.h"
+
+static inline int write_osd_command(int fd, osd_command_t *cmd)
+{
+ if(8 != timed_write(fd, "OSDCMD\r\n", 8, 200)) {
+ LOGDBG("write_osd_command: write (command) failed");
+ return 0;
+ }
+ if(sizeof(osd_command_t) !=
+ timed_write(fd, cmd, sizeof(osd_command_t), 200)) {
+ LOGDBG("write_osd_command: write (data) failed");
+ return 0;
+ }
+ if(cmd->palette && cmd->colors &&
+ (int)(sizeof(xine_clut_t)*ntohl(cmd->colors)) !=
+ timed_write(fd, cmd->palette, (int)(sizeof(xine_clut_t)*ntohl(cmd->colors)), 200)) {
+ LOGDBG("write_osd_command: write (palette) failed");
+ return 0;
+ }
+ if(cmd->data && cmd->datalen &&
+ (int)ntohl(cmd->datalen) != timed_write(fd, cmd->data, ntohl(cmd->datalen), 1000)) {
+ LOGDBG("write_osd_command: write (bitmap) failed");
+ return 0;
+ }
+ return 1;
+}
+
+static inline int write_str(int fd, const char *str, int timeout_ms=-1)
+{
+ return timed_write(fd, str, strlen(str), timeout_ms);
+}
+
+static inline int write_cmd(int fd, const char *str)
+{
+ return write_str(fd, str, 10);
+}
+
+static inline int udp_discovery_broadcast(int fd_discovery, int m_Port)
+{
+ if(!xc.remote_usebcast) {
+ LOGDBG("UDP broadcasts (discovery) disabled in configuration");
+ return -1;
+ }
+
+ struct sockaddr_in sin;
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(DISCOVERY_PORT);
+ sin.sin_addr.s_addr = INADDR_BROADCAST;
+
+ char *test = NULL;
+ asprintf(&test,
+ "VDR xineliboutput DISCOVERY 1.0\r\n"
+ "Server port: %d\r\n"
+ "\r\n",
+ m_Port);
+ int testlen = strlen(test);
+ if(testlen != sendto(fd_discovery, test, testlen, 0,
+ (struct sockaddr *)&sin, sizeof(sin))) {
+ LOGERR("UDP broadcast send failed (discovery)");
+ return -1;
+ } else {
+ LOGDBG("UDP broadcast send succeed (discovery)");
+ }
+ return 1;
+}
+
+
+#endif // __CXSOCKET_H
diff --git a/tools/future.h b/tools/future.h
new file mode 100644
index 00000000..73d4bfec
--- /dev/null
+++ b/tools/future.h
@@ -0,0 +1,84 @@
+/*
+ * future.h: A variable that gets its value in future.
+ * Used to convert asynchronous IPCs to synchronous.
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: future.h,v 1.1 2006-06-03 10:04:27 phintuka Exp $
+ *
+ */
+
+#ifndef __FUTURE_H
+#define __FUTURE_H
+
+#include <vdr/thread.h>
+
+template <class T>
+class cFuture {
+
+ private:
+ cMutex mutex;
+ cCondVar cond;
+ bool m_Ready;
+ T m_Value;
+
+ public:
+
+ cFuture()
+ {
+ m_Ready = false;
+ }
+
+ void Reset(void)
+ {
+ cMutexLock l(&mutex);
+ m_Ready = false;
+ }
+
+ //
+ // Producer interface
+ //
+
+ void Set(T& Value)
+ {
+ cMutexLock l(&mutex);
+ m_Value = Value;
+ m_Ready = true;
+ cond.Broadcast();
+ }
+
+ //
+ // Consumer interface
+ //
+
+ bool Wait(int Timeout = -1)
+ {
+ cMutexLock l(&mutex);
+
+ if(Timeout >= 0)
+ return cond.TimedWait(mutex, Timeout);
+
+ while(!m_Ready)
+ cond.Wait(mutex);
+
+ return true;
+ }
+
+ bool IsReady(void)
+ {
+ cMutexLock l(&mutex);
+ return m_Ready;
+ }
+
+ T Value(void)
+ {
+ cMutexLock l(&mutex);
+ while(!m_Ready)
+ cond.Wait(mutex);
+ return m_Value;
+ }
+};
+
+
+#endif // __FUTURE_H
diff --git a/tools/general_remote.h b/tools/general_remote.h
new file mode 100644
index 00000000..aed60463
--- /dev/null
+++ b/tools/general_remote.h
@@ -0,0 +1,27 @@
+/*
+ * general_remote.h:
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: general_remote.h,v 1.1 2006-06-03 10:04:27 phintuka Exp $
+ *
+ */
+
+#ifndef __GENERAL_REMOTE_H
+#define __GENERAL_REMOTE_H
+
+
+//----------------------------- cGeneralRemote --------------------------------
+
+#include <vdr/remote.h>
+
+class cGeneralRemote : public cRemote {
+ public:
+ cGeneralRemote(const char *Name) : cRemote(Name) {};
+ bool Put(const char *Code, bool Repeat=false, bool Release=false)
+ { return cRemote::Put(Code, Repeat, Release); };
+};
+
+
+#endif
diff --git a/tools/listiter.h b/tools/listiter.h
new file mode 100644
index 00000000..9c88940e
--- /dev/null
+++ b/tools/listiter.h
@@ -0,0 +1,83 @@
+/*
+ * listiter.h:
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: listiter.h,v 1.1 2006-06-03 10:04:27 phintuka Exp $
+ *
+ */
+
+
+#ifndef _LISTITER_H_
+#define _LISTITER_H_
+
+//------------------------------ list ----------------------------------------
+
+template <class LIST,class ITEM, class RESULT>
+void ForEach(LIST& List, RESULT (ITEM::*f)())
+{
+ for(ITEM *it = List.First(); it; it = List.Next(it))
+ (*it.*f)();
+}
+
+template <class LIST,class ITEM, class ARG1, class RESULT>
+void ForEach(LIST& List, RESULT (ITEM::*f)(ARG1), ARG1 arg1)
+{
+ for(ITEM *it = List.First(); it; it = List.Next(it))
+ (*it.*f)(arg1);
+}
+
+template <class LIST,class ITEM, class ARG1, class ARG2>
+void ForEach(LIST& List, void (ITEM::*f)(ARG1,ARG2), ARG1 arg1, ARG2 arg2)
+{
+ for(ITEM *it = List.First(); it; it = List.Next(it))
+ (*it.*f)(arg1,arg2);
+}
+
+template <class LIST,class ITEM, class ARG1, class RESULT>
+RESULT ForEach(LIST& List, RESULT (ITEM::*f)(ARG1), ARG1 arg1,
+ RESULT (*combiner)(RESULT,RESULT), RESULT def)
+{
+ RESULT result = def;
+ for(ITEM *it = List.First(); it; it = List.Next(it))
+ result = (*combiner)((*it.*f)(arg1),result);
+ return result;
+}
+
+template <class LIST,class ITEM, class ARG1, class ARG2, class RESULT>
+RESULT ForEach(LIST& List, RESULT (ITEM::*f)(ARG1,ARG2),
+ ARG1 arg1, ARG2 arg2,
+ RESULT (*combiner)(RESULT,RESULT), RESULT def)
+{
+ RESULT result = def;
+ for(ITEM *it = List.First(); it; it = List.Next(it))
+ result = (*combiner)((*it.*f)(arg1,arg2),result);
+ return result;
+}
+
+template <class LIST,class ITEM, class ARG1, class ARG2, class ARG3,
+ class RESULT>
+RESULT ForEach(LIST& List, RESULT (ITEM::*f)(ARG1,ARG2,ARG3),
+ ARG1 arg1, ARG2 arg2, ARG3 arg3,
+ RESULT (*combiner)(RESULT,RESULT), RESULT def)
+{
+ RESULT result = def;
+ for(ITEM *it = List.First(); it; it = List.Next(it))
+ result = (*combiner)((*it.*f)(arg1,arg2,arg3),result);
+ return result;
+}
+
+template<class T>
+T mmin(T a, T b) {return a<b ? a : b;}
+
+template<class T>
+T mmax(T a, T b) {return a>b ? a : b;}
+
+template<class T>
+T mand(T a, T b) {return a&&b;}
+
+template<class T>
+T mor(T a, T b) {return a||b;}
+
+#endif
diff --git a/tools/pes.h b/tools/pes.h
new file mode 100644
index 00000000..c01b98db
--- /dev/null
+++ b/tools/pes.h
@@ -0,0 +1,258 @@
+/*
+ * pes.h: PES header definitions
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: pes.h,v 1.1 2006-06-03 10:04:27 phintuka Exp $
+ *
+ */
+
+#ifndef _PES_H_
+#define _PES_H_
+
+
+#define PES_CHUNK_SIZE 2048
+
+#define MAX_SCR ((int64_t)0x1ffffffffLL)
+
+// PES PIDs
+#define PRIVATE_STREAM1 0xBD
+#define PADDING_STREAM 0xBE
+#define PRIVATE_STREAM2 0xBF
+#define AUDIO_STREAM_S 0xC0 /* 1100 0000 */
+#define AUDIO_STREAM_E 0xDF /* 1101 1111 */
+#define VIDEO_STREAM_S 0xE0 /* 1110 0000 */
+#define VIDEO_STREAM_E 0xEF /* 1110 1111 */
+
+#define AUDIO_STREAM_MASK 0x1F /* 0001 1111 */
+#define VIDEO_STREAM_MASK 0x0F /* 0000 1111 */
+#define AUDIO_STREAM 0xC0 /* 1100 0000 */
+#define VIDEO_STREAM 0xE0 /* 1110 0000 */
+
+#define ECM_STREAM 0xF0
+#define EMM_STREAM 0xF1
+#define DSM_CC_STREAM 0xF2
+#define ISO13522_STREAM 0xF3
+#define PROG_STREAM_DIR 0xFF
+
+// "picture header"
+#define SC_PICTURE 0x00
+
+// Picture types
+#define NO_PICTURE 0
+#define I_FRAME 1
+#define P_FRAME 2
+#define B_FRAME 3
+
+static inline int64_t pes_extract_pts(const uchar *Data, int Length,
+ bool& Audio, bool& Video)
+{
+ /* assume mpeg2 pes header ... */
+ Audio = Video = false;
+
+ if((VIDEO_STREAM == (Data[3] & ~VIDEO_STREAM_MASK) && (Video=true)) ||
+ (AUDIO_STREAM == (Data[3] & ~AUDIO_STREAM_MASK) && (Audio=true)) ||
+ (PRIVATE_STREAM1 == Data[3] && (Audio=true))) {
+
+ if ((Data[6] & 0xC0) != 0x80)
+ return -1;
+ if ((Data[6] & 0x30) != 0)
+ return -1;
+
+ if((Length > 14) && (Data[7] & 0x80)) { /* pts avail */
+ int64_t pts;
+ pts = ((int64_t)(Data[ 9] & 0x0E)) << 29 ;
+ pts |= ((int64_t) Data[10]) << 22 ;
+ pts |= ((int64_t)(Data[11] & 0xFE)) << 14 ;
+ pts |= ((int64_t) Data[12]) << 7 ;
+ pts |= ((int64_t)(Data[13] & 0xFE)) >> 1 ;
+ return pts;
+ }
+ }
+ return -1ULL;
+}
+
+static inline void pes_change_pts(uchar *Data, int Length)
+{
+ /* assume mpeg2 pes header ... Assume header already HAS pts */
+ if((VIDEO_STREAM == (Data[3] & ~VIDEO_STREAM_MASK)) ||
+ (AUDIO_STREAM == (Data[3] & ~AUDIO_STREAM_MASK)) ||
+ (PRIVATE_STREAM1 == Data[3])) {
+
+ if ((Data[6] & 0xC0) != 0x80)
+ return;
+ if ((Data[6] & 0x30) != 0)
+ return;
+
+ if((Length > 14) && (Data[7] & 0x80)) { /* pts avail */
+ int64_t pts;
+ //pts = ((int64_t)(Data[ 9] & 0x0E)) << 29 ;
+ Data[ 9] |= ((pts >> 29) & 0x0E);
+ //pts |= ((int64_t) Data[10]) << 22 ;
+ Data[10] |= ((pts >> 22) & 0xFF);
+ //pts |= ((int64_t)(Data[11] & 0xFE)) << 14 ;
+ Data[11] |= ((pts >> 14) & 0xFE);
+ //pts |= ((int64_t) Data[12]) << 7 ;
+ Data[12] |= ((pts >> 7 ) & 0xFF);
+ //pts |= ((int64_t)(Data[13] & 0xFE)) >> 1 ;
+ Data[13] |= ((pts << 1 ) & 0xFE);
+ }
+ }
+}
+
+// Remove pts from PES packet (zero it)
+static inline void pes_strip_pts(uchar *Data, int Len)
+{
+ if(VIDEO_STREAM == (Data[3] & ~VIDEO_STREAM_MASK) ||
+ AUDIO_STREAM == (Data[3] & ~AUDIO_STREAM_MASK) ||
+ PRIVATE_STREAM1 == Data[3]) {
+
+ // MPEG1 PES
+ if ((Data[6] & 0xC0) != 0x80) {
+ Data += 6;
+ Len -= 6;
+
+ // skip stuffing
+ while ((Data[0] & 0x80) == 0x80) {
+ Data++;
+ Len--;
+ }
+ if ((Data[0] & 0xc0) == 0x40) {
+ // STD_buffer_scale, STD_buffer_size
+ Data += 2;
+ Len -= 2;
+ }
+
+ if(Len<5) return;
+ if ((Data[0] & 0xf0) == 0x20) {
+ // zero PTS
+ Data[0] &= ~0x0E;
+ Data[1] = 0;
+ Data[2] &= ~0xFE;
+ Data[3] = 0;
+ Data[4] &= ~0xFE;
+ return;
+ }
+ if(Len<10) return;
+ if ((Data[0] & 0xf0) == 0x30) {
+ // zero PTS & DTS
+//((uint32*)Data)[0] &= 0x0E00FE00;
+ Data[0] &= ~0x0E;
+ Data[1] = 0;
+ Data[2] &= ~0xFE;
+ Data[3] = 0;
+//((uint32*)Data)[1] &= 0xFE0E00FE;
+ Data[4] &= ~0xFE;
+ Data[5] &= ~0x0E;
+ Data[6] = 0;
+ Data[7] &= ~0xFE;
+//((uint32*)Data)[2] &= 0x00FEFFFF;
+ Data[8] = 0;
+ Data[9] &= ~0xFE;
+ return;
+ }
+
+ // MPEG2 PES
+ } else {
+ if ((Data[6] & 0xC0) != 0x80)
+ return;
+ if ((Data[6] & 0x30) != 0)
+ return;
+
+ if(Len<14) return;
+ if (Data[7] & 0x80) {
+ // PTS
+ if(Data[8]<5) return;
+ Data[ 9] &= ~0x0E;
+ Data[10] = 0;
+ Data[11] &= ~0xFE;
+ Data[12] = 0;
+ Data[13] &= ~0xFE;
+ }
+ if(Len<19) return;
+ if (Data[7] & 0x40) {
+ // DTS
+ if(Data[8]<10) return;
+ Data[14] &= ~0x0E;
+ Data[15] = 0;
+ Data[16] &= ~0xFE;
+ Data[17] = 0;
+ Data[18] &= ~0xFE;
+ }
+ }
+ }
+}
+
+static inline int pes_packet_len(const uchar *header, const int maxlen, bool &isMpeg1)
+{
+ if(VIDEO_STREAM == (header[3] & ~VIDEO_STREAM_MASK) ||
+ AUDIO_STREAM == (header[3] & ~AUDIO_STREAM_MASK) ||
+ PRIVATE_STREAM1 == header[3]) {
+ isMpeg1 = ((header[6] & 0xC0) != 0x80);
+ return 6 + (header[4] << 8 | header[5]);
+ } else if (header[3] == PADDING_STREAM) {
+ isMpeg1 = false;
+ return (6 + (header[4] << 8 | header[5]));
+ } else if (header[3] == 0xBA) {
+ if ((header[4] & 0x40) == 0) { /* mpeg1 */
+ isMpeg1 = true;
+ return 12;
+ } else { /* mpeg 2 */
+ isMpeg1 = false;
+ return 14 + (header[0xD] & 0x07);
+ }
+ } else if (header[3] <= 0xB9) {
+ int len=3;
+ return -3;
+ isMpeg1 = false;
+ while(len+2<maxlen) {
+ if(!header[len] && !header[len+1] && header[len+2] == 1)
+ return len;
+ len++;
+ }
+ return -len;
+ }
+ isMpeg1 = false;
+ return -(6 + (header[4] << 8 | header[5]));
+}
+
+// from vdr/remux.c:
+static inline int ScanVideoPacket(const uchar *Data, int Count, /*int Offset,*/
+ uchar &PictureType)
+{
+ // Scans the video packet starting at Offset and returns its length.
+ // If the return value is -1 the packet was not completely in the buffer.
+ int Offset = 0;
+ int Length = Count;
+ if (Length > 0 && Offset + Length <= Count) {
+ int i = Offset + 8; // the minimum length of the video packet header
+ i += Data[i] + 1; // possible additional header bytes
+ for (; i < Offset + Length; i++) {
+ if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) {
+ switch (Data[i + 3]) {
+ case SC_PICTURE:
+ PictureType = (Data[i + 5] >> 3) & 0x07;
+ return Length;
+ }
+ }
+ }
+ PictureType = NO_PICTURE;
+ return Length;
+ }
+ return -1;
+}
+
+static inline const char *PictureTypeStr(int Type)
+{
+ switch(Type) {
+ case I_FRAME: return "I-Frame"; break;
+ case B_FRAME: return "B-Frame"; break;
+ case P_FRAME: return "P-Frame"; break;
+ case NO_PICTURE: return "(none)"; break;
+ default: break;
+ }
+ return "UNKNOWN";
+}
+
+#endif
diff --git a/tools/timer.c b/tools/timer.c
new file mode 100644
index 00000000..1730edab
--- /dev/null
+++ b/tools/timer.c
@@ -0,0 +1,292 @@
+/*
+ * timer.c: Threaded timer class
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: timer.c,v 1.1 2006-06-03 10:04:28 phintuka Exp $
+ *
+ */
+
+#include <sys/time.h>
+
+#include <vdr/config.h>
+#include <vdr/tools.h>
+#include <vdr/thread.h>
+
+#include "timer.h"
+
+//#define XINELIBOUTPUT_DEBUG
+//#define XINELIBOUTPUT_DEBUG_STDOUT
+#ifdef XINELIBOUTPUT_DEBUG
+# include "logdefs.h"
+#else
+# define TRACE(x)
+# define TRACEF(x)
+#endif
+
+#if VDRVERSNUM>10317
+ #define time_ms() cTimeMs::Now()
+#endif
+
+// ---------------------------- cTimerThreadEvent ----------------------------
+
+class cTimerThreadEvent : public cListObject {
+ public:
+ cTimerThreadEvent(cTimerCallback *Handler, unsigned int TimeoutMs,
+ bool DeleteOnCancel = false) :
+ m_Handler(Handler),
+ m_DeleteOnCancel(DeleteOnCancel),
+ m_TimeoutMs(TimeoutMs)
+ {
+ m_NextEventTime = time_ms();
+ UpdateEventTime();
+ }
+
+ ~cTimerThreadEvent()
+ {
+ if(m_DeleteOnCancel && m_Handler)
+ delete m_Handler;
+ }
+
+ void UpdateEventTime()
+ {
+ m_NextEventTime += m_TimeoutMs;
+ }
+
+ int TimeToNextEvent(void)
+ {
+ return m_NextEventTime - time_ms();
+ }
+
+ virtual bool operator< (const cListObject &ListObject)
+ {
+ const cTimerThreadEvent *o = (cTimerThreadEvent *)&ListObject;
+ return m_NextEventTime<o->m_NextEventTime;
+ }
+
+ virtual int Compare(const cListObject &ListObject) const
+ {
+ const cTimerThreadEvent *o = (cTimerThreadEvent *)&ListObject;
+ if(m_NextEventTime<o->m_NextEventTime)
+ return -1;
+ else if(m_NextEventTime>o->m_NextEventTime)
+ return 1;
+ return 0;
+ }
+
+ cTimerCallback *m_Handler;
+
+ protected:
+ bool m_DeleteOnCancel;
+ unsigned int m_TimeoutMs;
+ int64_t m_NextEventTime;
+};
+
+// ------------------------------- cTimerThread ------------------------------
+
+class cTimerThread : public cThread {
+ private:
+ cTimerThread(cTimerThread&); // copy not allowed
+
+ static cMutex m_InstanceLock;
+ static cTimerThread *m_Instance; // singleton
+
+ cMutex m_Lock;
+ cCondVar m_Signal;
+ cList<cTimerThreadEvent> m_Events;
+ cTimerThreadEvent *m_RunningEvent;
+ bool m_Finished;
+ bool m_HandlerRunning;
+
+ cTimerThread() :
+ m_RunningEvent(NULL),
+ m_Finished(false),
+ m_HandlerRunning(false)
+ {
+ }
+
+ virtual ~cTimerThread()
+ {
+ m_Lock.Lock();
+ cTimerThreadEvent *ev;
+ while(NULL != (ev = m_Events.First())) {
+ m_Events.Del(ev,true);
+ }
+ m_Lock.Unlock();
+ m_Signal.Broadcast();
+ Cancel(1);
+ }
+
+ protected:
+
+ virtual void Action()
+ {
+ TRACEF("cTimerThread::Action");
+ m_Lock.Lock();
+ while(m_Events.First()) {
+ m_Signal.TimedWait(m_Lock,
+ max(1, m_Events.First()->TimeToNextEvent()));
+ TRACE("cTimerThread::Action waked up");
+ while(NULL != (m_RunningEvent = m_Events.First()) &&
+ m_RunningEvent->TimeToNextEvent() <= 0) {
+ TRACE("cTimerThread::Action calling handler");
+ m_HandlerRunning=true;
+// m_Lock.Unlock();
+// - can't unlock or running timer handler may be deleted while
+// executing (or thread may be killed by Delete)
+ bool result = m_RunningEvent->m_Handler->TimerEvent();
+// m_Lock.Lock();
+ m_HandlerRunning=false;
+ if(!result) {
+ if(m_RunningEvent) { // check if event was cancelled in handler...
+ TRACE("cTimerThread::Action handler cancelled timer");
+ m_Events.Del(m_RunningEvent, true);
+ }
+ } else {
+ if(m_RunningEvent) {
+ TRACE("cTimerThread::Action timer re-scheduled");
+ m_RunningEvent->UpdateEventTime();
+ m_Events.Sort();
+ }
+ }
+ m_RunningEvent = NULL;
+ }
+ }
+ m_Finished = true;
+ m_Lock.Unlock();
+ }
+
+ void Add(cTimerThreadEvent *Event)
+ {
+ TRACEF("cTimerThread::Add");
+ //m_Events.Del(Event, false);
+ Event->Unlink();
+ Del(Event->m_Handler);
+ m_Events.Add(Event);
+ m_Events.Sort();
+ }
+
+ bool Del(cTimerCallback *Handler, void *TargetId=NULL,
+ bool inDestructor=false)
+ {
+ TRACEF("cTimerThread::Del");
+ cTimerThreadEvent *ev = m_Events.First();
+ while(ev) {
+ if(ev->m_Handler == Handler ||
+ (TargetId && ev->m_Handler->TargetId() == TargetId) ||
+ (Handler && ev->m_Handler->is(Handler,Handler->size()))) {
+ cTimerThreadEvent *nev = m_Events.Next(ev);
+ if(inDestructor) ev->m_Handler=NULL;
+ m_Events.Del(ev, true);
+ ev = nev;
+ } else
+ ev = m_Events.Next(ev);
+ }
+ if(m_RunningEvent &&
+ (m_RunningEvent->m_Handler == Handler ||
+ m_RunningEvent->m_Handler->TargetId() == TargetId))
+ m_RunningEvent = NULL;
+ return !m_HandlerRunning && !m_RunningEvent && !m_Events.First();
+ }
+
+ public:
+
+ static void AddEvent(cTimerCallback *Handler, unsigned int TimeoutMs,
+ bool DeleteOnCancel=false)
+ {
+ TRACEF("cTimerThread::AddEvent");
+ m_InstanceLock.Lock();
+ if(m_Instance && m_Instance->m_Finished) {
+ delete m_Instance;
+ m_Instance = NULL;
+ }
+ if(!m_Instance) {
+ m_Instance = new cTimerThread;
+ m_Instance->m_Lock.Lock();
+ m_Instance->Start();
+ } else {
+ m_Instance->m_Lock.Lock();
+ m_Instance->m_Signal.Broadcast();
+ }
+ m_Instance->Add(new cTimerThreadEvent(Handler, max(1U,TimeoutMs),
+ DeleteOnCancel));
+ m_Instance->m_Lock.Unlock();
+ m_InstanceLock.Unlock();
+ }
+
+ static void CancelEvent(cTimerCallback *Handler, void *TargetId = NULL,
+ bool inDestructor=false)
+ {
+ TRACEF("cTimerThread::CancelEvent");
+ m_InstanceLock.Lock();
+ if(m_Instance && !m_Instance->m_Finished) {
+ m_Instance->m_Lock.Lock();
+ if(m_Instance->Del(Handler, TargetId, inDestructor) && !inDestructor) {
+ m_Instance->m_Lock.Unlock();
+ delete m_Instance;
+ m_Instance = NULL;
+ } else
+ m_Instance->m_Lock.Unlock();
+ }
+ m_InstanceLock.Unlock();
+ }
+
+};
+
+cMutex cTimerThread::m_InstanceLock;
+cTimerThread *cTimerThread::m_Instance = NULL;
+
+// ------------------------------ cTimerCallback -----------------------------
+
+cTimerCallback::~cTimerCallback()
+{
+ TRACEF("cTimerCallback::~cTimerCallback");
+ cTimerThread::CancelEvent(this, NULL, true);
+}
+
+void cTimerCallback::Set(cTimerCallback *handler, unsigned int TimeoutMs)
+{
+ TRACEF("cTimerCallback::Set");
+ cTimerThread::AddEvent(handler, TimeoutMs);
+}
+
+void cTimerCallback::Cancel(cTimerCallback *handler)
+{
+ TRACEF("cTimerCallback::Cancel");
+ cTimerThread::CancelEvent(handler);
+}
+
+// ------------------------------- cTimerEvent -------------------------------
+
+//cTimerEvent::cTimerEvent(unsigned int TimeoutMs)
+//{
+// TRACEF("cTimerEvent::cTimerEvent");
+//// cTimerThread::AddEvent(this, TimeoutMs, true);
+//}
+
+void cTimerEvent::AddEvent(unsigned int TimeoutMs)
+{
+ TRACEF("cTimerEvent::AddEvent");
+ cTimerThread::AddEvent(this, TimeoutMs, true);
+}
+
+void cTimerEvent::Cancel(cTimerEvent *&event)
+{
+ TRACEF("cTimerEvent::Cancel");
+ cTimerThread::CancelEvent(event);
+ event = NULL;
+}
+
+void cTimerEvent::CancelAll(void *Target)
+{
+ TRACEF("cTimerEvent::CancelAll");
+ cTimerThread::CancelEvent(NULL, Target);
+}
+
+
+
+
+
+
+
diff --git a/tools/timer.h b/tools/timer.h
new file mode 100644
index 00000000..2ee8724b
--- /dev/null
+++ b/tools/timer.h
@@ -0,0 +1,296 @@
+/*
+ * timer.h: Threaded timer class
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: timer.h,v 1.1 2006-06-03 10:04:28 phintuka Exp $
+ *
+ */
+
+#ifndef __XINELIBOUTPUT_TIMER_H
+#define __XINELIBOUTPUT_TIMER_H
+
+//
+// cTimerCallback : timer callback handler interface
+//
+class cTimerCallback {
+ protected:
+ virtual bool TimerEvent() = 0; // return false to cancel timer
+
+ virtual void *TargetId() { return (void*)this; }
+ virtual int size() { return sizeof(*this); }
+ virtual bool is(void *data, int len)
+ {
+ return len==sizeof(*this) && TargetId()==data;
+ }
+
+ friend class cTimerThread;
+
+ public:
+ static void Set(cTimerCallback *, unsigned int TimeoutMs);
+ static void Cancel(cTimerCallback *);
+
+ virtual ~cTimerCallback();
+};
+
+//
+// cTimerEvent : base class for timer events
+//
+class cTimerEvent : protected cTimerCallback {
+ private:
+ cTimerEvent(cTimerEvent&);
+
+ protected:
+ cTimerEvent() {};
+
+ virtual void AddEvent(unsigned int TimeoutMs);
+
+ static void CancelAll(void *Target);
+
+ template<class TCLASS> friend void CancelTimerEvents(TCLASS*);
+ friend class cTimerThread;
+
+ public:
+ static void Cancel(cTimerEvent *&);
+};
+
+//
+// make gcc 3.4.5 happy
+//
+template<class TCLASS, class TRESULT>
+cTimerEvent *CreateTimerEvent(TCLASS *c, TRESULT (TCLASS::*fp)(void),
+ unsigned int TimeoutMs);
+template<class TCLASS, class TRESULT, class TARG1>
+cTimerEvent *CreateTimerEvent(TCLASS *c, TRESULT (TCLASS::*fp)(TARG1),
+ TARG1 arg1,
+ unsigned int TimeoutMs);
+template<class TCLASS>
+cTimerEvent *CreateTimerEvent(TCLASS *c, void (TCLASS::*fp)(void),
+ unsigned int TimeoutMs, bool runOnce = true);
+template<class TCLASS, class TARG1>
+cTimerEvent *CreateTimerEvent(TCLASS *c, void (TCLASS::*fp)(TARG1),
+ TARG1 arg1,
+ unsigned int TimeoutMs, bool runOnce = true);
+
+//
+// Timer event templates
+//
+
+template <class TCLASS, class TRESULT>
+class cTimerFunctorR0 : public cTimerEvent {
+
+ public:
+
+ protected:
+ typedef TRESULT (TCLASS::*TFUNC)(void);
+
+ cTimerFunctorR0(TCLASS *obj, TFUNC f, unsigned int TimeoutMs) :
+ m_obj(obj), m_f(f)
+ {
+ AddEvent(TimeoutMs);
+ }
+
+ virtual ~cTimerFunctorR0() {};
+
+ virtual bool TimerEvent(void)
+ {
+ return (*m_obj.*m_f)();
+ }
+
+ virtual void *TargetId() { return (void*)m_obj; }
+ virtual int size() { return sizeof(*this); }
+ virtual bool is(void *data, int len)
+ {
+ return sizeof(*this)==len && !memcmp(this,data,len);
+ }
+
+ private:
+ TCLASS *m_obj;
+ TFUNC m_f;
+
+ friend cTimerEvent *CreateTimerEvent<TCLASS,TRESULT>(TCLASS*,TFUNC,unsigned int);
+};
+
+template <class TCLASS, class TRESULT, class TARG1>
+class cTimerFunctorR1 : public cTimerEvent {
+
+ public:
+
+ protected:
+ typedef TRESULT (TCLASS::*TFUNC)(TARG1);
+
+ cTimerFunctorR1(TCLASS *obj, TFUNC f, TARG1 arg1, unsigned int TimeoutMs) :
+ m_obj(obj), m_f(f), m_arg1(arg1)
+ {
+ AddEvent(TimeoutMs);
+ }
+
+ virtual ~cTimerFunctorR1() {};
+
+ virtual bool TimerEvent(void)
+ {
+ return (*m_obj.*m_f)(m_arg1);
+ }
+
+ virtual void *TargetId() { return (void*)m_obj; }
+ virtual int size() { return sizeof(*this); }
+ virtual bool is(void *data, int len)
+ {
+ return sizeof(*this)==len && !memcmp(this,data,len);
+ }
+
+ private:
+ TCLASS *m_obj;
+ TFUNC m_f;
+ TARG1 m_arg1;
+
+ friend cTimerEvent *CreateTimerEvent<TCLASS,TRESULT,TARG1>(TCLASS*,TFUNC,TARG1,unsigned int);
+};
+
+template <class TCLASS>
+class cTimerFunctor0 : public cTimerEvent {
+
+ public:
+
+ protected:
+ typedef void (TCLASS::*TFUNC)(void);
+
+ cTimerFunctor0(TCLASS *obj, TFUNC f,
+ unsigned int TimeoutMs, bool runOnce) :
+ m_obj(obj), m_f(f), m_runAgain(!runOnce)
+ {
+ AddEvent(TimeoutMs);
+ }
+
+ virtual ~cTimerFunctor0() {};
+
+ virtual bool TimerEvent(void)
+ {
+ (*m_obj.*m_f)();
+ return m_runAgain;
+ }
+
+ virtual void *TargetId() { return (void*)m_obj; }
+ virtual int size() { return sizeof(*this); }
+ virtual bool is(void *data, int len)
+ {
+ return sizeof(*this)==len && !memcmp(this,data,len);
+ }
+
+ private:
+ TCLASS *m_obj;
+ TFUNC m_f;
+ bool m_runAgain;
+
+ friend cTimerEvent *CreateTimerEvent<TCLASS>(TCLASS*,TFUNC,unsigned int,bool);
+};
+
+template <class TCLASS, class TARG1>
+class cTimerFunctor1 : public cTimerEvent {
+
+ public:
+
+ protected:
+ typedef void (TCLASS::*TFUNC)(TARG1);
+
+ cTimerFunctor1(TCLASS *obj, TFUNC f, TARG1 arg1,
+ unsigned int TimeoutMs, bool runOnce) :
+ m_obj(obj), m_f(f), m_arg1(arg1), m_runAgain(!runOnce)
+ {
+ AddEvent(TimeoutMs);
+ }
+
+ virtual ~cTimerFunctor1() {};
+
+ virtual bool TimerEvent(void)
+ {
+ (*m_obj.*m_f)(m_arg1);
+ return m_runAgain;
+ }
+
+ virtual void *TargetId() { return (void*)m_obj; }
+ virtual int size() { return sizeof(*this); }
+ virtual bool is(void *data, int len)
+ {
+ return sizeof(*this)==len && !memcmp(this,data,len);
+ }
+
+ private:
+ TCLASS *m_obj;
+ TFUNC m_f;
+ TARG1 m_arg1;
+ bool m_runAgain;
+
+ friend cTimerEvent *CreateTimerEvent<TCLASS,TARG1>(TCLASS*,TFUNC,TARG1,unsigned int,bool);
+};
+
+//
+// Function templates for timer event creation and cancellation
+//
+
+template<class TCLASS, class TRESULT>
+cTimerEvent *CreateTimerEvent(TCLASS *c, TRESULT (TCLASS::*fp)(void),
+ unsigned int TimeoutMs)
+{
+ return new cTimerFunctorR0<TCLASS,TRESULT>(c,fp,TimeoutMs);
+}
+
+template<class TCLASS, class TRESULT, class TARG1>
+cTimerEvent *CreateTimerEvent(TCLASS *c, TRESULT (TCLASS::*fp)(TARG1),
+ TARG1 arg1,
+ unsigned int TimeoutMs)
+{
+ return new cTimerFunctorR1<TCLASS,TRESULT,TARG1>(c,fp,arg1,TimeoutMs);
+}
+
+template<class TCLASS>
+cTimerEvent *CreateTimerEvent(TCLASS *c, void (TCLASS::*fp)(void),
+ unsigned int TimeoutMs, bool runOnce = true)
+{
+ return new cTimerFunctor0<TCLASS>(c,fp,TimeoutMs,runOnce);
+}
+
+template<class TCLASS, class TARG1>
+cTimerEvent *CreateTimerEvent(TCLASS *c, void (TCLASS::*fp)(TARG1),
+ TARG1 arg1,
+ unsigned int TimeoutMs, bool runOnce = true)
+{
+ return new cTimerFunctor1<TCLASS,TARG1>(c,fp,arg1,TimeoutMs,runOnce);
+}
+
+template<class TCLASS>
+void CancelTimerEvents(TCLASS *c)
+{
+ cTimerEvent::CancelAll((void*)c);
+}
+
+
+// usage:
+//
+// 'this' derived from cTimerHandler:
+// Set timer:
+// cTimerCallback::Set(this, TimeoutMs);
+// Cancel timer:
+// - return false from handler or
+// - call cTimerCallback::Cancel(this); or
+// - delete 'this' object
+//
+// any function of any class:
+// Set timer:
+// - cTimerEvent *event = CreateTimerEvent(...);
+// example:
+// CreateTimerEvent(this, &cXinelibDevice::TimerEvent, 1, 1000);
+// -> calls this->cXinelibDevice::TimerEvent(1) every second until stopped.
+// Cancel timer:
+// - if handler returns bool: return false from handler
+// - handler is type of void: timer runs only once
+// - call cTimerEvent::Cancel(event)
+// Cancel all timers for object:
+// - Call CancelTimerEvents(object)
+// - Call CancelTimerEvents(this)
+
+
+#endif // __XINELIBOUTPUT_TIMER_H
+
+
diff --git a/tools/udp_buffer.h b/tools/udp_buffer.h
new file mode 100644
index 00000000..d3a5313d
--- /dev/null
+++ b/tools/udp_buffer.h
@@ -0,0 +1,115 @@
+/*
+ * udp_buffer.h: Ring buffer for UDP/RTP streams
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: udp_buffer.h,v 1.1 2006-06-03 10:04:28 phintuka Exp $
+ *
+ */
+
+#ifndef __UDP_BUFFER_H
+#define __UDP_BUFFER_H
+
+#include <stdint.h>
+
+#include "../xine_input_vdr_net.h" // frame headers
+
+
+#define UDP_BUFFER_SIZE 0x100 // 2^n
+#define UDP_BUFFER_MASK 0xff // 2^n - 1
+
+#if UDP_BUFFER_MASK != UDP_SEQ_MASK
+# error Buffer handling error !!!
+#endif
+
+
+class cUdpBackLog
+{
+ friend class cUdpScheduler;
+
+ private:
+
+ cUdpBackLog(cUdpBackLog&);
+
+ stream_udp_header_t *m_UdpBuffer[UDP_BUFFER_SIZE];
+ int m_UdpBufLen[UDP_BUFFER_SIZE]; /* size of allocated memory, not frame */
+ int m_PayloadSize[UDP_BUFFER_SIZE]; /* size of frame */
+ unsigned int m_SeqNo; /* next (outgoing) sequence number */
+
+ protected:
+
+ cUdpBackLog()
+ {
+ memset(m_UdpBuffer, 0, sizeof(stream_udp_header_t *)*UDP_BUFFER_SIZE);
+ memset(m_UdpBufLen, 0, sizeof(int) * UDP_BUFFER_SIZE);
+ memset(m_PayloadSize, 0, sizeof(int) * UDP_BUFFER_SIZE);
+ m_SeqNo = 0;
+ }
+
+ void Clear(int HowManyFrames)
+ {
+ // Clear n last frames from buffer.
+ // (called to adjust sequence numbering when some
+ // already allocated frames won't be sent)
+ //
+ // Note: Nothing is freed.
+ // To completely reset buffer it must be deleted and re-created.
+ //
+ m_SeqNo = (m_SeqNo + UDP_BUFFER_SIZE - HowManyFrames) & UDP_BUFFER_MASK;
+ }
+
+ virtual ~cUdpBackLog()
+ {
+ for(int i=0; i<UDP_BUFFER_SIZE; i++)
+ if(m_UdpBuffer[i]) {
+ //m_UdpBufLen[i] = 0;
+ DELETENULL(m_UdpBuffer[i]);
+ }
+ }
+
+ stream_udp_header_t *Get(int UdpSeqNo)
+ {
+ int BufIndex = UdpSeqNo & UDP_BUFFER_MASK;
+ return m_UdpBuffer[BufIndex];
+ }
+
+ int PayloadSize(int UdpSeqNo)
+ {
+ int BufIndex = UdpSeqNo & UDP_BUFFER_MASK;
+ return m_UdpBuffer[BufIndex] ? m_PayloadSize[BufIndex] : 0;
+ }
+
+ stream_udp_header_t *MakeFrame(uint64_t StreamPos,
+ const uchar *Data, int DataLen)
+ {
+ int UdpPacketLen = DataLen + sizeof(stream_udp_header_t);
+ int BufIndex = m_SeqNo & UDP_BUFFER_MASK;
+
+ // old buffer too small ? free it
+ if(m_UdpBuffer[BufIndex] && m_UdpBufLen[BufIndex] < UdpPacketLen)
+ DELETENULL(m_UdpBuffer[BufIndex]);
+
+ // no buffer ? alloc it
+ if(!m_UdpBuffer[BufIndex]) {
+ m_UdpBuffer[BufIndex] = (stream_udp_header_t*)new uchar[UdpPacketLen];
+ m_UdpBufLen[BufIndex] = UdpPacketLen;
+ }
+ m_PayloadSize[BufIndex] = DataLen;
+
+ // Fill frame to buffer
+ stream_udp_header_t *header = m_UdpBuffer[BufIndex];
+ uchar *Payload = UDP_PAYLOAD(header);
+
+ memcpy(Payload, Data, DataLen);
+ header->pos = htonll(StreamPos);
+ header->seq = htons(m_SeqNo);
+
+ m_SeqNo = (m_SeqNo + 1) & UDP_SEQ_MASK;
+
+ return header;
+ }
+};
+
+
+#endif
diff --git a/tools/udp_pes_scheduler.c b/tools/udp_pes_scheduler.c
new file mode 100644
index 00000000..5da4be74
--- /dev/null
+++ b/tools/udp_pes_scheduler.c
@@ -0,0 +1,595 @@
+/*
+ * udp_pes_scheduler.h: PES scheduler for UDP/RTP streams
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: udp_pes_scheduler.c,v 1.1 2006-06-03 10:04:28 phintuka Exp $
+ *
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include <vdr/config.h>
+#include <vdr/tools.h>
+
+#include "../logdefs.h"
+
+#include "udp_buffer.h"
+#include "pes.h"
+
+#include "udp_pes_scheduler.h"
+
+#include "../xine_input_vdr_net.h" // frame headers
+
+
+//----------------------- cTimePts ------------------------------------------
+
+cTimePts::cTimePts(void)
+{
+ Set();
+}
+
+int64_t cTimePts::Now(void)
+{
+ struct timeval t;
+
+ if (gettimeofday(&t, NULL) == 0) {
+ t.tv_sec -= tbegin.tv_sec;
+ if(t.tv_usec < tbegin.tv_usec) {
+ t.tv_sec--;
+ t.tv_usec += 1000000;
+ }
+ t.tv_usec -= tbegin.tv_usec;
+
+ return (uint64(t.tv_sec)) * 90000LL +
+ (uint64(t.tv_usec)) * 90LL / 1000LL +
+ begin;
+ }
+
+ return 0;
+}
+
+void cTimePts::Set(int64_t Pts)
+{
+ gettimeofday(&tbegin, NULL);
+ begin = Pts;
+}
+
+//----------------------- cUdpScheduler -------------------------------------
+
+//#define LOG_RESEND
+//#define LOG_SCR
+
+const int MAX_QUEUE_SIZE = 64; // ~ 65 ms with typical DVB stream
+const int MAX_LIVE_QUEUE_SIZE = (64+32); // ~ 100 ms with typical DVB stream
+const int HARD_LIMIT = (4*1024); // ~ 40 Mbit/s === 4 Mb/s
+
+// initial burst length after seek (500ms = ~13 video frames)
+const int64_t INITIAL_BURST_TIME = (int64_t)(45000); // pts units (90kHz)
+
+// assume seek when when pts difference between two frames exceeds this (1.5 seconds)
+const int64_t JUMP_LIMIT_TIME = (int64_t)(3*90000/2); // pts units (90kHz)
+
+cUdpScheduler::cUdpScheduler()
+{
+
+ // Scheduler data
+
+ current_audio_vtime = 0;
+ current_video_vtime = 0;
+
+#ifdef LOG_SCR
+ data_sent = 0;
+ frames_sent = 0;
+ frame_rate = 2000;
+ prev_frames = 200;
+#endif
+
+ last_delay_time = 0;
+
+ // queuing
+
+ int i;
+ for(i=0; i<MAX_UDP_HANDLES; i++)
+ m_Handles[i] = -1;
+
+ m_BackLog = new cUdpBackLog;
+
+ m_QueueNextSeq = 0;
+ m_QueuePending = 0;
+
+ // Thread
+
+ m_Running = 1;
+
+ Start();
+}
+
+cUdpScheduler::~cUdpScheduler()
+{
+ m_Lock.Lock();
+ m_Running = 0;
+ m_Cond.Broadcast();
+ m_Lock.Unlock();
+
+ Cancel(3);
+
+ delete m_BackLog;
+}
+
+bool cUdpScheduler::AddHandle(int fd)
+{
+ cMutexLock ml(&m_Lock);
+
+ int i;
+
+ for(i=0; i<MAX_UDP_HANDLES; i++)
+ if(m_Handles[i] < 0 || m_Handles[i] == fd) {
+ m_Handles[i] = fd;
+ return true;
+ }
+
+ return false;
+}
+
+void cUdpScheduler::RemoveHandle(int fd)
+{
+ cMutexLock ml(&m_Lock);
+
+ int i;
+ for(i=0; i<MAX_UDP_HANDLES; i++)
+ if(m_Handles[i] == fd)
+ break;
+
+ for(; i<MAX_UDP_HANDLES-1; i++)
+ m_Handles[i] = m_Handles[i+1];
+
+ m_Handles[MAX_UDP_HANDLES-1] = -1;
+
+ if(m_Handles[0] < 0) {
+ // No clients left ...
+
+ // Flush all buffers
+ m_QueueNextSeq = 0;
+ m_QueuePending = 0;
+ delete m_BackLog;
+ m_BackLog = new cUdpBackLog;
+ }
+}
+
+bool cUdpScheduler::Poll(int TimeoutMs, bool Master)
+{
+ cMutexLock ml(&m_Lock);
+
+ m_Master = Master;
+
+ if(m_Handles[0] < 0) {
+ // no clients, so we can eat all data we are given ...
+ return true;
+ }
+
+ uint64_t WaitEnd = cTimeMs::Now();
+ if(TimeoutMs >= 0)
+ WaitEnd += (uint64_t)TimeoutMs;
+
+ int limit = m_Master ? MAX_QUEUE_SIZE : MAX_LIVE_QUEUE_SIZE;
+ while(cTimeMs::Now() < WaitEnd &&
+ m_Running &&
+ m_QueuePending >= limit)
+ m_Cond.TimedWait(m_Lock, 5);
+
+ return m_QueuePending < limit;
+}
+
+bool cUdpScheduler::Flush(int TimeoutMs)
+{
+ uint64_t WaitEnd = cTimeMs::Now();
+ if(TimeoutMs >= 0)
+ WaitEnd += (uint64_t)TimeoutMs;
+
+ cMutexLock ml(&m_Lock);
+
+ if(m_Handles[0] < 0)
+ return true;
+
+ while(cTimeMs::Now() < WaitEnd &&
+ m_Running &&
+ m_QueuePending > 0)
+ m_Cond.TimedWait(m_Lock, 25);
+
+ return m_QueuePending == 0;
+}
+
+void cUdpScheduler::Clear(void)
+{
+ cMutexLock ml(&m_Lock);
+
+ m_BackLog->Clear(m_QueuePending);
+ m_QueuePending = 0;
+
+ m_Cond.Broadcast();
+}
+
+bool cUdpScheduler::Queue(uint64_t StreamPos, const uchar *Data, int Length)
+{
+ cMutexLock ml(&m_Lock);
+
+ if(m_Handles[0] < 0)
+ return true;
+
+ if(m_QueuePending >= MAX_QUEUE_SIZE)
+ return false;
+
+ m_BackLog->MakeFrame(StreamPos, Data, Length);
+ m_QueuePending++;
+
+ m_Cond.Broadcast();
+
+ return true;
+}
+
+int cUdpScheduler::calc_elapsed_vtime(int64_t pts, bool Audio)
+{
+ int64_t diff = 0;
+
+ if(!Audio /*Video*/) {
+ /* #warning TODO: should be possible to use video pts too (if ac3 or muted ...) */
+ diff = pts - current_video_vtime;
+ if(diff < 0) diff = -diff;
+ if(diff > JUMP_LIMIT_TIME) { // 1 s (must be > GOP)
+ // RESET
+#ifdef LOG_SCR
+ LOGDBG("cUdpScheduler RESET (Video jump %lld->%lld)",
+ current_audio_vtime, pts);
+ data_sent = frames_sent = 0;
+#endif
+ //diff = 0;
+ current_video_vtime = pts;
+ return -1;
+ }
+ current_video_vtime = pts;
+
+ } else if(Audio) {
+ diff = pts - current_audio_vtime;
+ if(diff < 0) diff = -diff;
+ if(diff > JUMP_LIMIT_TIME) { // 1 sec
+ // RESET
+#ifdef LOG_SCR
+ LOGDBG("cUdpScheduler RESET (Audio jump %lld->%lld)",
+ current_audio_vtime, pts);
+ data_sent = frames_sent = 0;
+#endif
+ //diff = 0;
+ current_audio_vtime = pts;
+
+ // Use audio pts for sync (audio has constant and increasing intervals)
+ MasterClock.Set(current_audio_vtime + INITIAL_BURST_TIME);
+
+ return -1;
+ }
+ current_audio_vtime = pts;
+ }
+
+#ifdef LOG_SCR
+ if(diff && Audio) {
+ frame_rate = (int)(90000*frames_sent/(int)diff);
+ LOGDBG("rate %d kbit/s (%d frames/s)",
+ (int)(90*data_sent/((int)diff)*8), frame_rate);
+ prev_frames = frames_sent;
+ data_sent = frames_sent = 0;
+ }
+#endif
+
+ return (int) diff;
+}
+
+void cUdpScheduler::Schedule(const uchar *Data, int Length)
+{
+ bool Audio=false, Video=false;
+ int64_t pts = pes_extract_pts(Data, Length, Audio, Video);
+ int elapsed = pts>0 ? calc_elapsed_vtime(pts, Audio) : 0;
+
+#ifdef LOG_SCR
+ if(elapsed > 0)
+ LOGMSG("PTS: %lld (%s) elapsed %d ms",
+ pts, Video?"Video":Audio?"Audio":"?", elapsed/90);
+#endif
+
+ if(elapsed > 0 && Audio/*Video*/) {
+ int64_t now = MasterClock.Now();
+ if(now > current_audio_vtime && (now - current_audio_vtime)>JUMP_LIMIT_TIME) {
+#ifdef LOG_SCR
+ LOGMSG("cUdpScheduler MasterClock init (was in past)");
+ elapsed = -1;
+#endif
+ MasterClock.Set(current_audio_vtime + INITIAL_BURST_TIME);
+ } else if(now < current_audio_vtime && (current_audio_vtime-now)>JUMP_LIMIT_TIME) {
+#ifdef LOG_SCR
+ LOGMSG("cUdpScheduler MasterClock init (was in future)");
+ elapsed = -1;
+#endif
+ MasterClock.Set(current_audio_vtime + INITIAL_BURST_TIME);
+ } else if(!last_delay_time) {
+ // first burst done, no delay yet ???
+ // (queue up to xxx bytes first)
+ } else {
+ if(current_audio_vtime > now) {
+ int delay_ms = (int)(current_audio_vtime - now)/90;
+#ifdef LOG_SCR
+ LOGDBG("cUdpScheduler sleeping %d ms "
+ "(time reference: %s, beat interval %d ms)",
+ delay_ms, (Audio?"Audio PTS":"Video PTS"), elapsed);
+#endif
+ if(delay_ms > 3) {
+ //LOGMSG("sleep %d ms (%d f)", delay_ms, prev_frames);
+ CondWait.Wait(delay_ms);
+ }
+ }
+ }
+ last_delay_time = now;
+ }
+
+#if 0
+ static int win = 0;
+ static int64_t prev;
+
+ if(data_sent == 0 || elapsed < 0) {
+ win = 0;
+ prev = MasterClock.Now();
+ }
+ win ++;
+ int mrate = 3*frame_rate/2;
+ if(mrate < 100) mrate = 100;
+ if(mrate > 2000) mrate = 2000;
+ if(MasterClock.Now() - prev >= win*90000 / frame_rate) {
+ LOGMSG("sleep:3");
+ CondWait.Wait(3);
+ }
+#endif
+
+#ifdef LOG_SCR
+ data_sent += Length;
+ frames_sent ++;
+#endif
+}
+
+void cUdpScheduler::Action(void)
+{
+#if 0
+ {
+ // Request real-time scheduling
+ sched_param temp;
+ temp.sched_priority = 2;
+
+ if (!pthread_setschedparam(pthread_self(), SCHED_RR, &temp)) {
+ LOGMSG("cUdpScheduler priority set successful SCHED_RR %d [%d,%d]",
+ temp.sched_priority,
+ sched_get_priority_min(SCHED_RR),
+ sched_get_priority_max(SCHED_RR));
+ } else {
+ LOGMSG("cUdpScheduer: Can't set priority to SCHED_RR %d [%d,%d]",
+ temp.sched_priority,
+ sched_get_priority_min(SCHED_RR),
+ sched_get_priority_max(SCHED_RR));
+ }
+
+ /* UDP Scheduler needs high priority */
+ SetPriority(0);
+ SetPriority(-1);
+ SetPriority(-2);
+ SetPriority(-3);
+ SetPriority(-4);
+ SetPriority(-5);
+ }
+#endif
+
+ m_Lock.Lock();
+
+ while(m_Running) {
+
+ if(m_Handles[0] < 0) {
+ m_Cond.TimedWait(m_Lock, 5000);
+ continue;
+ }
+
+ // Wait until we have outgoing data in queue
+ if(m_QueuePending <= 0) {
+ m_Cond.TimedWait(m_Lock, 100);
+ if(m_QueuePending <= 0) {
+ static unsigned char padding[] = {0x00,0x00,0x01,0xBE,0x00,0x02,0xff,0xff};
+ int prevseq = (m_QueueNextSeq + UDP_BUFFER_SIZE - 1) & UDP_BUFFER_MASK;
+ stream_udp_header_t *frame = m_BackLog->Get(prevseq);
+ if(frame)
+ m_BackLog->MakeFrame(ntohll(frame->pos), padding, 8);
+ else
+ m_BackLog->MakeFrame(0, padding, 8);
+ m_QueuePending++;
+ }
+ continue; // to check m_Running
+ }
+
+ // Take next frame from queue
+ stream_udp_header_t *frame = m_BackLog->Get(m_QueueNextSeq);
+ int PayloadSize = m_BackLog->PayloadSize(m_QueueNextSeq);
+ int UdpPacketLen = PayloadSize + sizeof(stream_udp_header_t);
+ m_QueueNextSeq = (m_QueueNextSeq + 1) & UDP_BUFFER_MASK;
+ m_QueuePending--;
+
+ m_Cond.Broadcast();
+
+ m_Lock.Unlock();
+
+#if 0 /* debugging checks */
+ {
+ if(!frame)
+ LOGMSG("frame == NULL !");
+ uint8_t *p = UDP_PAYLOAD(frame);
+
+ if(p[0] || p[1] || p[2]!=1)
+ LOGMSG("cUdpScheduler: invalid content");
+
+ int n = sizeof(stream_udp_header_t) + (p[4]<<8) + p[5] + 6;
+ if(n != UdpPacketLen)
+ LOGMSG("cUdpScheduler: length error -- %d != %d", n, UdpPacketLen);
+
+ static int seq = 0;
+ if(seq != ntohs(frame->seq))
+ LOGMSG("cUdpScheduler: SEQ jump %d -> %d !", seq, ntohs(frame->seq));
+ seq = (ntohs(frame->seq) + 1) & UDP_BUFFER_MASK;
+
+ if(PayloadSize != 8) {
+ static uint64_t pos = 0;
+ if(pos != ntohull(frame->pos))
+ LOGMSG("cUdpScheduler: POS jump %lld -> %lld !", pos, ntohull(frame->pos));
+ pos = ntohull(frame->pos) + PayloadSize;
+ }
+ }
+#endif
+
+ // Schedule frame
+ if(m_Master)
+ Schedule(UDP_PAYLOAD(frame), PayloadSize);
+
+ /* need some limit here for ex. sequence of stills when moving cutting marks very fast
+ (no audio or PTS available) */
+#if 1
+ // hard limit for used bandwidth:
+ // - ~1 frames/ms & 8kb/ms -> 8mb/s -> ~ 80 Mbit/s ( / client)
+ // - max burst 15 frames or 30kb
+ static int cnt = 0, bytes = 0;
+ static uint64_t dbg_timer = cTimeMs::Now();
+ static int dbg_bytes = 0;
+ cnt++;
+ bytes += PayloadSize;
+ if(cnt>=15 && bytes >= 30000) {
+ CondWait.Wait(4);
+ dbg_bytes += bytes;
+ cnt = 0;
+ bytes = 0;
+ if(dbg_timer+60000 <= cTimeMs::Now()) {
+ LOGDBG("UDP rate: %4d Kbps (queue %d)", dbg_bytes/(60*1024/8),
+ m_QueuePending);
+ dbg_bytes = 0;
+ dbg_timer = cTimeMs::Now();
+ }
+ }
+#endif
+
+ for(int i=0; i<MAX_UDP_HANDLES && m_Handles[i]>=0; i++) {
+
+ //
+ // use TIOCOUTQ ioctl instead of poll/select.
+ // - poll/select for UDP/RTP may return true even when queue
+ // is (almost) full
+ // - kernel silently drops frames it cant send
+ // -> poll() + send() just causes frames to be dropped
+ //
+ int size = 0;
+ if(!ioctl(m_Handles[i], TIOCOUTQ, &size))
+ if(size > ((0x10000)/2 - 2048)) { // assume 64k kernel buffer
+ int wmem=0;
+ socklen_t l = sizeof(int);
+ if(!getsockopt(m_Handles[i], SOL_SOCKET, SO_SNDBUF, &wmem, &l)) {
+#if 0
+// Large bursts cause client to loose data :(
+ if(size >= (wmem/2 - 8128)) {
+ LOGMSG("cUdpScheduler: kernel transmit queue > ~%dkb ! (master=%d)",
+ (wmem/2-8128)/1024, m_Master);
+ CondWait.Wait(2);
+ }
+ else
+#endif
+ {
+ if(m_QueuePending > (MAX_QUEUE_SIZE-5))
+ LOGMSG("cUdpScheduler: kernel transmit queue > ~30kb ! (master=%d ; Queue=%d)",
+ m_Master, m_QueuePending);
+ CondWait.Wait(2);
+ }
+ }
+ }
+
+ if(send(m_Handles[i], frame, UdpPacketLen, 0) <= 0)
+ LOGERR("cUdpScheduler: UDP send() failed !");
+ }
+
+ m_Lock.Lock();
+ }
+
+ m_Lock.Unlock();
+}
+
+void cUdpScheduler::ReSend(int fd, uint64_t Pos, int Seq1, int Seq2)
+{
+ cMutexLock ml(&m_Lock); // keeps also scheduler thread suspended ...
+
+ // Handle buffer wrap
+ if(Seq1 > Seq2)
+ Seq2 += UDP_BUFFER_SIZE;
+
+ if(Seq2-Seq1 > 64) {
+ LOGDBG("cUdpScheduler::ReSend: requested range too large (%d-%d)",
+ Seq1, Seq2);
+ return;
+ }
+
+ // re-send whole range
+ for(; Seq1 <= Seq2; Seq1++) {
+
+ // Wait if kernel queue is full
+ int size=0;
+ if(!ioctl(fd, TIOCOUTQ, &size))
+ if(size > ((0x10000)/2 - 2048)) { // assume 64k kernel buffer
+ LOGDBG("cUdpScheduler::ReSend: kernel transmit queue > ~30kb !");
+ cCondWait::SleepMs(2);
+ }
+
+ stream_udp_header_t *frame = m_BackLog->Get(Seq1);
+
+ if(frame) {
+ if(ntohull(frame->pos) == Pos) {
+ send(fd,
+ frame,
+ m_BackLog->PayloadSize(Seq1) + sizeof(stream_udp_header_t),
+ 0);
+#ifdef LOG_RESEND
+ LOGDBG("cUdpScheduler::ReSend: %d (%d bytes) @%lld sent",
+ Seq1, m_BackLog->PayloadSize(Seq1), Pos);
+#endif
+ Pos += m_BackLog->PayloadSize(Seq1);
+ continue;
+ } else {
+ // buffer has been lost long time ago...
+#ifdef LOG_RESEND
+ LOGDBG("cUdpScheduler::ReSend: Requested position does not match "
+ "(%lld ; has %lld)", Pos, ntohll(frame->pos));
+#endif
+ }
+ } else {
+#ifdef LOG_RESEND
+ LOGDBG("cUdpScheduler::ReSend: %d @%lld missing", Seq1, Pos);
+#endif
+ }
+ // buffer has been lost
+ // send packet missing info
+ char udp_ctrl[64];
+ ((stream_udp_header_t *)udp_ctrl)->seq = (uint16_t)(-1);
+ ((stream_udp_header_t *)udp_ctrl)->pos = (uint64_t)(-1);
+
+#ifdef LOG_RESEND
+ LOGDBG("cUdpScheduler::ReSend: missing %d-%d @%d (hdr 0x%llx 0x%x)",
+ Seq1, Seq1, Pos,
+ ((stream_udp_header_t *)udp_ctrl)->pos,
+ ((stream_udp_header_t *)udp_ctrl)->seq);
+#endif
+ sprintf((udp_ctrl+sizeof(stream_udp_header_t)),
+ "UDP MISSING %d-%d %lld",
+ Seq1, Seq1, Pos);
+
+ send(fd, udp_ctrl, 64, 0);
+ }
+}
diff --git a/tools/udp_pes_scheduler.h b/tools/udp_pes_scheduler.h
new file mode 100644
index 00000000..57fce688
--- /dev/null
+++ b/tools/udp_pes_scheduler.h
@@ -0,0 +1,103 @@
+/*
+ * udp_pes_scheduler.h: PES scheduler for UDP/RTP streams
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: udp_pes_scheduler.h,v 1.1 2006-06-03 10:04:28 phintuka Exp $
+ *
+ */
+
+#ifndef __UDP_PES_SCHEDULER_H
+#define __UDP_PES_SCHEDULER_H
+
+#include <stdint.h>
+
+#include <vdr/tools.h> // uchar
+#include <vdr/thread.h>
+
+//----------------------- cTimePts ------------------------------------------
+
+class cTimePts
+{
+ private:
+ int64_t begin;
+ struct timeval tbegin;
+
+ public:
+ cTimePts(void);
+
+ int64_t Now(void);
+ void Set(int64_t Pts = 0LL);
+};
+
+//----------------------- cUdpPesScheduler ----------------------------------
+
+#define MAX_UDP_HANDLES 16
+
+class cUdpBackLog;
+
+class cUdpScheduler : public cThread
+{
+ public:
+
+ cUdpScheduler();
+ virtual ~cUdpScheduler();
+
+ // fd should be binded & connected to IP:PORT (local+remote) pair !
+ bool AddHandle(int fd);
+ void RemoveHandle(int fd);
+
+ bool Poll(int TimeoutMs, bool Master);
+ bool Queue(uint64_t StreamPos, const uchar *Data, int Length);
+ void ReSend(int fd, uint64_t Pos, int Seq1, int Seq2);
+
+ void Clear(void);
+ bool Flush(int TimeoutMs);
+
+ protected:
+
+ // Data for payload handling & buffering
+
+ // Signalling
+ cCondVar m_Cond;
+ cMutex m_Lock;
+
+ // Clients
+ int m_Handles[MAX_UDP_HANDLES];
+
+ // Queue
+ int m_QueueNextSeq; /* next outgoing */
+ int m_QueuePending; /* outgoing queue size */
+ cUdpBackLog *m_BackLog; /* queue for incoming data (not yet send) and retransmissions */
+
+ // Data for scheduling algorithm
+
+ cTimePts RtpScr; // 90 kHz monotonic time source for RTP packets
+ cTimePts MasterClock; // Current MPEG PTS (synchronized with current stream)
+ cCondWait CondWait;
+
+ int64_t current_audio_vtime;
+ int64_t current_video_vtime;
+
+#if 0
+ int data_sent; /* in current time interval, bytes */
+ int frames_sent; /* in current time interval */
+ int frame_rate; /* pes frames / second */
+ int prev_frames;
+#endif
+
+ int64_t last_delay_time;
+
+ bool m_Master; /* if true, we are master metronom for playback */
+
+ // Scheduling
+
+ int calc_elapsed_vtime(int64_t pts, bool Audio);
+ void Schedule(const uchar *Data, int Length);
+
+ bool m_Running;
+ virtual void Action(void);
+};
+
+#endif
diff --git a/xine/post.c b/xine/post.c
new file mode 100644
index 00000000..615f8b37
--- /dev/null
+++ b/xine/post.c
@@ -0,0 +1,868 @@
+/*
+ * Copyright (C) 2003 by Dirk Meyer
+ *
+ * This file is part of xine, a unix video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * The code is taken from xine-ui/src/xitk/post.c at changed to work with fbxine
+ *
+ * Modified for VDR xineliboutput plugin by Petri Hintukainen, 2006
+ * - runtime re-configuration (load/unload, enable/disable)
+ * - support for multiple streams
+ * - support for mosaico post plugin (picture-in-picture)
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "post.h"
+
+#if 1
+# include <sys/types.h>
+# include <linux/unistd.h>
+# include <errno.h>
+
+# define LOG_MODULENAME "[xine-post] "
+# include "../logdefs.h"
+
+ /* from xine_frontend.c or vdr tools.c: */
+ extern int SysLogLevel; /* errors and info, no debug */
+ /* from xine_frontend.c: */
+ extern int LogToSysLog; /* log to syslog instead of console */
+
+# if !defined(XINELIBOUTPUT_DEBUG_STDOUT) && \
+ !defined(XINELIBOUTPUT_DEBUG_STDERR)
+
+# undef x_syslog
+
+ pid_t gettid(void); /*_syscall0(pid_t, gettid);*/
+
+ static void x_syslog(int level, const char *fmt, ...)
+ {
+ va_list argp;
+ char buf[512];
+ va_start(argp, fmt);
+ vsnprintf(buf, 512, fmt, argp);
+ if(!LogToSysLog) {
+ printf(LOG_MODULENAME "%s\n", buf);
+ } else {
+ syslog(level, "[%d] " LOG_MODULENAME "%s", gettid(), buf);
+ }
+ va_end(argp);
+ }
+# endif
+
+#else
+# define LOGERR(x...)
+# define LOGMSG(x...)
+# define LOGDBG(x...)
+# define TRACELINE (void)
+#endif
+
+#define fe_t post_plugins_t
+
+typedef struct {
+ xine_post_t *post;
+ xine_post_api_t *api;
+ xine_post_api_descr_t *descr;
+ xine_post_api_parameter_t *param;
+ char *param_data;
+
+ int x;
+ int y;
+
+ int readonly;
+
+ char **properties_names;
+} post_object_t;
+
+
+static int __pplugin_retrieve_parameters(post_object_t *pobj)
+{
+ xine_post_in_t *input_api;
+
+ if((input_api = (xine_post_in_t *) xine_post_input(pobj->post,
+ "parameters"))) {
+ xine_post_api_t *post_api;
+ xine_post_api_descr_t *api_descr;
+ xine_post_api_parameter_t *parm;
+ int pnum = 0;
+
+ post_api = (xine_post_api_t *) input_api->data;
+
+ api_descr = post_api->get_param_descr();
+
+ parm = api_descr->parameter;
+ pobj->param_data = malloc(api_descr->struct_size);
+
+ while(parm->type != POST_PARAM_TYPE_LAST) {
+
+ post_api->get_parameters(pobj->post, pobj->param_data);
+
+ if(!pnum)
+ pobj->properties_names = (char **) xine_xmalloc(sizeof(char *) * 2);
+ else
+ pobj->properties_names = (char **)
+ realloc(pobj->properties_names, sizeof(char *) * (pnum + 2));
+
+ pobj->properties_names[pnum] = strdup(parm->name);
+ pobj->properties_names[pnum + 1] = NULL;
+ pnum++;
+ parm++;
+ }
+
+ pobj->api = post_api;
+ pobj->descr = api_descr;
+ pobj->param = api_descr->parameter;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static void _pplugin_update_parameter(post_object_t *pobj)
+{
+ pobj->api->set_parameters(pobj->post, pobj->param_data);
+ pobj->api->get_parameters(pobj->post, pobj->param_data);
+}
+
+static void __pplugin_update_parameters(xine_post_t *post, char *args)
+{
+ post_object_t pobj;
+ char *p;
+
+ memset(&pobj, 0, sizeof(post_object_t));
+ pobj.post = post;
+
+ if(__pplugin_retrieve_parameters(&pobj)) {
+ int i;
+
+ if(pobj.properties_names && args && *args) {
+ char *param;
+
+ while((param = xine_strsep(&args, ",")) != NULL) {
+
+ p = param;
+
+ while((*p != '\0') && (*p != '='))
+ p++;
+
+ if(p && strlen(p)) {
+ int param_num = 0;
+
+ *p++ = '\0';
+
+ while(pobj.properties_names[param_num]
+ && strcasecmp(pobj.properties_names[param_num], param))
+ param_num++;
+
+ if(pobj.properties_names[param_num]) {
+
+ pobj.param = pobj.descr->parameter;
+ pobj.param += param_num;
+ pobj.readonly = pobj.param->readonly;
+
+ switch(pobj.param->type) {
+ case POST_PARAM_TYPE_INT:
+ if(!pobj.readonly) {
+ if(pobj.param->enum_values) {
+ char **values = pobj.param->enum_values;
+ int i = 0;
+
+ while(values[i]) {
+ if(!strcasecmp(values[i], p)) {
+ *(int *)(pobj.param_data + pobj.param->offset) = i;
+ break;
+ }
+ i++;
+ }
+
+ if( !values[i] )
+ *(int *)(pobj.param_data + pobj.param->offset) = (int) strtol(p, &p, 10);
+ } else {
+ *(int *)(pobj.param_data + pobj.param->offset) = (int) strtol(p, &p, 10);
+ }
+ _pplugin_update_parameter(&pobj);
+ }
+ break;
+
+ case POST_PARAM_TYPE_DOUBLE:
+ if(!pobj.readonly) {
+ *(double *)(pobj.param_data + pobj.param->offset) = strtod(p, &p);
+ _pplugin_update_parameter(&pobj);
+ }
+ break;
+
+ case POST_PARAM_TYPE_CHAR:
+ case POST_PARAM_TYPE_STRING:
+ if(!pobj.readonly) {
+ if(pobj.param->type == POST_PARAM_TYPE_CHAR) {
+ int maxlen = pobj.param->size / sizeof(char);
+
+ snprintf((char *)(pobj.param_data + pobj.param->offset), maxlen, "%s", p);
+ _pplugin_update_parameter(&pobj);
+ }
+ else
+ fprintf(stderr, "parameter type POST_PARAM_TYPE_STRING not supported yet.\n");
+ }
+ break;
+
+ case POST_PARAM_TYPE_STRINGLIST: /* unsupported */
+ if(!pobj.readonly)
+ fprintf(stderr, "parameter type POST_PARAM_TYPE_STRINGLIST not supported yet.\n");
+ break;
+
+ case POST_PARAM_TYPE_BOOL:
+ if(!pobj.readonly) {
+ *(int *)(pobj.param_data + pobj.param->offset) = ((int) strtol(p, &p, 10)) ? 1 : 0;
+ _pplugin_update_parameter(&pobj);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ i = 0;
+
+ while(pobj.properties_names[i]) {
+ free(pobj.properties_names[i]);
+ i++;
+ }
+
+ free(pobj.properties_names);
+ }
+
+ free(pobj.param_data);
+ }
+}
+
+/* -post <name>:option1=value1,option2=value2... */
+static post_element_t **pplugin_parse_and_load(fe_t *fe,
+ int plugin_type,
+ const char *pchain,
+ int *post_elements_num)
+{
+ post_element_t **post_elements = NULL;
+ char *post_chain;
+
+ *post_elements_num = 0;
+
+ if(pchain && strlen(pchain)) {
+ char *p;
+
+ xine_strdupa(post_chain, pchain);
+
+ while((p = xine_strsep(&post_chain, ";"))) {
+
+ if(p && strlen(p)) {
+ char *plugin, *args = NULL;
+ xine_post_t *post;
+
+ while(*p == ' ')
+ p++;
+
+ plugin = strdup(p);
+
+ if((p = strchr(plugin, ':')))
+ *p++ = '\0';
+
+ if(p && (strlen(p) > 1))
+ args = p;
+#if 0
+ post = xine_post_init(fe->xine, plugin, 0,
+ &fe->audio_port, &fe->video_port);
+#else
+ if(plugin_type == XINE_POST_TYPE_VIDEO_COMPOSE) {
+ post = xine_post_init(fe->xine, plugin, 2,
+ &fe->audio_port, &fe->video_port);
+ } else
+ post = xine_post_init(fe->xine, plugin, 0,
+ &fe->audio_port, &fe->video_port);
+#endif
+
+ if (post && plugin_type) {
+ if (post->type != plugin_type) {
+ xine_post_dispose(fe->xine, post);
+ post = NULL;
+ }
+ }
+
+ if(post) {
+
+ if(!(*post_elements_num))
+ post_elements = (post_element_t **) xine_xmalloc(sizeof(post_element_t *) * 2);
+ else
+ post_elements = (post_element_t **)
+ realloc(post_elements, sizeof(post_element_t *) * ((*post_elements_num) + 2));
+
+ post_elements[(*post_elements_num)] = (post_element_t *)
+ xine_xmalloc(sizeof(post_element_t));
+ post_elements[(*post_elements_num)]->post = post;
+ post_elements[(*post_elements_num)]->name = strdup(plugin);
+#if 1
+ post_elements[(*post_elements_num)]->args = args ? strdup(args) : NULL;
+ post_elements[(*post_elements_num)]->enable = 0;
+#endif
+ (*post_elements_num)++;
+ post_elements[(*post_elements_num)] = NULL;
+
+ __pplugin_update_parameters(post, args);
+ }
+
+ free(plugin);
+ }
+ }
+ }
+
+ return post_elements;
+}
+
+void pplugin_parse_and_store_post(fe_t *fe, int plugin_type,
+ const char *post_chain)
+{
+ post_element_t ***_post_elements;
+ int *_post_elements_num;
+ post_element_t **posts = NULL;
+ int num;
+
+ switch(plugin_type) {
+ case XINE_POST_TYPE_VIDEO_FILTER:
+ _post_elements = &fe->post_video_elements;
+ _post_elements_num = &fe->post_video_elements_num;
+ break;
+ case XINE_POST_TYPE_VIDEO_COMPOSE:
+ _post_elements = &fe->post_pip_elements;
+ _post_elements_num = &fe->post_pip_elements_num;
+ break;
+ case XINE_POST_TYPE_AUDIO_VISUALIZATION:
+ _post_elements = &fe->post_vis_elements;
+ _post_elements_num = &fe->post_vis_elements_num;
+ break;
+ default:
+ _post_elements = &fe->post_audio_elements;
+ _post_elements_num = &fe->post_audio_elements_num;
+ break;
+ }
+
+ if((posts = pplugin_parse_and_load(fe, plugin_type, post_chain, &num))) {
+ if(*_post_elements_num) {
+ int i;
+ int ptot = *_post_elements_num + num;
+
+ *_post_elements = (post_element_t **) realloc(*_post_elements,
+ sizeof(post_element_t *) * (ptot + 1));
+ for(i = *_post_elements_num; i < ptot; i++)
+ (*_post_elements)[i] = posts[i - *_post_elements_num];
+
+ (*_post_elements)[i] = NULL;
+ (*_post_elements_num) += num;
+
+ }
+ else {
+ *_post_elements = posts;
+ *_post_elements_num = num;
+ }
+ }
+}
+
+
+void vpplugin_parse_and_store_post(fe_t *fe, const char *post_chain)
+{
+ pplugin_parse_and_store_post(fe, XINE_POST_TYPE_VIDEO_FILTER, post_chain);
+ pplugin_parse_and_store_post(fe, XINE_POST_TYPE_VIDEO_COMPOSE, post_chain);
+}
+
+
+void applugin_parse_and_store_post(fe_t *fe, const char *post_chain)
+{
+ pplugin_parse_and_store_post(fe, XINE_POST_TYPE_AUDIO_FILTER, post_chain);
+ pplugin_parse_and_store_post(fe, XINE_POST_TYPE_AUDIO_VISUALIZATION, post_chain);
+}
+
+
+static void _vpplugin_unwire(fe_t *fe)
+{
+ xine_post_out_t *vo_source;
+
+ vo_source = xine_get_video_source(fe->stream);
+
+ (void) xine_post_wire_video_port(vo_source, fe->video_port);
+}
+
+
+static void _applugin_unwire(fe_t *fe)
+{
+ xine_post_out_t *ao_source;
+
+ ao_source = xine_get_audio_source(fe->stream);
+
+ (void) xine_post_wire_audio_port(ao_source, fe->audio_port);
+}
+
+
+static void _vpplugin_rewire_from_post_elements(fe_t *fe, post_element_t **post_elements, int post_elements_num)
+{
+ if(post_elements_num) {
+ xine_post_out_t *vo_source;
+ int i = 0;
+
+ for(i = (post_elements_num - 1); i >= 0; i--) {
+ const char *const *outs = xine_post_list_outputs(post_elements[i]->post);
+ const xine_post_out_t *vo_out = xine_post_output(post_elements[i]->post, (char *) *outs);
+ if(i == (post_elements_num - 1)) {
+ xine_post_wire_video_port((xine_post_out_t *) vo_out, fe->video_port);
+ }
+ else {
+ const xine_post_in_t *vo_in;
+ int err;
+
+ /* look for standard input names */
+ vo_in = xine_post_input(post_elements[i + 1]->post, "video");
+ if( !vo_in )
+ vo_in = xine_post_input(post_elements[i + 1]->post, "video in");
+ err = xine_post_wire((xine_post_out_t *) vo_out,
+ (xine_post_in_t *) vo_in);
+ }
+ }
+
+ if(fe->post_pip_enable &&
+ !strcmp(post_elements[0]->name, "mosaico")) {
+ vo_source = xine_get_video_source(fe->pip_stream);
+ xine_post_wire_video_port(vo_source,
+ post_elements[0]->post->video_input[1]);
+ }
+
+ if(fe->slave_stream)
+ vo_source = xine_get_video_source(fe->slave_stream);
+ else
+ vo_source = xine_get_video_source(fe->stream);
+ xine_post_wire_video_port(vo_source,
+ post_elements[0]->post->video_input[0]);
+ }
+}
+
+
+static void _applugin_rewire_from_post_elements(fe_t *fe, post_element_t **post_elements, int post_elements_num)
+{
+ if(post_elements_num) {
+ xine_post_out_t *ao_source;
+ int i = 0;
+
+ for(i = (post_elements_num - 1); i >= 0; i--) {
+ const char *const *outs = xine_post_list_outputs(post_elements[i]->post);
+ const xine_post_out_t *ao_out = xine_post_output(post_elements[i]->post, (char *) *outs);
+
+ if(i == (post_elements_num - 1)) {
+ xine_post_wire_audio_port((xine_post_out_t *) ao_out, fe->audio_port);
+ }
+ else {
+ const xine_post_in_t *ao_in;
+ int err;
+
+ /* look for standard input names */
+ ao_in = xine_post_input(post_elements[i + 1]->post, "audio");
+ if( !ao_in )
+ ao_in = xine_post_input(post_elements[i + 1]->post, "audio in");
+
+ err = xine_post_wire((xine_post_out_t *) ao_out, (xine_post_in_t *) ao_in);
+ }
+ }
+
+ if(fe->slave_stream)
+ ao_source = xine_get_audio_source(fe->slave_stream);
+ else
+ ao_source = xine_get_audio_source(fe->stream);
+ xine_post_wire_audio_port(ao_source, post_elements[0]->post->audio_input[0]);
+ }
+}
+
+static post_element_t **_pplugin_join_deinterlace_and_post_elements(fe_t *fe, int *post_elements_num)
+{
+ post_element_t **post_elements;
+ int i = 0, j = 0, n = 0;
+
+ *post_elements_num = 0;
+ if( fe->post_video_enable )
+ *post_elements_num += fe->post_video_elements_num;
+
+ if( fe->post_pip_enable )
+ *post_elements_num += fe->post_pip_elements_num;
+
+ if( *post_elements_num == 0 )
+ return NULL;
+
+ post_elements = (post_element_t **)
+ xine_xmalloc(sizeof(post_element_t *) * (*post_elements_num));
+
+ if(fe->post_pip_enable)
+ for( i = 0; i < fe->post_pip_elements_num; i++ ) {
+ if(fe->post_pip_elements[i]->enable)
+ post_elements[i+j-n] = fe->post_pip_elements[i];
+ else
+ n++;
+ }
+
+ if(fe->post_video_enable)
+ for( j = 0; j < fe->post_video_elements_num; j++ ) {
+ if(fe->post_video_elements[j]->enable)
+ post_elements[i+j-n] = fe->post_video_elements[j];
+ else
+ n++;
+ }
+
+ *post_elements_num -= n;
+ if( *post_elements_num == 0 ) {
+ free(post_elements);
+ return NULL;
+ }
+
+ return post_elements;
+}
+
+static post_element_t **_pplugin_join_visualization_and_post_elements(fe_t *fe, int *post_elements_num)
+{
+ post_element_t **post_elements;
+ int i = 0, j = 0, n = 0;
+
+ *post_elements_num = 0;
+ if( fe->post_audio_enable )
+ *post_elements_num += fe->post_audio_elements_num;
+
+ if( fe->post_vis_enable )
+ *post_elements_num += fe->post_vis_elements_num;
+
+ if( *post_elements_num == 0 )
+ return NULL;
+
+ post_elements = (post_element_t **)
+ xine_xmalloc(sizeof(post_element_t *) * (*post_elements_num));
+
+ if(fe->post_audio_enable)
+ for( j = 0; j < fe->post_audio_elements_num; j++ ) {
+ if(fe->post_audio_elements[j]->enable)
+ post_elements[i+j-n] = fe->post_audio_elements[j];
+ else
+ n++;
+ }
+
+ if(fe->post_vis_enable)
+ for( i = 0; i < fe->post_vis_elements_num; i++ ) {
+ if(fe->post_vis_elements[i]->enable)
+ post_elements[i+j-n] = fe->post_vis_elements[i];
+ else
+ n++;
+ }
+
+ *post_elements_num -= n;
+ if( *post_elements_num == 0 ) {
+ free(post_elements);
+ return NULL;
+ }
+
+ return post_elements;
+}
+
+static void _vpplugin_rewire(fe_t *fe)
+{
+ static post_element_t **post_elements;
+ int post_elements_num;
+
+ post_elements = _pplugin_join_deinterlace_and_post_elements(fe, &post_elements_num);
+
+ if( post_elements ) {
+ _vpplugin_rewire_from_post_elements(fe, post_elements, post_elements_num);
+
+ free(post_elements);
+ }
+}
+
+static void _applugin_rewire(fe_t *fe)
+{
+ static post_element_t **post_elements;
+ int post_elements_num;
+
+ post_elements = _pplugin_join_visualization_and_post_elements(fe, &post_elements_num);
+
+ if( post_elements ) {
+ _applugin_rewire_from_post_elements(fe, post_elements, post_elements_num);
+
+ free(post_elements);
+ }
+}
+
+void vpplugin_rewire_posts(fe_t *fe)
+{
+ /*TRACELINE;*/
+ _vpplugin_unwire(fe);
+ _vpplugin_rewire(fe);
+}
+
+void applugin_rewire_posts(fe_t *fe)
+{
+ /*TRACELINE;*/
+ _applugin_unwire(fe);
+ _applugin_rewire(fe);
+}
+
+static int _pplugin_enable_post(post_plugins_t *fe, const char *name,
+ const char *args,
+ post_element_t **post_elements,
+ int post_elements_num,
+ int *found)
+{
+ int i, result = 0;
+
+ for(i=0; i<post_elements_num; i++)
+ if(post_elements[i])
+ if(!strcmp(post_elements[i]->name, name)) {
+ if(post_elements[i]->enable == 0) {
+ post_elements[i]->enable = 1;
+ result = 1;
+ }
+
+ *found = 1;
+
+ if(args && *args) {
+ char *tmp = strdup(args);
+ __pplugin_update_parameters(post_elements[i]->post, tmp);
+ free(tmp);
+ if(post_elements[i]->args)
+ free(post_elements[i]->args);
+ post_elements[i]->args = strdup(args);
+ }
+ }
+
+ return result;
+}
+
+static int _vpplugin_enable_post(post_plugins_t *fe, const char *name,
+ const char *args, int *found)
+{
+ return
+ _pplugin_enable_post(fe, name, args, fe->post_video_elements,
+ fe->post_video_elements_num, found) +
+ _pplugin_enable_post(fe, name, args, fe->post_pip_elements,
+ fe->post_pip_elements_num, found);
+}
+
+static int _applugin_enable_post(post_plugins_t *fe, const char *name,
+ const char *args, int *found)
+{
+ return
+ _pplugin_enable_post(fe, name, args, fe->post_audio_elements,
+ fe->post_audio_elements_num, found) +
+ _pplugin_enable_post(fe, name, args, fe->post_vis_elements,
+ fe->post_vis_elements_num, found);
+}
+
+static char * _pp_name(const char *initstr)
+{
+ char *name = strdup(initstr), *pt;
+
+ if(NULL != (pt = strchr(name, ':')))
+ *pt = 0;
+
+ return name;
+}
+
+static const char * _pp_args(const char *initstr)
+{
+ char *pt = strchr(initstr, ':');
+ if(pt && *(pt+1))
+ return pt+1;
+ return NULL;
+}
+
+int vpplugin_enable_post(post_plugins_t *fe, const char *initstr,
+ int *found)
+{
+ char *name = _pp_name(initstr);
+ const char *args = _pp_args(initstr);
+
+ int result = _vpplugin_enable_post(fe, name, args, found);
+
+ LOGDBG(" * enable post %s --> %s, %s", name,
+ *found ? "found" : "not found",
+ result ? "enabled" : "no action");
+
+ if(!*found) {
+ LOGDBG(" * loading post %s", initstr);
+ vpplugin_parse_and_store_post(fe, initstr);
+ result = _vpplugin_enable_post(fe, name, NULL, found);
+ LOGDBG(" * enable post %s --> %s, %s", name,
+ *found ? "found" : "not found",
+ result ? "enabled" : "no action");
+ }
+
+ if(result)
+ _vpplugin_unwire(fe);
+
+ free(name);
+ return result;
+}
+
+int applugin_enable_post(post_plugins_t *fe, const char *initstr,
+ int *found)
+{
+ const char *args = _pp_args(initstr);
+ char *name = _pp_name(initstr);
+
+ int result = _vpplugin_enable_post(fe, name, args, found);
+
+ LOGDBG(" * enable post %s --> %s, %s", name,
+ *found ? "found" : "not found",
+ result ? "enabled" : "no action");
+
+ if(!*found) {
+ LOGDBG(" * loading post %s", initstr);
+ applugin_parse_and_store_post(fe, initstr);
+ result = _applugin_enable_post(fe, name, NULL, found);
+ LOGDBG(" * enable post %s --> %s, %s", name,
+ *found ? "found" : "not found",
+ result ? "enabled" : "no action");
+ }
+
+ if(result)
+ _applugin_unwire(fe);
+
+ free(name);
+ return result;
+}
+
+static int _pplugin_disable_post(post_plugins_t *fe, const char *name,
+ post_element_t **post_elements,
+ int post_elements_num)
+{
+ int i, result = 0;
+ /*TRACELINE;*/
+ if(post_elements)
+ for(i = 0; i < post_elements_num; i++)
+ if(post_elements[i])
+ if(!name || !strcmp(post_elements[i]->name, name))
+ if(post_elements[i]->enable == 1) {
+ post_elements[i]->enable = 0;
+ result = 1;
+ }
+ return result;
+}
+
+int vpplugin_disable_post(post_plugins_t *fe, const char *name)
+{
+ /*TRACELINE;*/
+ if(_pplugin_disable_post(fe, name, fe->post_video_elements,
+ fe->post_video_elements_num) ||
+ _pplugin_disable_post(fe, name, fe->post_pip_elements,
+ fe->post_pip_elements_num)) {
+ _vpplugin_unwire(fe);
+ return 1;
+ }
+ return 0;
+}
+
+int applugin_disable_post(post_plugins_t *fe, const char *name)
+{
+ /*TRACELINE;*/
+ if(_pplugin_disable_post(fe, name, fe->post_audio_elements,
+ fe->post_audio_elements_num) ||
+ _pplugin_disable_post(fe, name, fe->post_vis_elements,
+ fe->post_vis_elements_num)) {
+ _applugin_unwire(fe);
+ return 1;
+ }
+ return 0;
+}
+
+static int _pplugin_unload_post(post_plugins_t *fe, const char *name,
+ post_element_t ***post_elements,
+ int *post_elements_num)
+{
+ /* does not unwrire plugins ! */
+ int i, j, result = 0;
+ /*TRACELINE;*/
+
+ if(!*post_elements || !*post_elements_num)
+ return 0;
+
+ for(i=0; i < *post_elements_num; i++)
+ if((*post_elements)[i])
+ if(!name || !strcmp((*post_elements)[i]->name, name)) {
+
+ if((*post_elements)[i]->enable == 0) {
+ xine_post_dispose(fe->xine, (*post_elements)[i]->post);
+
+ free((*post_elements)[i]->name);
+
+ if((*post_elements)[i]->args)
+ free((*post_elements)[i]->args);
+
+ free((*post_elements)[i]);
+
+ for(j=i; j < *post_elements_num - 1; j++)
+ (*post_elements)[j] = (*post_elements)[j+1];
+
+ (*post_elements_num) --;
+ (*post_elements)[(*post_elements_num)] = 0;
+
+ result = 1;
+ } else {
+ LOGDBG("Unload %s failed: plugin enabled and in use", name);
+ }
+ }
+
+ if(*post_elements_num <= 0) {
+ if(*post_elements)
+ free(*post_elements);
+ *post_elements = NULL;
+ }
+
+ return result;
+}
+
+int vpplugin_unload_post(post_plugins_t *fe, const char *name)
+{
+ int result = vpplugin_disable_post(fe, name);
+
+ /* unload already disabled plugins too (result=0) */
+ _pplugin_unload_post(fe, name, &fe->post_video_elements,
+ &fe->post_video_elements_num);
+ _pplugin_unload_post(fe, name, &fe->post_pip_elements,
+ &fe->post_pip_elements_num);
+
+ /* result indicates only unwiring condition, not unload result */
+ return result;
+}
+
+int applugin_unload_post(post_plugins_t *fe, const char *name)
+{
+ int result = applugin_disable_post(fe, name);
+
+ /* unload already disabled plugins too (result=0) */
+ _pplugin_unload_post(fe, name, &fe->post_audio_elements,
+ &fe->post_audio_elements_num);
+ _pplugin_unload_post(fe, name, &fe->post_vis_elements,
+ &fe->post_vis_elements_num);
+
+ /* result indicates only unwiring condition, not unload result */
+ return result;
+}
+
+
+/* end of post.c */
+
diff --git a/xine/post.h b/xine/post.h
new file mode 100644
index 00000000..c1bf39f4
--- /dev/null
+++ b/xine/post.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2003 by Dirk Meyer
+ *
+ * This file is part of xine, a unix video player.
+ *
+ * xine is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * xine is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Modified for xineliboutput by Petri Hintukainen, 2006
+ *
+ */
+
+#ifndef POST_HH
+#define POST_HH
+
+#include <xine.h>
+#include <xine/xine_internal.h>
+#include <xine/xineutils.h>
+#include <xine/plugin_catalog.h>
+
+typedef struct {
+ xine_post_t *post;
+ char *name;
+ char *args;
+ int enable; /* 0 - disabled, 1 - enabled, 2 - can't disable */
+} post_element_t;
+
+
+typedef struct post_plugins_s post_plugins_t;
+
+struct post_plugins_s {
+
+ /* frontend data */
+ char *static_post_plugins; /* post plugins from command line; always on */
+ xine_stream_t *stream; /* main stream (VDR output) */
+ xine_stream_t *pip_stream; /* pip stream */
+ xine_stream_t *slave_stream; /* slave stream for file playing */
+
+ /* xine */
+ xine_t *xine;
+ xine_video_port_t *video_port;
+ xine_audio_port_t *audio_port;
+
+ /* post.c internal use */
+ int post_audio_elements_num;
+ int post_video_elements_num;
+ int post_vis_elements_num;
+ int post_pip_elements_num;
+
+ post_element_t **post_audio_elements;
+ post_element_t **post_video_elements;
+ post_element_t **post_vis_elements; /* supports only one */
+ post_element_t **post_pip_elements; /* supports only one and two input */
+
+ int post_audio_enable;
+ int post_video_enable;
+ int post_vis_enable;
+ int post_pip_enable;
+};
+
+
+void vpplugin_rewire_posts(post_plugins_t *fe);
+void applugin_rewire_posts(post_plugins_t *fe);
+
+/* load and config post plugin(s) */
+/* post == "plugin:arg1=val1,arg2=val2;plugin2..." */
+void vpplugin_parse_and_store_post(post_plugins_t *fe, const char *post);
+void applugin_parse_and_store_post(post_plugins_t *fe, const char *post);
+
+/* enable (and load if not loaded), but don't rewire */
+/* result indicates only unwiring condition, not enable result */
+/* -> if result <> 0, something was enabled and post chain is unwired */
+int vpplugin_enable_post(post_plugins_t *fe, const char *name, int *found);
+int applugin_enable_post(post_plugins_t *fe, const char *name, int *found);
+
+/* disable (and unwire if found), but don't unload */
+/* result indicates only unwiring condition, not disable result */
+int vpplugin_disable_post(post_plugins_t *fe, const char *name);
+int applugin_disable_post(post_plugins_t *fe, const char *name);
+
+/* unload (and unwire) plugin(s) */
+/* result indicates only unwiring condition, not unload result */
+int vpplugin_unload_post(post_plugins_t *fe, const char *name);
+int applugin_unload_post(post_plugins_t *fe, const char *name);
+
+#endif
+
+/* end of post.h */
diff --git a/xine_fbfe_frontend.c b/xine_fbfe_frontend.c
new file mode 100644
index 00000000..5f51b057
--- /dev/null
+++ b/xine_fbfe_frontend.c
@@ -0,0 +1,269 @@
+/*
+ * xine_fbfe_frontend.c: Simple front-end, framebuffer functions
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: xine_fbfe_frontend.c,v 1.1 2006-06-03 10:01:18 phintuka Exp $
+ *
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+#include <pthread.h>
+#include <sched.h>
+#include <linux/unistd.h> /* gettid() */
+
+#ifdef boolean
+# define HAVE_BOOLEAN
+#endif
+#include <jpeglib.h>
+#undef boolean
+
+#include <xine.h>
+#ifndef XINE_ENGINE_INTERNAL
+# define XINE_ENGINE_INTERNAL
+# include <xine/xine_internal.h>
+# undef XINE_ENGINE_INTERNAL
+#else
+# include <xine/xine_internal.h>
+#endif
+#include <xine/xineutils.h>
+#include <xine/input_plugin.h>
+#include <xine/plugin_catalog.h>
+
+#include "xine_input_vdr.h"
+
+#include "xine_frontend.h"
+#include "xine/post.h"
+
+/*
+ * data
+ */
+
+typedef struct fbfe_t {
+
+ /* function pointers */
+ frontend_t fe;
+ void (*update_display_size)(frontend_t*);
+
+ /* xine stuff */
+ xine_t *xine;
+ xine_stream_t *stream;
+ input_plugin_t *input;
+ xine_video_port_t *video_port;
+ xine_audio_port_t *audio_port;
+ xine_event_queue_t *event_queue;
+
+ post_plugins_t *postplugins;
+
+ char configfile[256];
+
+ int xine_visual_type;
+ fb_visual_t vis;
+
+ double display_ratio;
+
+ int pes_buffers;
+ int aspect;
+ int cropping;
+ int scale_video;
+ int priority;
+
+ /* frontend */
+ int playback_finished;
+
+ /* vdr */
+ fe_keypress_f keypress;
+
+ /* display */
+ int display;
+ int fullscreen;
+ int vmode_switch;
+ int field_order;
+ char modeline[256];
+
+ int xpos, ypos;
+ int width, height;
+
+} fe_t;
+
+/* Common (non-X11/FB) frontend functions */
+#include "xine_frontend.c"
+
+static void fbfe_update_display_size(frontend_t *this_gen)
+{
+ fe_t *this = (fe_t*)this_gen;
+ if(this->fullscreen) {
+ this->width = this->video_port->get_property(this->video_port,
+ VO_PROP_WINDOW_WIDTH);
+ this->height = this->video_port->get_property(this->video_port,
+ VO_PROP_WINDOW_HEIGHT);
+ LOGMSG("Framebuffer size after initialization: %dx%d",
+ this->width, this->height);
+ }
+}
+
+/*
+ * fbfe_display_open
+ */
+static int fbfe_display_open(frontend_t *this_gen, int width, int height, int fullscreen,
+ int modeswitch, char *modeline, int aspect,
+ fe_keypress_f keyfunc, char *video_port,
+ int scale_video, int field_order)
+{
+ fe_t *this = (fe_t*)this_gen;
+
+ if(!this)
+ return 0;
+
+ if(this->display)
+ this->fe.fe_display_close(this_gen);
+
+ this->display = 1;
+ if(keyfunc)
+ this->keypress = keyfunc;
+
+ LOGDBG("fbfe_display_open(width=%d, height=%d, fullscreen=%d, display=%s)",
+ width, height, fullscreen, video_port);
+
+ this->xpos = 0;
+ this->ypos = 0;
+ this->width = width;
+ this->height = height;
+ this->fullscreen = fullscreen;
+ this->vmode_switch = 0/*modeswitch*/;
+ this->aspect = aspect;
+ this->cropping = 0;
+ this->field_order = 0/*field_order ? 1 : 0*/;
+ this->scale_video = scale_video;
+ strcpy(this->modeline, modeline);
+ this->display_ratio = 1.0;
+
+ this->xine_visual_type = XINE_VISUAL_TYPE_FB;
+ this->vis.frame_output_cb = fe_frame_output_cb;
+ this->vis.user_data = this;
+
+ this->update_display_size = fbfe_update_display_size;
+
+ return 1;
+}
+
+/*
+ * fbfe_display_config
+ *
+ * configure windows
+ */
+static int fbfe_display_config(frontend_t *this_gen, int width, int height, int fullscreen,
+ int modeswitch, char *modeline, int aspect,
+ int scale_video, int field_order)
+{
+ fe_t *this = (fe_t*)this_gen;
+
+ if(!this)
+ return 0;
+
+ if(this->width != width || this->height != height) {
+ this->width = width;
+ this->height = height;
+ }
+
+ if(fullscreen != this->fullscreen) {
+ this->fullscreen = fullscreen;
+ }
+
+ if(!modeswitch && strcmp(modeline, this->modeline)) {
+ strcpy(this->modeline, modeline);
+ /* XXX TODO - switch vmode */
+#ifdef LOG
+ LOGDBG("fbfe_display_config: TODO: switch vmode\n");fflush(stdout);
+#endif
+ }
+
+ this->vmode_switch = modeswitch;
+ this->aspect = aspect;
+ this->scale_video = scale_video;
+ this->field_order = field_order ? 1 : 0;
+
+ return 1;
+}
+
+static void fbfe_interrupt(frontend_t *this_gen)
+{
+ /* stop fbfe_run() */
+}
+
+static int fbfe_run(frontend_t *this_gen)
+{
+ struct timeval tv;
+ fe_t *this = (fe_t*)this_gen;
+
+ if(this && this->playback_finished)
+ return !this->playback_finished;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 500*1000;
+ select(0, NULL, NULL, NULL, &tv); /* just sleep 500ms */
+
+ return !(!this || this->playback_finished);
+}
+
+static void fbfe_display_close(frontend_t *this_gen)
+{
+ fe_t *this = (fe_t*)this_gen;
+
+ if(this) {
+ this->display = 0;
+
+ if(this->xine)
+ this->fe.xine_exit(this_gen);
+ }
+}
+
+static frontend_t *fbfe_get_frontend(void)
+{
+ fe_t *this = malloc(sizeof(fe_t));
+ memset(this, 0, sizeof(fe_t));
+
+ this->fe.fe_display_open = fbfe_display_open;
+ this->fe.fe_display_config = fbfe_display_config;
+ this->fe.fe_display_close = fbfe_display_close;
+
+ this->fe.xine_init = fe_xine_init;
+ this->fe.xine_open = fe_xine_open;
+ this->fe.xine_play = fe_xine_play;
+ this->fe.xine_stop = fe_xine_stop;
+ this->fe.xine_close = fe_xine_close;
+ this->fe.xine_exit = fe_xine_exit;
+ this->fe.xine_is_finished = fe_is_finished;
+
+ this->fe.fe_run = fbfe_run;
+ this->fe.fe_interrupt = fbfe_interrupt;
+
+ this->fe.fe_free = fe_free;
+
+#ifndef FE_STANDALONE
+ this->fe.grab = fe_grab;
+ this->fe.xine_osd_command = xine_osd_command;
+ this->fe.xine_control = xine_control;
+
+ this->fe.xine_queue_pes_packet = xine_queue_pes_packet;
+#endif /*#ifndef FE_STANDALONE */
+
+ return (frontend_t*)this;
+}
+
+/* ENTRY POINT */
+const fe_creator_f fe_creator __attribute__((visibility("default"))) = fbfe_get_frontend;
+
+
diff --git a/xine_frontend.c b/xine_frontend.c
new file mode 100644
index 00000000..65ca1801
--- /dev/null
+++ b/xine_frontend.c
@@ -0,0 +1,1207 @@
+/*
+ * xine_frontend.c:
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: xine_frontend.c,v 1.1 2006-06-03 10:01:18 phintuka Exp $
+ *
+ */
+
+#ifndef XINE_VERSION_CODE
+# define XINE_VERSION_CODE (XINE_MAJOR_VERSION*10000 + \
+ XINE_MINOR_VERSION*100 + \
+ XINE_SUB_VERSION)
+#endif
+
+#define LOG_MODULENAME "[vdr-fe] "
+#include "logdefs.h"
+
+#include "xine/post.h"
+
+#ifdef FE_STANDALONE
+ /* next two symbols are dynamically linked from input plugin */
+ int SysLogLevel __attribute__((visibility("default"))) = 2; /* errors and info, no debug */
+ int LogToSysLog __attribute__((visibility("default"))) = 0; /* log to syslog instead of console */
+
+ static int verbose_xine_log = 0;
+#else
+ extern int SysLogLevel; /* vdr tools.c */
+ int LogToSysLog __attribute__((visibility("default"))) = 1; /* dynamically linked from input plugin */
+#endif
+
+/* from vdr_input_plugin: */
+typedef struct {
+ input_plugin_t input_plugin;
+ vdr_input_plugin_funcs_t f;
+ /* ... */
+} vdr_input_plugin_t;
+
+
+#if !defined(XINELIBOUTPUT_DEBUG_STDOUT) && \
+ !defined(XINELIBOUTPUT_DEBUG_STDERR)
+# undef x_syslog
+
+ _syscall0(pid_t, gettid)
+
+ static void x_syslog(int level, const char *fmt, ...)
+ {
+ va_list argp;
+ char buf[512];
+ va_start(argp, fmt);
+ vsnprintf(buf, 512, fmt, argp);
+ if(!LogToSysLog) {
+ printf(LOG_MODULENAME "%s\n", buf);
+ } else {
+ syslog(level, "[%d] " LOG_MODULENAME "%s", gettid(), buf);
+ }
+ va_end(argp);
+ }
+#endif
+
+
+/* detect input plugin */
+static int find_input(fe_t *this)
+{
+ if(!this->input) {
+ if(!this->stream || !this->stream->input_plugin ||
+ !this->stream->input_plugin->input_class || this->playback_finished) {
+ LOGMSG("find_input: stream not initialized or playback finished !");
+ usleep(100*1000);
+ return 0;
+ }
+ if(strcmp(this->stream->input_plugin->input_class->get_identifier(
+ this->stream->input_plugin->input_class),
+ "xvdr")) {
+ LOGMSG("find_input: current xine input plugin is not xvdr !");
+ /*usleep(100*1000);*/
+ return 0;
+ }
+ this->input = this->stream->input_plugin;
+ }
+ return 1;
+}
+
+static void *fe_control(void *fe_handle, char *cmd);
+
+/*
+ * xine callbacks
+ */
+
+static double fe_dest_pixel_aspect(fe_t *this, double video_pixel_aspect,
+ int video_width, int video_height)
+{
+ int new_cropping = 0;
+ double result = 1.0;
+
+ if(!this->scale_video) {
+
+ /*#warning what to do if scaling disabled ???*/
+
+ /*return video_pixel_aspect;*/
+ }
+
+ switch(this->aspect) {
+ /* Auto */
+ default: {
+ double correction =
+ ((double)video_width/(double)video_height) /
+ ((double)this->width/(double)this->height);
+
+ result = video_pixel_aspect * correction;
+ if(result > (16.9/9.0 * (double)this->height/
+ (double)this->width))
+ result = (16.0/9.0 * (double)this->height/
+ (double)this->width);
+ break;
+ }
+ /* Default */
+ case 1: result = this->display_ratio; break;
+
+ /* 4:3 */
+ case 2: result = (4.0/3.0 * (double)this->height/(double)this->width); break;
+ /* 16:9 */
+ case 3: result = (16.0/9.0 * (double)this->height/(double)this->width); break;
+ /* Pan&Scan */
+ case 4: {
+ double aspect_diff /*= video_pixel_aspect - 1.0*/;
+ /* TODO */
+ /* does not work (?) */
+aspect_diff=(video_pixel_aspect*(double)video_width/(double)video_height) - 4.0 / 3.0;
+ if ((aspect_diff < 0.05) && (aspect_diff > -0.05)) {
+ result = (4.0/3.0 * (double)this->height/(double)this->width);
+ /*LOGDBG("diff: %f", aspect_diff);*/
+ new_cropping = 1;
+ } else {
+ result = (16.0/9.0 * (double)this->height/(double)this->width);
+ }
+ /*result = (4.0/3.0 * (double)this->height/(double)this->width);*/
+ break;
+ }
+ /* center cut-out */
+ case 5: {
+/*#warning center cut-out mode not implemented*/
+ break;
+ }
+
+ }
+#if 0
+ if(this->cropping && !new_cropping) {
+ LOGDBG("pan&scan CROP OFF");
+ xine_set_param(this->stream, XINE_PARAM_VO_CROP_LEFT, 0);
+ xine_set_param(this->stream, XINE_PARAM_VO_CROP_TOP, 72);
+ xine_set_param(this->stream, XINE_PARAM_VO_CROP_RIGHT, 0);
+ xine_set_param(this->stream, XINE_PARAM_VO_CROP_BOTTOM, 72);
+ this->cropping = 0;
+ }
+ if(!this->cropping && new_cropping) {
+ LOGDBG("pan&scan CROP ON");
+ /*** Should set unscaled osd (or top & bottom will be cropped off) */
+ xine_set_param(this->stream, XINE_PARAM_VO_CROP_LEFT, 0);
+ xine_set_param(this->stream, XINE_PARAM_VO_CROP_TOP, 0);
+ xine_set_param(this->stream, XINE_PARAM_VO_CROP_RIGHT, 0);
+ xine_set_param(this->stream, XINE_PARAM_VO_CROP_BOTTOM, 0);
+ this->cropping = 1;
+ }
+#endif
+ return result;
+}
+
+static void fe_frame_output_cb (void *data,
+ int video_width, int video_height,
+ double video_pixel_aspect,
+ int *dest_x, int *dest_y,
+ int *dest_width, int *dest_height,
+ double *dest_pixel_aspect,
+ int *win_x, int *win_y)
+{
+ fe_t *this = (fe_t *)data;
+
+ if (!this)
+ return;
+
+ *dest_width = this->width;
+ *dest_height = this->height;
+ *dest_x = 0;
+#ifndef HAVE_XV_FIELD_ORDER
+ *dest_y = 0 + this->field_order;
+#else
+ *dest_y = 0;
+#endif
+
+ if(!this->scale_video) {
+ if(video_height < this->height) {
+ *dest_height = video_height;
+ *dest_y = (this->height - video_height) / 2;
+ }
+ if(video_width < this->width) {
+ *dest_width = video_width;
+ *dest_x = (this->width - video_width) / 2;
+ }
+ }
+
+ *win_x = this->xpos;
+ *win_y = this->ypos;
+
+ *dest_pixel_aspect = fe_dest_pixel_aspect(this, video_pixel_aspect,
+ video_width, video_height);
+#if 0
+ if(this->cropping) {
+ *dest_pixel_aspect = *dest_pixel_aspect * (16.0/9.0)/(4.0/3.0);
+ *dest_y = *dest_y - 72;
+ *dest_height = *dest_height + 144;
+ }
+#endif
+
+#if 0
+ static int n=0,t=25/**10*/; n++;
+ static char *s_aspect[] = {"Auto","Default","4:3","16:9","Pan&Scan"};
+ if((n % t) == 0) {
+ LOGMSG("fe_frame_output_cb:");
+ LOGMSG(" vid=%dx%d (frame %2d:9, pixel %2d:9)",
+ video_width, video_height,
+ (int)(video_pixel_aspect * 9.0 * ((double)video_width)/((double)video_height) + 0.5),
+ (int)(video_pixel_aspect * 9.0 + 0.5));
+ LOGMSG(" %2.3f %2.3f",
+ (video_pixel_aspect*((double)video_width)/((double)video_height)),
+ (video_pixel_aspect));
+
+
+ LOGMSG(" win=%dx%d (frame %2d:9, pixel %2d:9)",
+ this->width, this->height,
+ (int)(this->display_ratio * 9.0 + 0.5),
+ (int)(*dest_pixel_aspect * 9.0 + 0.5));
+ LOGMSG(" %2.3f %2.3f",
+ (this->display_ratio),
+ (*dest_pixel_aspect));
+
+ LOGMSG(" given display aspect=%s (%d), zoom=%s",
+ s_aspect[this->aspect], this->aspect,
+ this->scale_video?"ON":"OFF");
+
+#warning TODO: vmode_switch, scale_video
+ }
+#endif
+}
+
+static void xine_event_cb (void *user_data, const xine_event_t *event)
+{
+ fe_t *this = (fe_t *)user_data;
+
+ switch (event->type) {
+ /* in local mode: vdr stream / slave stream ; in remote mode: vdr stream only */
+ case XINE_EVENT_UI_PLAYBACK_FINISHED:
+ LOGMSG("xine_event_cb: XINE_EVENT_UI_PLAYBACK_FINISHED");
+ if(this)
+ this->playback_finished = 1;
+ else
+ LOGMSG("xine_event_cb: NO USER DATA !");
+ break;
+ }
+}
+
+/*
+ * fe_xine_init
+ *
+ * initialize xine engine, load audio and video ports, setup stream
+ */
+
+static int fe_xine_init(frontend_t *this_gen, char *audio_driver, char *audio_port,
+ char *video_driver, int pes_buffers, int priority,
+ char *static_post_plugins)
+{
+ fe_t *this = (fe_t*)this_gen;
+ post_plugins_t *posts = NULL;
+
+ if(!this)
+ return 0;
+
+ /*
+ * init xine engine
+ */
+
+ if(this->xine)
+ this->fe.xine_exit(this_gen);
+
+ this->stream = NULL;
+ this->video_port = NULL;
+ this->audio_port = NULL;
+ this->input = NULL;
+
+ /* create a new xine and load config file */
+ this->xine = xine_new();
+ if(!this->xine)
+ return 0;
+
+#ifdef FE_STANDALONE
+ this->xine->verbosity = verbose_xine_log;
+#else
+ this->xine->verbosity = (SysLogLevel>2);
+#endif
+
+ /*xine_register_log_cb(this->xine, xine_log_cb, this);*/
+
+ /* TODO: use different config file ? (vdr conf.dir/xine/config_vdr ?) */
+ sprintf(this->configfile, "%s%s", xine_get_homedir(),
+ "/.xine/config_xineliboutput");
+
+ xine_config_load (this->xine, this->configfile);
+
+ /*this->xine->config->update_num(this->xine->config, "video.device.xv_double_buffer", 1); */
+ /*this->xine->config->update_num(this->xine->config, "engine.buffers.video_num_buffers",
+ pes_buffers); */
+ /*#warning update ??? add TYPE_UNKNOWN ... ? like when xine reads config file ...?*/
+ this->xine->config->register_num (this->xine->config,
+ "engine.buffers.video_num_buffers",
+ 500,
+ "number of video buffers",
+ "The number of video buffers "
+ "(each is 8k in size) "
+ "xine uses in its internal queue. "
+ "Higher values "
+ "mean smoother playback for unreliable "
+ "inputs, but "
+ "also increased latency and memory "
+ "consumption.",
+ 20, NULL, NULL);
+
+ xine_init (this->xine);
+
+ this->xine->config->update_num(this->xine->config,"video.device.xv_double_buffer", 1);
+ this->xine->config->update_num(this->xine->config,"engine.buffers.video_num_buffers", pes_buffers);
+
+ this->playback_finished = 0;
+
+ /* create video port */
+
+ this->video_port = xine_open_video_driver(this->xine,
+ video_driver,
+ this->xine_visual_type,
+ (void *) &(this->vis));
+ if(!this->video_port) {
+ LOGMSG("fe_xine_init: xine_open_video_driver(\"%s\") failed",
+ video_driver?video_driver:"(NULL)");
+ xine_exit(this->xine);
+ this->xine = NULL;
+ return 0;
+ }
+
+ /* re-configure display size (DirectFB driver changes display mode in init) */
+ if(this->update_display_size)
+ this->update_display_size(this_gen);
+
+ /* create audio port */
+
+ if(audio_driver && audio_port) {
+ if(!strcmp("alsa", audio_driver) && strlen(audio_port)>0) {
+ this->xine->config->register_string(this->xine->config,
+ "audio.device.alsa_default_device",
+ "default",
+ "device used for mono output",
+ "xine will use this alsa device "
+ "to output mono sound.\n"
+ "See the alsa documentation "
+ "for information on alsa devices.",
+ 10, NULL,
+ NULL);
+ this->xine->config->register_string(this->xine->config,
+ "audio.device.alsa_front_device",
+ "plug:front:default",
+ "device used for stereo output",
+ "xine will use this alsa device "
+ "to output stereo sound.\n"
+ "See the alsa documentation "
+ "for information on alsa devices.",
+ 10, NULL,
+ NULL);
+ this->xine->config->update_string(this->xine->config,
+ "audio.device.alsa_front_device",
+ audio_port);
+ this->xine->config->update_string(this->xine->config,
+ "audio.device.alsa_default_device",
+ audio_port);
+ }
+ if(!strcmp("oss", audio_driver) && !strncmp("/dev/dsp", audio_port, 8)) {
+ int num = 0;
+ sscanf(audio_port+8,"%d",&num);
+ this->xine->config->update_num(this->xine->config,"audio.device.oss_device_num",num);
+ }
+ }
+
+ if(audio_driver && !strcmp(audio_driver, "auto"))
+ this->audio_port = xine_open_audio_driver (this->xine, NULL, NULL);
+ if(audio_driver && !strcmp(audio_driver, "none"))
+ ;
+ else
+ this->audio_port = xine_open_audio_driver (this->xine, audio_driver, NULL);
+
+ if(!this->audio_port && (audio_driver && !!strcmp(audio_driver, "none"))) {
+ LOGMSG("fe_xine_init: xine_open_audio_driver(\"%s%s%s\") failed",
+ audio_driver?audio_driver:"(NULL)",
+ audio_port?":":"", audio_port?audio_port:"");
+ }
+
+ /* create stream */
+
+ this->stream = xine_stream_new(this->xine, this->audio_port, this->video_port);
+
+ if(!this->stream) {
+ LOGMSG("fe_xine_init: xine_stream_new failed");
+
+ if(this->audio_port)
+ xine_close_audio_driver(this->xine, this->audio_port);
+ this->audio_port = NULL;
+ xine_close_video_driver(this->xine, this->video_port);
+ this->video_port = NULL;
+ xine_exit(this->xine);
+ this->xine = NULL;
+
+ return 0;
+ }
+
+ /* event handling */
+
+ this->event_queue = xine_event_new_queue (this->stream);
+ xine_event_create_listener_thread (this->event_queue, xine_event_cb, this);
+
+ if(!this->event_queue)
+ LOGMSG("fe_xine_init: xine_event_new_queue failed");
+
+ /* misc. config */
+
+ this->priority = priority;
+ this->pes_buffers = pes_buffers;
+
+ posts = this->postplugins = malloc(sizeof(post_plugins_t));
+ memset(posts, 0, sizeof(post_plugins_t));
+ posts->xine = this->xine;
+ posts->audio_port = this->audio_port;
+ posts->video_port = this->video_port;
+ posts->stream = this->stream;
+
+ if(static_post_plugins && *static_post_plugins) {
+ int i;
+ LOGDBG("static post plugins (from command line): %s", static_post_plugins);
+ posts->static_post_plugins = strdup(static_post_plugins);
+ vpplugin_parse_and_store_post(posts, posts->static_post_plugins);
+ applugin_parse_and_store_post(posts, posts->static_post_plugins);
+
+ for(i=0; i<posts->post_audio_elements_num; i++)
+ if(posts->post_audio_elements[i])
+ posts->post_audio_elements[i]->enable = 2;
+ for(i=0; i<posts->post_video_elements_num; i++)
+ if(posts->post_video_elements[i])
+ posts->post_video_elements[i]->enable = 2;
+ posts->post_video_enable = 1;
+ posts->post_audio_enable = 1;
+ }
+
+ return 1;
+}
+
+/*
+ * fe_xine_open
+ *
+ * open xine stream
+ */
+
+static int fe_xine_open(frontend_t *this_gen, char *mrl)
+{
+ fe_t *this = (fe_t*)this_gen;
+ int result = 0;
+ char url[1024] = "";
+
+ if(!this)
+ return 0;
+
+ this->input = NULL;
+ this->playback_finished = 1;
+
+ if(!mrl)
+ mrl = "xvdr://";
+
+ sprintf(url, "%s#nocache;demux:mpeg_block", mrl);
+
+ result = xine_open(this->stream, url);
+
+ if(!result) {
+ LOGMSG("fe_xine_open: xine_open(\"%s\") failed", mrl);
+ return 0;
+ }
+
+#if 0
+ /* priority */
+ {
+ struct sched_param temp;
+ temp.sched_priority = this->priority;
+ if(!pthread_setschedparam(this->stream->demux_thread, SCHED_RR, &temp))
+ LOGERR("pthread_setschedparam(demux_thread, SCHED_RR, %d) failed",
+ temp.sched_priority);
+ if(!pthread_setschedparam(this->stream->video_thread, SCHED_RR, &temp))
+ LOGERR("pthread_setschedparam(video_thread, SCHED_RR, %d) failed",
+ temp.sched_priority);
+ if(!pthread_setschedparam(this->stream->audio_thread, SCHED_RR, &temp))
+ LOGERR("pthread_setschedparam(audio_thread, SCHED_RR, %d) failed",
+ temp.sched_priority);
+ }
+#endif
+
+#if 0
+ this->xine->config->update_num(this->xine->config,
+ "video.output.xv_double_buffer",
+ 1);
+#endif
+ this->xine->config->update_num(this->xine->config,
+ "engine.buffers.video_num_buffers",
+ this->pes_buffers);
+ return result;
+}
+
+/*
+ * post plugin handling
+ *
+ */
+
+#define POST_AUDIO_VIS 0
+#define POST_AUDIO 1
+#define POST_VIDEO 2
+#define POST_VIDEO_PIP 3
+
+static void fe_post_unwire(fe_t *this)
+{
+ xine_post_out_t *vo_source = xine_get_video_source(this->stream);
+ xine_post_out_t *ao_source = xine_get_audio_source(this->stream);
+ LOGDBG("unwiring post plugins");
+ (void) xine_post_wire_video_port(vo_source, this->video_port);
+ (void) xine_post_wire_audio_port(ao_source, this->audio_port);
+}
+
+static void fe_post_rewire(fe_t *this)
+{
+ LOGDBG("re-wiring post plugins");
+ vpplugin_rewire_posts(this->postplugins);
+ applugin_rewire_posts(this->postplugins);
+}
+
+static void fe_post_unload(fe_t *this)
+{
+ LOGDBG("unloading post plugins");
+ vpplugin_unload_post(this->postplugins, NULL);
+ applugin_unload_post(this->postplugins, NULL);
+}
+
+static int fe_post_close(fe_t *this, char *name, int which)
+{
+ post_plugins_t *posts = this->postplugins;
+ int result = 0;
+
+ if(!this)
+ return 0;
+
+ if(name && !strcmp(name, "AudioVisualization")) {
+ name = NULL;
+ which = POST_AUDIO_VIS;
+ }
+ if(name && !strcmp(name, "Pip")) {
+ name = NULL;
+ which = POST_VIDEO_PIP;
+ }
+
+ /* by name */
+ if(name) {
+ LOGMSG("closing post plugin: %s", name);
+ if(applugin_unload_post(posts, name)) {
+ /*LOGDBG(" * rewiring audio");*/
+ applugin_rewire_posts(posts);
+ return 1;
+ }
+ if(vpplugin_unload_post(posts, name)) {
+ /*LOGDBG(" * rewiring video");*/
+ vpplugin_rewire_posts(posts);
+ return 1;
+ }
+ return 0;
+ }
+
+ /* by type */
+ if(which == POST_AUDIO_VIS || which < 0) { /* audio visualization */
+ if(posts->post_vis_elements_num &&
+ posts->post_vis_elements &&
+ posts->post_vis_elements[0]) {
+ LOGMSG("Closing audio visualization post plugins");
+ if(applugin_unload_post(posts, posts->post_vis_elements[0]->name)) {
+ /*LOGDBG(" * rewiring audio");*/
+ applugin_rewire_posts(posts);
+ result = 1;
+ }
+ }
+ }
+
+ if(which == POST_AUDIO || which < 0) { /* audio effect(s) */
+ LOGMSG("Closing audio post plugins");
+ if(applugin_disable_post(posts, NULL)) {
+ /*LOGDBG(" * rewiring audio");*/
+ applugin_rewire_posts(posts);
+ result = 1;
+ }
+ }
+ if(which == POST_VIDEO || which < 0) { /* video effect(s) */
+ LOGMSG("Closing video post plugins");
+ if(vpplugin_unload_post(posts, NULL)) {
+ /*LOGDBG(" * rewiring video");*/
+ vpplugin_rewire_posts(posts);
+ result = 1;
+ }
+ }
+
+ if(which == POST_VIDEO_PIP || which < 0) { /* Picture-In-Picture */
+ if(posts->post_pip_elements_num &&
+ posts->post_pip_elements &&
+ posts->post_pip_elements[0]) {
+ LOGMSG("Closing PIP (mosaico) post plugins");
+ if(vpplugin_unload_post(posts, "mosaico")) {
+ /*LOGDBG(" * rewiring video");*/
+ vpplugin_rewire_posts(posts);
+ result = 1;
+ }
+ }
+ }
+
+ /*LOGDBG("Post plugin(s) closed : result=%d", result);*/
+
+ return result;
+}
+
+static int fe_post_open(fe_t *this, char *name, char *args)
+{
+ post_plugins_t *posts = this->postplugins;
+ char initstr[1024];
+ int found = 0;
+
+ if(!this || !this->xine || !this->stream)
+ return 0;
+
+ /* pip */
+ if(!strcmp(name, "Pip")) {
+ posts->post_pip_enable = 1;
+ name = "mosaico";
+ if(!posts->post_pip_elements ||
+ !posts->post_vis_elements[0] ||
+ !posts->post_vis_elements[0]->enable)
+ LOGMSG("enabling picture-in-picture (\"%s\") post plugin", initstr);
+ }
+
+ if(args)
+ sprintf(initstr, "%s:%s", name, args);
+ else
+ strcpy(initstr, name);
+
+ LOGDBG("opening post plugin: %s", initstr);
+
+ /* close old audio visualization plugin */
+ if(!strcmp(name,"goom") || !strcmp(name,"oscope") ||
+ !strcmp(name,"fftscope") || !strcmp(name,"fftgraph")) {
+
+ /*LOGDBG(" * %s is audio visualization", name);*/
+ /* close if changed */
+ if(posts->post_vis_elements_num &&
+ posts->post_vis_elements &&
+ posts->post_vis_elements[0] &&
+ strcmp(name, posts->post_vis_elements[0]->name)) {
+ /*LOGDBG(" * visualization changed, unloading %s",
+ posts->post_vis_elements[0]->name);*/
+ fe_post_close(this, NULL, POST_AUDIO_VIS);
+ }
+
+ posts->post_vis_enable = 1;
+ }
+
+ if(vpplugin_enable_post(posts, initstr, &found)) {
+ posts->post_video_enable = 1;
+ /*LOGDBG(" * rewiring video");*/
+ vpplugin_rewire_posts(posts);
+ return 1;
+ }
+ if(!found && applugin_enable_post(posts, initstr, &found)) {
+ posts->post_audio_enable = 1;
+ /*LOGDBG(" * rewiring audio");*/
+ applugin_rewire_posts(posts);
+ return 1;
+ }
+
+ if(!found)
+ LOGERR("Can't load post plugin %s", name);
+ else
+ LOGDBG("Post plugin %s loaded and wired", name);
+
+ return 0;
+}
+
+static int fe_xine_play(frontend_t *this_gen)
+{
+ fe_t *this = (fe_t*)this_gen;
+ vdr_input_plugin_t *input_vdr;
+
+ if(!this)
+ return 0;
+
+ fe_post_rewire(this);
+
+ this->input = NULL;
+ this->playback_finished = xine_play(this->stream, 0, 0) ? 0 : 1;
+
+ if(!this->input && !find_input(this))
+ return -1;
+ input_vdr = (vdr_input_plugin_t *)this->input;
+ input_vdr->f.xine_input_event = this->keypress;
+ input_vdr->f.fe_control = fe_control;
+ input_vdr->f.fe_handle = (void*)this;
+
+ if(!this->playback_finished && this->keypress)
+ this->keypress("XKeySym", "");
+
+ if(this->playback_finished)
+ LOGMSG("Error playing xvdr:// !");
+
+ return !this->playback_finished;
+}
+
+static int fe_xine_stop(frontend_t *this_gen)
+{
+ fe_t *this = (fe_t*)this_gen;
+
+ if(!this)
+ return 0;
+
+ this->input = NULL;
+ this->playback_finished = 1;
+
+ xine_stop(this->stream);
+
+ fe_post_unwire(this);
+
+ return 1;
+}
+
+static void fe_xine_close(frontend_t *this_gen)
+{
+ fe_t *this = (fe_t*)this_gen;
+
+ if(!this)
+ return;
+
+ if (this && this->xine) {
+#ifndef FE_STANDALONE
+ if(this->input) {
+ vdr_input_plugin_t *input_vdr;
+ input_vdr = (vdr_input_plugin_t *)this->input;
+ input_vdr->f.xine_input_event = NULL;
+ }
+#endif
+
+ fe_xine_stop(this_gen);
+
+ fe_post_unload(this);
+
+ xine_close(this->stream);
+ if(this->postplugins->pip_stream)
+ xine_close(this->postplugins->pip_stream);
+ }
+}
+
+static void fe_xine_exit(frontend_t *this_gen)
+{
+ fe_t *this = (fe_t*)this_gen;
+
+ if (this && this->xine) {
+
+ if(this->input || !this->playback_finished)
+ fe_xine_close(this_gen);
+ fe_post_unload(this);
+
+ xine_config_save (this->xine, this->configfile);
+ if(this->event_queue)
+ xine_event_dispose_queue(this->event_queue);
+ this->event_queue = NULL;
+
+ if(this->stream)
+ xine_dispose(this->stream);
+ this->stream = NULL;
+
+ if(this->postplugins->pip_stream)
+ xine_dispose(this->postplugins->pip_stream);
+ this->postplugins->pip_stream = NULL;
+
+ if(this->postplugins->slave_stream)
+ xine_dispose(this->postplugins->slave_stream);
+ this->postplugins->slave_stream = NULL;
+
+ if(this->audio_port)
+ xine_close_audio_driver(this->xine, this->audio_port);
+ this->audio_port = NULL;
+
+ if(this->video_port)
+ xine_close_video_driver(this->xine, this->video_port);
+ this->video_port = NULL;
+
+ if(this->postplugins->static_post_plugins)
+ free(this->postplugins->static_post_plugins);
+ free(this->postplugins);
+ this->postplugins = NULL;
+
+ xine_exit(this->xine);
+ this->xine = NULL;
+ }
+}
+
+static void fe_free(frontend_t *this_gen)
+{
+ fe_t *this = (fe_t*)this_gen;
+
+ if (this) {
+ if(this->display)
+ this->fe.fe_display_close(this_gen);
+ free(this);
+ }
+}
+
+static int fe_is_finished(frontend_t *this_gen)
+{
+ fe_t *this = (fe_t*)this_gen;
+ return this ? this->playback_finished : 1;
+}
+
+/************************** hooks to input plugin ****************************/
+
+#ifndef FE_STANDALONE
+
+static int xine_control(frontend_t *this_gen, char *cmd)
+{
+ fe_t *this = (fe_t*)this_gen;
+ vdr_input_plugin_t *input_vdr;
+
+ if(!this->input && !find_input(this))
+ return -1;
+
+ input_vdr = (vdr_input_plugin_t *)this->input;
+ return input_vdr->f.push_input_control(this->input, cmd);
+}
+
+static int xine_osd_command(frontend_t *this_gen, struct osd_command_s *cmd) {
+ fe_t *this = (fe_t*)this_gen;
+ vdr_input_plugin_t *input_vdr;
+
+ if(!this->input && !find_input(this))
+ return -1;
+
+ input_vdr = (vdr_input_plugin_t *)this->input;
+ return input_vdr->f.push_input_osd(this->input, cmd);
+}
+
+static int xine_queue_pes_packet(frontend_t *this_gen, char *data, int len)
+{
+ fe_t *this = (fe_t*)this_gen;
+ vdr_input_plugin_t *input_vdr;
+
+ if(!this->input && !find_input(this))
+ return 0/*-1*/;
+
+#if 0
+ if(len<6) {
+ LOGMSG("xine_queue_pes_packet: len == %d, too short!", len);
+ abort();
+ }
+ /* must contain single pes packet and nothing else */
+ if(data[0] || data[1] || (data[2] != 1)) {
+ static int counter=0;
+ counter++;
+ LOGMSG("xine_queue_pes_packet: packet not starting with 00 00 01 \n"
+ " packet #%d, size=%d : %02x %02x %02x %02x %02x %02x\n",
+ counter, len,
+ data[0], data[1], data[2], data[3], data[4], data[5]);
+ abort();
+ }
+#endif
+
+ input_vdr = (vdr_input_plugin_t *)this->input;
+ return input_vdr->f.push_input_write(this->input, data, len);
+}
+
+#else /* #ifndef FE_STANDALONE */
+
+static void process_xine_keypress(input_plugin_t *input, char *map, char *key,
+ int repeat, int release)
+{
+ /* from UI --> input plugin --> vdr */
+ LOGDBG("Keypress: %s %s %s %s\n",
+ map, key, repeat?"Repeat":"", release?"Release":"");
+ if(input) {
+ vdr_input_plugin_t *input_vdr = (vdr_input_plugin_t *)input;
+ if(input_vdr->f.input_control) {
+ input_vdr->f.input_control(input, map, key, repeat, release);
+ } else {
+ LOGMSG("Keypress --- NO HANDLER SET");
+ }
+ } else {
+ LOGMSG("Keypress --- NO PLUGIN FOUND");
+ }
+}
+
+#endif /* #ifndef FE_STANDALONE */
+
+/*
+ * Control messages from input plugin
+ */
+static void *fe_control(void *fe_handle, char *cmd)
+{
+ fe_t *this = (fe_t*)fe_handle;
+ post_plugins_t *posts = this->postplugins;
+
+ /*LOGDBG("fe_control(\"%s\")", cmd);*/
+
+ if(!strncmp(cmd, "SLAVE 0x", 8)) {
+ unsigned int pt;
+ if(1 == sscanf(cmd, "SLAVE 0x%x", &pt)) {
+ xine_stream_t *slave_stream = (xine_stream_t*)pt;
+ if(posts->slave_stream != slave_stream) {
+ fe_post_unwire(this);
+ posts->slave_stream = slave_stream;
+ fe_post_rewire(this);
+ }
+ }
+
+ } else if(!strncmp(cmd, "SUBSTREAM ", 10)) {
+ unsigned int pid;
+ int x, y, w, h;
+ if(5 == sscanf(cmd, "SUBSTREAM 0x%x %d %d %d %d", &pid, &x, &y, &w, &h)) {
+ char mrl[256];
+ if(!posts->pip_stream)
+ posts->pip_stream = xine_stream_new(this->xine,
+ this->audio_port,
+ this->video_port);
+ LOGMSG(" PIP %d: %dx%d @ (%d,%d)", pid & 0xf0, w, h, x, y);
+ LOGMSG("create pip stream done");
+ sprintf(mrl, "xvdr:slave:0x%x#nocache;demux:mpeg_block",(int)this);
+ if(!xine_open(posts->pip_stream, mrl) ||
+ !xine_play(posts->pip_stream, 0, 0)) {
+ LOGERR(" pip stream open/play failed");
+ } else {
+ char params[256];
+ sprintf(params, "pip_num=1,x=%d,y=%d,w=%d,h=%d", x,y,w,h);
+ fe_post_open(this, "Pip", params);
+ return posts->pip_stream;
+ }
+ }
+ fe_post_close(this, NULL, POST_VIDEO_PIP);
+ if(posts->pip_stream) {
+ xine_close(posts->pip_stream);
+ xine_dispose(posts->pip_stream);
+ posts->pip_stream = NULL;
+ }
+ return NULL;
+
+ } else if(!strncmp(cmd, "POST ", 5)) {
+ char *name = strdup(cmd+5), *args = name, *pt;
+
+ if(NULL != (pt=strchr(name, '\r')))
+ *pt = 0;
+ if(NULL != (pt=strchr(name, '\n')))
+ *pt = 0;
+
+ while(*args && *args != ' ') /* skip name */
+ args++;
+ if(*args /*== ' '*/)
+ *args++ = 0;
+
+ while(*args && *args == ' ') /* skip whitespace between name and args */
+ args++;
+
+ if(!strncmp(args, "On", 2)) {
+ args += 2;
+ while(*args == ' ')
+ args++;
+ /*LOGDBG(" POST: %s On \"%s\"", name, args);*/
+ fe_post_open(this, name, args);
+ } else if(!strncmp(args, "Off", 3)) {
+ /*LOGDBG(" POST: %s Off (name len=%d), name => int = %d", name, strlen(name), atoi(name));*/
+ if(strlen(name) == 1)
+ fe_post_close(this, NULL, atoi(name));
+ else
+ fe_post_close(this, name, -1);
+ } else {
+ LOGMSG("fe_control: POST: unknown command %s", cmd);
+ }
+ free(name);
+ return NULL;
+ }
+
+ return NULL;
+}
+
+#ifndef FE_STANDALONE
+
+/*
+ * --- RgbToJpeg -------------------------------------------------------------
+ *
+ * source: vdr-1.3.42, tools.c
+ * modified to accept YUV data
+ *
+ * TODO: remote version: send to ctrl stream
+ * - move to xine_input_vdr ?
+ */
+
+#define JPEGCOMPRESSMEM 500000
+
+typedef struct tJpegCompressData_s {
+ int size;
+ unsigned char *mem;
+} tJpegCompressData;
+
+static void JpegCompressInitDestination(j_compress_ptr cinfo)
+{
+ tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
+ if (jcd) {
+ cinfo->dest->free_in_buffer = jcd->size = JPEGCOMPRESSMEM;
+ cinfo->dest->next_output_byte = jcd->mem =
+ (unsigned char *)malloc(jcd->size);
+ }
+}
+
+static boolean JpegCompressEmptyOutputBuffer(j_compress_ptr cinfo)
+{
+ tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
+ if (jcd) {
+ int Used = jcd->size;
+ jcd->size += JPEGCOMPRESSMEM;
+ jcd->mem = (unsigned char *)realloc(jcd->mem, jcd->size);
+ if (jcd->mem) {
+ cinfo->dest->next_output_byte = jcd->mem + Used;
+ cinfo->dest->free_in_buffer = jcd->size - Used;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void JpegCompressTermDestination(j_compress_ptr cinfo)
+{
+ tJpegCompressData *jcd = (tJpegCompressData *)cinfo->client_data;
+ if (jcd) {
+ int Used = cinfo->dest->next_output_byte - jcd->mem;
+ if (Used < jcd->size) {
+ jcd->size = Used;
+ jcd->mem = (unsigned char *)realloc(jcd->mem, jcd->size);
+ }
+ }
+}
+
+static char *fe_grab(frontend_t *this_gen, int *size, int jpeg,
+ int quality, int width, int height)
+{
+ struct jpeg_destination_mgr jdm;
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ tJpegCompressData jcd;
+
+ fe_t *this = (fe_t*)this_gen;
+ vo_frame_t *frame;
+
+#ifndef PPM_SUPPORTED
+ if(!jpeg) {
+ LOGMSG("fe_grab: PPM grab not implemented");
+ return 0;
+ }
+#else
+ /* #warning TODO: convert to RGB PPM */
+#endif
+
+ if(!this->input && !find_input(this))
+ return 0;
+
+ LOGMSG("fe_grab: grabbing %s %d %dx%d",
+ jpeg ? "JPEG" : "PNM", quality, width, height);
+
+ if (quality < 0 || quality > 100)
+ quality = 100; /* -1 defaults to 100 */
+
+ this->stream->xine->port_ticket->acquire(this->stream->xine->port_ticket, 0);
+ frame = this->stream->video_out->get_last_frame (this->stream->video_out);
+ if(frame)
+ frame->lock(frame);
+ this->stream->xine->port_ticket->release(this->stream->xine->port_ticket, 0);
+
+ if(!frame)
+ return NULL;
+
+ /* #warning TODO: no scaling implemented */
+ if(width != frame->width)
+ width = frame->width;
+ if(height != frame->height)
+ height = frame->height;
+
+ /* Compress JPEG */
+
+ jdm.init_destination = JpegCompressInitDestination;
+ jdm.empty_output_buffer = JpegCompressEmptyOutputBuffer;
+ jdm.term_destination = JpegCompressTermDestination;
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+ cinfo.dest = &jdm;
+
+ cinfo.client_data = &jcd;
+ cinfo.image_width = width;
+ cinfo.image_height = height;
+ cinfo.input_components = 3;
+ /*cinfo.in_color_space = JCS_RGB;*/
+ cinfo.in_color_space = JCS_YCbCr;
+
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, TRUE);
+
+ switch (frame->format) {
+ case XINE_IMGFMT_YV12: {
+ JSAMPARRAY pp[3];
+ JSAMPROW *rpY = (JSAMPROW*)malloc(sizeof(JSAMPROW) * height);
+ JSAMPROW *rpU = (JSAMPROW*)malloc(sizeof(JSAMPROW) * height);
+ JSAMPROW *rpV = (JSAMPROW*)malloc(sizeof(JSAMPROW) * height);
+ int k;
+ cinfo.raw_data_in = TRUE;
+ jpeg_set_colorspace(&cinfo, JCS_YCbCr);
+ cinfo.comp_info[0].h_samp_factor =
+ cinfo.comp_info[0].v_samp_factor = 2;
+ cinfo.comp_info[1].h_samp_factor =
+ cinfo.comp_info[1].v_samp_factor =
+ cinfo.comp_info[2].h_samp_factor =
+ cinfo.comp_info[2].v_samp_factor = 1;
+ jpeg_start_compress(&cinfo, TRUE);
+
+ for (k = 0; k < height; k+=2) {
+ rpY[k] = frame->base[0] + k*frame->pitches[0];
+ rpY[k+1] = frame->base[0] + (k+1)*frame->pitches[0];
+ rpU[k/2] = frame->base[1] + (k/2)*frame->pitches[1];
+ rpV[k/2] = frame->base[2] + (k/2)*frame->pitches[2];
+ }
+ for (k = 0; k < height; k+=2*DCTSIZE) {
+ pp[0] = &rpY[k];
+ pp[1] = &rpU[k/2];
+ pp[2] = &rpV[k/2];
+ jpeg_write_raw_data(&cinfo, pp, 2*DCTSIZE);
+ }
+ free(rpY);
+ free(rpU);
+ free(rpV);
+ break;
+ }
+ case XINE_IMGFMT_YUY2: {
+ JSAMPROW *rp = (JSAMPROW*)malloc(sizeof(JSAMPROW) * height);
+ int rs, k;
+ jpeg_start_compress(&cinfo, TRUE);
+ rs = frame->pitches[0];
+ for (k = 0; k < height; k++)
+ rp[k] = frame->base[0] + k*rs;
+ jpeg_write_scanlines(&cinfo, rp, height);
+ free(rp);
+ break;
+ }
+#if 0
+ case XINE_IMGFMT_RGB: {
+ JSAMPROW rp[height];
+ int rs, k;
+ cinfo.in_color_space = JCS_RGB;
+ jpeg_start_compress(&cinfo, TRUE);
+ rs = frame->pitches[0];
+ for (k = 0; k < height; k++)
+ rp[k] = frame->base[0] + k*rs;
+ jpeg_write_scanlines(&cinfo, rp, height);
+ break;
+ }
+#endif
+ default:
+ LOGMSG("fe_grab: grabbing failed (unsupported image format %d)",
+ frame->format);
+ break;
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ frame->free(frame);
+
+ *size = jcd.size;
+ return (char*) jcd.mem;
+}
+
+#endif /* #ifndef FE_STANDALONE */
+
+#ifdef FE_STANDALONE
+
+/* VDR discovery protocol code */
+#include "xine_frontend_vdrdiscovery.c"
+/* LIRC forwarding code */
+#include "xine_frontend_lirc.c"
+/* frontend main() */
+#include "xine_frontend_main.c"
+
+
+#endif /* #ifdef FE_STANDALONE */
+
diff --git a/xine_frontend_lirc.c b/xine_frontend_lirc.c
new file mode 100644
index 00000000..dd1a8e09
--- /dev/null
+++ b/xine_frontend_lirc.c
@@ -0,0 +1,157 @@
+/*
+ * xine_frontend_lirc.c
+ *
+ * Forward (local) lirc keys to VDR (server)
+ *
+ *
+ * Almost directly copied from vdr-1.3.34 (lirc.c : cLircRemote)
+ *
+ * $Id: xine_frontend_lirc.c,v 1.1 2006-06-03 10:01:18 phintuka Exp $
+ *
+ */
+
+#include <stdint.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define REPEATLIMIT 20 /* ms */
+#define REPEATDELAY 350 /* ms */
+#define KEYPRESSDELAY 150 /* ms */
+#define LIRC_KEY_BUF 30
+#define LIRC_BUFFER_SIZE 128
+
+/* static data */
+pthread_t lirc_thread;
+volatile char *lirc_device_name = NULL;
+static volatile int fd_lirc = -1;
+
+static uint64_t time_ms()
+{
+ struct timeval t;
+ if (gettimeofday(&t, NULL) == 0)
+ return ((uint64_t)t.tv_sec) * 1000ULL + t.tv_usec / 1000ULL;
+ return 0;
+}
+
+static uint64_t elapsed(uint64_t t)
+{
+ return time_ms() - t;
+}
+
+void *lirc_receiver_thread(void *fe)
+{
+ struct sockaddr_un addr;
+ int timeout = -1;
+ uint64_t FirstTime = time_ms();
+ uint64_t LastTime = time_ms();
+ char buf[LIRC_BUFFER_SIZE];
+ char LastKeyName[LIRC_KEY_BUF] = "";
+ int repeat = 0;
+
+ if(!lirc_device_name) {
+ LOGDBG("no lirc device given");
+ goto out;
+ }
+
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, (char*)lirc_device_name);
+ if ((fd_lirc = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ LOGERR("lirc error: socket() < 0");
+ goto out;
+ }
+ if (connect(fd_lirc, (struct sockaddr *)&addr, sizeof(addr))) {
+ LOGERR("lirc error: connect(%s) < 0", lirc_device_name);
+ goto out;
+ }
+
+ while(lirc_device_name && fd_lirc >= 0) {
+ fd_set set;
+ int ready, ret = -1;
+ FD_ZERO(&set);
+ FD_SET(fd_lirc, &set);
+
+ if (timeout >= 0) {
+ struct timeval tv;
+#if 0
+ if(TimeoutMs < 100)
+ TimeoutMs = 100;
+#endif
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+ ready = select(FD_SETSIZE, &set, NULL, NULL, &tv) > 0 && FD_ISSET(fd_lirc, &set);
+ } else {
+ ready = select(FD_SETSIZE, &set, NULL, NULL, NULL) > 0 && FD_ISSET(fd_lirc, &set);
+ }
+
+ if(ready) {
+
+ do {
+ ret = read(fd_lirc, buf, sizeof(buf));
+ } while(ret < 0 && errno == EINTR);
+
+ if (ret <= 0 ) {
+ LOGERR("LIRC connection lost");
+ break;
+ }
+
+ if (ret > 21) {
+ unsigned int count;
+ char KeyName[LIRC_KEY_BUF];
+ LOGDBG("LIRC: %s", buf);
+ if (sscanf(buf, "%*x %x %29s", &count, KeyName) != 2) {
+ /* '29' in '%29s' is LIRC_KEY_BUF-1! */
+ LOGMSG("unparseable lirc command: %s", buf);
+ continue;
+ }
+
+ if (count == 0) {
+ if (strcmp(KeyName, LastKeyName) == 0 && elapsed(FirstTime) < KEYPRESSDELAY)
+ continue; /* skip keys coming in too fast */
+ if (repeat)
+ if(find_input((fe_t*)fe))
+ process_xine_keypress(((fe_t*)fe)->input, "LIRC", KeyName, 0, 1);
+ /* Put(LastKeyName, false, true); code, repeat, release */
+ strcpy(LastKeyName, KeyName);
+ repeat = 0;
+ FirstTime = time_ms();
+ timeout = -1;
+ }
+ else {
+ if (elapsed(FirstTime) < REPEATDELAY)
+ continue; /* repeat function kicks in after a short delay */
+ repeat = 1;
+ timeout = REPEATDELAY;
+ }
+ LastTime = time_ms();
+
+
+ if(find_input((fe_t*)fe))
+ process_xine_keypress(((fe_t*)fe)->input, "LIRC", KeyName, repeat, 0);
+
+ /*Put(KeyName, repeat);*/
+
+
+ }
+ else if (repeat) { /* the last one was a repeat, so let's generate a release */
+ if (elapsed(LastTime) >= REPEATDELAY) {
+ /* Put(LastKeyName, false, true); */
+ if(find_input((fe_t*)fe))
+ process_xine_keypress(((fe_t*)fe)->input, "LIRC", LastKeyName, 0, 1);
+ repeat = 0;
+ *LastKeyName = 0;
+ timeout = -1;
+ }
+ }
+
+ }
+ }
+
+
+ out:
+ if(fd_lirc >= 0)
+ close(fd_lirc);
+ fd_lirc = -1;
+ pthread_exit(NULL);
+ return NULL; /* never reached */
+}
diff --git a/xine_frontend_main.c b/xine_frontend_main.c
new file mode 100644
index 00000000..48c13971
--- /dev/null
+++ b/xine_frontend_main.c
@@ -0,0 +1,379 @@
+/*
+ * Simple main() routine for stand-alone frontends.
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: xine_frontend_main.c,v 1.1 2006-06-03 10:01:18 phintuka Exp $
+ *
+ */
+
+#include <termios.h>
+#include <unistd.h>
+
+#if 0
+static void xine_log_cb(void *data, int section)
+{
+ fprintf(stderr, "xine: log section %d\n",section);
+}
+
+static void print_xine_log(xine_t *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
+
+/* static data */
+pthread_t kbd_thread;
+struct termios tm, saved_tm;
+volatile int terminate_key_pressed = 0;
+
+void *kbd_receiver_thread(void *fe)
+{
+ struct pollfd pfd;
+ char ch;
+ int err;
+
+ terminate_key_pressed = 0;
+
+ /* Set stdin to deliver keypresses without buffering whole lines */
+ tcgetattr(STDIN_FILENO, &saved_tm);
+ if (tcgetattr(STDIN_FILENO, &tm) == 0) {
+ tm.c_iflag = 0;
+ tm.c_lflag &= ~(ICANON | ECHO);
+ tm.c_cc[VMIN] = 0;
+ tm.c_cc[VTIME] = 0;
+ tcsetattr(STDIN_FILENO, TCSANOW, &tm);
+ }
+
+ do {
+ errno = 0;
+ pfd.fd = STDIN_FILENO;
+ pfd.events = POLLIN;
+ err = poll(&pfd, 1, 250);
+ if(err==1) {
+ if(1 == read(STDIN_FILENO, &ch, 1)) {
+ /* forward keyboard input to server */
+ uint64_t code = ch;
+ char str[64];
+ while(poll(&pfd,1,0) == 1 && read(STDIN_FILENO,&ch,1) == 1)
+ code = (code<<8) | (ch & 0xff);
+
+ if(code == 27) { //ch == 'q' || ch == 'Q' /*|| ch == 27*/) {
+ terminate_key_pressed = 1;
+ break;
+ } //else {
+
+ snprintf(str, sizeof(str), "%016LX", code);
+ if(find_input((fe_t*)fe))
+ process_xine_keypress(((fe_t*)fe)->input, "KBD", str, 0, 0);
+ }
+ }
+
+ } while(err >= 0 || errno == EINTR);
+
+ LOGMSG("Keyboard thread terminated");
+ tcsetattr(STDIN_FILENO, TCSANOW, &saved_tm);
+ pthread_exit(NULL);
+ return NULL; /* never reached */
+}
+
+static char *strcatrealloc(char *dest, const char *src)
+{
+ int l;
+
+ if (!src || !*src)
+ return dest;
+
+ l = (dest ? strlen(dest) : 0) + strlen(src) + 1;
+ if(dest) {
+ dest = (char *)realloc(dest, l);
+ strcat(dest, src);
+ } else {
+ dest = (char*)malloc(l);
+ strcpy(dest, src);
+ }
+ return dest;
+}
+
+int main(int argc, char *argv[])
+{
+ char *mrl = NULL, *gdrv = NULL, *adrv = NULL, *adev = NULL;
+ int ftcp = 0, fudp = 0, frtp = 0;
+ int fullscreen = 0, width = 720, height = 576;
+ int scale_video = 1, aspect = 1;
+ char *video_port = NULL;
+ int xmajor, xminor, xsub;
+ int i, err;
+ frontend_t *fe = NULL;
+ extern const fe_creator_f fe_creator;
+ char *static_post_plugins = NULL;
+ void *p;
+ char *exec_name = argv[0];
+
+ if(strrchr(argv[0],'/'))
+ exec_name = strrchr(argv[0],'/')+1;
+
+ xine_get_version(&xmajor, &xminor, &xsub);
+ printf("%s %s (build with xine-lib %d.%d.%d, using xine-lib %d.%d.%d)\n\n",
+ exec_name,
+ FE_VERSION_STR,
+ XINE_MAJOR_VERSION, XINE_MINOR_VERSION, XINE_SUB_VERSION,
+ xmajor, xminor, xsub);
+
+ /* Parse arguments */
+ for(i=1; i<argc; i++) {
+ if(!strncmp(argv[i], "--help", 6)) {
+ printf("\n"
+ "Usage: %s [options] [xvdr:[udp:|tcp:|rtp:]//host:port] \n"
+ "\n"
+ "Available options:\n"
+ " --help \n"
+ " --fullscreen \n"
+ " --width x \n"
+ " --height x \n"
+ " --lirc [devicename] \n"
+ " --audio audiodriver[:device] \n"
+ " --video videodriver \n",
+ exec_name);
+ printf(" --display displayaddress \n"
+ " --verbose \n"
+ " --silent \n"
+ " --syslog \n"
+ " --tcp \n"
+ " --udp \n"
+ " --rtp \n"
+ " --aspect [auto|4:3|16:9|default] \n"
+ " --noscaling \n"
+ " --post name[:arg=val[,arg=val]]\n");
+ exit(0);
+ } else if(!strncmp(argv[i], "--fullscreen", 12)) {
+ fullscreen=1;
+ printf("Fullscreen mode\n");
+ } else if(!strncmp(argv[i], "--verbose", 9)) {
+ verbose_xine_log = 1;
+ SysLogLevel = 3;
+ printf("Verbose mode\n");
+ } else if(!strncmp(argv[i], "--silent", 8)) {
+ verbose_xine_log = 0;
+ SysLogLevel = 1;
+ printf("Silent mode\n");
+ } else if(!strncmp(argv[i], "--noscaling", 11)) {
+ scale_video = 0;
+ printf("Video scaling disabled\n");
+ } else if(!strncmp(argv[i], "--tcp", 5)) {
+ ftcp = 1;
+ printf("Protocol: TCP\n");
+ } else if(!strncmp(argv[i], "--udp", 5)) {
+ fudp = 1;
+ printf("Protocol: UDP\n");
+ } else if(!strncmp(argv[i], "--rtp", 5)) {
+ frtp = 1;
+ printf("Protocol: RTP\n");
+ } else if(!strncmp(argv[i], "--syslog", 8)) {
+ LogToSysLog = 1;
+ openlog(exec_name, LOG_PID|LOG_CONS, LOG_USER);
+ } else if(!strncmp(argv[i], "--video", 7)) {
+ if(argc > ++i) {
+ gdrv = strdup(argv[i]);
+ printf("Video driver: %s\n",gdrv);
+ }
+ } else if(!strncmp(argv[i], "--audio", 7)) {
+ if(argc > ++i) {
+ adrv = strdup(argv[i]);
+ adev = strchr(adrv, ':');
+ if(adev)
+ *(adev++) = 0;
+ printf("Audio driver: %s\n",adrv);
+ if(adev)
+ printf("Audio device: %s\n",adev);
+ }
+ } else if(!strncmp(argv[i], "--post", 6)) {
+ if(argc > ++i) {
+ if(static_post_plugins)
+ strcatrealloc(static_post_plugins, ";");
+ static_post_plugins = strcatrealloc(static_post_plugins, argv[i]);
+ printf("Post plugins: %s\n", static_post_plugins);
+ }
+ } else if(!strncmp(argv[i], "--height", 8)) {
+ if(argc > ++i) height = atoi(argv[i]);
+ printf("Height: %d\n", height);
+ } else if(!strncmp(argv[i], "--aspect", 8)) {
+ if(argc > ++i) {
+ if(!strncmp(argv[i], "auto", 4))
+ aspect = 0;
+ if(!strncmp(argv[i], "4:3", 3))
+ aspect = 2;
+ if(!strncmp(argv[i], "16:9", 4))
+ aspect = 3;
+ printf("Aspect ratio: %s\n",
+ aspect==0?"Auto":aspect==2?"4:3":aspect==3?"16:9":"Default");
+ }
+ } else if(!strncmp(argv[i], "--display", 9)) {
+ if(argc > ++i) video_port = strdup(argv[i]);
+ } else if(!strncmp(argv[i], "--width", 7)) {
+ if(argc > ++i) width = atoi(argv[i]);
+ } else if(!strncmp(argv[i], "--lirc", 6)) {
+ if(argc > i+1 && argv[i+1][0] == '/')
+ lirc_device_name = strdup(argv[++i]);
+ else
+ lirc_device_name = strdup("/dev/lircd");
+ printf("LIRC device: %s\n", lirc_device_name);
+ } else {
+ if(argv[i][0] != '-') {
+ mrl = strdup(argv[i]);
+ printf("VDR Server: %s\n", mrl);
+ } else {
+ fprintf(stderr, "Unknown argument: %s\n", argv[i]);
+ exit(-1);
+ }
+ }
+ }
+ printf("\n");
+
+ /* check xine-lib version */
+ if(!xine_check_version(1, 1, 0)) {
+ fprintf(stderr,"ERROR: xine-lib is too old, require at least "
+ "xine library version 1.1.0\n");
+ return 1;
+ }
+
+ /* If server address not given, try to find server automatically */
+ if(!mrl ||
+ !strcmp(mrl, "xvdr:") ||
+ !strcmp(mrl, "xvdr:tcp:") ||
+ !strcmp(mrl, "xvdr:udp:") ||
+ !strcmp(mrl, "xvdr:rtp:") ||
+ !strcmp(mrl, "xvdr:pipe:")) {
+ char address[1024] = "";
+ int port = -1;
+ printf("VDR server not given, searching ...\n");
+ if(search_vdr_server(&port, &address[0])) {
+ printf("Found VDR server: host %s, port %d\n", address, port);
+ if(mrl) {
+ char *tmp = mrl;
+ mrl = NULL;
+ asprintf(&mrl, "%s//%s:%d", tmp, address, port);
+ free(tmp);
+ } else
+ asprintf(&mrl, "xvdr://%s:%d", address, port);
+ } else {
+ printf("WARNING:\n"
+ " MRL not given and server not found from local network.\n"
+ " Trying to connect to default port on local host.\n");
+ mrl = strdup("xvdr://127.0.0.1");
+ }
+ }
+
+ {
+ char *tmp = NULL, *mrl2 = mrl;
+ if(frtp && !strstr(mrl, "rtp:"))
+ tmp = strdup("xvdr:rtp:");
+ else if(fudp && !strstr(mrl, "udp:"))
+ tmp = strdup("xvdr:udp:");
+ else if(ftcp && !strstr(mrl, "tcp:"))
+ tmp = strdup("xvdr:tcp:");
+ if(tmp) {
+ mrl = strcatrealloc(tmp, strchr(mrl, '/'));
+ free(mrl2);
+ }
+ }
+
+ /* Create front-end */
+ fe = (*fe_creator)();
+ if(!fe) {
+ printf("Error initializing frontend\n");
+ return 3;
+ }
+
+ /* Initialize display */
+ if(!fe->fe_display_open(fe, width, height, fullscreen, 0,
+ "", aspect, NULL, video_port, scale_video, 0)) {
+ printf("Error opening display\n");
+ fe->fe_free(fe);
+ return 4;
+ }
+
+ /* Initialize xine */
+ if(!fe->xine_init(fe, adrv, adev, gdrv, 250, 1, static_post_plugins)) {
+ printf("Error initializing xine\n");
+ fe->fe_free(fe);
+ return 5;
+ }
+
+ /* Connect to VDR xineliboutput server */
+ if(!fe_xine_open(fe, mrl)) {
+ /*print_xine_log(((fe_t *)fe)->xine);*/
+ printf("Error opening %s\n", mrl);
+ fe->fe_free(fe);
+ return 6;
+ }
+
+ printf("\n\nPress Esc to exit\n\n");
+
+ if(!fe->xine_play(fe)) {
+ /*print_xine_log(((fe_t *)fe)->xine);*/
+ /*printf("Error playing %s//%s:%s\n", argv[1], host, port);*/
+ printf("Error playing %s\n", argv[1]);
+ fe->fe_free(fe);
+ return 7;
+ }
+
+ /* Start LIRC forwarding */
+ if(lirc_device_name) {
+ if ((err = pthread_create (&lirc_thread,
+ NULL, lirc_receiver_thread,
+ (void*)fe)) != 0) {
+ fprintf(stderr, "can't create new thread for lirc (%s)\n",
+ strerror(err));
+ }
+ }
+
+ /* Start keyboard listener thread */
+ if ((err = pthread_create (&kbd_thread,
+ NULL, kbd_receiver_thread,
+ (void*)fe)) != 0) {
+ fprintf(stderr, "can't create new thread for lirc (%s)\n",
+ strerror(err));
+ }
+
+ /* Main loop */
+
+ sleep(2); /* give input_vdr some time to establish connection */
+
+ while(fe->fe_run(fe) && !fe->xine_is_finished(fe) && !terminate_key_pressed)
+ pthread_yield();
+
+ /* Clean up */
+
+ printf("Terminating...\n");
+
+ if(lirc_device_name) {
+ /*free(lirc_device_name);*/
+ lirc_device_name = NULL;
+ if(fd_lirc >= 0)
+ close(fd_lirc);
+ fd_lirc = -1;
+ pthread_cancel (lirc_thread);
+ pthread_join (lirc_thread, &p);
+ }
+
+ pthread_cancel (kbd_thread);
+ pthread_join (kbd_thread, &p);
+
+ tcsetattr(STDIN_FILENO, TCSANOW, &saved_tm);
+
+ fe->fe_free(fe);
+ return 0;
+}
diff --git a/xine_frontend_vdrdiscovery.c b/xine_frontend_vdrdiscovery.c
new file mode 100644
index 00000000..fd0637ff
--- /dev/null
+++ b/xine_frontend_vdrdiscovery.c
@@ -0,0 +1,115 @@
+/*
+ * xine_frontend_vdrdiscovery.c
+ *
+ * Try to found VDR with xineliboutput server from (local) network.
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: xine_frontend_vdrdiscovery.c,v 1.1 2006-06-03 10:01:18 phintuka Exp $
+ *
+ */
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "xine_input_vdr_net.h" /* default ports */
+
+static int search_vdr_server(int *port, char *address)
+{
+ struct sockaddr_in sin;
+ int fd_broadcast = -1;
+ int dummy = 1, trycount = 0;
+ struct pollfd pfd;
+ char pktbuf[1024];
+
+ *port = DEFAULT_VDR_PORT;
+ strcpy(address, "vdr");
+
+ if ((fd_broadcast = socket(PF_INET, SOCK_DGRAM, 0/*IPPROTO_TCP*/)) < 0) {
+ LOGERR("fd_broadcast = socket() failed");
+ } else {
+
+ if(setsockopt(fd_broadcast, SOL_SOCKET, SO_BROADCAST,
+ &dummy, sizeof(int)) < 0)
+ LOGERR("setsockopt(SO_BROADCAST) failed");
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(DISCOVERY_PORT);
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind(fd_broadcast, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+ LOGERR("Can't bind fd_broadcast to %d", DISCOVERY_PORT);
+ } else {
+ char *test = "VDR xineliboutput DISCOVERY 1.0\r\n"
+ "Client: 192.168.1.21:37890\r\n"
+ "\r\n";
+ int testlen = strlen(test);
+retry:
+ sin.sin_addr.s_addr = INADDR_BROADCAST;
+ if(testlen != sendto(fd_broadcast, test, testlen, 0,
+ (struct sockaddr *)&sin, sizeof(sin))) {
+ LOGERR("UDP broadcast send failed (discovery)");
+ } else {
+
+ while(1) {
+ int err;
+ pfd.fd = fd_broadcast;
+ pfd.events = POLLIN;
+
+ errno=0;
+ while(1==(err=poll(&pfd, 1, 500))) {
+ struct sockaddr_in from;
+ socklen_t fromlen = sizeof(from);
+ memset(&from, 0, sizeof(from));
+ memset(pktbuf, 0, sizeof(pktbuf));
+
+ errno=0;
+ if((err=recvfrom(fd_broadcast, pktbuf, 1023, 0,
+ (struct sockaddr *)&from, &fromlen)) > 0) {
+ char *mystring = "VDR xineliboutput DISCOVERY 1.0\r\n"
+ "Server port: ";
+ uint32_t tmp = ntohl(from.sin_addr.s_addr);
+
+ pktbuf[err] = 0;
+ LOGDBG("Reveived broadcast: %d bytes from %d.%d.%d.%d\n %s",
+ err,
+ ((tmp>>24)&0xff), ((tmp>>16)&0xff),
+ ((tmp>>8)&0xff), ((tmp)&0xff),
+ pktbuf);
+ if(!strncmp(mystring, pktbuf, strlen(mystring))) {
+ LOGDBG("Valid discovery message");
+ close(fd_broadcast);
+ sprintf(address, "%d.%d.%d.%d",
+ ((tmp>>24)&0xff), ((tmp>>16)&0xff),
+ ((tmp>>8)&0xff), ((tmp)&0xff));
+ if(1==sscanf(pktbuf+strlen(mystring), "%d", port))
+ return 1;
+ } else {
+ LOGDBG("NOT valid discovery message");
+ }
+ } else {
+ LOGERR("broadcast recvfrom failed");
+ break;
+ }
+ }
+ if(!err) {
+ /* timeout */
+ trycount++;
+ if(trycount < 3)
+ goto retry;
+ break;
+ }
+ LOGERR("broadcast poll error");
+ break;
+ }
+ }
+ }
+ }
+
+ /* failed */
+ close(fd_broadcast);
+ return 0;
+}
+
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;
+
+
diff --git a/xine_sxfe_frontend.c b/xine_sxfe_frontend.c
new file mode 100644
index 00000000..49e2b632
--- /dev/null
+++ b/xine_sxfe_frontend.c
@@ -0,0 +1,601 @@
+/*
+ * xine_sxfe_frontend.c: Simple front-end, X11 functions
+ *
+ * See the main source file 'xineliboutput.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: xine_sxfe_frontend.c,v 1.1 2006-06-03 10:01:18 phintuka Exp $
+ *
+ */
+
+#define HAVE_XF86VIDMODE
+#define HAVE_XDPMS
+
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+#include <pthread.h>
+#include <sched.h>
+#include <poll.h>
+#include <linux/unistd.h> /* gettid() */
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/extensions/XShm.h>
+#include <X11/Xutil.h>
+#ifdef HAVE_XF86VIDMODE
+# include <X11/extensions/xf86vmode.h>
+#endif
+#ifdef HAVE_XDPMS
+# include <X11/extensions/dpms.h>
+#endif
+#ifdef HAVE_XV_FIELD_ORDER
+# include <X11/extensions/Xvlib.h>
+#endif
+
+#ifdef boolean
+# define HAVE_BOOLEAN
+#endif
+#include <jpeglib.h>
+#undef boolean
+
+#include <xine.h>
+#ifndef XINE_ENGINE_INTERNAL
+# define XINE_ENGINE_INTERNAL
+# include <xine/xine_internal.h>
+# undef XINE_ENGINE_INTERNAL
+#else
+# include <xine/xine_internal.h>
+#endif
+#include <xine/xineutils.h>
+#include <xine/input_plugin.h>
+#include <xine/plugin_catalog.h>
+
+#include "xine_input_vdr.h"
+
+#include "xine_frontend.h"
+#include "xine/post.h"
+
+#define MWM_HINTS_DECORATIONS (1L << 1)
+#define PROP_MWM_HINTS_ELEMENTS 5
+typedef struct _mwmhints {
+ uint32_t flags;
+ uint32_t functions;
+ uint32_t decorations;
+ int32_t input_mode;
+ uint32_t status;
+} MWMHints;
+
+
+/*
+ * data
+ */
+
+typedef struct sxfe_s {
+
+ /* function pointers */
+ frontend_t fe;
+ void (*update_display_size)(frontend_t*);
+
+ /* xine stuff */
+ xine_t *xine;
+ xine_stream_t *stream;
+ input_plugin_t *input;
+ xine_video_port_t *video_port;
+ xine_audio_port_t *audio_port;
+ xine_event_queue_t *event_queue;
+
+ post_plugins_t *postplugins;
+
+ char configfile[256];
+
+ int xine_visual_type;
+ x11_visual_t vis;
+
+ int pes_buffers;
+ int aspect;
+ int cropping;
+ int scale_video;
+ int priority;
+
+
+ double display_ratio;
+
+ /* frontend */
+ int playback_finished;
+
+ /* vdr */
+ fe_keypress_f keypress;
+
+ /* X11 */
+ Display *display;
+ int screen;
+ Window window[2];
+ int fullscreen;
+ int vmode_switch;
+ int field_order;
+ char modeline[256];
+#ifdef HAVE_XF86VIDMODE
+ /* XF86VidMode Extension */
+ XF86VidModeModeInfo** XF86_modelines;
+ int XF86_modelines_count;
+#endif
+
+ int completion_event;
+
+ int xpos, ypos;
+ int width, height;
+
+ Atom wm_del_win;
+ Atom sxfe_interrupt;
+
+} fe_t, sxfe_t;
+
+
+/* Common (non-X11/FB) frontend functions */
+#include "xine_frontend.c"
+
+
+static void fe_dest_size_cb (void *data,
+ int video_width, int video_height, double video_pixel_aspect,
+ int *dest_width, int *dest_height, double *dest_pixel_aspect)
+{
+ fe_t *this = (fe_t *)data;
+
+ if (!this)
+ return;
+
+ *dest_width = this->width;
+ *dest_height = this->height;
+
+ *dest_pixel_aspect = fe_dest_pixel_aspect(this, video_pixel_aspect,
+ video_width, video_height);
+}
+
+/*
+ * sxfe_display_open
+ *
+ * connect to X server, create windows
+ */
+
+static int sxfe_display_open(frontend_t *this_gen, int width, int height, int fullscreen,
+ int modeswitch, char *modeline, int aspect,
+ fe_keypress_f keyfunc, char *video_port,
+ int scale_video, int field_order)
+{
+ sxfe_t *this = (sxfe_t*)this_gen;
+
+ Atom prop;
+ MWMHints mwmhints;
+ XSizeHints hint;
+ double res_h, res_v, aspect_diff;
+
+ if(this->display)
+ this->fe.fe_display_close(this_gen);
+
+ if(keyfunc)
+ this->keypress = keyfunc;
+
+ LOGDBG("sxfe_display_open(width=%d, height=%d, fullscreen=%d, display=%s)",
+ width, height, fullscreen, video_port);
+
+ this->xpos = 0;
+ this->ypos = 0;
+ this->width = width;
+ this->height = height;
+
+ this->fullscreen = fullscreen;
+ this->vmode_switch = modeswitch;
+ this->aspect = aspect;
+ this->cropping = 0;
+ this->field_order = field_order ? 1 : 0;
+ this->scale_video = scale_video;
+ strcpy(this->modeline, modeline);
+
+ /*
+ * init x11 stuff
+ */
+
+ if (!XInitThreads ()) {
+ LOGERR("sxfe_display_open: XInitThreads failed");
+ free(this);
+ return 0;
+ }
+
+ if(video_port && strlen(video_port)>3) {
+ if(!(this->display = XOpenDisplay(video_port)))
+ LOGERR("sxfe_display_open: failed to connect to X server (%s)",
+ video_port);
+ }
+ if(!this->display) {
+ if(NULL!=(video_port=getenv("DISPLAY")) && !(this->display = XOpenDisplay(video_port)))
+ LOGERR("sxfe_display_open: failed to connect to X server (%s)",
+ video_port);
+ }
+ if(!this->display) {
+ video_port = "127.0.0.1:0.0";
+ if(!(this->display = XOpenDisplay(video_port)))
+ LOGERR("sxfe_display_open: failed to connect to X server (%s)",
+ video_port);
+ }
+ if(!this->display) {
+ this->display = XOpenDisplay(NULL);
+ }
+ if (!this->display) {
+ LOGERR("sxfe_display_open: failed to connect to X server");
+ free(this);
+ return 0;
+ }
+
+ XLockDisplay (this->display);
+
+ this->screen = DefaultScreen(this->display);
+
+ /* #warning sxfe_display_open: TODO: switch vmode */
+
+ /* completion event */
+ if (XShmQueryExtension (this->display) == True) {
+ this->completion_event = XShmGetEventBase (this->display) + ShmCompletion;
+ } else {
+ this->completion_event = -1;
+ }
+
+ if(fullscreen) {
+ this->width = DisplayWidth(this->display, this->screen);
+ this->height = DisplayHeight(this->display, this->screen);
+ }
+
+ /* create and display our video window */
+ this->window[0] = XCreateSimpleWindow (this->display,
+ DefaultRootWindow(this->display),
+ this->xpos, this->ypos,
+ this->width, this->height,
+ 1, 0, 0);
+ this->window[1] = XCreateSimpleWindow(this->display, XDefaultRootWindow(this->display),
+ 0, 0, (DisplayWidth(this->display, this->screen)),
+ (DisplayHeight(this->display, this->screen)), 0, 0, 0);
+
+ hint.flags = USSize | USPosition | PPosition | PSize;
+ hint.x = 0;
+ hint.y = 0;
+ hint.width = DisplayWidth(this->display, this->screen);
+ hint.height = DisplayHeight(this->display, this->screen);
+ XSetNormalHints(this->display, this->window[1], &hint);
+
+ /* no border in fullscreen window */
+ prop = XInternAtom(this->display, "_MOTIF_WM_HINTS", False);
+ mwmhints.flags = MWM_HINTS_DECORATIONS;
+ mwmhints.decorations = 0;
+ XChangeProperty(this->display, this->window[1], prop, prop, 32,
+ PropModeReplace, (unsigned char *) &mwmhints,
+ PROP_MWM_HINTS_ELEMENTS);
+
+ XSelectInput (this->display, this->window[0],
+ StructureNotifyMask |
+ ExposureMask |
+ KeyPressMask );
+ XSelectInput (this->display, this->window[1],
+ StructureNotifyMask |
+ ExposureMask |
+ KeyPressMask );
+
+ XMapRaised (this->display, this->window[this->fullscreen]);
+
+ /* determine display aspect ratio */
+ res_h = (DisplayWidth (this->display, this->screen)*1000
+ / DisplayWidthMM (this->display, this->screen));
+ res_v = (DisplayHeight (this->display, this->screen)*1000
+ / DisplayHeightMM (this->display, this->screen));
+ this->display_ratio = res_v / res_h;
+ aspect_diff = this->display_ratio - 1.0;
+ if ((aspect_diff < 0.01) && (aspect_diff > -0.01)) {
+ this->display_ratio = 1.0;
+ }
+ LOGDBG("Display size : %d x %d mm",
+ DisplayWidthMM (this->display, this->screen),
+ DisplayHeightMM (this->display, this->screen));
+ LOGDBG(" %d x %d pixels",
+ DisplayWidth (this->display, this->screen),
+ DisplayHeight (this->display, this->screen));
+ LOGDBG(" %ddpi / %ddpi",
+ (int)(res_v/1000*25.4), (int)(res_h/1000*25.4));
+ LOGDBG("Display ratio: %f/%f = %f", res_v, res_h, this->display_ratio);
+
+ /* we want to get notified if user closes the window */
+ this->wm_del_win = XInternAtom(this->display, "WM_DELETE_WINDOW", False);
+ this->sxfe_interrupt = XInternAtom(this->display, "SXFE_INTERRUPT", False);
+
+ XSetWMProtocols(this->display, this->window[fullscreen], &(this->wm_del_win), 1);
+
+ /* no cursor */
+ XDefineCursor(this->display, this->window[0], None);
+ XDefineCursor(this->display, this->window[1], None);
+ {
+ static char bm_no_data[] = { 0,0,0,0, 0,0,0,0 };
+ Pixmap bm_no;
+ Cursor no_ptr;
+ XColor black, dummy;
+ bm_no = XCreateBitmapFromData(this->display, this->window[fullscreen], bm_no_data, 8, 8);
+ XAllocNamedColor(this->display, DefaultColormapOfScreen(DefaultScreenOfDisplay(this->display)),
+ "black", &black, &dummy);
+ no_ptr = XCreatePixmapCursor(this->display, bm_no, bm_no, &black, &black, 0, 0);
+ XDefineCursor(this->display, this->window[0], no_ptr);
+ XDefineCursor(this->display, this->window[1], no_ptr);
+ }
+
+ XUnlockDisplay (this->display);
+
+ /* No screen saver */
+ /* #warning TODO: suspend --> activate blank screen saver / DPMS display off ? */
+ XSetScreenSaver(this->display, 0, 0, DefaultBlanking, DefaultExposures);
+#ifdef HAVE_XDPMS
+ {
+ int dpms_dummy;
+ if (DPMSQueryExtension(this->display, &dpms_dummy, &dpms_dummy) && DPMSCapable(this->display)) {
+/* DPMSInfo(dpy, &dpms_state, &dpms_on); */
+ DPMSDisable(this->display);
+ }
+ }
+#endif
+
+ this->xine_visual_type = XINE_VISUAL_TYPE_X11;
+ this->vis.display = this->display;
+ this->vis.screen = this->screen;
+ this->vis.d = this->window[this->fullscreen];
+ this->vis.dest_size_cb = fe_dest_size_cb;
+ this->vis.frame_output_cb = fe_frame_output_cb;
+ this->vis.user_data = this;
+
+ return 1;
+}
+
+/*
+ * sxfe_display_config
+ *
+ * configure windows
+ */
+static int sxfe_display_config(frontend_t *this_gen,
+ int width, int height, int fullscreen,
+ int modeswitch, char *modeline,
+ int aspect, int scale_video,
+ int field_order)
+{
+ sxfe_t *this = (sxfe_t*)this_gen;
+
+ if(this->width != width || this->height != height) {
+ this->width = width;
+ this->height = height;
+ this->fullscreen = fullscreen;
+
+ if(!fullscreen) {
+ XLockDisplay(this->display);
+ XResizeWindow(this->display, this->window[0], this->width, this->height);
+ XUnlockDisplay(this->display);
+ if(!fullscreen && !this->fullscreen)
+ xine_gui_send_vo_data(this->stream, XINE_GUI_SEND_DRAWABLE_CHANGED,
+ (void*) this->window[0]);
+ }
+ }
+
+ if(fullscreen) {
+ this->width = DisplayWidth(this->display, this->screen);
+ this->height = DisplayHeight(this->display, this->screen);
+ }
+
+ if(fullscreen != this->fullscreen) {
+ Window tmp_win;
+ XLockDisplay(this->display);
+ XUnmapWindow(this->display, this->window[this->fullscreen]);
+ this->fullscreen = fullscreen;
+ XMapRaised(this->display, this->window[this->fullscreen]);
+ if(!fullscreen)
+ XResizeWindow(this->display, this->window[0], this->width, this->height);
+ XSync(this->display, False);
+ XTranslateCoordinates(this->display, this->window[this->fullscreen],
+ DefaultRootWindow(this->display),
+ 0, 0, &this->xpos, &this->ypos, &tmp_win);
+ XUnlockDisplay(this->display);
+ xine_gui_send_vo_data(this->stream, XINE_GUI_SEND_DRAWABLE_CHANGED,
+ (void*) this->window[this->fullscreen]);
+ }
+
+ if(!modeswitch && strcmp(modeline, this->modeline)) {
+ strcpy(this->modeline, modeline);
+ /* #warning TODO - switch vmode */
+ }
+
+ this->vmode_switch = modeswitch;
+ this->aspect = aspect;
+ this->scale_video = scale_video;
+#ifdef HAVE_XV_FIELD_ORDER
+ if(this->field_order != field_order) {
+ if(XInternAtom(this->display, "XV_SWAP_FIELDS", True) != None)
+ XvSetPortAttribute (this->display, 53,
+ XInternAtom (this->display, "XV_SWAP_FIELDS", False),
+ field_order);
+ }
+#endif
+ this->field_order = field_order ? 1 : 0;
+
+ return 1;
+}
+
+
+/*
+ * X event loop
+ */
+
+static void sxfe_interrupt(frontend_t *this_gen)
+{
+ sxfe_t *this = (sxfe_t*)this_gen;
+ XClientMessageEvent ev2;
+
+ ev2.type = ClientMessage;
+ ev2.display = this->display;
+ ev2.window = this->window[this->fullscreen];
+ ev2.message_type = this->sxfe_interrupt;
+ ev2.format = 32;
+
+ if(!XSendEvent(ev2.display, ev2.window, TRUE, /*KeyPressMask*/0, (XEvent *)&ev2))
+ LOGERR("sxfe_interrupt: XSendEvent(ClientMessage) FAILED\n");
+
+ XFlush(this->display);
+}
+
+static int sxfe_run(frontend_t *this_gen)
+{
+ sxfe_t *this = (sxfe_t*)this_gen;
+
+ int keep_going = 1;
+ XEvent event;
+
+ /* poll X server (connection socket).
+ (XNextEvent will block if no events are queued).
+ We want to use timeout, blocking for long time usually causes vdr
+ watchdog to emergency exit ... */
+ if (! XPending(this->display)) {
+ struct pollfd pfd[2];
+ pfd[0].fd = ConnectionNumber(this->display);
+ pfd[0].events = POLLIN;
+ if(poll(pfd, 1, 500) < 1 || !(pfd[0].revents & POLLIN)) {
+ return 1;
+ }
+ }
+
+ XNextEvent (this->display, &event);
+
+ switch (event.type) {
+ case Expose:
+ if (event.xexpose.count == 0)
+ xine_gui_send_vo_data (this->stream, XINE_GUI_SEND_EXPOSE_EVENT, &event);
+ break;
+
+ case ConfigureNotify:
+ {
+ XConfigureEvent *cev = (XConfigureEvent *) &event;
+ Window tmp_win;
+
+ this->width = cev->width;
+ this->height = cev->height;
+
+ if ((cev->x == 0) && (cev->y == 0)) {
+ XLockDisplay(cev->display);
+ XTranslateCoordinates(cev->display, cev->window,
+ DefaultRootWindow(cev->display),
+ 0, 0, &this->xpos, &this->ypos, &tmp_win);
+ XUnlockDisplay(cev->display);
+ } else {
+ this->xpos = cev->x;
+ this->ypos = cev->y;
+ }
+ break;
+ }
+
+ case KeyPress:
+ case KeyRelease:
+ {
+ XKeyEvent *kevent = (XKeyEvent *) &event;
+ KeySym ks;
+ char *ksname;
+ char buffer[20];
+ int buf_len = 20;
+ XComposeStatus status;
+
+ if(kevent->keycode) {
+ XLookupString(kevent, buffer, buf_len, &ks, &status);
+ ksname = XKeysymToString(ks);
+#ifdef FE_STANDALONE
+ if(/*ks == XK_q || ks == XK_Q ||*/ ks == XK_Escape)
+ keep_going = 0;
+ else if(this->input || find_input(this))
+ process_xine_keypress(this->input, "XKeySym",ksname, 0, 0);
+#else
+ if(this->keypress)
+ this->keypress("XKeySym",ksname);
+#endif
+ }
+ }
+ break;
+
+ case ClientMessage:
+ {
+ XClientMessageEvent *cmessage = (XClientMessageEvent *) &event;
+ if ( cmessage->message_type == this->sxfe_interrupt )
+ LOGDBG("ClientMessage: sxfe_interrupt");
+
+ if ( cmessage->data.l[0] == this->wm_del_win )
+ /* we got a window deletion message from out window manager.*/
+ keep_going=0;
+ }
+ }
+
+ if (event.type == this->completion_event)
+ xine_gui_send_vo_data (this->stream, XINE_GUI_SEND_COMPLETION_EVENT, &event);
+
+ return keep_going;
+}
+
+static void sxfe_display_close(frontend_t *this_gen)
+{
+ sxfe_t *this = (sxfe_t*)this_gen;
+
+ if(this && this->display) {
+
+ if(this->xine)
+ this->fe.xine_exit(this_gen);
+
+ XLockDisplay(this->display);
+ XUnmapWindow(this->display, this->window[this->fullscreen]);
+ XDestroyWindow(this->display, this->window[0]);
+ XDestroyWindow(this->display, this->window[1]);
+ XUnlockDisplay(this->display);
+ XCloseDisplay (this->display);
+ this->display = NULL;
+ }
+}
+
+static frontend_t *sxfe_get_frontend(void)
+{
+ sxfe_t *this = malloc(sizeof(sxfe_t));
+ memset(this, 0, sizeof(sxfe_t));
+
+ this->fe.fe_display_open = sxfe_display_open;
+ this->fe.fe_display_config = sxfe_display_config;
+ this->fe.fe_display_close = sxfe_display_close;
+
+ this->fe.xine_init = fe_xine_init;
+ this->fe.xine_open = fe_xine_open;
+ this->fe.xine_play = fe_xine_play;
+ this->fe.xine_stop = fe_xine_stop;
+ this->fe.xine_close = fe_xine_close;
+ this->fe.xine_exit = fe_xine_exit;
+ this->fe.xine_is_finished = fe_is_finished;
+
+ this->fe.fe_run = sxfe_run;
+ this->fe.fe_interrupt = sxfe_interrupt;
+ this->fe.fe_free = fe_free;
+
+#ifndef FE_STANDALONE
+ this->fe.grab = fe_grab;
+ this->fe.xine_osd_command = xine_osd_command;
+ this->fe.xine_control = xine_control;
+
+ this->fe.xine_queue_pes_packet = xine_queue_pes_packet;
+#endif /*#ifndef FE_STANDALONE */
+
+ return (frontend_t*)this;
+}
+
+/* ENTRY POINT */
+const fe_creator_f fe_creator __attribute__((visibility("default"))) = sxfe_get_frontend;
+
+
+