diff options
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(); +} @@ -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; +} @@ -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; +} @@ -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; + + + |