OK,I think I bit off more that I can chew here ;-). I'mno coder lets face it. I've read through the expand plugin and it does not have an existing event structure. So I'mnot sure where exactly or what type of queue to use.
From xine.h, the options for queue's are: i) A callback: this is what the vdr plugin uses to send key events to vdr from xine. ii) a non-blocking wait. this is what the also sound plugin uses.
OK cool, so I can read code, that was pretty hard ;-) (no really, I had to get out my '12 easy lessons of C' book from 10 years ago).
So where too from here? Can anyone offer some psudo code ideas on how it couldwork?
From what I can see in event.c, "static post_plugin_t *expand_open_plugin" (what does static mean refer to?) is the function which makes call draw (function expand_draw): {{{ ... port = _x_post_intercept_video_port(&this->post, video_target[0], &input, &output); port->new_port.get_frame = expand_get_frame; port->intercept_ovl = expand_intercept_ovl; port->new_manager->add_event = expand_overlay_add_event; port->new_frame->draw = expand_draw; ... }}}
So, what I'm thinking, in the spirit of KISS (this is a necessity for me, because the last S is me *wink*) is to simply: i) initiate the event_function with zoom setting of 0 (expected range 0-6) if event = zoom in, zoom+=1 if event = zoom out, zoom-=1
ii) create a callback on an event_function.
iii) in expand_draw, modify centre_cut_out_mode code to "levels" of zoom, 0-6 (where 0 is no zoom and 6 is zoom as is). Then do zoom as per zoom level. If center_cut_out_mode=1 then do zoom 6 where appropriate.
OK wow, i hope the above make sense.
Comments PLEASE ;-)
I have tried to implement this, at least with a callback to spit a message to stdout when a particular type of event is seen. However, fbxine just dies during execution. Is there any way to debug the code? Without expand on, fbxine launches. With it on, I just get this: --- /usr/local/bin/fbxine -d --stdctl -V vidixfb -A alsa --no-lirc --post=expand:centre_cut_out=1 -D vdr://tmp/vdr-xine/stream#demux:mpeg_pes mga_crtc2_vid: Found MGA G400/G450 mga_crtc2_vid: detected RAMSIZE is 16 MB mga_crtc2_vid: Set write-combining type of video memory mga_crtc2_vid: IRQ support disabled --- With the old expand plugin, i the above plus normal operation (video out ;-): --- ... 1 time: 0 libmpeg2: stream not demultiplexed ? libmpeg2: stream not demultiplexed ? mga_crtc2_vid: Saved colorkey (ON: 0 B9:72:32) libmpeg2: stream not demultiplexed ? ... ---
Here is the new expand plugin... freevo src # cat post/planar/expand.c /* * Copyright (C) 2003 the xine project * * This file is part of xine, a free 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 * * $Id: * * expand video filter by James Stembridge 24/05/2003 * improved by Michael Roitzsch * centre_crop_out_mode by Reinhard Nissl * * based on invert.c * */
#include "xine_internal.h" #include "post.h"
/* The expand trick explained: * * The expand plugin is meant to take frames of arbitrary aspect ratio and * converts them to 4:3 aspect by adding black bars on the top and bottom * of the frame. This allows us to shift overlays down into the black area * so they don't cover the image. * * How do we do that? The naive approach would be to intercept the frame's * draw() function and simply copy the frame's content into a larger one. * This is quite CPU intensive because of the huge memcpy()s involved. * * Therefore the better idea is to trick the decoder into rendering the * image into a frame with pre-attached black borders. This is the way: * - when the decoder asks for a new frame, we allocate an enlarged * frame from the original port and prepare it with black borders * - we modify this frame's base pointers so that the decoder will only see * the area between the black bars * - this frame is given to the decoder, which paints its image inside * - when the decoder draws the frame, the post plugin architecture * will automatically restore the old pointers * This way, the decoder (or any other post plugin up the tree) will only * see the frame area between the black bars and by that modify the * enlarged version directly. No need for later copying. * * When centre_crop_out_mode is enabled, the plugin will detect the black * bars to the left and right of the image and will then set up cropping * to efficiently remove the black border around the 4:3 image, which the * plugin would produce otherwise for this case. */
/* plugin class initialization function */ void *expand_init_plugin(xine_t *xine, void *);
/* plugin structures */ typedef struct expand_parameters_s { int enable_automatic_shift; int overlay_y_offset; int centre_cut_out_mode; } expand_parameters_t;
START_PARAM_DESCR(expand_parameters_t) PARAM_ITEM(POST_PARAM_TYPE_BOOL, enable_automatic_shift, NULL, 0, 1, 0, "enable automatic overlay shifting") PARAM_ITEM(POST_PARAM_TYPE_INT, overlay_y_offset, NULL, -500, 500, 0, "manually shift the overlay vertically") PARAM_ITEM(POST_PARAM_TYPE_BOOL, centre_cut_out_mode, NULL, 0, 1, 0, "cut out centered 4:3 image contained in 16:9 frame") END_PARAM_DESCR(expand_param_descr)
typedef struct post_expand_s {
xine_stream_t *stream;
post_plugin_t post;
xine_post_in_t parameter_input;
int enable_automatic_shift; int overlay_y_offset; int top_bar_height; int centre_cut_out_mode; int cropping_active;
xine_event_queue_t *event_queue; xine_event_queue_t *event_queue_external;
} post_expand_t;
/* plugin class functions */ static post_plugin_t *expand_open_plugin(post_class_t *class_gen, int inputs, xine_audio_port_t **audio_target, xine_video_port_t **video_target, xine_stream_t *stream);
static char *expand_get_identifier(post_class_t *class_gen); static char *expand_get_description(post_class_t *class_gen); static void expand_class_dispose(post_class_t *class_gen);
/* plugin instance functions */ static void expand_dispose(post_plugin_t *this_gen);
/* parameter functions */ static xine_post_api_descr_t *expand_get_param_descr(void); static int expand_set_parameters(xine_post_t *this_gen, void *param_gen); static int expand_get_parameters(xine_post_t *this_gen, void *param_gen); static char *expand_get_help (void);
/* replaced video port functions */ static vo_frame_t *expand_get_frame(xine_video_port_t *port_gen, uint32_t width, uint32_t height, double ratio, int format, int flags);
/* replaced vo_frame functions */ static int expand_draw(vo_frame_t *frame, xine_stream_t *stream);
/* event listener */ static int is_event_callback (void *user_data, const xine_event_t *event);
/* overlay manager intercept check */ static int expand_intercept_ovl(post_video_port_t *port);
/* replaced overlay manager functions */ static int32_t expand_overlay_add_event(video_overlay_manager_t *this_gen, void *event);
void *expand_init_plugin(xine_t *xine, void *data) { post_class_t *class = (post_class_t *)malloc(sizeof(post_class_t));
if (!class) return NULL;
class->open_plugin = expand_open_plugin; class->get_identifier = expand_get_identifier; class->get_description = expand_get_description; class->dispose = expand_class_dispose;
return class; }
static post_plugin_t *expand_open_plugin(post_class_t *class_gen, int inputs, xine_audio_port_t **audio_target, xine_video_port_t **video_target, xine_stream_t *stream) { post_expand_t *this = (post_expand_t *)xine_xmalloc(sizeof(post_expand_t)); post_in_t *input; xine_post_in_t *input_param; post_out_t *output; post_video_port_t *port; static xine_post_api_t post_api = { expand_set_parameters, expand_get_parameters, expand_get_param_descr, expand_get_help };
if (!this || !video_target || !video_target[0]) { free(this); return NULL; }
_x_post_init(&this->post, 0, 1);
this->stream = stream;
this->enable_automatic_shift = 0; this->overlay_y_offset = 0; this->centre_cut_out_mode = 0; this->cropping_active = 0;
this->event_queue = xine_event_new_queue (this->stream);
xine_event_create_listener_thread (this->event_queue, is_event_callback, this);
port = _x_post_intercept_video_port(&this->post, video_target[0], &input, &output); port->new_port.get_frame = expand_get_frame; port->intercept_ovl = expand_intercept_ovl; port->new_manager->add_event = expand_overlay_add_event; port->new_frame->draw = expand_draw;
input_param = &this->parameter_input; input_param->name = "parameters"; input_param->type = XINE_POST_DATA_PARAMETERS; input_param->data = &post_api; xine_list_append_content(this->post.input, input_param);
input->xine_in.name = "video"; output->xine_out.name = "expanded video";
this->post.xine_post.video_input[0] = &port->new_port;
this->post.dispose = expand_dispose;
return &this->post; }
static char *expand_get_identifier(post_class_t *class_gen) { return "expand"; }
static char *expand_get_description(post_class_t *class_gen) { return "add black borders to top and bottom of video to expand it to 4:3 aspect ratio"; }
static void expand_class_dispose(post_class_t *class_gen) { free(class_gen); }
static void expand_dispose(post_plugin_t *this_gen) { post_expand_t *this = (post_expand_t *)this_gen;
if (_x_post_dispose(this_gen)) free(this); }
static xine_post_api_descr_t *expand_get_param_descr(void) { return &expand_param_descr; }
static int expand_set_parameters(xine_post_t *this_gen, void *param_gen) { post_expand_t *this = (post_expand_t *)this_gen; expand_parameters_t *param = (expand_parameters_t *)param_gen;
this->enable_automatic_shift = param->enable_automatic_shift; this->overlay_y_offset = param->overlay_y_offset; this->centre_cut_out_mode = param->centre_cut_out_mode;
return 1; }
static int expand_get_parameters(xine_post_t *this_gen, void *param_gen) { post_expand_t *this = (post_expand_t *)this_gen; expand_parameters_t *param = (expand_parameters_t *)param_gen;
param->enable_automatic_shift = this->enable_automatic_shift; param->overlay_y_offset = this->overlay_y_offset; param->centre_cut_out_mode = this->centre_cut_out_mode;
return 1; }
static char *expand_get_help(void) { return _("The expand plugin is meant to take frames of arbitrary aspect ratio and " "converts them to 4:3 aspect by adding black bars on the top and bottom " "of the frame. This allows us to shift overlays down into the black area " "so they don't cover the image.\n" "\n" "Parameters (FIXME: better help)\n" " Enable_automatic_shift: Enable automatic overlay shifting\n" " Overlay_y_offset: Manually shift the overlay vertically\n" " Centre_cut_out_mode: extracts 4:3 image contained in 16:9 frame\n" "\n" ); }
static vo_frame_t *expand_get_frame(xine_video_port_t *port_gen, uint32_t width, uint32_t height, double ratio, int format, int flags) { post_video_port_t *port = (post_video_port_t *)port_gen; post_expand_t *this = (post_expand_t *)port->post; vo_frame_t *frame; uint32_t new_height, top_bar_height; int i, end;
_x_post_rewire(&this->post);
if (ratio <= 0.0) ratio = (double)width / (double)height;
/* Calculate height of expanded frame */ new_height = (double)height * ratio * 3.0 / 4.0; new_height = (new_height + 1) & ~1; top_bar_height = (new_height - height) / 2; top_bar_height = (top_bar_height + 1) & ~1;
this->top_bar_height = top_bar_height;
if (new_height > height && (format == XINE_IMGFMT_YV12 || format == XINE_IMGFMT_YUY2)) { frame = port->original_port->get_frame(port->original_port, width, new_height, 4.0 / 3.0, format, flags);
_x_post_inc_usage(port); frame = _x_post_intercept_video_frame(frame, port);
/* paint black bars in the top and bottom of the frame and hide these * from the decoders by modifying the pointers to and * the size of the drawing area */ frame->height = height; frame->ratio = ratio; switch (format) { case XINE_IMGFMT_YV12: /* paint top bar */ memset(frame->base[0], 0, frame->pitches[0] * top_bar_height ); memset(frame->base[1], 128, frame->pitches[1] * top_bar_height / 2); memset(frame->base[2], 128, frame->pitches[2] * top_bar_height / 2); /* paint bottom bar */ memset(frame->base[0] + frame->pitches[0] * (top_bar_height + height) , 0, frame->pitches[0] * (new_height - top_bar_height - height) ); memset(frame->base[1] + frame->pitches[1] * (top_bar_height + height) / 2, 128, frame->pitches[1] * (new_height - top_bar_height - height) / 2); memset(frame->base[2] + frame->pitches[2] * (top_bar_height + height) / 2, 128, frame->pitches[2] * (new_height - top_bar_height - height) / 2); /* modify drawing area */ frame->base[0] += frame->pitches[0] * top_bar_height; frame->base[1] += frame->pitches[1] * top_bar_height / 2; frame->base[2] += frame->pitches[2] * top_bar_height / 2; break; case XINE_IMGFMT_YUY2: /* paint top bar */ end = frame->pitches[0] * top_bar_height; for (i = 0; i < end; i += 2) { frame->base[0][i] = 0; frame->base[0][i+1] = 128; } /* paint bottom bar */ end = frame->pitches[0] * new_height; for (i = frame->pitches[0] * (top_bar_height + height); i < end; i += 2) { frame->base[0][i] = 0; frame->base[0][i+1] = 128; } /* modify drawing area */ frame->base[0] += frame->pitches[0] * top_bar_height; } } else { frame = port->original_port->get_frame(port->original_port, width, height, ratio, format, flags); /* no need to intercept this one, we are not going to do anything with it */ }
return frame; }
static int expand_intercept_ovl(post_video_port_t *port) { post_expand_t *this = (post_expand_t *)port->post;
if (this->centre_cut_out_mode && this->cropping_active) return 0;
/* we always intercept overlay manager */ return 1; }
static int32_t expand_overlay_add_event(video_overlay_manager_t *this_gen, void *event_gen) { video_overlay_event_t *event = (video_overlay_event_t *)event_gen; post_video_port_t *port = _x_post_ovl_manager_to_port(this_gen); post_expand_t *this = (post_expand_t *)port->post;
if (event->event_type == OVERLAY_EVENT_SHOW) { switch (event->object.object_type) { case 0: /* regular subtitle */ if (this->enable_automatic_shift) event->object.overlay->y += 2 * this->top_bar_height; else event->object.overlay->y += this->overlay_y_offset; break; case 1: /* menu overlay */ event->object.overlay->y += this->top_bar_height; } }
return port->original_manager->add_event(port->original_manager, event_gen); }
static int is_pixel_black(vo_frame_t *frame, int x, int y) { int Y = 0x00, Cr = 0x00, Cb = 0x00;
if (x < 0) x = 0; if (x >= frame->width) x = frame->width - 1; if (y < 0) y = 0; if (y >= frame->height) y = frame->height - 1;
switch (frame->format) { case XINE_IMGFMT_YV12: Y = *(frame->base[ 0 ] + frame->pitches[ 0 ] * y + x); Cr = *(frame->base[ 1 ] + frame->pitches[ 1 ] * y / 2 + x / 2); Cb = *(frame->base[ 2 ] + frame->pitches[ 2 ] * y / 2 + x / 2); break;
case XINE_IMGFMT_YUY2: Y = *(frame->base[ 0 ] + frame->pitches[ 0 ] * y + x * 2 + 0); x &= ~1; Cr = *(frame->base[ 0 ] + frame->pitches[ 0 ] * y + x * 2 + 1); Cb = *(frame->base[ 0 ] + frame->pitches[ 0 ] * y + x * 2 + 3); break; }
return (Y == 0x10 && Cr == 0x80 && Cb == 0x80); }
static int is_event_callback (void *user_data, const xine_event_t *event) { /* this doesn't do anything for now: it's a start at attempting to display * CMML clips which occur at 0 seconds into the track. see * * http://marc.theaimsgroup.com/?l=xine-devel&m=109202443013890&w=2 * * for a description of the problem. */
switch (event->type) { case XINE_EVENT_INPUT_MENU1: lprintf("video_frame_format_change_callback called!\n"); break; default: lprintf("video_frame_format_change_callback called with unknown event %d\n", event->type); break; } }
static int expand_draw(vo_frame_t *frame, xine_stream_t *stream) { post_video_port_t *port = (post_video_port_t *)frame->port; post_expand_t *this = (post_expand_t *)port->post; int skip;
if (this->centre_cut_out_mode && !frame->bad_frame) { /* expected area of inner 4:3 image */ int centre_width = frame->width * (9 * 4) / (16 * 3); int centre_left = (frame->width - centre_width ) / 2;
/* centre point for detecting a black frame */ int centre_x = frame->width / 2; int centre_y = frame->height / 2;
/* ignore a black frame as it could lead to wrong results */ if (!is_pixel_black(frame, centre_x, centre_y)) { /* coordinates for testing black border near the centre area */ int test_left = centre_left - 16; int test_right = centre_left + 16 + centre_width;
/* enable cropping when these pixels are black */ this->cropping_active = is_pixel_black(frame, test_left, centre_y) && is_pixel_black(frame, test_right, centre_y); }
/* crop frame */ if (this->centre_cut_out_mode && this->cropping_active) { frame->crop_left += centre_left; frame->crop_right += centre_left;
/* get_frame() allocated an extra high frame */ frame->crop_top += (frame->next->height - frame->height) / 2; frame->crop_bottom += (frame->next->height - frame->height) / 2; } }
_x_post_frame_copy_down(frame, frame->next); skip = frame->next->draw(frame->next, stream); _x_post_frame_copy_up(frame, frame->next);
return skip; }
Mick