To re-capitulate: XvShmPutImage seems to be slow, can anyone see why that might be so, am I initiating Xv and its buffers correctly?
(p4 2ghz, 512 MB ram, 32Mb nvideo gforce go, calling XvShmPutImage on a 512x256 YUV422 image takes around 4msecs)
etienne
/* * jMax * Copyright (C) 1994, 1995, 1998, 1999 by IRCAM-Centre Georges Pompidou, Paris, France. * * This program 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. * * See file LICENSE for further informations on licensing terms. * * This program 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. * * Based on Max/ISPW by Miller Puckette. * * Author: Etienne Deleflie based * */
#include <fts/fts.h>
#include <sys/time.h>
#include "../../vdsp/h/vdsp.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xv.h>
#include <X11/extensions/Xvlib.h>
#include <X11/extensions/XShm.h>
#include "split_channel.h"
#define vmonitor_VERSION "0.1"
#define DOUBLEW
#define YV12 0x32315659 //(YV12) PLANAR
#define I420 0x30323449 //(I420) PLANAR
#define YUY2 0x32595559 //(YUY2) PACKED
#define UYVY 0x59565955 //(UYVY) PACKED
extern int XShmQueryExtension(Display*);
extern int XShmGetEventBase(Display*);
extern XvImage *XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*);
static void vmonitor_drawTestPattern(XvImage *img);
static void vmonitor_send_drawable(fts_object_t *o);
/* ---------------------------------------------------------------------- */
/* The fts_videoport_t derived class */
/* ---------------------------------------------------------------------- */
typedef struct
{
fts_videoport_t head;
// X object attributes
Display *dpy;
Window window, _dw;
int _d, _w, _h;
int xv_port;
GC gc;
XvImage *yuv_image;
XShmSegmentInfo yuv_shminfo;
char constrained;
char centered;
char scaled;
// on off
int run;
// flag if output image proportions have changed (in which case we have to background colour)
int image_parameters_changed;
// the default background image
unsigned char *background_image;
// whether the X stuff has been called or not
char x_has_been_setup;
} vmonitor_t;
static void vmonitor_output( fts_word_t *argv)
{
// this next line is for timing exclusively
struct timeval tv_s, tv_e, result;
vmonitor_t *this = (vmonitor_t *)fts_word_get_ptr( argv+0);
long n = fts_word_get_int(argv + 1);
char *in_Y = (char *) fts_word_get_ptr( argv + 2);
char *in_UV = (char *) fts_word_get_ptr( argv + 3);
long i;
// (OLD VERSION) copy the current incoming data to the buffer
//v_byte_copy(in_Y, (this->yuv_image->data) + pdata[0].count*4, VTICK_SIZE*4);
// START TIMING CODE
gettimeofday(&tv_s, NULL);
// END TIMING CODE
/* begin SSE2 assembly function call */
// asm_interleave(in_Y, in_UV, this->yuv_image->data, /* size */);
/* begin SSE2 assembly function call */
n = n*4;
for(i=0; i<n; i+=2) //n is 1 pixel
{
this->yuv_image->data[i*2] = in_Y[i];
this->yuv_image->data[(i+1)*2] = in_Y[i+1];
}
for(i=0; i<n; i+=2) //n is 1 pixel
{
this->yuv_image->data[i*2 + 1] = in_UV[i];
this->yuv_image->data[(i+1)*2 + 1] = in_UV[i+1];
}
// START TIMING CODE
gettimeofday(&tv_e, NULL);
timeval_subtract (&result, &tv_e, &tv_s);
fprintf(stderr, "Took %d micro seconds to write data into Hardware buffer.\n", (&result)->tv_usec );
// END TIMING CODE
// START TIMING CODE
gettimeofday(&tv_s, NULL);
// END TIMING CODE
vmonitor_send_drawable(this);
// START TIMING CODE
gettimeofday(&tv_e, NULL);
timeval_subtract (&result, &tv_e, &tv_s);
fprintf(stderr, "Took %d micro seconds to send drawable to Xv.\n", (&result)->tv_usec );
// END TIMING CODE
//XFlush(this->dpy);
}
static void vmonitor_nullinput( fts_word_t *argv)
{
vmonitor_t *port = (vmonitor_t *)fts_word_get_ptr( argv+0);
int32 *out = (int32 *) fts_word_get_ptr( argv + 2);
//v_vec_const(out, 0x00FF0000, VTICK_SIZE);
}
////////////////////////////////////////////////////////////////
////////////// 2 ////////////////
////////////////////////////////////////////////////////////////
static void vmonitor_init( fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *att)
{
vmonitor_t *this = (vmonitor_t *)o;
fts_symbol_t which_display;
// variables necessary for checking x windows and the like
int screen, encodings, attributes, formats;
XSizeHints hint;
XSetWindowAttributes xswa;
XVisualInfo vinfo;
unsigned long mask;
XEvent event;
/** for shm */
int shmem_flag = 0;
int CompletionType;
/** for xv */
int ret, i, p, j;
unsigned int p_version, p_release,p_request_base, p_event_base, p_error_base;
int p_num_adaptors;
XvAdaptorInfo *ai;
XvEncodingInfo *ei;
XvAttribute *at;
XvImageFormatValues *fo;
/** for user defnied parameters */
fts_symbol_t first_arg; //geometry string
fts_symbol_t second_arg; //scalable or not
fts_symbol_t third_arg; //centered or not
fts_symbol_t fourth_arg; //scaled or not
fts_symbol_t fifth_arg; //name of window
char * window_name = "video monitor"; //name as string
/** for printing out the first image */
int drawable_width, drawable_height, drawable_origin_x, drawable_origin_y;
/** for window geometry */
char * mainGeometry="";
char defaultMainGeometry[13] = "";
struct coordinates { int x, y; };
struct coordinates total_size;
int x_negative = 0;
int y_negative = 0;
int result = 0;
int gravity = 0;
total_size.x = total_size.y = 54;
// initiate some of the basic variables
this->constrained = 0;
this->centered = 0;
this->scaled = 0;
this->xv_port = -1;
this->x_has_been_setup=0;
// the flag
this->image_parameters_changed = 0;
fts_videoport_init( &this->head);
// retrieve user defined variables (window size + scalable)
first_arg = fts_get_symbol_arg(ac, att, 1, 0);
if (first_arg)
{
mainGeometry = fts_symbol_name(first_arg);
}
second_arg = fts_get_symbol_arg(ac, att, 2, 0);
if (second_arg)
this->constrained = 1;
third_arg = fts_get_symbol_arg(ac, att, 3, 0);
if (third_arg)
this->centered = 1;
fourth_arg = fts_get_symbol_arg(ac, att, 4, 0);
if (fourth_arg)
this->scaled = 1;
fifth_arg = fts_get_symbol_arg(ac, att, 5, 0);
if (fifth_arg)
window_name = fts_symbol_name (fifth_arg);
// set up and test what is available for X
// can we open an X display ?
this->dpy = XOpenDisplay(NULL);
//this->dpy = XOpenDisplay(":1.0");
if (this->dpy == NULL)
{
post("Cannot open X Display.\n");
return;
}
screen = DefaultScreen(this->dpy);
/** find best display */
if (XMatchVisualInfo(this->dpy, screen, 24, TrueColor, &vinfo)) {post("vmonitor found 24bit TrueColor display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 16, TrueColor, &vinfo)) {post("vmonitor found 16bit TrueColor display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 15, TrueColor, &vinfo)) {post("vmonitor found 15bit TrueColor display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 8, PseudoColor, &vinfo)) {post("vmonitor found 8bit PseudoColor display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 8, GrayScale, &vinfo)) {post("vmonitor found 8bit GrayScale display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 8, StaticGray, &vinfo)) {post("vmonitor found 8bit StaticGray display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 1, StaticGray, &vinfo)) {post("vmonitor found 1bit StaticGray display\n");}
else { post("requires 16 bit display\n"); return ;}
CompletionType = -1;
/** hints **/ // window size and position.
if (!strlen(mainGeometry))
{
char width[4], height[4];
char by[] = "x";
char position[] = "+20+60";
sprintf (width, "%d",V_WIDTH);
sprintf (height, "%d",V_HEIGHT);
//post ("width is -%s-\n", width);
strcat (defaultMainGeometry, width);
strcat (defaultMainGeometry, by);
strcat (defaultMainGeometry, height);
strcat (defaultMainGeometry, position);
mainGeometry = defaultMainGeometry;
}
// set up and test what is available for X
// can we open an X display ?
this->dpy = XOpenDisplay(NULL);
//this->dpy = XOpenDisplay(":1.0");
if (this->dpy == NULL)
{
post("Cannot open X Display.\n");
return;
}
screen = DefaultScreen(this->dpy);
/** find best display */
if (XMatchVisualInfo(this->dpy, screen, 24, TrueColor, &vinfo)) {post("vmonitor found 24bit TrueColor display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 16, TrueColor, &vinfo)) {post("vmonitor found 16bit TrueColor display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 15, TrueColor, &vinfo)) {post("vmonitor found 15bit TrueColor display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 8, PseudoColor, &vinfo)) {post("vmonitor found 8bit PseudoColor display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 8, GrayScale, &vinfo)) {post("vmonitor found 8bit GrayScale display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 8, StaticGray, &vinfo)) {post("vmonitor found 8bit StaticGray display\n");}
else if (XMatchVisualInfo(this->dpy, screen, 1, StaticGray, &vinfo)) {post("vmonitor found 1bit StaticGray display\n");}
else { post("requires 16 bit display\n"); return ;}
CompletionType = -1;
/** hints **/ // window size and position.
// Check the user-specified size
result = XParseGeometry(mainGeometry,
&hint.x,
&hint.y,
&hint.width,
&hint.height);
if (result & WidthValue)
total_size.x = hint.width;
if (result & HeightValue)
total_size.y = hint.height;
if (result & XNegative)
x_negative = 1;
if (result & YNegative)
y_negative = 1;
hint.flags = USSize | USPosition;
hint.x = 0;
hint.y = 0;
hint.width = V_WIDTH;
hint.height = V_HEIGHT;
XWMGeometry(this->dpy, screen, mainGeometry, NULL, 0,
&hint, &hint.x, &hint.y,
&hint.width, &hint.height, &gravity);
/** take care of the gravity */
hint.win_gravity = StaticGravity;
hint.flags |= PWinGravity;
/** set some X window attributes **/
xswa.colormap = XCreateColormap(this->dpy, DefaultRootWindow(this->dpy), vinfo.visual, AllocNone);
xswa.event_mask = StructureNotifyMask | ExposureMask;
xswa.background_pixel = 0;
xswa.border_pixel = 0;
/** some mask business **/
mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
/** initialise the window **/
this->window = XCreateWindow( this->dpy, DefaultRootWindow(this->dpy),
hint.x, hint.y,
hint.width,
hint.height,
0, vinfo.depth,
InputOutput,
vinfo.visual,
mask, &xswa);
/** set the window manager hints */
XSetWMNormalHints(this->dpy, this->window, &hint);
/** a bit more about the window **/
XStoreName(this->dpy, this->window, window_name);
XSetIconName(this->dpy, this->window, window_name);
XSelectInput(this->dpy, this->window, StructureNotifyMask);
/** Map window */
XMapWindow(this->dpy, this->window);
/** Wait for map. (this opens an X window) */
do
{
XNextEvent(this->dpy, &event);
} while (event.type != MapNotify || event.xmap.event != this->window);
/** test that Shm is ready ?**/
if (XShmQueryExtension(this->dpy)) shmem_flag = 1;
if (!shmem_flag)
{
printf("no shmem available.\n");
return;
}
if (shmem_flag==1) CompletionType = XShmGetEventBase(this->dpy) + ShmCompletion;
/**--------------------------------- XV ------------------------------------*/
/** query Xvideo Extension */
ret = XvQueryExtension(this->dpy, &p_version, &p_release, &p_request_base, &p_event_base, &p_error_base);
if (ret != Success)
{
if (ret == XvBadExtension)
post ("XvBadExtension returned at XvQueryExtension.\n");
else if (ret == XvBadAlloc)
post ("XvBadAlloc returned at XvQueryExtension.\n");
else post ("other error happened at XvQueryExtension.\n");
}
/*
post("========================================\n");
post("XvQueryExtension returned the following:\n");
post("p_version : %u\n", p_version);
post("p_release : %u\n", p_release);
post("p_request_base : %u\n", p_request_base);
post("p_event_base : %u\n", p_event_base);
post("p_error_base : %u\n", p_error_base);
post("========================================\n");
*/
/** query Xvideo Adaptors */
ret = XvQueryAdaptors(this->dpy, DefaultRootWindow(this->dpy), &p_num_adaptors, &ai);
if (ret != Success)
{
if (ret == XvBadExtension)
post("XvBadExtension returned at XvQueryExtension.\n");
else if (ret == XvBadAlloc)
post("XvBadAlloc returned at XvQueryExtension.\n");
else
post("other error happaned at XvQueryAdaptors.\n");
}
// post ("%d xv adaptors available.\n", p_num_adaptors);
/** loop through all the adaptors */
for (i = 0; i < p_num_adaptors; i++)
{
this->xv_port = ai[i].base_id;
for (p = ai[i].base_id; p < ai[i].base_id+ai[i].num_ports; p++)
{
if (XvQueryEncodings(this->dpy, p, &encodings, &ei) != Success)
continue;
XvFreeEncodingInfo(ei);
at = XvQueryPortAttributes(this->dpy, p, &attributes);
if (at)
XFree(at);
fo = XvListImageFormats(this->dpy, p, &formats);
/* UNCOMMENT TO FIND OUT WHAT COLOUR MODELS THE GRAPHICS CARD CAN HANDLE
// to find out what colour models the card accepts
for (j = 0; j < formats; j++)
{
post(" 0x%x (%4.4s) %s\n",
fo[j].id,
(char *)&fo[j].id,
(fo[j].format == XvPacked) ? "packed" : "planar");
}
*/
if (fo)
XFree(fo);
}
}
if (p_num_adaptors > 0)
XvFreeAdaptorInfo(ai);
if (this->xv_port == -1)
{
post ("could not find any xv_ports ..... does your graphics card have overlay support?");
return;
}
this->gc = XCreateGC(this->dpy, this->window, 0, 0);
// create the Xv image
this->yuv_image = XvShmCreateImage(this->dpy, this->xv_port, YUY2 , 0, V_WIDTH, V_HEIGHT, &(this->yuv_shminfo));
this->yuv_shminfo.shmid = shmget(IPC_PRIVATE, this->yuv_image->data_size, IPC_CREAT | 0777);
this->yuv_shminfo.shmaddr = this->yuv_image->data = shmat(this->yuv_shminfo.shmid, 0, 0);
this->yuv_shminfo.readOnly = True;
XSync (this->dpy, False);
if (!XShmAttach(this->dpy, &(this->yuv_shminfo)))
{
post("XShmAttach failed !\n");
return;
}
// TODO: the background image needs to be an XV image
//
// allocate space for the background image that will be re-drawn any time the aspect ratio of the image changes.
// this image is the size of the X output window
this->background_image = (unsigned char *) fts_malloc( (size_t)(this->_w * this->_h * 2)*sizeof(unsigned char *));
// populate it with black (for now)
for (i = (this->_w * this->_h * 2) -1 ; i >= 0; i-- )
{
this->background_image[i] = 100;
}
//vmonitor_setup_X_window_geometry(this);
// print out a default image
vmonitor_drawTestPattern(this->yuv_image);
// set up the window geometry
vmonitor_send_drawable(this);
XFlush(this->dpy);
// object is on by default
this->run = 1;
// define the number of input channels (which is 2 in the case of YUV)
fts_videoport_set_output_function( (fts_videoport_t *)this, vmonitor_output);
fts_videoport_set_output_channels( (fts_videoport_t *)this, 2);
fts_videoport_set_input_channels( (fts_videoport_t *)this, 2);
fts_videoport_set_input_function( (fts_videoport_t *)this, vmonitor_nullinput);
}
/* CLEAN RESOURCES */
static void vmonitor_delete( fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at)
{
vmonitor_t *this = (vmonitor_t *)o;
free(this->background_image);
if (&(this->yuv_shminfo))
{
//fprintf(stderr, "cleaning, found some shminfo");
XShmDetach(this->dpy, &(this->yuv_shminfo));
XFree(this->yuv_image);
this->yuv_image = NULL;
shmdt( this->yuv_shminfo.shmaddr);
shmctl( this->yuv_shminfo.shmid, IPC_RMID, 0);
} else
XFree(this->yuv_image);
XCloseDisplay(this->dpy);
fts_videoport_delete( (fts_videoport_t *)o);
}
/* GET STATE (?) */
static void vmonitor_get_state( fts_daemon_action_t action, fts_object_t *o, fts_symbol_t property, fts_atom_t *value)
{
fts_set_object( value, o);
}
static void vmonitor_constrained( fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at)
{
vmonitor_t *this = (vmonitor_t *)o;
this->constrained = fts_get_int_arg( ac, at, 0, 0); //fts_get_number_int(at);
this->image_parameters_changed = 1;
}
static void vmonitor_centered( fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at)
{
vmonitor_t *this = (vmonitor_t *)o;
this->centered = fts_get_int_arg( ac, at, 0, 0); //fts_get_number_int(at);
this->image_parameters_changed = 1;
}
static void vmonitor_scaled( fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at)
{
vmonitor_t *this = (vmonitor_t *)o;
this->scaled = fts_get_int_arg( ac, at, 0, 0); //fts_get_number_int(at);
this->image_parameters_changed = 1;
}
////////////////////////////////////////////////////////////////
////////////// 1 ////////////////
////////////////////////////////////////////////////////////////
static fts_status_t vmonitor_instantiate(fts_class_t *cl, int ac, const fts_atom_t *at)
{
fts_symbol_t a[6];
a[0] = fts_s_symbol; /* class */
a[1] = fts_s_symbol; /* X11 params */
a[2] = fts_s_int; /* constrained or not */
a[3] = fts_s_int; /* cenetered or not */
a[4] = fts_s_int; /* scaled or not */
a[5] = fts_s_symbol; /* name of output window */
fts_class_init( cl, sizeof( vmonitor_t), 1, 0, 0);
// takes up to 5 arguments ......... but at least 2 are required
fts_method_define_optargs( cl, fts_SystemInlet, fts_s_init, vmonitor_init, 6, a, 1);
fts_method_define_varargs( cl, fts_SystemInlet, fts_s_delete, vmonitor_delete);
// user methods
a[0] = fts_t_int;
fts_method_define_optargs(cl, 0, fts_new_symbol("constrained"), vmonitor_constrained, 1, a, 1);
fts_method_define_optargs(cl, 0, fts_new_symbol("centered"), vmonitor_centered, 1, a, 1);
fts_method_define_optargs(cl, 0, fts_new_symbol("scaled"), vmonitor_scaled, 1, a, 1);
fts_class_add_daemon ( cl, obj_property_get, fts_s_state, vmonitor_get_state);
/*
* NOTE: there are no vdsp inlets decalred, because this cass uses a metaclass (is that what they are called ?)
*
*/
return fts_Success;
}
static void vmonitor_drawTestPattern(XvImage *img)
{
int i,j, n;
int unit, num_in_width;
int last_width, last_height;
n = V_SIZE*2;
for(i=0; i<n; i+=4) //n is 1 pixel
{
img->data[i] = 0; // Y1
img->data[i+2] = 255; // Y2
img->data[i+1] = 100; // U
img->data[i+3] = 100; // V
}
}
static void vmonitor_send_drawable(fts_object_t *o)
{
vmonitor_t *this = (vmonitor_t *)o;
int drawable_width, drawable_height, drawable_origin_x, drawable_origin_y;
XGetGeometry(this->dpy, this->window, &(this->_dw), &(this->_d), &(this->_d), &(this->_w), &(this->_h), &(this->_d), &(this->_d));
if (this->image_parameters_changed)
{
// TODO: if the image parameters change, make screen go back to black, to avoid part of the image lying around.
this->image_parameters_changed = o;
}
// now work out what is the drawable area within the X window
if (this->scaled)
{
if (this->constrained)
{
// check to see if the shortest side is length or height
float proport_w = (float)this->_w / this->_h;
float proport_v = (float)V_WIDTH / V_HEIGHT;
if (proport_w < proport_v)
{
// if we get here then the window width is skinny.... we have to scale down height
drawable_width = this->_w;
drawable_height = (rint)(drawable_width / proport_v);
// position
if (this->centered)
{
drawable_origin_x = 0;
drawable_origin_y = (this->_h - drawable_height)/2;
} else
drawable_origin_x = drawable_origin_y = 0;
}
else
{
// if we get here then the window height is skinny
drawable_height = this->_h;
drawable_width = (int)(drawable_height * proport_v);
// position
if (this->centered)
{
drawable_origin_y = 0;
drawable_origin_x = (this->_w - drawable_width)/2;
} else
drawable_origin_x = drawable_origin_y = 0;
}
}
else
{
// if we get here then we are scaled but not constrained.
drawable_width = this->_w;
drawable_height = this->_h;
drawable_origin_x = drawable_origin_y = 0;
}
}
else
{
// if we get here then we are not constrained
drawable_width = V_WIDTH;
drawable_height = V_HEIGHT;
if (this->centered)
{
drawable_origin_x = (this->_w - V_WIDTH)/2;
drawable_origin_y = (this->_h - V_HEIGHT)/2;
}
else
{
drawable_origin_x = 0;
drawable_origin_y = 0;
}
}
XvShmPutImage(
this->dpy //The connection to the X-Server
, this->xv_port //The port id of a port on an XvImage capable adaptor
, this->window //The target drawable
, this->gc //the graphics context specifying the clip mask to use, if any.
, this->yuv_image //A pointer to the XvImage to be displayed
, 0, 0 //The portion of the XvImage to be displayed.
, this->yuv_image->width
, this->yuv_image->height
, drawable_origin_x //The portion of the destination drawable to be filled by the image.
, drawable_origin_y
, drawable_width, drawable_height
, True); //Indicates whether or not an XShmCompletionEvent should be sent.
}
void vmonitor_config( void)
{
fts_class_install( fts_new_symbol("monitor*"), vmonitor_instantiate);
}
