mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-26 18:20:44 +00:00
e135961e1e
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1017>
1194 lines
35 KiB
C
1194 lines
35 KiB
C
/* GStreamer
|
|
* Copyright (C) <2005> Julien Moutte <julien@moutte.net>
|
|
* <2009>,<2010> Stefan Kost <stefan.kost@nokia.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/* for developers: there are two useful tools : xvinfo and xvattr */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
/* Object header */
|
|
#include "xvcontext.h"
|
|
|
|
/* Debugging category */
|
|
#include <gst/gstinfo.h>
|
|
|
|
/* for XkbKeycodeToKeysym */
|
|
#include <X11/XKBlib.h>
|
|
|
|
GST_DEBUG_CATEGORY_EXTERN (gst_debug_xv_context);
|
|
#define GST_CAT_DEFAULT gst_debug_xv_context
|
|
|
|
void
|
|
gst_xvcontext_config_clear (GstXvContextConfig * config)
|
|
{
|
|
if (config->display_name) {
|
|
g_free (config->display_name);
|
|
config->display_name = NULL;
|
|
}
|
|
config->adaptor_nr = -1;
|
|
}
|
|
|
|
GST_DEFINE_MINI_OBJECT_TYPE (GstXvContext, gst_xvcontext);
|
|
|
|
typedef struct
|
|
{
|
|
unsigned long flags;
|
|
unsigned long functions;
|
|
unsigned long decorations;
|
|
long input_mode;
|
|
unsigned long status;
|
|
}
|
|
MotifWmHints, MwmHints;
|
|
|
|
#define MWM_HINTS_DECORATIONS (1L << 1)
|
|
|
|
|
|
static void
|
|
gst_lookup_xv_port_from_adaptor (GstXvContext * context,
|
|
XvAdaptorInfo * adaptors, guint adaptor_nr)
|
|
{
|
|
gint j;
|
|
gint res;
|
|
|
|
/* Do we support XvImageMask ? */
|
|
if (!(adaptors[adaptor_nr].type & XvImageMask)) {
|
|
GST_DEBUG ("XV Adaptor %s has no support for XvImageMask",
|
|
adaptors[adaptor_nr].name);
|
|
return;
|
|
}
|
|
|
|
/* We found such an adaptor, looking for an available port */
|
|
for (j = 0; j < adaptors[adaptor_nr].num_ports && !context->xv_port_id; j++) {
|
|
/* We try to grab the port */
|
|
res = XvGrabPort (context->disp, adaptors[adaptor_nr].base_id + j, 0);
|
|
if (Success == res) {
|
|
context->xv_port_id = adaptors[adaptor_nr].base_id + j;
|
|
GST_DEBUG ("XV Adaptor %s with %ld ports", adaptors[adaptor_nr].name,
|
|
adaptors[adaptor_nr].num_ports);
|
|
} else {
|
|
GST_DEBUG ("GrabPort %d for XV Adaptor %s failed: %d", j,
|
|
adaptors[adaptor_nr].name, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This function generates a caps with all supported format by the first
|
|
Xv grabable port we find. We store each one of the supported formats in a
|
|
format list and append the format to a newly created caps that we return
|
|
If this function does not return NULL because of an error, it also grabs
|
|
the port via XvGrabPort */
|
|
static GstCaps *
|
|
gst_xvcontext_get_xv_support (GstXvContext * context,
|
|
const GstXvContextConfig * config, GError ** error)
|
|
{
|
|
gint i;
|
|
XvAdaptorInfo *adaptors;
|
|
gint nb_formats;
|
|
XvImageFormatValues *formats = NULL;
|
|
guint nb_encodings;
|
|
XvEncodingInfo *encodings = NULL;
|
|
gulong max_w = G_MAXINT, max_h = G_MAXINT;
|
|
GstCaps *caps = NULL;
|
|
GstCaps *rgb_caps = NULL;
|
|
|
|
g_return_val_if_fail (context != NULL, NULL);
|
|
|
|
/* First let's check that XVideo extension is available */
|
|
if (!XQueryExtension (context->disp, "XVideo", &i, &i, &i))
|
|
goto no_xv;
|
|
|
|
/* Then we get adaptors list */
|
|
if (Success != XvQueryAdaptors (context->disp, context->root,
|
|
&context->nb_adaptors, &adaptors))
|
|
goto no_adaptors;
|
|
|
|
context->xv_port_id = 0;
|
|
|
|
GST_DEBUG ("Found %u XV adaptor(s)", context->nb_adaptors);
|
|
|
|
context->adaptors =
|
|
(gchar **) g_malloc0 (context->nb_adaptors * sizeof (gchar *));
|
|
|
|
/* Now fill up our adaptor name array */
|
|
for (i = 0; i < context->nb_adaptors; i++) {
|
|
context->adaptors[i] = g_strdup (adaptors[i].name);
|
|
}
|
|
|
|
if (config->adaptor_nr != -1 && config->adaptor_nr < context->nb_adaptors) {
|
|
/* Find xv port from user defined adaptor */
|
|
gst_lookup_xv_port_from_adaptor (context, adaptors, config->adaptor_nr);
|
|
}
|
|
|
|
if (!context->xv_port_id) {
|
|
/* Now search for an adaptor that supports XvImageMask */
|
|
for (i = 0; i < context->nb_adaptors && !context->xv_port_id; i++) {
|
|
gst_lookup_xv_port_from_adaptor (context, adaptors, i);
|
|
context->adaptor_nr = i;
|
|
}
|
|
}
|
|
|
|
XvFreeAdaptorInfo (adaptors);
|
|
|
|
if (!context->xv_port_id)
|
|
goto no_ports;
|
|
|
|
/* Set XV_AUTOPAINT_COLORKEY and XV_DOUBLE_BUFFER and XV_COLORKEY */
|
|
{
|
|
int count, todo = 4;
|
|
XvAttribute *const attr = XvQueryPortAttributes (context->disp,
|
|
context->xv_port_id, &count);
|
|
static const char autopaint[] = "XV_AUTOPAINT_COLORKEY";
|
|
static const char dbl_buffer[] = "XV_DOUBLE_BUFFER";
|
|
static const char colorkey[] = "XV_COLORKEY";
|
|
static const char iturbt709[] = "XV_ITURBT_709";
|
|
static const char *xv_colorspace = "XV_COLORSPACE";
|
|
|
|
GST_DEBUG ("Checking %d Xv port attributes", count);
|
|
|
|
context->have_autopaint_colorkey = FALSE;
|
|
context->have_double_buffer = FALSE;
|
|
context->have_colorkey = FALSE;
|
|
context->have_iturbt709 = FALSE;
|
|
context->have_xvcolorspace = FALSE;
|
|
|
|
for (i = 0; ((i < count) && todo); i++) {
|
|
GST_DEBUG ("Got attribute %s", attr[i].name);
|
|
|
|
if (!strcmp (attr[i].name, autopaint)) {
|
|
const Atom atom = XInternAtom (context->disp, autopaint, False);
|
|
|
|
/* turn on autopaint colorkey */
|
|
XvSetPortAttribute (context->disp, context->xv_port_id, atom,
|
|
(config->autopaint_colorkey ? 1 : 0));
|
|
todo--;
|
|
context->have_autopaint_colorkey = TRUE;
|
|
} else if (!strcmp (attr[i].name, dbl_buffer)) {
|
|
const Atom atom = XInternAtom (context->disp, dbl_buffer, False);
|
|
|
|
XvSetPortAttribute (context->disp, context->xv_port_id, atom,
|
|
(config->double_buffer ? 1 : 0));
|
|
todo--;
|
|
context->have_double_buffer = TRUE;
|
|
} else if (!strcmp (attr[i].name, colorkey)) {
|
|
/* Set the colorkey, default is something that is dark but hopefully
|
|
* won't randomly appear on the screen elsewhere (ie not black or greys)
|
|
* can be overridden by setting "colorkey" property
|
|
*/
|
|
const Atom atom = XInternAtom (context->disp, colorkey, False);
|
|
guint32 ckey = 0;
|
|
gboolean set_attr = TRUE;
|
|
guint cr, cg, cb;
|
|
|
|
/* set a colorkey in the right format RGB565/RGB888
|
|
* We only handle these 2 cases, because they're the only types of
|
|
* devices we've encountered. If we don't recognise it, leave it alone
|
|
*/
|
|
cr = (config->colorkey >> 16);
|
|
cg = (config->colorkey >> 8) & 0xFF;
|
|
cb = (config->colorkey) & 0xFF;
|
|
switch (context->depth) {
|
|
case 16: /* RGB 565 */
|
|
cr >>= 3;
|
|
cg >>= 2;
|
|
cb >>= 3;
|
|
ckey = (cr << 11) | (cg << 5) | cb;
|
|
break;
|
|
case 24:
|
|
case 32: /* RGB 888 / ARGB 8888 */
|
|
ckey = (cr << 16) | (cg << 8) | cb;
|
|
break;
|
|
default:
|
|
GST_DEBUG ("Unknown bit depth %d for Xv Colorkey - not adjusting",
|
|
context->depth);
|
|
set_attr = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (set_attr) {
|
|
ckey = CLAMP (ckey, (guint32) attr[i].min_value,
|
|
(guint32) attr[i].max_value);
|
|
GST_LOG ("Setting color key for display depth %d to 0x%x",
|
|
context->depth, ckey);
|
|
|
|
XvSetPortAttribute (context->disp, context->xv_port_id, atom,
|
|
(gint) ckey);
|
|
}
|
|
todo--;
|
|
context->have_colorkey = TRUE;
|
|
} else if (!strcmp (attr[i].name, iturbt709)) {
|
|
todo--;
|
|
context->have_iturbt709 = TRUE;
|
|
} else if (!strcmp (attr[i].name, xv_colorspace)) {
|
|
context->have_xvcolorspace = TRUE;
|
|
todo--;
|
|
}
|
|
}
|
|
|
|
XFree (attr);
|
|
}
|
|
|
|
/* Get the list of encodings supported by the adapter and look for the
|
|
* XV_IMAGE encoding so we can determine the maximum width and height
|
|
* supported */
|
|
XvQueryEncodings (context->disp, context->xv_port_id, &nb_encodings,
|
|
&encodings);
|
|
|
|
for (i = 0; i < nb_encodings; i++) {
|
|
GST_LOG ("Encoding %d, name %s, max wxh %lux%lu rate %d/%d",
|
|
i, encodings[i].name, encodings[i].width, encodings[i].height,
|
|
encodings[i].rate.numerator, encodings[i].rate.denominator);
|
|
if (strcmp (encodings[i].name, "XV_IMAGE") == 0) {
|
|
max_w = encodings[i].width;
|
|
max_h = encodings[i].height;
|
|
}
|
|
}
|
|
|
|
XvFreeEncodingInfo (encodings);
|
|
|
|
/* We get all image formats supported by our port */
|
|
formats = XvListImageFormats (context->disp,
|
|
context->xv_port_id, &nb_formats);
|
|
caps = gst_caps_new_empty ();
|
|
for (i = 0; i < nb_formats; i++) {
|
|
GstCaps *format_caps = NULL;
|
|
gboolean is_rgb_format = FALSE;
|
|
GstVideoFormat vformat;
|
|
|
|
/* We set the image format of the context to an existing one. This
|
|
is just some valid image format for making our xshm calls check before
|
|
caps negotiation really happens. */
|
|
context->im_format = formats[i].id;
|
|
|
|
switch (formats[i].type) {
|
|
case XvRGB:
|
|
{
|
|
XvImageFormatValues *fmt = &(formats[i]);
|
|
gint endianness;
|
|
|
|
endianness =
|
|
(fmt->byte_order == LSBFirst ? G_LITTLE_ENDIAN : G_BIG_ENDIAN);
|
|
|
|
vformat = gst_video_format_from_masks (fmt->depth, fmt->bits_per_pixel,
|
|
endianness, fmt->red_mask, fmt->green_mask, fmt->blue_mask, 0);
|
|
if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
|
|
break;
|
|
|
|
format_caps = gst_caps_new_simple ("video/x-raw",
|
|
"format", G_TYPE_STRING, gst_video_format_to_string (vformat),
|
|
"width", GST_TYPE_INT_RANGE, 1, max_w,
|
|
"height", GST_TYPE_INT_RANGE, 1, max_h,
|
|
"framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
|
|
|
|
is_rgb_format = TRUE;
|
|
break;
|
|
}
|
|
case XvYUV:
|
|
{
|
|
vformat = gst_video_format_from_fourcc (formats[i].id);
|
|
if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
|
|
break;
|
|
|
|
format_caps = gst_caps_new_simple ("video/x-raw",
|
|
"format", G_TYPE_STRING, gst_video_format_to_string (vformat),
|
|
"width", GST_TYPE_INT_RANGE, 1, max_w,
|
|
"height", GST_TYPE_INT_RANGE, 1, max_h,
|
|
"framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
|
|
break;
|
|
}
|
|
default:
|
|
vformat = GST_VIDEO_FORMAT_UNKNOWN;
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
if (format_caps) {
|
|
GstXvImageFormat *format = NULL;
|
|
|
|
format = g_new0 (GstXvImageFormat, 1);
|
|
if (format) {
|
|
format->format = formats[i].id;
|
|
format->vformat = vformat;
|
|
format->caps = gst_caps_copy (format_caps);
|
|
context->formats_list = g_list_append (context->formats_list, format);
|
|
}
|
|
|
|
if (is_rgb_format) {
|
|
if (rgb_caps == NULL)
|
|
rgb_caps = format_caps;
|
|
else
|
|
gst_caps_append (rgb_caps, format_caps);
|
|
} else
|
|
gst_caps_append (caps, format_caps);
|
|
}
|
|
}
|
|
|
|
/* Collected all caps into either the caps or rgb_caps structures.
|
|
* Append rgb_caps on the end of YUV, so that YUV is always preferred */
|
|
if (rgb_caps)
|
|
gst_caps_append (caps, rgb_caps);
|
|
|
|
if (formats)
|
|
XFree (formats);
|
|
|
|
GST_DEBUG ("Generated the following caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
if (gst_caps_is_empty (caps))
|
|
goto no_caps;
|
|
|
|
return caps;
|
|
|
|
/* ERRORS */
|
|
no_xv:
|
|
{
|
|
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
|
|
("XVideo extension is not available"));
|
|
return NULL;
|
|
}
|
|
no_adaptors:
|
|
{
|
|
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
|
|
("Failed getting XV adaptors list"));
|
|
return NULL;
|
|
}
|
|
no_ports:
|
|
{
|
|
context->adaptor_nr = -1;
|
|
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_BUSY,
|
|
("No Xv Port available"));
|
|
return NULL;
|
|
}
|
|
no_caps:
|
|
{
|
|
gst_caps_unref (caps);
|
|
g_set_error (error, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE,
|
|
("No supported format found"));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* This function calculates the pixel aspect ratio based on the properties
|
|
* in the context structure and stores it there. */
|
|
static void
|
|
gst_xvcontext_calculate_pixel_aspect_ratio (GstXvContext * context)
|
|
{
|
|
static const gint par[][2] = {
|
|
{1, 1}, /* regular screen */
|
|
{16, 15}, /* PAL TV */
|
|
{11, 10}, /* 525 line Rec.601 video */
|
|
{54, 59}, /* 625 line Rec.601 video */
|
|
{64, 45}, /* 1280x1024 on 16:9 display */
|
|
{5, 3}, /* 1280x1024 on 4:3 display */
|
|
{4, 3} /* 800x600 on 16:9 display */
|
|
};
|
|
gint i;
|
|
gint index;
|
|
gdouble ratio;
|
|
gdouble delta;
|
|
|
|
#define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
|
|
|
|
/* first calculate the "real" ratio based on the X values;
|
|
* which is the "physical" w/h divided by the w/h in pixels of the display */
|
|
ratio = (gdouble) (context->widthmm * context->height)
|
|
/ (context->heightmm * context->width);
|
|
|
|
/* DirectFB's X in 720x576 reports the physical dimensions wrong, so
|
|
* override here */
|
|
if (context->width == 720 && context->height == 576) {
|
|
ratio = 4.0 * 576 / (3.0 * 720);
|
|
}
|
|
GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
|
|
|
|
/* now find the one from par[][2] with the lowest delta to the real one */
|
|
delta = DELTA (0);
|
|
index = 0;
|
|
|
|
for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
|
|
gdouble this_delta = DELTA (i);
|
|
|
|
if (this_delta < delta) {
|
|
index = i;
|
|
delta = this_delta;
|
|
}
|
|
}
|
|
|
|
GST_DEBUG ("Decided on index %d (%d/%d)", index,
|
|
par[index][0], par[index][1]);
|
|
|
|
g_free (context->par);
|
|
context->par = g_new0 (GValue, 1);
|
|
g_value_init (context->par, GST_TYPE_FRACTION);
|
|
gst_value_set_fraction (context->par, par[index][0], par[index][1]);
|
|
GST_DEBUG ("set context PAR to %d/%d",
|
|
gst_value_get_fraction_numerator (context->par),
|
|
gst_value_get_fraction_denominator (context->par));
|
|
}
|
|
|
|
#ifdef HAVE_XSHM
|
|
/* X11 stuff */
|
|
static gboolean error_caught = FALSE;
|
|
|
|
static int
|
|
gst_xvimage_handle_xerror (Display * display, XErrorEvent * xevent)
|
|
{
|
|
char error_msg[1024];
|
|
|
|
XGetErrorText (display, xevent->error_code, error_msg, 1024);
|
|
GST_DEBUG ("xvimage triggered an XError. error: %s", error_msg);
|
|
error_caught = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
/* This function checks that it is actually really possible to create an image
|
|
using XShm */
|
|
static gboolean
|
|
gst_xvcontext_check_xshm_calls (GstXvContext * context)
|
|
{
|
|
XvImage *xvimage;
|
|
XShmSegmentInfo SHMInfo;
|
|
size_t size;
|
|
int (*handler) (Display *, XErrorEvent *);
|
|
gboolean result = FALSE;
|
|
gboolean did_attach = FALSE;
|
|
|
|
g_return_val_if_fail (context != NULL, FALSE);
|
|
|
|
/* Sync to ensure any older errors are already processed */
|
|
XSync (context->disp, FALSE);
|
|
|
|
/* Set defaults so we don't free these later unnecessarily */
|
|
SHMInfo.shmaddr = ((void *) -1);
|
|
SHMInfo.shmid = -1;
|
|
|
|
/* Setting an error handler to catch failure */
|
|
error_caught = FALSE;
|
|
handler = XSetErrorHandler (gst_xvimage_handle_xerror);
|
|
|
|
/* Trying to create a 1x1 picture */
|
|
GST_DEBUG ("XvShmCreateImage of 1x1");
|
|
xvimage = XvShmCreateImage (context->disp, context->xv_port_id,
|
|
context->im_format, NULL, 1, 1, &SHMInfo);
|
|
|
|
/* Might cause an error, sync to ensure it is noticed */
|
|
XSync (context->disp, FALSE);
|
|
if (!xvimage || error_caught) {
|
|
GST_WARNING ("could not XvShmCreateImage a 1x1 image");
|
|
goto beach;
|
|
}
|
|
size = xvimage->data_size;
|
|
SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777);
|
|
if (SHMInfo.shmid == -1) {
|
|
GST_WARNING ("could not get shared memory of %" G_GSIZE_FORMAT " bytes",
|
|
size);
|
|
goto beach;
|
|
}
|
|
|
|
SHMInfo.shmaddr = shmat (SHMInfo.shmid, NULL, 0);
|
|
if (SHMInfo.shmaddr == ((void *) -1)) {
|
|
GST_WARNING ("Failed to shmat: %s", g_strerror (errno));
|
|
/* Clean up the shared memory segment */
|
|
shmctl (SHMInfo.shmid, IPC_RMID, NULL);
|
|
goto beach;
|
|
}
|
|
|
|
xvimage->data = SHMInfo.shmaddr;
|
|
SHMInfo.readOnly = FALSE;
|
|
|
|
if (XShmAttach (context->disp, &SHMInfo) == 0) {
|
|
GST_WARNING ("Failed to XShmAttach");
|
|
/* Clean up the shared memory segment */
|
|
shmctl (SHMInfo.shmid, IPC_RMID, NULL);
|
|
goto beach;
|
|
}
|
|
|
|
/* Sync to ensure we see any errors we caused */
|
|
XSync (context->disp, FALSE);
|
|
|
|
/* Delete the shared memory segment as soon as everyone is attached.
|
|
* This way, it will be deleted as soon as we detach later, and not
|
|
* leaked if we crash. */
|
|
shmctl (SHMInfo.shmid, IPC_RMID, NULL);
|
|
|
|
if (!error_caught) {
|
|
GST_DEBUG ("XServer ShmAttached to 0x%x, id 0x%lx", SHMInfo.shmid,
|
|
SHMInfo.shmseg);
|
|
|
|
did_attach = TRUE;
|
|
/* store whether we succeeded in result */
|
|
result = TRUE;
|
|
} else {
|
|
GST_WARNING ("MIT-SHM extension check failed at XShmAttach. "
|
|
"Not using shared memory.");
|
|
}
|
|
|
|
beach:
|
|
/* Sync to ensure we swallow any errors we caused and reset error_caught */
|
|
XSync (context->disp, FALSE);
|
|
|
|
error_caught = FALSE;
|
|
XSetErrorHandler (handler);
|
|
|
|
if (did_attach) {
|
|
GST_DEBUG ("XServer ShmDetaching from 0x%x id 0x%lx",
|
|
SHMInfo.shmid, SHMInfo.shmseg);
|
|
XShmDetach (context->disp, &SHMInfo);
|
|
XSync (context->disp, FALSE);
|
|
}
|
|
if (SHMInfo.shmaddr != ((void *) -1))
|
|
shmdt (SHMInfo.shmaddr);
|
|
if (xvimage)
|
|
XFree (xvimage);
|
|
return result;
|
|
}
|
|
#endif /* HAVE_XSHM */
|
|
|
|
static GstXvContext *
|
|
gst_xvcontext_copy (GstXvContext * context)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
gst_xvcontext_free (GstXvContext * context)
|
|
{
|
|
GList *formats_list, *channels_list;
|
|
gint i = 0;
|
|
|
|
GST_LOG ("free %p", context);
|
|
|
|
formats_list = context->formats_list;
|
|
|
|
while (formats_list) {
|
|
GstXvImageFormat *format = formats_list->data;
|
|
|
|
gst_caps_unref (format->caps);
|
|
g_free (format);
|
|
formats_list = g_list_next (formats_list);
|
|
}
|
|
|
|
if (context->formats_list)
|
|
g_list_free (context->formats_list);
|
|
|
|
channels_list = context->channels_list;
|
|
|
|
while (channels_list) {
|
|
GstColorBalanceChannel *channel = channels_list->data;
|
|
|
|
g_object_unref (channel);
|
|
channels_list = g_list_next (channels_list);
|
|
}
|
|
|
|
if (context->channels_list)
|
|
g_list_free (context->channels_list);
|
|
|
|
if (context->caps)
|
|
gst_caps_unref (context->caps);
|
|
if (context->last_caps)
|
|
gst_caps_unref (context->last_caps);
|
|
|
|
for (i = 0; i < context->nb_adaptors; i++) {
|
|
g_free (context->adaptors[i]);
|
|
}
|
|
|
|
g_free (context->adaptors);
|
|
|
|
g_free (context->par);
|
|
|
|
GST_DEBUG ("Closing display and freeing X Context");
|
|
|
|
if (context->xv_port_id)
|
|
XvUngrabPort (context->disp, context->xv_port_id, 0);
|
|
|
|
if (context->disp)
|
|
XCloseDisplay (context->disp);
|
|
|
|
g_mutex_clear (&context->lock);
|
|
|
|
g_slice_free1 (sizeof (GstXvContext), context);
|
|
}
|
|
|
|
|
|
/* This function gets the X Display and global info about it. Everything is
|
|
stored in our object and will be cleaned when the object is disposed. Note
|
|
here that caps for supported format are generated without any window or
|
|
image creation */
|
|
GstXvContext *
|
|
gst_xvcontext_new (GstXvContextConfig * config, GError ** error)
|
|
{
|
|
GstXvContext *context = NULL;
|
|
XPixmapFormatValues *px_formats = NULL;
|
|
gint nb_formats = 0, i, j, N_attr;
|
|
XvAttribute *xv_attr;
|
|
Atom prop_atom;
|
|
const char *channels[4] = { "XV_HUE", "XV_SATURATION",
|
|
"XV_BRIGHTNESS", "XV_CONTRAST"
|
|
};
|
|
int opcode, event, err;
|
|
int major = XkbMajorVersion;
|
|
int minor = XkbMinorVersion;
|
|
|
|
g_return_val_if_fail (config != NULL, NULL);
|
|
|
|
context = g_slice_new0 (GstXvContext);
|
|
|
|
gst_mini_object_init (GST_MINI_OBJECT_CAST (context), 0,
|
|
gst_xvcontext_get_type (),
|
|
(GstMiniObjectCopyFunction) gst_xvcontext_copy,
|
|
(GstMiniObjectDisposeFunction) NULL,
|
|
(GstMiniObjectFreeFunction) gst_xvcontext_free);
|
|
|
|
g_mutex_init (&context->lock);
|
|
context->im_format = 0;
|
|
context->adaptor_nr = -1;
|
|
|
|
if (!(context->disp = XOpenDisplay (config->display_name)))
|
|
goto no_display;
|
|
|
|
context->screen = DefaultScreenOfDisplay (context->disp);
|
|
context->screen_num = DefaultScreen (context->disp);
|
|
context->visual = DefaultVisual (context->disp, context->screen_num);
|
|
context->root = DefaultRootWindow (context->disp);
|
|
context->white = XWhitePixel (context->disp, context->screen_num);
|
|
context->black = XBlackPixel (context->disp, context->screen_num);
|
|
context->depth = DefaultDepthOfScreen (context->screen);
|
|
|
|
context->width = DisplayWidth (context->disp, context->screen_num);
|
|
context->height = DisplayHeight (context->disp, context->screen_num);
|
|
context->widthmm = DisplayWidthMM (context->disp, context->screen_num);
|
|
context->heightmm = DisplayHeightMM (context->disp, context->screen_num);
|
|
|
|
GST_DEBUG ("X reports %dx%d pixels and %d mm x %d mm",
|
|
context->width, context->height, context->widthmm, context->heightmm);
|
|
|
|
gst_xvcontext_calculate_pixel_aspect_ratio (context);
|
|
/* We get supported pixmap formats at supported depth */
|
|
px_formats = XListPixmapFormats (context->disp, &nb_formats);
|
|
|
|
if (!px_formats)
|
|
goto no_pixel_formats;
|
|
|
|
/* We get bpp value corresponding to our running depth */
|
|
for (i = 0; i < nb_formats; i++) {
|
|
if (px_formats[i].depth == context->depth)
|
|
context->bpp = px_formats[i].bits_per_pixel;
|
|
}
|
|
|
|
XFree (px_formats);
|
|
|
|
context->endianness =
|
|
(ImageByteOrder (context->disp) ==
|
|
LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
|
|
|
|
/* our caps system handles 24/32bpp RGB as big-endian. */
|
|
if ((context->bpp == 24 || context->bpp == 32) &&
|
|
context->endianness == G_LITTLE_ENDIAN) {
|
|
context->endianness = G_BIG_ENDIAN;
|
|
context->visual->red_mask = GUINT32_TO_BE (context->visual->red_mask);
|
|
context->visual->green_mask = GUINT32_TO_BE (context->visual->green_mask);
|
|
context->visual->blue_mask = GUINT32_TO_BE (context->visual->blue_mask);
|
|
if (context->bpp == 24) {
|
|
context->visual->red_mask >>= 8;
|
|
context->visual->green_mask >>= 8;
|
|
context->visual->blue_mask >>= 8;
|
|
}
|
|
}
|
|
|
|
if (!(context->caps = gst_xvcontext_get_xv_support (context, config, error)))
|
|
goto no_caps;
|
|
|
|
/* Search for XShm extension support */
|
|
#ifdef HAVE_XSHM
|
|
if (XShmQueryExtension (context->disp) &&
|
|
gst_xvcontext_check_xshm_calls (context)) {
|
|
context->use_xshm = TRUE;
|
|
GST_DEBUG ("xvimagesink is using XShm extension");
|
|
} else
|
|
#endif /* HAVE_XSHM */
|
|
{
|
|
context->use_xshm = FALSE;
|
|
GST_DEBUG ("xvimagesink is not using XShm extension");
|
|
}
|
|
if (XkbQueryExtension (context->disp, &opcode, &event, &err, &major, &minor)) {
|
|
context->use_xkb = TRUE;
|
|
GST_DEBUG ("xvimagesink is using Xkb extension");
|
|
} else {
|
|
context->use_xkb = FALSE;
|
|
GST_DEBUG ("xvimagesink is not using Xkb extension");
|
|
}
|
|
|
|
xv_attr = XvQueryPortAttributes (context->disp, context->xv_port_id, &N_attr);
|
|
|
|
/* Generate the channels list */
|
|
for (i = 0; i < (sizeof (channels) / sizeof (char *)); i++) {
|
|
XvAttribute *matching_attr = NULL;
|
|
|
|
/* Retrieve the property atom if it exists. If it doesn't exist,
|
|
* the attribute itself must not either, so we can skip */
|
|
prop_atom = XInternAtom (context->disp, channels[i], True);
|
|
if (prop_atom == None)
|
|
continue;
|
|
|
|
if (xv_attr != NULL) {
|
|
for (j = 0; j < N_attr && matching_attr == NULL; ++j)
|
|
if (!g_ascii_strcasecmp (channels[i], xv_attr[j].name))
|
|
matching_attr = xv_attr + j;
|
|
}
|
|
|
|
if (matching_attr) {
|
|
GstColorBalanceChannel *channel;
|
|
|
|
channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
|
|
channel->label = g_strdup (channels[i]);
|
|
channel->min_value = matching_attr->min_value;
|
|
channel->max_value = matching_attr->max_value;
|
|
|
|
context->channels_list = g_list_append (context->channels_list, channel);
|
|
|
|
/* If the colorbalance settings have not been touched we get Xv values
|
|
as defaults and update our internal variables */
|
|
if (!config->cb_changed) {
|
|
gint val;
|
|
|
|
XvGetPortAttribute (context->disp, context->xv_port_id,
|
|
prop_atom, &val);
|
|
/* Normalize val to [-1000, 1000] */
|
|
val = floor (0.5 + -1000 + 2000 * (val - channel->min_value) /
|
|
(double) (channel->max_value - channel->min_value));
|
|
|
|
if (!g_ascii_strcasecmp (channels[i], "XV_HUE"))
|
|
config->hue = val;
|
|
else if (!g_ascii_strcasecmp (channels[i], "XV_SATURATION"))
|
|
config->saturation = val;
|
|
else if (!g_ascii_strcasecmp (channels[i], "XV_BRIGHTNESS"))
|
|
config->brightness = val;
|
|
else if (!g_ascii_strcasecmp (channels[i], "XV_CONTRAST"))
|
|
config->contrast = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (xv_attr)
|
|
XFree (xv_attr);
|
|
|
|
return context;
|
|
|
|
/* ERRORS */
|
|
no_display:
|
|
{
|
|
gst_xvcontext_unref (context);
|
|
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_WRITE,
|
|
"Could not open display %s", config->display_name);
|
|
return NULL;
|
|
}
|
|
no_pixel_formats:
|
|
{
|
|
gst_xvcontext_unref (context);
|
|
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_SETTINGS,
|
|
("Could not get pixel formats"));
|
|
return NULL;
|
|
}
|
|
no_caps:
|
|
{
|
|
gst_xvcontext_unref (context);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_xvcontext_set_synchronous (GstXvContext * context, gboolean synchronous)
|
|
{
|
|
/* call XSynchronize with the current value of synchronous */
|
|
GST_DEBUG ("XSynchronize called with %s", synchronous ? "TRUE" : "FALSE");
|
|
g_mutex_lock (&context->lock);
|
|
XSynchronize (context->disp, synchronous);
|
|
g_mutex_unlock (&context->lock);
|
|
}
|
|
|
|
void
|
|
gst_xvcontext_update_colorbalance (GstXvContext * context,
|
|
GstXvContextConfig * config)
|
|
{
|
|
GList *channels = NULL;
|
|
|
|
/* Don't set the attributes if they haven't been changed, to avoid
|
|
* rounding errors changing the values */
|
|
if (!config->cb_changed)
|
|
return;
|
|
|
|
/* For each channel of the colorbalance we calculate the correct value
|
|
doing range conversion and then set the Xv port attribute to match our
|
|
values. */
|
|
channels = context->channels_list;
|
|
|
|
while (channels) {
|
|
if (channels->data && GST_IS_COLOR_BALANCE_CHANNEL (channels->data)) {
|
|
GstColorBalanceChannel *channel = NULL;
|
|
Atom prop_atom;
|
|
gint value = 0;
|
|
gdouble convert_coef;
|
|
|
|
channel = GST_COLOR_BALANCE_CHANNEL (channels->data);
|
|
g_object_ref (channel);
|
|
|
|
/* Our range conversion coef */
|
|
convert_coef = (channel->max_value - channel->min_value) / 2000.0;
|
|
|
|
if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) {
|
|
value = config->hue;
|
|
} else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) {
|
|
value = config->saturation;
|
|
} else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) {
|
|
value = config->contrast;
|
|
} else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) {
|
|
value = config->brightness;
|
|
} else {
|
|
g_warning ("got an unknown channel %s", channel->label);
|
|
g_object_unref (channel);
|
|
return;
|
|
}
|
|
|
|
/* Committing to Xv port */
|
|
g_mutex_lock (&context->lock);
|
|
prop_atom = XInternAtom (context->disp, channel->label, True);
|
|
if (prop_atom != None) {
|
|
int xv_value;
|
|
xv_value =
|
|
floor (0.5 + (value + 1000) * convert_coef + channel->min_value);
|
|
XvSetPortAttribute (context->disp,
|
|
context->xv_port_id, prop_atom, xv_value);
|
|
}
|
|
g_mutex_unlock (&context->lock);
|
|
|
|
g_object_unref (channel);
|
|
}
|
|
channels = g_list_next (channels);
|
|
}
|
|
}
|
|
|
|
/* This function tries to get a format matching with a given caps in the
|
|
supported list of formats we generated in gst_xvimagesink_get_xv_support */
|
|
gint
|
|
gst_xvcontext_get_format_from_info (GstXvContext * context,
|
|
const GstVideoInfo * info)
|
|
{
|
|
GList *list = NULL;
|
|
|
|
list = context->formats_list;
|
|
|
|
while (list) {
|
|
GstXvImageFormat *format = list->data;
|
|
|
|
if (format && format->vformat == GST_VIDEO_INFO_FORMAT (info))
|
|
return format->format;
|
|
|
|
list = g_list_next (list);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
gst_xvcontext_set_colorimetry (GstXvContext * context,
|
|
GstVideoColorimetry * colorimetry)
|
|
{
|
|
Atom prop_atom;
|
|
int xv_value;
|
|
|
|
if (!context->have_iturbt709 && !context->have_xvcolorspace)
|
|
return;
|
|
|
|
switch (colorimetry->matrix) {
|
|
case GST_VIDEO_COLOR_MATRIX_SMPTE240M:
|
|
case GST_VIDEO_COLOR_MATRIX_BT709:
|
|
xv_value = 1;
|
|
break;
|
|
default:
|
|
xv_value = 0;
|
|
break;
|
|
}
|
|
|
|
g_mutex_lock (&context->lock);
|
|
if (context->have_iturbt709) {
|
|
prop_atom = XInternAtom (context->disp, "XV_ITURBT_709", True);
|
|
if (prop_atom != None) {
|
|
XvSetPortAttribute (context->disp,
|
|
context->xv_port_id, prop_atom, xv_value);
|
|
}
|
|
}
|
|
|
|
if (context->have_xvcolorspace) {
|
|
prop_atom = XInternAtom (context->disp, "XV_COLORSPACE", True);
|
|
if (prop_atom != None) {
|
|
XvSetPortAttribute (context->disp,
|
|
context->xv_port_id, prop_atom, xv_value);
|
|
}
|
|
}
|
|
g_mutex_unlock (&context->lock);
|
|
}
|
|
|
|
GstXWindow *
|
|
gst_xvcontext_create_xwindow (GstXvContext * context, gint width, gint height)
|
|
{
|
|
GstXWindow *window;
|
|
Atom wm_delete;
|
|
Atom hints_atom = None;
|
|
|
|
g_return_val_if_fail (GST_IS_XVCONTEXT (context), NULL);
|
|
|
|
window = g_slice_new0 (GstXWindow);
|
|
|
|
window->context = gst_xvcontext_ref (context);
|
|
window->render_rect.x = window->render_rect.y = 0;
|
|
window->render_rect.w = width;
|
|
window->render_rect.h = height;
|
|
window->have_render_rect = FALSE;
|
|
|
|
window->width = width;
|
|
window->height = height;
|
|
window->internal = TRUE;
|
|
|
|
g_mutex_lock (&context->lock);
|
|
|
|
window->win = XCreateSimpleWindow (context->disp,
|
|
context->root, 0, 0, width, height, 0, 0, context->black);
|
|
|
|
/* We have to do that to prevent X from redrawing the background on
|
|
* ConfigureNotify. This takes away flickering of video when resizing. */
|
|
XSetWindowBackgroundPixmap (context->disp, window->win, None);
|
|
|
|
/* Tell the window manager we'd like delete client messages instead of
|
|
* being killed */
|
|
wm_delete = XInternAtom (context->disp, "WM_DELETE_WINDOW", True);
|
|
if (wm_delete != None) {
|
|
(void) XSetWMProtocols (context->disp, window->win, &wm_delete, 1);
|
|
}
|
|
|
|
hints_atom = XInternAtom (context->disp, "_MOTIF_WM_HINTS", True);
|
|
if (hints_atom != None) {
|
|
MotifWmHints *hints;
|
|
|
|
hints = g_malloc0 (sizeof (MotifWmHints));
|
|
|
|
hints->flags |= MWM_HINTS_DECORATIONS;
|
|
hints->decorations = 1 << 0;
|
|
|
|
XChangeProperty (context->disp, window->win,
|
|
hints_atom, hints_atom, 32, PropModeReplace,
|
|
(guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
|
|
|
|
XSync (context->disp, FALSE);
|
|
|
|
g_free (hints);
|
|
}
|
|
|
|
window->gc = XCreateGC (context->disp, window->win, 0, NULL);
|
|
|
|
XMapRaised (context->disp, window->win);
|
|
|
|
XSync (context->disp, FALSE);
|
|
|
|
g_mutex_unlock (&context->lock);
|
|
|
|
return window;
|
|
}
|
|
|
|
GstXWindow *
|
|
gst_xvcontext_create_xwindow_from_xid (GstXvContext * context, XID xid)
|
|
{
|
|
GstXWindow *window;
|
|
XWindowAttributes attr;
|
|
|
|
window = g_slice_new0 (GstXWindow);
|
|
window->win = xid;
|
|
window->context = gst_xvcontext_ref (context);
|
|
|
|
/* Set the event we want to receive and create a GC */
|
|
g_mutex_lock (&context->lock);
|
|
|
|
XGetWindowAttributes (context->disp, window->win, &attr);
|
|
|
|
window->width = attr.width;
|
|
window->height = attr.height;
|
|
window->internal = FALSE;
|
|
|
|
window->have_render_rect = FALSE;
|
|
window->render_rect.x = window->render_rect.y = 0;
|
|
window->render_rect.w = attr.width;
|
|
window->render_rect.h = attr.height;
|
|
|
|
window->gc = XCreateGC (context->disp, window->win, 0, NULL);
|
|
g_mutex_unlock (&context->lock);
|
|
|
|
return window;
|
|
}
|
|
|
|
void
|
|
gst_xwindow_destroy (GstXWindow * window)
|
|
{
|
|
GstXvContext *context;
|
|
|
|
g_return_if_fail (window != NULL);
|
|
|
|
context = window->context;
|
|
|
|
g_mutex_lock (&context->lock);
|
|
|
|
/* If we did not create that window we just free the GC and let it live */
|
|
if (window->internal)
|
|
XDestroyWindow (context->disp, window->win);
|
|
else
|
|
XSelectInput (context->disp, window->win, 0);
|
|
|
|
XFreeGC (context->disp, window->gc);
|
|
|
|
XSync (context->disp, FALSE);
|
|
|
|
g_mutex_unlock (&context->lock);
|
|
|
|
gst_xvcontext_unref (context);
|
|
|
|
g_slice_free1 (sizeof (GstXWindow), window);
|
|
}
|
|
|
|
void
|
|
gst_xwindow_set_event_handling (GstXWindow * window, gboolean handle_events)
|
|
{
|
|
GstXvContext *context;
|
|
|
|
g_return_if_fail (window != NULL);
|
|
|
|
context = window->context;
|
|
|
|
g_mutex_lock (&context->lock);
|
|
if (handle_events) {
|
|
if (window->internal) {
|
|
XSelectInput (context->disp, window->win,
|
|
ExposureMask | StructureNotifyMask | PointerMotionMask |
|
|
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
|
|
} else {
|
|
XSelectInput (context->disp, window->win,
|
|
ExposureMask | StructureNotifyMask | PointerMotionMask |
|
|
KeyPressMask | KeyReleaseMask);
|
|
}
|
|
} else {
|
|
XSelectInput (context->disp, window->win, 0);
|
|
}
|
|
g_mutex_unlock (&context->lock);
|
|
}
|
|
|
|
void
|
|
gst_xwindow_set_title (GstXWindow * window, const gchar * title)
|
|
{
|
|
GstXvContext *context;
|
|
|
|
g_return_if_fail (window != NULL);
|
|
|
|
context = window->context;
|
|
|
|
/* we have a window */
|
|
if (window->internal && title) {
|
|
XTextProperty xproperty;
|
|
XClassHint *hint = XAllocClassHint ();
|
|
Atom _NET_WM_NAME = XInternAtom (context->disp, "_NET_WM_NAME", 0);
|
|
Atom UTF8_STRING = XInternAtom (context->disp, "UTF8_STRING", 0);
|
|
|
|
if ((XStringListToTextProperty (((char **) &title), 1, &xproperty)) != 0) {
|
|
XSetWMName (context->disp, window->win, &xproperty);
|
|
XFree (xproperty.value);
|
|
|
|
XChangeProperty (context->disp, window->win, _NET_WM_NAME, UTF8_STRING, 8,
|
|
0, (unsigned char *) title, strlen (title));
|
|
XSync (context->disp, False);
|
|
|
|
if (hint) {
|
|
hint->res_name = (char *) title;
|
|
hint->res_class = (char *) "GStreamer";
|
|
XSetClassHint (context->disp, window->win, hint);
|
|
}
|
|
XFree (hint);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_xwindow_update_geometry (GstXWindow * window)
|
|
{
|
|
XWindowAttributes attr;
|
|
GstXvContext *context;
|
|
|
|
g_return_if_fail (window != NULL);
|
|
|
|
context = window->context;
|
|
|
|
/* Update the window geometry */
|
|
g_mutex_lock (&context->lock);
|
|
XGetWindowAttributes (context->disp, window->win, &attr);
|
|
|
|
window->width = attr.width;
|
|
window->height = attr.height;
|
|
|
|
if (!window->have_render_rect) {
|
|
window->render_rect.x = window->render_rect.y = 0;
|
|
window->render_rect.w = attr.width;
|
|
window->render_rect.h = attr.height;
|
|
}
|
|
|
|
g_mutex_unlock (&context->lock);
|
|
}
|
|
|
|
|
|
void
|
|
gst_xwindow_clear (GstXWindow * window)
|
|
{
|
|
GstXvContext *context;
|
|
|
|
g_return_if_fail (window != NULL);
|
|
|
|
context = window->context;
|
|
|
|
g_mutex_lock (&context->lock);
|
|
|
|
XvStopVideo (context->disp, context->xv_port_id, window->win);
|
|
|
|
XSync (context->disp, FALSE);
|
|
|
|
g_mutex_unlock (&context->lock);
|
|
}
|
|
|
|
void
|
|
gst_xwindow_set_render_rectangle (GstXWindow * window,
|
|
gint x, gint y, gint width, gint height)
|
|
{
|
|
g_return_if_fail (window != NULL);
|
|
|
|
if (width >= 0 && height >= 0) {
|
|
window->render_rect.x = x;
|
|
window->render_rect.y = y;
|
|
window->render_rect.w = width;
|
|
window->render_rect.h = height;
|
|
window->have_render_rect = TRUE;
|
|
} else {
|
|
window->render_rect.x = 0;
|
|
window->render_rect.y = 0;
|
|
window->render_rect.w = window->width;
|
|
window->render_rect.h = window->height;
|
|
window->have_render_rect = FALSE;
|
|
}
|
|
}
|