/* RealVideo wrapper plugin * * Copyright (C) 2005 Lutz Mueller * Copyright (C) 2006 Edward Hervey * * 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstreal.h" #include "gstrealvideodec.h" #include GST_DEBUG_CATEGORY_STATIC (realvideode_debug); #define GST_CAT_DEFAULT realvideode_debug static GstStaticPadTemplate snk_t = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-pn-realvideo, " "rmversion = (int) [ 2, 4 ]")); static GstStaticPadTemplate src_t = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-raw-yuv, " "format = (fourcc) I420, " "framerate = (fraction) [0/1, MAX], " "width = (int) [ 16, 4096 ], " "height = (int) [ 16, 4096 ] ")); #define DEFAULT_RV20_NAMES "drv2.so:drv2.so.6.0" #define DEFAULT_RV30_NAMES "drvc.so:drv3.so.6.0" #define DEFAULT_RV40_NAMES "drvc.so:drv4.so.6.0" #define DEFAULT_MAX_ERRORS 25 enum { PROP_0, PROP_REAL_CODECS_PATH, PROP_RV20_NAMES, PROP_RV30_NAMES, PROP_RV40_NAMES, PROP_MAX_ERRORS }; GST_BOILERPLATE (GstRealVideoDec, gst_real_video_dec, GstElement, GST_TYPE_ELEMENT); static gboolean open_library (GstRealVideoDec * dec, GstRealVideoDecVersion version, GstRVDecLibrary * lib); static void close_library (GstRealVideoDec * dec, GstRVDecLibrary * lib); typedef struct { guint32 datalen; gint32 interpolate; gint32 nfragments; gpointer fragments; guint32 flags; guint32 timestamp; } RVInData; typedef struct { guint32 frames; guint32 notes; guint32 timestamp; guint32 width; guint32 height; } RVOutData; static GstFlowReturn gst_real_video_dec_chain (GstPad * pad, GstBuffer * in) { GstRealVideoDec *dec; guint8 *data; guint size; GstFlowReturn ret; RVInData tin; RVOutData tout; GstClockTime timestamp, duration; GstBuffer *out; guint32 result; guint frag_count, frag_size; dec = GST_REAL_VIDEO_DEC (GST_PAD_PARENT (pad)); if (G_UNLIKELY (dec->lib.Transform == NULL || dec->lib.module == NULL)) goto not_negotiated; data = GST_BUFFER_DATA (in); size = GST_BUFFER_SIZE (in); timestamp = GST_BUFFER_TIMESTAMP (in); duration = GST_BUFFER_DURATION (in); GST_DEBUG_OBJECT (dec, "got buffer of size %u, timestamp %" GST_TIME_FORMAT, size, GST_TIME_ARGS (timestamp)); /* alloc output buffer */ ret = gst_pad_alloc_buffer (dec->src, GST_BUFFER_OFFSET_NONE, dec->width * dec->height * 3 / 2, GST_PAD_CAPS (dec->src), &out); if (ret != GST_FLOW_OK) goto alloc_failed; GST_BUFFER_TIMESTAMP (out) = timestamp; GST_BUFFER_DURATION (out) = duration; frag_count = *data++; frag_size = (frag_count + 1) * 8; size -= (frag_size + 1); GST_DEBUG_OBJECT (dec, "frag_count %u, frag_size %u, data size %u", frag_count, frag_size, size); /* Decode. * * The Buffers contain * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | nfragments | fragment1 ... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | .... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ... | fragment2 ... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * .... * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ... | fragment data | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * nfragments: number of fragments * fragmentN: 8 bytes of fragment data (nfragements + 1) of them * fragment data: the data of the fragments. */ tin.datalen = size; tin.interpolate = 0; tin.nfragments = frag_count; tin.fragments = data; tin.flags = 0; tin.timestamp = timestamp; /* jump over the frag table to the fragments */ data += frag_size; result = dec->lib.Transform ( (gchar *) data, (gchar *) GST_BUFFER_DATA (out), &tin, &tout, dec->lib.context); if (result) goto could_not_transform; /* When we decoded a frame, reset the error counter. We only fail after N * consecutive decoding errors. */ dec->error_count = 0; gst_buffer_unref (in); /* Check for new dimensions */ if (tout.frames && ((dec->width != tout.width) || (dec->height != tout.height))) { GstCaps *caps = gst_caps_copy (GST_PAD_CAPS (dec->src)); GstStructure *s = gst_caps_get_structure (caps, 0); GST_DEBUG_OBJECT (dec, "New dimensions: %" G_GUINT32_FORMAT " x %" G_GUINT32_FORMAT, tout.width, tout.height); gst_structure_set (s, "width", G_TYPE_INT, (gint) tout.width, "height", G_TYPE_INT, (gint) tout.height, NULL); gst_pad_set_caps (dec->src, caps); gst_buffer_set_caps (out, caps); gst_caps_unref (caps); dec->width = tout.width; dec->height = tout.height; GST_BUFFER_SIZE (out) = dec->width * dec->height * 3 / 2; } GST_DEBUG_OBJECT (dec, "Pushing out buffer with timestamp %" GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (out))); if ((ret = gst_pad_push (dec->src, out)) != GST_FLOW_OK) goto could_not_push; return ret; /* Errors */ not_negotiated: { GST_WARNING_OBJECT (dec, "decoder not open, probably no input caps set " "yet, caps on input buffer: %" GST_PTR_FORMAT, GST_BUFFER_CAPS (in)); gst_buffer_unref (in); return GST_FLOW_NOT_NEGOTIATED; } alloc_failed: { GST_DEBUG_OBJECT (dec, "buffer alloc failed: %s", gst_flow_get_name (ret)); gst_buffer_unref (in); return ret; } could_not_transform: { gst_buffer_unref (out); gst_buffer_unref (in); dec->error_count++; if (dec->max_errors && dec->error_count >= dec->max_errors) { GST_ELEMENT_ERROR (dec, STREAM, DECODE, ("Could not decode buffer: %" G_GUINT32_FORMAT, result), (NULL)); return GST_FLOW_ERROR; } else { GST_ELEMENT_WARNING (dec, STREAM, DECODE, ("Could not decode buffer: %" G_GUINT32_FORMAT, result), (NULL)); return GST_FLOW_OK; } } could_not_push: { GST_DEBUG_OBJECT (dec, "Could not push buffer: %s", gst_flow_get_name (ret)); return ret; } } static GstCaps * gst_real_video_dec_getcaps (GstPad * pad) { GstRealVideoDec *dec = GST_REAL_VIDEO_DEC (GST_PAD_PARENT (pad)); GstCaps *res; if (dec->checked_modules) { GValue versions = { 0 }; GValue version = { 0 }; GST_LOG_OBJECT (dec, "constructing caps"); res = gst_caps_new_empty (); g_value_init (&versions, GST_TYPE_LIST); g_value_init (&version, G_TYPE_INT); if (dec->valid_rv20) { g_value_set_int (&version, GST_REAL_VIDEO_DEC_VERSION_2); gst_value_list_append_value (&versions, &version); } if (dec->valid_rv30) { g_value_set_int (&version, GST_REAL_VIDEO_DEC_VERSION_3); gst_value_list_append_value (&versions, &version); } if (dec->valid_rv40) { g_value_set_int (&version, GST_REAL_VIDEO_DEC_VERSION_4); gst_value_list_append_value (&versions, &version); } if (gst_value_list_get_size (&versions) > 0) { res = gst_caps_new_simple ("video/x-pn-realvideo", NULL); gst_structure_set_value (gst_caps_get_structure (res, 0), "rmversion", &versions); } else { res = gst_caps_new_empty (); } g_value_unset (&versions); g_value_unset (&version); } else { GST_LOG_OBJECT (dec, "returning padtemplate caps"); res = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); } GST_LOG_OBJECT (dec, "returning caps %" GST_PTR_FORMAT, res); return res; } static gboolean gst_real_video_dec_setcaps (GstPad * pad, GstCaps * caps) { GstRealVideoDec *dec = GST_REAL_VIDEO_DEC (GST_PAD_PARENT (pad)); GstStructure *s = gst_caps_get_structure (caps, 0); gint version, res, width, height, format, subformat; gint framerate_num, framerate_denom; gchar data[36]; gboolean bres; const GValue *v; if (!gst_structure_get_int (s, "rmversion", &version) || !gst_structure_get_int (s, "width", (gint *) & width) || !gst_structure_get_int (s, "height", (gint *) & height) || !gst_structure_get_int (s, "format", &format) || !gst_structure_get_int (s, "subformat", &subformat) || !gst_structure_get_fraction (s, "framerate", &framerate_num, &framerate_denom)) goto missing_keys; GST_LOG_OBJECT (dec, "Setting version to %d", version); close_library (dec, &dec->lib); if (!open_library (dec, version, &dec->lib)) goto open_failed; /* Initialize REAL driver. */ GST_WRITE_UINT16_LE (data + 0, 11); GST_WRITE_UINT16_LE (data + 2, width); GST_WRITE_UINT16_LE (data + 4, height); GST_WRITE_UINT16_LE (data + 6, 0); GST_WRITE_UINT32_LE (data + 8, 0); GST_WRITE_UINT32_LE (data + 12, subformat); GST_WRITE_UINT32_LE (data + 16, 1); GST_WRITE_UINT32_LE (data + 20, format); if ((res = dec->lib.Init (&data, &dec->lib.context))) goto could_not_initialize; if ((v = gst_structure_get_value (s, "codec_data"))) { GstBuffer *buf; guint32 *msgdata; guint i; guint8 *bufdata; guint bufsize; struct { guint32 type; guint32 msg; gpointer data; guint32 extra[6]; } msg; buf = g_value_peek_pointer (v); bufdata = GST_BUFFER_DATA (buf); bufsize = GST_BUFFER_SIZE (buf); /* skip format and subformat */ bufdata += 8; bufsize -= 8; GST_LOG_OBJECT (dec, "Creating custom message of length %d", bufsize); msgdata = g_new0 (guint32, bufsize + 2); if (!msgdata) goto could_not_allocate; msg.type = 0x24; msg.msg = 1 + ((subformat >> 16) & 7); msg.data = msgdata; for (i = 0; i < 6; i++) msg.extra[i] = 0; msgdata[0] = width; msgdata[1] = height; for (i = 0; i < bufsize; i++) msgdata[i + 2] = 4 * (guint32) bufdata[i]; res = dec->lib.Message (&msg, dec->lib.context); g_free (msgdata); if (res) goto could_not_send_message; } caps = gst_caps_new_simple ("video/x-raw-yuv", "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'), "framerate", GST_TYPE_FRACTION, framerate_num, framerate_denom, "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, NULL); /* set PAR if one was specified in the sink caps */ if ((v = gst_structure_get_value (s, "pixel-aspect-ratio"))) { gst_structure_set_value (gst_caps_get_structure (caps, 0), "pixel-aspect-ratio", v); } bres = gst_pad_set_caps (GST_PAD (dec->src), caps); gst_caps_unref (caps); if (!bres) goto could_not_set_caps; dec->version = version; dec->width = width; dec->height = height; dec->format = format; dec->subformat = subformat; dec->framerate_num = framerate_num; dec->framerate_denom = framerate_denom; return TRUE; missing_keys: { GST_ERROR_OBJECT (dec, "Could not find all necessary keys in structure."); return FALSE; } open_failed: { GST_ERROR_OBJECT (dec, "failed to open library"); return FALSE; } could_not_initialize: { GST_ERROR_OBJECT (dec, "Initialization of REAL driver failed (%i).", res); close_library (dec, &dec->lib); return FALSE; } could_not_allocate: { GST_ERROR_OBJECT (dec, "Could not allocate memory."); close_library (dec, &dec->lib); return FALSE; } could_not_send_message: { GST_ERROR_OBJECT (dec, "Failed to send custom message needed for " "initialization (%i).", res); close_library (dec, &dec->lib); return FALSE; } could_not_set_caps: { GST_ERROR_OBJECT (dec, "Could not convince peer to accept dimensions " "%i x %i.", dec->width, dec->height); close_library (dec, &dec->lib); return FALSE; } } /* Attempts to open the correct library for the configured version */ static gboolean open_library (GstRealVideoDec * dec, GstRealVideoDecVersion version, GstRVDecLibrary * lib) { gpointer rv_custom_msg, rv_free, rv_init, rv_transform; GModule *module = NULL; const gchar *path, *names; gchar **split_names, **split_path; int i, j; GST_DEBUG_OBJECT (dec, "Attempting to open shared library for real video version %d", version); path = dec->real_codecs_path ? dec->real_codecs_path : DEFAULT_REAL_CODECS_PATH; switch (version) { case GST_REAL_VIDEO_DEC_VERSION_2: names = dec->rv20_names ? dec->rv20_names : DEFAULT_RV20_NAMES; break; case GST_REAL_VIDEO_DEC_VERSION_3: names = dec->rv30_names ? dec->rv30_names : DEFAULT_RV30_NAMES; break; case GST_REAL_VIDEO_DEC_VERSION_4: names = dec->rv40_names ? dec->rv40_names : DEFAULT_RV40_NAMES; break; default: goto unknown_version; } split_path = g_strsplit (path, ":", 0); split_names = g_strsplit (names, ":", 0); for (i = 0; split_path[i]; i++) { for (j = 0; split_names[j]; j++) { gchar *codec = g_strconcat (split_path[i], "/", split_names[j], NULL); GST_DEBUG_OBJECT (dec, "trying %s", codec); /* This is racy, but it doesn't matter here; would be nice if GModule * gave us a GError instead of an error string, but it doesn't, so.. */ if (g_file_test (codec, G_FILE_TEST_EXISTS)) { module = g_module_open (codec, G_MODULE_BIND_LAZY); if (module == NULL) { GST_ERROR_OBJECT (dec, "Could not open codec library '%s': %s", codec, g_module_error ()); } } else { GST_LOG_OBJECT (dec, "%s does not exist", codec); } g_free (codec); if (module) goto codec_search_done; } } codec_search_done: g_strfreev (split_path); g_strfreev (split_names); if (module == NULL) return FALSE; GST_DEBUG_OBJECT (dec, "module opened, finding symbols"); /* First try opening legacy symbols, if that fails try loading new symbols */ if (g_module_symbol (module, "RV20toYUV420Init", &rv_init) && g_module_symbol (module, "RV20toYUV420Free", &rv_free) && g_module_symbol (module, "RV20toYUV420Transform", &rv_transform) && g_module_symbol (module, "RV20toYUV420CustomMessage", &rv_custom_msg)) { GST_LOG_OBJECT (dec, "Loaded legacy symbols"); } else if (g_module_symbol (module, "RV40toYUV420Init", &rv_init) && g_module_symbol (module, "RV40toYUV420Free", &rv_free) && g_module_symbol (module, "RV40toYUV420Transform", &rv_transform) && g_module_symbol (module, "RV40toYUV420CustomMessage", &rv_custom_msg)) { GST_LOG_OBJECT (dec, "Loaded new symbols"); } else { goto could_not_load; } lib->Init = (guint32 (*)(gpointer, gpointer)) rv_init; lib->Free = (guint32 (*)(gpointer)) rv_free; lib->Transform = (guint32 (*)(gchar *, gchar *, gpointer, gpointer, gpointer)) rv_transform; lib->Message = (guint32 (*)(gpointer, gpointer)) rv_custom_msg; lib->module = module; dec->error_count = 0; return TRUE; unknown_version: { GST_ERROR_OBJECT (dec, "Cannot handle version %i.", version); return FALSE; } could_not_load: { close_library (dec, lib); GST_ERROR_OBJECT (dec, "Could not load all symbols: %s", g_module_error ()); return FALSE; } } static void close_library (GstRealVideoDec * dec, GstRVDecLibrary * lib) { if (lib->context) { GST_LOG_OBJECT (dec, "closing library"); if (lib->Free) lib->Free (lib->context); } if (lib->module) { GST_LOG_OBJECT (dec, "closing library module"); g_module_close (lib->module); lib->module = NULL; } memset (lib, 0, sizeof (*lib)); } static void gst_real_video_dec_probe_modules (GstRealVideoDec * dec) { GstRVDecLibrary dummy = { NULL }; if ((dec->valid_rv20 = open_library (dec, GST_REAL_VIDEO_DEC_VERSION_2, &dummy))) close_library (dec, &dummy); if ((dec->valid_rv30 = open_library (dec, GST_REAL_VIDEO_DEC_VERSION_3, &dummy))) close_library (dec, &dummy); if ((dec->valid_rv40 = open_library (dec, GST_REAL_VIDEO_DEC_VERSION_4, &dummy))) close_library (dec, &dummy); } static GstStateChangeReturn gst_real_video_dec_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstRealVideoDec *dec = GST_REAL_VIDEO_DEC (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: gst_real_video_dec_probe_modules (dec); dec->checked_modules = TRUE; break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: close_library (dec, &dec->lib); break; case GST_STATE_CHANGE_READY_TO_NULL: dec->checked_modules = FALSE; break; default: break; } return ret; } static void gst_real_video_dec_init (GstRealVideoDec * dec, GstRealVideoDecClass * klass) { dec->snk = gst_pad_new_from_static_template (&snk_t, "sink"); gst_pad_set_getcaps_function (dec->snk, GST_DEBUG_FUNCPTR (gst_real_video_dec_getcaps)); gst_pad_set_setcaps_function (dec->snk, GST_DEBUG_FUNCPTR (gst_real_video_dec_setcaps)); gst_pad_set_chain_function (dec->snk, GST_DEBUG_FUNCPTR (gst_real_video_dec_chain)); gst_element_add_pad (GST_ELEMENT (dec), dec->snk); dec->src = gst_pad_new_from_static_template (&src_t, "src"); gst_pad_use_fixed_caps (dec->src); gst_element_add_pad (GST_ELEMENT (dec), dec->src); dec->max_errors = DEFAULT_MAX_ERRORS; dec->error_count = 0; } static void gst_real_video_dec_base_init (gpointer g_class) { GstElementClass *ec = GST_ELEMENT_CLASS (g_class); gst_element_class_add_pad_template (ec, gst_static_pad_template_get (&snk_t)); gst_element_class_add_pad_template (ec, gst_static_pad_template_get (&src_t)); gst_element_class_set_details_simple (ec, "RealVideo decoder", "Codec/Decoder/Video", "Decoder for RealVideo streams", "Lutz Mueller "); } static void gst_real_video_dec_finalize (GObject * object) { GstRealVideoDec *dec = GST_REAL_VIDEO_DEC (object); close_library (dec, &dec->lib); if (dec->real_codecs_path) { g_free (dec->real_codecs_path); dec->real_codecs_path = NULL; } if (dec->rv20_names) { g_free (dec->rv20_names); dec->rv20_names = NULL; } if (dec->rv30_names) { g_free (dec->rv30_names); dec->rv30_names = NULL; } if (dec->rv40_names) { g_free (dec->rv40_names); dec->rv40_names = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_real_video_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstRealVideoDec *dec = GST_REAL_VIDEO_DEC (object); /* Changing the location of the .so supposes it's not being done * in a state greater than READY ! */ switch (prop_id) { case PROP_REAL_CODECS_PATH: if (dec->real_codecs_path) g_free (dec->real_codecs_path); dec->real_codecs_path = g_value_dup_string (value); break; case PROP_RV20_NAMES: if (dec->rv20_names) g_free (dec->rv20_names); dec->rv20_names = g_value_dup_string (value); break; case PROP_RV30_NAMES: if (dec->rv30_names) g_free (dec->rv30_names); dec->rv30_names = g_value_dup_string (value); break; case PROP_RV40_NAMES: if (dec->rv40_names) g_free (dec->rv40_names); dec->rv40_names = g_value_dup_string (value); break; case PROP_MAX_ERRORS: dec->max_errors = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_real_video_dec_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstRealVideoDec *dec = GST_REAL_VIDEO_DEC (object); switch (prop_id) { case PROP_REAL_CODECS_PATH: g_value_set_string (value, dec->real_codecs_path ? dec->real_codecs_path : DEFAULT_REAL_CODECS_PATH); break; case PROP_RV20_NAMES: g_value_set_string (value, dec->rv20_names ? dec->rv20_names : DEFAULT_RV20_NAMES); break; case PROP_RV30_NAMES: g_value_set_string (value, dec->rv30_names ? dec->rv30_names : DEFAULT_RV30_NAMES); break; case PROP_RV40_NAMES: g_value_set_string (value, dec->rv40_names ? dec->rv40_names : DEFAULT_RV40_NAMES); break; case PROP_MAX_ERRORS: g_value_set_int (value, dec->max_errors); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_real_video_dec_class_init (GstRealVideoDecClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); object_class->set_property = gst_real_video_dec_set_property; object_class->get_property = gst_real_video_dec_get_property; object_class->finalize = gst_real_video_dec_finalize; element_class->change_state = gst_real_video_dec_change_state; g_object_class_install_property (object_class, PROP_REAL_CODECS_PATH, g_param_spec_string ("real-codecs-path", "Path where to search for RealPlayer codecs", "Path where to search for RealPlayer codecs", DEFAULT_REAL_CODECS_PATH, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RV20_NAMES, g_param_spec_string ("rv20-names", "Names of rv20 driver", "Names of rv20 driver", DEFAULT_RV20_NAMES, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RV30_NAMES, g_param_spec_string ("rv30-names", "Names of rv30 driver", "Names of rv30 driver", DEFAULT_RV30_NAMES, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RV40_NAMES, g_param_spec_string ("rv40-names", "Names of rv40 driver", "Names of rv40 driver", DEFAULT_RV40_NAMES, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_MAX_ERRORS, g_param_spec_int ("max-errors", "Max errors", "Maximum number of consecutive errors (0 = unlimited)", 0, G_MAXINT, DEFAULT_MAX_ERRORS, G_PARAM_READWRITE)); GST_DEBUG_CATEGORY_INIT (realvideode_debug, "realvideodec", 0, "RealVideo decoder"); }