summaryrefslogtreecommitdiff
path: root/xine_frontend.c
diff options
context:
space:
mode:
Diffstat (limited to 'xine_frontend.c')
-rw-r--r--xine_frontend.c1207
1 files changed, 1207 insertions, 0 deletions
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 */
+