From 39f82f2f193807b9c894cf5145b4b09e7804186e Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Fri, 12 May 2006 09:28:15 +0000 Subject: ext/xvid/: xvid plugin to support more capabilities of XviD codec. Original commit message from CVS: Patch by: Mark Nauwelaerts * ext/xvid/gstxvid.c: (gst_xvid_init), (gst_xvid_structure_to_csp), (gst_xvid_csp_to_caps), (gst_xvid_image_get_size), (gst_xvid_image_fill): * ext/xvid/gstxvid.h: * ext/xvid/gstxviddec.c: (gst_xviddec_class_init), (gst_xviddec_init), (gst_xviddec_reset), (gst_xviddec_unset), (gst_xviddec_handle_sink_event), (gst_xviddec_setup), (gst_xviddec_add_par), (gst_xviddec_negotiate), (gst_xviddec_decode), (gst_xviddec_chain), (gst_xviddec_flush_buffers), (gst_xviddec_src_getcaps), (gst_xviddec_src_link), (gst_xviddec_setcaps), (gst_xviddec_change_state): * ext/xvid/gstxviddec.h: * ext/xvid/gstxvidenc.c: (gst_xvidenc_profile_get_type), (gst_xvidenc_quant_type_get_type), (gst_xvidenc_pass_get_type), (gst_xvidenc_get_type), (gst_xvidenc_base_init), (gst_xvidenc_class_init), (gst_xvidenc_init), (gst_xvidenc_finalize), (gst_xvidenc_handle_sink_event), (gst_xvidenc_setup), (gst_xvidenc_setcaps), (gst_xvidenc_encode), (gst_xvidenc_chain), (gst_xvidenc_flush_buffers), (gst_xvidenc_set_property), (gst_xvidenc_get_property), (gst_xvidenc_change_state): * ext/xvid/gstxvidenc.h: xvid plugin to support more capabilities of XviD codec. Fixes #339462. Some more cleanups here and there. --- ChangeLog | 31 ++ ext/xvid/gstxvid.c | 110 +++-- ext/xvid/gstxvid.h | 12 +- ext/xvid/gstxviddec.c | 547 +++++++++++++++++++------ ext/xvid/gstxviddec.h | 20 +- ext/xvid/gstxvidenc.c | 1071 ++++++++++++++++++++++++++++++++++++++----------- ext/xvid/gstxvidenc.h | 91 ++++- 7 files changed, 1478 insertions(+), 404 deletions(-) diff --git a/ChangeLog b/ChangeLog index 448473ab..eb9dc903 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,34 @@ +2006-05-12 Wim Taymans + + Patch by: Mark Nauwelaerts + + * ext/xvid/gstxvid.c: (gst_xvid_init), (gst_xvid_structure_to_csp), + (gst_xvid_csp_to_caps), (gst_xvid_image_get_size), + (gst_xvid_image_fill): + * ext/xvid/gstxvid.h: + * ext/xvid/gstxviddec.c: (gst_xviddec_class_init), + (gst_xviddec_init), (gst_xviddec_reset), (gst_xviddec_unset), + (gst_xviddec_handle_sink_event), (gst_xviddec_setup), + (gst_xviddec_add_par), (gst_xviddec_negotiate), + (gst_xviddec_decode), (gst_xviddec_chain), + (gst_xviddec_flush_buffers), (gst_xviddec_src_getcaps), + (gst_xviddec_src_link), (gst_xviddec_setcaps), + (gst_xviddec_change_state): + * ext/xvid/gstxviddec.h: + * ext/xvid/gstxvidenc.c: (gst_xvidenc_profile_get_type), + (gst_xvidenc_quant_type_get_type), (gst_xvidenc_pass_get_type), + (gst_xvidenc_get_type), (gst_xvidenc_base_init), + (gst_xvidenc_class_init), (gst_xvidenc_init), + (gst_xvidenc_finalize), (gst_xvidenc_handle_sink_event), + (gst_xvidenc_setup), (gst_xvidenc_setcaps), (gst_xvidenc_encode), + (gst_xvidenc_chain), (gst_xvidenc_flush_buffers), + (gst_xvidenc_set_property), (gst_xvidenc_get_property), + (gst_xvidenc_change_state): + * ext/xvid/gstxvidenc.h: + xvid plugin to support more capabilities of XviD codec. + Fixes #339462. + Some more cleanups here and there. + 2006-05-11 Edgard Lima * sys/v4l2/Makefile.am: diff --git a/ext/xvid/gstxvid.c b/ext/xvid/gstxvid.c index c532b244..96e11e66 100644 --- a/ext/xvid/gstxvid.c +++ b/ext/xvid/gstxvid.c @@ -42,6 +42,7 @@ gst_xvid_init (void) /* set up xvid initially (function pointers, CPU flags) */ gst_xvid_init_struct (xinit); + if ((ret = xvid_global (NULL, XVID_GBL_INIT, &xinit, NULL)) < 0) { g_warning ("Failed to initialize XviD: %s (%d)", gst_xvid_error (ret), ret); return FALSE; @@ -87,11 +88,10 @@ gst_xvid_error (int errorcode) } gint -gst_xvid_structure_to_csp (GstStructure * structure, - gint w, gint * _stride, gint * _bpp) +gst_xvid_structure_to_csp (GstStructure * structure) { const gchar *mime = gst_structure_get_name (structure); - gint xvid_cs = -1, stride = -1, bpp = -1; + gint xvid_cs = -1; if (!strcmp (mime, "video/x-raw-yuv")) { guint32 fourcc; @@ -100,32 +100,22 @@ gst_xvid_structure_to_csp (GstStructure * structure, switch (fourcc) { case GST_MAKE_FOURCC ('I', '4', '2', '0'): xvid_cs = XVID_CSP_I420; - stride = w; - bpp = 12; break; case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'): xvid_cs = XVID_CSP_YUY2; - stride = w * 2; - bpp = 16; break; case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): xvid_cs = XVID_CSP_YV12; - stride = w; - bpp = 12; break; case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): xvid_cs = XVID_CSP_UYVY; - stride = w * 2; - bpp = 16; break; case GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U'): xvid_cs = XVID_CSP_YVYU; - stride = w * 2; - bpp = 16; break; } } else { - gint depth, r_mask; + gint depth, bpp, r_mask; gst_structure_get_int (structure, "depth", &depth); gst_structure_get_int (structure, "bpp", &bpp); @@ -164,20 +154,15 @@ gst_xvid_structure_to_csp (GstStructure * structure, break; } - stride = w * bpp / 8; } - if (_stride) - *_stride = stride; - if (_bpp) - *_bpp = bpp; return xvid_cs; } GstCaps * -gst_xvid_csp_to_caps (gint csp, gint w, gint h, gint fps_n, gint fps_d) +gst_xvid_csp_to_caps (gint csp, gint w, gint h) { - GstCaps *caps = NULL; + GstStructure *structure = NULL; switch (csp) { case XVID_CSP_RGB555: @@ -253,7 +238,7 @@ gst_xvid_csp_to_caps (gint csp, gint w, gint h, gint fps_n, gint fps_d) break; } - caps = gst_caps_new_simple ("video/x-raw-rgb", + structure = gst_structure_new ("video/x-raw-rgb", "width", G_TYPE_INT, w, "height", G_TYPE_INT, h, "depth", G_TYPE_INT, depth, @@ -261,8 +246,7 @@ gst_xvid_csp_to_caps (gint csp, gint w, gint h, gint fps_n, gint fps_d) "endianness", G_TYPE_INT, endianness, "red_mask", G_TYPE_INT, r_mask, "green_mask", G_TYPE_INT, g_mask, - "blue_mask", G_TYPE_INT, b_mask, - "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL); + "blue_mask", G_TYPE_INT, b_mask, NULL); break; } @@ -290,18 +274,86 @@ gst_xvid_csp_to_caps (gint csp, gint w, gint h, gint fps_n, gint fps_d) fourcc = GST_MAKE_FOURCC ('Y', 'V', '1', '2'); break; } - caps = gst_caps_new_simple ("video/x-raw-yuv", + + structure = gst_structure_new ("video/x-raw-yuv", "width", G_TYPE_INT, w, - "height", G_TYPE_INT, h, - "format", GST_TYPE_FOURCC, fourcc, - "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL); + "height", G_TYPE_INT, h, "format", GST_TYPE_FOURCC, fourcc, NULL); break; } } - return caps; + return gst_caps_new_full (structure, NULL); +} + + +gint +gst_xvid_image_get_size (gint csp, gint width, gint height) +{ + xvid_image_t dummy_im; + + return gst_xvid_image_fill (&dummy_im, NULL, csp, width, height); } +gint +gst_xvid_image_fill (xvid_image_t * im, void *ptr, gint csp, + gint width, gint height) +{ + gint stride, h2, size = 0; + + im->csp = csp; + + switch (csp) { + case XVID_CSP_I420: + case XVID_CSP_YV12: + /* planar */ + /* luma */ + stride = GST_ROUND_UP_4 (width); + h2 = GST_ROUND_UP_2 (height); + im->stride[0] = stride; + im->plane[0] = ptr; + /* chroma */ + im->plane[1] = im->plane[0] + (stride * h2); + size += stride * height; + stride = GST_ROUND_UP_8 (width) / 2; + h2 = GST_ROUND_UP_2 (height) / 2; + im->stride[1] = stride; + + im->plane[2] = im->plane[1] + (stride * h2); + im->stride[2] = stride; + size += 2 * (stride * height); + break; + case XVID_CSP_RGB555: + case XVID_CSP_RGB565: + case XVID_CSP_YUY2: + case XVID_CSP_UYVY: + case XVID_CSP_YVYU: + /* packed */ + stride = GST_ROUND_UP_4 (width * 2); + im->plane[0] = ptr; + im->stride[0] = stride; + size = stride * height; + break; + case XVID_CSP_BGR: + stride = GST_ROUND_UP_4 (width * 3); + im->plane[0] = ptr; + im->stride[0] = stride; + size = stride * height * 2; + break; + case XVID_CSP_ABGR: + case XVID_CSP_BGRA: + case XVID_CSP_RGBA: +#ifdef XVID_CSP_ARGB + case XVID_CSP_ARGB: +#endif + stride = width * 4; + im->plane[0] = ptr; + im->stride[0] = stride; + size = stride * height; + break; + } + + return size; +} static gboolean plugin_init (GstPlugin * plugin) diff --git a/ext/xvid/gstxvid.h b/ext/xvid/gstxvid.h index ab24088d..4dca2c21 100644 --- a/ext/xvid/gstxvid.h +++ b/ext/xvid/gstxvid.h @@ -34,7 +34,7 @@ G_BEGIN_DECLS "video/x-raw-rgb, " \ "width = (int) [ 0, MAX ], " \ "height = (int) [ 0, MAX], " \ - "framerate = (double) [ 0.0, MAX], " \ + "framerate = (fraction) [ 0, MAX], " \ "depth = (int) 24, " \ "bpp = (int) " G_STRINGIFY (bpp) ", " \ "endianness = (int) BIG_ENDIAN, " \ @@ -45,10 +45,12 @@ G_BEGIN_DECLS extern gchar * gst_xvid_error (int errorcode); extern gboolean gst_xvid_init (void); -extern gint gst_xvid_structure_to_csp (GstStructure *structure, - gint w, gint *stride, gint *bpp); -extern GstCaps *gst_xvid_csp_to_caps (gint csp, gint w, gint h, - gint fps_n, gint fps_d); +extern gint gst_xvid_structure_to_csp (GstStructure *structure); +extern GstCaps *gst_xvid_csp_to_caps (gint csp, gint w, gint h); +extern gint gst_xvid_image_get_size (gint csp, + gint width, gint height); +extern gint gst_xvid_image_fill (xvid_image_t * im, void * ptr, gint csp, + gint width, gint height); G_END_DECLS diff --git a/ext/xvid/gstxviddec.c b/ext/xvid/gstxviddec.c index 5825dcb0..5aa59b09 100644 --- a/ext/xvid/gstxviddec.c +++ b/ext/xvid/gstxviddec.c @@ -1,5 +1,6 @@ /* GStreamer xvid decoder plugin * Copyright (C) 2003 Ronald Bultje + * (C) 2006 Mark Nauwelaerts * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -17,6 +18,7 @@ * Boston, MA 02111-1307, USA. */ + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -39,7 +41,7 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-xvid, " "width = (int) [ 0, MAX ], " - "height = (int) [ 0, MAX ], " "framerate = (fraction) [0/1, MAX]") + "height = (int) [ 0, MAX ], " "framerate = (fraction) [ 0/1, MAX ]") ); static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", @@ -68,19 +70,32 @@ enum /* FILL ME */ }; +GST_DEBUG_CATEGORY_STATIC (xviddec_debug); +#define GST_CAT_DEFAULT xviddec_debug + static void gst_xviddec_base_init (GstXvidDecClass * klass); static void gst_xviddec_class_init (GstXvidDecClass * klass); static void gst_xviddec_init (GstXvidDec * xviddec); +static void gst_xviddec_reset (GstXvidDec * xviddec); +static gboolean gst_xviddec_handle_sink_event (GstPad * pad, GstEvent * event); static GstFlowReturn gst_xviddec_chain (GstPad * pad, GstBuffer * buf); static gboolean gst_xviddec_setcaps (GstPad * pad, GstCaps * caps); -static gboolean gst_xviddec_negotiate (GstXvidDec * xviddec); +static void gst_xviddec_flush_buffers (GstXvidDec * xviddec, gboolean send); + +#if 0 +static GstPadLinkReturn +gst_xviddec_src_link (GstPad * pad, const GstCaps * vscapslist); +*/static GstCaps *gst_xviddec_src_getcaps (GstPad * pad); +#endif static GstStateChangeReturn gst_xviddec_change_state (GstElement * element, GstStateChange transition); + static GstElementClass *parent_class = NULL; /* static guint gst_xviddec_signals[LAST_SIGNAL] = { 0 }; */ + GType gst_xviddec_get_type (void) { @@ -121,25 +136,31 @@ gst_xviddec_base_init (GstXvidDecClass * klass) static void gst_xviddec_class_init (GstXvidDecClass * klass) { - GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GstElementClass *gstelement_class = (GstElementClass *) klass; parent_class = g_type_class_peek_parent (klass); - gstelement_class->change_state = gst_xviddec_change_state; + GST_DEBUG_CATEGORY_INIT (xviddec_debug, "xviddec", 0, "XviD decoder"); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_xviddec_change_state); } + static void gst_xviddec_init (GstXvidDec * xviddec) { - gst_xvid_init (); - /* create the sink pad */ xviddec->sinkpad = gst_pad_new_from_template (gst_static_pad_template_get (&sink_template), "sink"); gst_element_add_pad (GST_ELEMENT (xviddec), xviddec->sinkpad); - gst_pad_set_chain_function (xviddec->sinkpad, gst_xviddec_chain); - gst_pad_set_setcaps_function (xviddec->sinkpad, gst_xviddec_setcaps); + + gst_pad_set_chain_function (xviddec->sinkpad, + GST_DEBUG_FUNCPTR (gst_xviddec_chain)); + gst_pad_set_setcaps_function (xviddec->sinkpad, + GST_DEBUG_FUNCPTR (gst_xviddec_setcaps)); + gst_pad_set_event_function (xviddec->sinkpad, + GST_DEBUG_FUNCPTR (gst_xviddec_handle_sink_event)); /* create the src pad */ xviddec->srcpad = @@ -148,129 +169,380 @@ gst_xviddec_init (GstXvidDec * xviddec) gst_element_add_pad (GST_ELEMENT (xviddec), xviddec->srcpad); gst_pad_use_fixed_caps (xviddec->srcpad); + gst_xviddec_reset (xviddec); +} + + +static void +gst_xviddec_reset (GstXvidDec * xviddec) +{ /* size, etc. */ xviddec->width = xviddec->height = xviddec->csp = -1; + xviddec->fps_n = xviddec->par_n = -1; + xviddec->fps_d = xviddec->par_d = 1; + xviddec->next_ts = xviddec->next_dur = GST_CLOCK_TIME_NONE; /* set xvid handle to NULL */ xviddec->handle = NULL; + + /* no delayed timestamp to start with */ + xviddec->have_ts = FALSE; + + /* need keyframe to get going */ + xviddec->waiting_for_key = TRUE; } static void gst_xviddec_unset (GstXvidDec * xviddec) { - /* unref this instance */ + /* release XviD decoder */ xvid_decore (xviddec->handle, XVID_DEC_DESTROY, NULL, NULL); xviddec->handle = NULL; } +static gboolean +gst_xviddec_handle_sink_event (GstPad * pad, GstEvent * event) +{ + GstXvidDec *xviddec = GST_XVIDDEC (GST_PAD_PARENT (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + gst_xviddec_flush_buffers (xviddec, TRUE); + break; + case GST_EVENT_FLUSH_STOP: + gst_xviddec_flush_buffers (xviddec, FALSE); + break; + case GST_EVENT_NEWSEGMENT: + /* don't really mind about the actual segment info, + * but we do need to recover from this possible jump */ + /* FIXME, NEWSEGMENT is not a discontinuity. A decoder + * should clip the output to the segment boundaries. + * Also the rate field of the segment can be used to + * optimize the decoding, like skipping B frames when + * playing at double speed. + * The DISCONT flag on buffers should be used to detect + * discontinuities. + */ + xviddec->waiting_for_key = TRUE; + break; + default: + break; + } + + return gst_pad_push_event (xviddec->srcpad, event); +} + + static gboolean gst_xviddec_setup (GstXvidDec * xviddec) { xvid_dec_create_t xdec; - int ret; + gint ret; /* initialise parameters, see xvid documentation */ gst_xvid_init_struct (xdec); - xdec.width = xviddec->width; - xdec.height = xviddec->height; + /* let the decoder handle this, don't trust the container */ + xdec.width = 0; + xdec.height = 0; xdec.handle = NULL; if ((ret = xvid_decore (NULL, XVID_DEC_CREATE, &xdec, NULL)) < 0) { - GST_ELEMENT_ERROR (xviddec, LIBRARY, SETTINGS, (NULL), - ("Setting parameters %dx%d@%d failed: %s (%d)", - xviddec->width, xviddec->height, xviddec->csp, - gst_xvid_error (ret), ret)); + GST_DEBUG_OBJECT (xviddec, + "Initializing xvid decoder with parameters %dx%d@%d failed: %s (%d)", + xviddec->width, xviddec->height, xviddec->csp, + gst_xvid_error (ret), ret); return FALSE; } xviddec->handle = xdec.handle; + return TRUE; } + +static void +gst_xviddec_add_par (GstStructure * structure, + gint mux_par_n, gint mux_par_d, gint dec_par_n, gint dec_par_d) +{ + /* muxer wins if decoder has nothing interesting to offer */ + if (dec_par_n == dec_par_d) { + gst_structure_set (structure, "pixel-aspect-ratio", GST_TYPE_FRACTION, + mux_par_n, mux_par_d, NULL); + } else { + gst_structure_set (structure, "pixel-aspect-ratio", GST_TYPE_FRACTION, + dec_par_n, dec_par_d, NULL); + } +} + + +/* based on the decoder info, if provided, and xviddec info, + construct a caps and send on to src pad */ +static gboolean +gst_xviddec_negotiate (GstXvidDec * xviddec, xvid_dec_stats_t * xstats) +{ + gint par_width, par_height; + GstCaps *caps; + + /* note: setcaps call with no xstats info, + so definitely need to negotiate then */ + if (xstats && (xstats->type != XVID_TYPE_VOL + || (xstats->type == XVID_TYPE_VOL + && xviddec->width == xstats->data.vol.width + && xviddec->height == xstats->data.vol.height))) + return TRUE; + + switch (xstats ? xstats->data.vol.par : XVID_PAR_11_VGA) { + case XVID_PAR_11_VGA: + par_width = par_height = 1; + break; + case XVID_PAR_43_PAL: + case XVID_PAR_43_NTSC: + par_width = 4; + par_height = 3; + break; + case XVID_PAR_169_PAL: + case XVID_PAR_169_NTSC: + par_width = 16; + par_height = 9; + break; + case XVID_PAR_EXT: + default: + par_width = xstats->data.vol.par_width; + par_height = xstats->data.vol.par_height; + } + + caps = gst_xvid_csp_to_caps (xviddec->csp, xviddec->width, xviddec->height); + + /* can only provide framerate if we received one */ + if (xviddec->fps_n != -1) { + gst_structure_set (gst_caps_get_structure (caps, 0), "framerate", + GST_TYPE_FRACTION, xviddec->fps_n, xviddec->fps_d, NULL); + } + + gst_xviddec_add_par (gst_caps_get_structure (caps, 0), + xviddec->par_n, xviddec->par_d, par_width, par_height); + + return gst_pad_set_caps (xviddec->srcpad, caps); +} + + +/* decodes frame according to info in xframe; + - outbuf must not be NULL + - xstats can be NULL, if not, it has been init'ed + - output placed in outbuf, which is also allocated if NULL, + caller must unref when needed + - xvid stats placed in xstats, if not NULL + - xvid return code is returned, which is usually the size of the output frame +*/ +static gint +gst_xviddec_decode (GstXvidDec * xviddec, xvid_dec_frame_t xframe, + GstBuffer ** outbuf, xvid_dec_stats_t * xstats) +{ + + g_return_val_if_fail (outbuf, -1); + + if (!*outbuf) { + gint size = gst_xvid_image_get_size (xviddec->csp, + xviddec->width, xviddec->height); + + gst_pad_alloc_buffer (xviddec->srcpad, GST_BUFFER_OFFSET_NONE, size, + GST_PAD_CAPS (xviddec->srcpad), outbuf); + } + + gst_xvid_image_fill (&xframe.output, GST_BUFFER_DATA (*outbuf), + xviddec->csp, xviddec->width, xviddec->height); + + GST_DEBUG_OBJECT (xviddec, "decoding into buffer %" GST_PTR_FORMAT + ", data %" GST_PTR_FORMAT ", %" GST_PTR_FORMAT, *outbuf, + GST_BUFFER_MALLOCDATA (*outbuf), GST_BUFFER_DATA (*outbuf)); + + return xvid_decore (xviddec->handle, XVID_DEC_DECODE, &xframe, xstats); +} + static GstFlowReturn gst_xviddec_chain (GstPad * pad, GstBuffer * buf) { - GstXvidDec *xviddec = GST_XVIDDEC (gst_pad_get_parent (pad)); + GstXvidDec *xviddec; GstBuffer *outbuf = NULL; xvid_dec_frame_t xframe; - GstFlowReturn ret = GST_FLOW_OK; - guint bufsize; - int error = 0; + xvid_dec_stats_t xstats; + gint ret; + guint8 *data; + guint size; + GstFlowReturn fret; - if (xviddec->handle == NULL) { - if (!gst_xviddec_negotiate (xviddec)) - goto not_negotiated; - } + xviddec = GST_XVIDDEC (GST_OBJECT_PARENT (pad)); - bufsize = (xviddec->width * xviddec->height * xviddec->bpp / 8); + if (!xviddec->handle) + goto not_negotiated; - outbuf = gst_buffer_new_and_alloc (bufsize); + fret = GST_FLOW_OK; - GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buf); - GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buf); - GST_BUFFER_SIZE (outbuf) = bufsize; + GST_DEBUG_OBJECT (xviddec, + "Received buffer of time %" GST_TIME_FORMAT ", size %d", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_SIZE (buf)); - /* decode and so ... */ - gst_xvid_init_struct (xframe); - xframe.general = XVID_LOWDELAY; - xframe.bitstream = (void *) GST_BUFFER_DATA (buf); - xframe.length = GST_BUFFER_SIZE (buf); - xframe.output.csp = xviddec->csp; - if (xviddec->width == xviddec->stride) { - xframe.output.plane[0] = GST_BUFFER_DATA (outbuf); - xframe.output.plane[1] = - xframe.output.plane[0] + (xviddec->width * xviddec->height); - xframe.output.plane[2] = - xframe.output.plane[1] + (xviddec->width * xviddec->height / 4); - xframe.output.stride[0] = xviddec->width; - xframe.output.stride[1] = xviddec->width / 2; - xframe.output.stride[2] = xviddec->width / 2; - } else { - xframe.output.plane[0] = GST_BUFFER_DATA (outbuf); - xframe.output.stride[0] = xviddec->stride; - } + data = GST_BUFFER_DATA (buf); + size = GST_BUFFER_SIZE (buf); + + do { /* loop needed because xvidcore may return vol information */ + /* decode and so ... */ + gst_xvid_init_struct (xframe); + xframe.general = XVID_LOWDELAY; + xframe.bitstream = (void *) data; + xframe.length = size; + + gst_xvid_init_struct (xstats); + + if ((ret = gst_xviddec_decode (xviddec, xframe, &outbuf, &xstats)) < 0) + goto decode_error; + + GST_DEBUG_OBJECT (xviddec, "xvid produced output, type %d, consumed %d", + xstats.type, ret); + + if (xstats.type == XVID_TYPE_VOL) + gst_xviddec_negotiate (xviddec, &xstats); - if ((error = - xvid_decore (xviddec->handle, XVID_DEC_DECODE, &xframe, NULL)) < 0) { - goto not_decoding; + data += ret; + size -= ret; + } while (xstats.type <= 0 && size > 0); + + if (size > 1) /* 1 byte is frequently left over */ + GST_WARNING_OBJECT (xviddec, + "xvid decoder returned frame without consuming all input"); + + /* FIXME, reflow the multiple return exit points */ + if (xstats.type > 0) { /* some real output was produced */ + if (G_UNLIKELY (xviddec->waiting_for_key)) { + if (xstats.type != XVID_TYPE_IVOP) + goto dropping; + + xviddec->waiting_for_key = FALSE; + } + /* bframes can cause a delay in frames being returned + non keyframe timestamps can permute a bit between + encode and display order, but should match for keyframes */ + if (xviddec->have_ts) { + GST_BUFFER_TIMESTAMP (outbuf) = xviddec->next_ts; + GST_BUFFER_DURATION (outbuf) = xviddec->next_dur; + xviddec->next_ts = GST_BUFFER_TIMESTAMP (buf); + xviddec->next_dur = GST_BUFFER_DURATION (buf); + } else { + GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buf); + GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buf); + } + gst_buffer_set_caps (outbuf, GST_PAD_CAPS (xviddec->srcpad)); + + fret = gst_pad_push (xviddec->srcpad, outbuf); + + } else { /* no real output yet, delay in frames being returned */ + if (G_UNLIKELY (xviddec->have_ts)) { + GST_WARNING_OBJECT (xviddec, + "xvid decoder produced no output, but timestamp %" GST_TIME_FORMAT + " already queued", GST_TIME_ARGS (xviddec->next_ts)); + } else { + xviddec->have_ts = TRUE; + xviddec->next_ts = GST_BUFFER_TIMESTAMP (buf); + xviddec->next_dur = GST_BUFFER_TIMESTAMP (buf); + } + gst_buffer_unref (outbuf); } - gst_buffer_set_caps (outbuf, GST_PAD_CAPS (xviddec->srcpad)); - ret = gst_pad_push (xviddec->srcpad, outbuf); +done: + gst_buffer_unref (buf); - goto cleanup; + return fret; + /* ERRORS */ not_negotiated: { GST_ELEMENT_ERROR (xviddec, CORE, NEGOTIATION, (NULL), ("format wasn't negotiated before chain function")); - ret = GST_FLOW_NOT_NEGOTIATED; - goto cleanup; + fret = GST_FLOW_NOT_NEGOTIATED; + goto done; } - -not_decoding: +decode_error: { - GST_ELEMENT_ERROR (xviddec, STREAM, DECODE, (NULL), - ("Error decoding xvid frame: %s (%d)\n", gst_xvid_error (error), - error)); - gst_buffer_unref (outbuf); - ret = GST_FLOW_ERROR; - goto cleanup; + GST_ELEMENT_WARNING (xviddec, STREAM, DECODE, (NULL), + ("Error decoding xvid frame: %s (%d)", gst_xvid_error (ret), ret)); + if (outbuf) + gst_buffer_unref (outbuf); + goto done; } +dropping: + { + GST_WARNING_OBJECT (xviddec, "Dropping non-keyframe (seek/init)"); + if (outbuf) + gst_buffer_unref (outbuf); + goto done; + } +} -cleanup: - gst_buffer_unref (buf); - gst_object_unref (xviddec); - return ret; +/* flush xvid encoder buffers caused by bframe usage; + not well tested */ +static void +gst_xviddec_flush_buffers (GstXvidDec * xviddec, gboolean send) +{ +#if 0 + gint ret; + GstBuffer *outbuf = NULL; + xvid_dec_frame_t xframe; + xvid_dec_stats_t xstats; +#endif + + GST_DEBUG_OBJECT (xviddec, "flushing buffers with send %d, have_ts %d", + send, xviddec->have_ts); + + /* no need to flush if there is no delayed time-stamp */ + if (!xviddec->have_ts) + return; + + /* flushing must reset the timestamp keeping */ + xviddec->have_ts = FALSE; + + /* also no need to flush if no handle */ + if (!xviddec->handle) + return; + /* unlike encoder, decoder does not seem to like flushing, disable for now */ +#if 0 + gst_xvid_init_struct (xframe); + gst_xvid_init_struct (xstats); + + /* init a fake frame to force flushing */ + xframe.bitstream = NULL; + xframe.length = -1; + + ret = gst_xviddec_decode (xviddec, xframe, &outbuf, &xstats); + GST_DEBUG_OBJECT (xviddec, "received frame when flushing, type %d, size %d", + xstats.type, ret); + + if (ret > 0 && send) { + /* we have some valid return frame, give it the delayed timestamp and send */ + GST_BUFFER_TIMESTAMP (outbuf) = xviddec->next_ts; + GST_BUFFER_DURATION (outbuf) = xviddec->next_dur; + + gst_buffer_set_caps (outbuf, GST_PAD_CAPS (xviddec->srcpad)); + gst_pad_push (xviddec->srcpad, outbuf); + return; + } + + if (outbuf) + gst_buffer_unref (outbuf); +#else + return; +#endif } -static gboolean -gst_xviddec_negotiate (GstXvidDec * xviddec) +#if 0 +static GstCaps * +gst_xviddec_src_getcaps (GstPad * pad) { + GstXvidDec *xviddec = GST_XVIDDEC (GST_PAD_PARENT (pad)); + GstCaps *caps; gint csp[] = { XVID_CSP_I420, XVID_CSP_YV12, @@ -289,75 +561,125 @@ gst_xviddec_negotiate (GstXvidDec * xviddec) 0 }, i; + if (!GST_PAD_CAPS (xviddec->sinkpad)) { + GstPadTemplate *templ = gst_static_pad_template_get (&src_template); + + return gst_caps_copy (gst_pad_template_get_caps (templ)); + } + + caps = gst_caps_new_empty (); for (i = 0; csp[i] != 0; i++) { GstCaps *one = gst_xvid_csp_to_caps (csp[i], xviddec->width, - xviddec->height, xviddec->fps_n, xviddec->fps_d); + xviddec->height, xviddec->fps, xviddec->par); - if (one) { + gst_caps_append (caps, one); + } - if (gst_pad_set_caps (xviddec->srcpad, one)) { - GstStructure *structure = gst_caps_get_structure (one, 0); + return caps; +} - xviddec->csp = gst_xvid_structure_to_csp (structure, xviddec->width, - &xviddec->stride, &xviddec->bpp); - if (xviddec->csp < 0) { - return FALSE; - } +static GstPadLinkReturn +gst_xviddec_src_link (GstPad * pad, const GstCaps * vscaps) +{ + GstXvidDec *xviddec = GST_XVIDDEC (gst_pad_get_parent (pad)); + GstStructure *structure = gst_caps_get_structure (vscaps, 0); - break; - } + if (!GST_PAD_CAPS (xviddec->sinkpad)) + return GST_PAD_LINK_DELAYED; - gst_caps_unref (one); + /* if there's something old around, remove it */ + if (xviddec->handle) { + gst_xviddec_unset (xviddec); + } + xviddec->csp = gst_xvid_structure_to_csp (structure); - } + if (xviddec->csp < 0) + return GST_PAD_LINK_REFUSED; - } + if (!gst_xviddec_setup (xviddec)) + return GST_PAD_LINK_REFUSED; - gst_xviddec_setup (xviddec); - return TRUE; + return GST_PAD_LINK_OK; } +#endif static gboolean gst_xviddec_setcaps (GstPad * pad, GstCaps * caps) { - GstXvidDec *xviddec = GST_XVIDDEC (gst_pad_get_parent (pad)); + GstXvidDec *xviddec = GST_XVIDDEC (GST_PAD_PARENT (pad)); GstStructure *structure; - const GValue *fps; - gboolean ret = FALSE; + const GValue *val; + + GST_DEBUG ("setcaps called"); /* if there's something old around, remove it */ if (xviddec->handle) { gst_xviddec_unset (xviddec); } - if (!gst_pad_set_caps (xviddec->srcpad, caps)) { - ret = FALSE; - goto done; - } - - /* if we get here, we know the input is xvid. we - * only need to bother with the output colorspace, - * which the src_link function takes care of. */ structure = gst_caps_get_structure (caps, 0); gst_structure_get_int (structure, "width", &xviddec->width); gst_structure_get_int (structure, "height", &xviddec->height); - fps = gst_structure_get_value (structure, "framerate"); - if (fps != NULL) { - xviddec->fps_n = gst_value_get_fraction_numerator (fps); - xviddec->fps_d = gst_value_get_fraction_denominator (fps); + /* perhaps some fps info */ + val = gst_structure_get_value (structure, "framerate"); + if ((val != NULL) && GST_VALUE_HOLDS_FRACTION (val)) { + xviddec->fps_n = gst_value_get_fraction_numerator (val); + xviddec->fps_d = gst_value_get_fraction_denominator (val); } else { xviddec->fps_n = -1; + xviddec->fps_d = 1; } - ret = gst_xviddec_negotiate (xviddec); + /* perhaps some par info */ + val = gst_structure_get_value (structure, "pixel-aspect-ratio"); + if ((val != NULL) && GST_VALUE_HOLDS_FRACTION (val)) { + xviddec->par_n = gst_value_get_fraction_numerator (val); + xviddec->par_d = gst_value_get_fraction_denominator (val); + } else { + xviddec->par_n = 1; + xviddec->par_d = 1; + } -done: - gst_object_unref (xviddec); + if (gst_xviddec_setup (xviddec)) { + GstCaps *allowed_caps; + + /* we try to find the preferred/accept csp */ + allowed_caps = gst_pad_get_allowed_caps (xviddec->srcpad); + if (!allowed_caps) { + GST_DEBUG_OBJECT (xviddec, "... but no peer, using template caps"); + /* need to copy because get_allowed_caps returns a ref, + and get_pad_template_caps doesn't */ + allowed_caps = + gst_caps_copy (gst_pad_get_pad_template_caps (xviddec->srcpad)); + } + /* pick the first one ... */ + structure = gst_caps_get_structure (allowed_caps, 0); + val = gst_structure_get_value (structure, "format"); + if (G_VALUE_TYPE (val) == GST_TYPE_LIST) { + GValue temp = { 0 }; + gst_value_init_and_copy (&temp, gst_value_list_get_value (val, 0)); + gst_structure_set_value (structure, "format", &temp); + g_value_unset (&temp); + } - return ret; + /* ... and use its info to get the csp */ + xviddec->csp = gst_xvid_structure_to_csp (structure); + if (xviddec->csp == -1) { + gchar *sstr = gst_structure_to_string (structure); + GST_INFO_OBJECT (xviddec, + "failed to decide upon csp from caps %s, trying I420", sstr); + g_free (sstr); + xviddec->csp = XVID_CSP_I420; + } + gst_caps_unref (allowed_caps); + + return gst_xviddec_negotiate (xviddec, NULL); + + } else /* setup did not work out */ + return FALSE; } static GstStateChangeReturn @@ -368,28 +690,29 @@ gst_xviddec_change_state (GstElement * element, GstStateChange transition) switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + if (!gst_xvid_init ()) + return GST_STATE_CHANGE_FAILURE; break; default: break; } - ret = parent_class->change_state (element, transition); + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + goto done; switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_xviddec_flush_buffers (xviddec, FALSE); if (xviddec->handle) { gst_xviddec_unset (xviddec); } - break; - case GST_STATE_CHANGE_READY_TO_NULL: + gst_xviddec_reset (xviddec); break; default: break; } +done: return ret; } diff --git a/ext/xvid/gstxviddec.h b/ext/xvid/gstxviddec.h index e5741458..fb128354 100644 --- a/ext/xvid/gstxviddec.h +++ b/ext/xvid/gstxviddec.h @@ -23,10 +23,7 @@ #include #include "gstxvid.h" -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - +G_BEGIN_DECLS #define GST_TYPE_XVIDDEC \ (gst_xviddec_get_type()) @@ -52,9 +49,16 @@ struct _GstXvidDec { void *handle; /* video (output) settings */ - gint csp, bpp, stride; + gint csp; gint width, height; - gint fps_n, fps_d; + gint fps_n, fps_d, par_n, par_d; + + /* whether in need for keyframe */ + gboolean waiting_for_key; + + /* retain some info on delayed frame */ + gboolean have_ts; + GstClockTime next_ts, next_dur; }; struct _GstXvidDecClass { @@ -63,8 +67,6 @@ struct _GstXvidDecClass { GType gst_xviddec_get_type(void); -#ifdef __cplusplus -} -#endif /* __cplusplus */ +G_END_DECLS #endif /* __GST_XVIDDEC_H__ */ diff --git a/ext/xvid/gstxvidenc.c b/ext/xvid/gstxvidenc.c index 9bbb5393..4b185516 100644 --- a/ext/xvid/gstxvidenc.c +++ b/ext/xvid/gstxvidenc.c @@ -1,5 +1,6 @@ /* GStreamer xvid encoder plugin * Copyright (C) 2003 Ronald Bultje + * (C) 2006 Mark Nauwelaerts * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -17,6 +18,13 @@ * Boston, MA 02111-1307, USA. */ +/* based on: + * - the original xvidenc (by Ronald Bultje) + * - transcode/mplayer's xvid encoder (by Edouard Gomez) + * + * TODO some documentation (e.g. on properties) + */ + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -52,39 +60,34 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-xvid, " "width = (int) [ 0, MAX ], " - "height = (int) [ 0, MAX ], " "framerate = (fraction) [0/1, MAX]") + "height = (int) [ 0, MAX ], " "framerate = (fraction) [ 0/1, MAX ]") ); -/* XvidEnc signals and args */ +/* XvidEnc signals and properties */ enum { FRAME_ENCODED, LAST_SIGNAL }; -enum -{ - ARG_0, - ARG_PROFILE, - ARG_BITRATE, - ARG_MAXKEYINTERVAL, - ARG_BUFSIZE - /* FILL ME: - * - ME - * - VOP - * - VOL - * - PAR - * - max b frames - */ -}; +/* maximum property-id */ +static int xvidenc_prop_count; + +/* quark used for named pointer on param specs */ +static GQuark xvidenc_pspec_quark; -static void gst_xvidenc_base_init (gpointer g_class); +GST_DEBUG_CATEGORY_STATIC (xvidenc_debug); +#define GST_CAT_DEFAULT xvidenc_debug + +static void gst_xvidenc_base_init (GstXvidEncClass * klass); static void gst_xvidenc_class_init (GstXvidEncClass * klass); static void gst_xvidenc_init (GstXvidEnc * xvidenc); -static GstFlowReturn gst_xvidenc_chain (GstPad * pad, GstBuffer * buf); -static gboolean gst_xvidenc_setcaps (GstPad * pad, GstCaps * caps); - +static void gst_xvidenc_finalize (GObject * object); +static GstFlowReturn gst_xvidenc_chain (GstPad * pad, GstBuffer * data); +static gboolean gst_xvidenc_setcaps (GstPad * pad, GstCaps * vscapslist); +static void gst_xvidenc_flush_buffers (GstXvidEnc * xvidenc, gboolean send); +static gboolean gst_xvidenc_handle_sink_event (GstPad * pad, GstEvent * event); /* properties */ static void gst_xvidenc_set_property (GObject * object, @@ -98,7 +101,6 @@ static GstElementClass *parent_class = NULL; static guint gst_xvidenc_signals[LAST_SIGNAL] = { 0 }; #define GST_TYPE_XVIDENC_PROFILE (gst_xvidenc_profile_get_type ()) - static GType gst_xvidenc_profile_get_type (void) { @@ -106,6 +108,7 @@ gst_xvidenc_profile_get_type (void) if (!xvidenc_profile_type) { static const GEnumValue xvidenc_profiles[] = { + {0, "UNP", "Unrestricted profile"}, {XVID_PROFILE_S_L0, "S_L0", "Simple profile, L0"}, {XVID_PROFILE_S_L1, "S_L1", "Simple profile, L1"}, {XVID_PROFILE_S_L2, "S_L2", "Simple profile, L2"}, @@ -133,6 +136,58 @@ gst_xvidenc_profile_get_type (void) return xvidenc_profile_type; } +#define GST_TYPE_XVIDENC_QUANT_TYPE (gst_xvidenc_quant_type_get_type ()) +static GType +gst_xvidenc_quant_type_get_type (void) +{ + static GType xvidenc_quant_type_type = 0; + + if (!xvidenc_quant_type_type) { + static const GEnumValue xvidenc_quant_types[] = { + {0, "H263 quantization", "h263"}, + {XVID_VOL_MPEGQUANT, "MPEG quantization", "mpeg"}, + {0, NULL, NULL}, + }; + + xvidenc_quant_type_type = + g_enum_register_static ("GstXvidEncQuantTypes", xvidenc_quant_types); + } + + return xvidenc_quant_type_type; +} + + +enum +{ + XVIDENC_CBR, + XVIDENC_VBR_PASS1, + XVIDENC_VBR_PASS2, + XVIDENC_QUANT +}; + +#define GST_TYPE_XVIDENC_PASS (gst_xvidenc_pass_get_type ()) +static GType +gst_xvidenc_pass_get_type (void) +{ + static GType xvidenc_pass_type = 0; + + if (!xvidenc_pass_type) { + static const GEnumValue xvidenc_passes[] = { + {XVIDENC_CBR, "Constant Bitrate Encoding", "cbr"}, + {XVIDENC_QUANT, "Constant Quantizer", "quant"}, + {XVIDENC_VBR_PASS1, "VBR Encoding - Pass 1", "pass1"}, + {XVIDENC_VBR_PASS2, "VBR Encoding - Pass 2", "pass2"}, + {0, NULL, NULL}, + }; + + xvidenc_pass_type = + g_enum_register_static ("GstXvidEncPasses", xvidenc_passes); + } + + return xvidenc_pass_type; +} + + GType gst_xvidenc_get_type (void) { @@ -141,7 +196,7 @@ gst_xvidenc_get_type (void) if (!xvidenc_type) { static const GTypeInfo xvidenc_info = { sizeof (GstXvidEncClass), - gst_xvidenc_base_init, + (GBaseInitFunc) gst_xvidenc_base_init, NULL, (GClassInitFunc) gst_xvidenc_class_init, NULL, @@ -158,9 +213,9 @@ gst_xvidenc_get_type (void) } static void -gst_xvidenc_base_init (gpointer g_class) +gst_xvidenc_base_init (GstXvidEncClass * klass) { - GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&sink_template)); @@ -169,332 +224,878 @@ gst_xvidenc_base_init (gpointer g_class) gst_element_class_set_details (element_class, &gst_xvidenc_details); } +/* add property pspec to klass using the counter count, + * and place info based on struct_type and member as a named pointer + * specified by quark */ +#define gst_xvidenc_add_pspec_full(klass, pspec, count, quark, \ + struct_type, member) \ +G_STMT_START { \ + guint _offset = G_STRUCT_OFFSET (struct_type, member); \ + g_param_spec_set_qdata (pspec, quark, \ + GUINT_TO_POINTER (_offset)); \ + g_object_class_install_property (klass, ++count, pspec); \ +} G_STMT_END + +#define gst_xvidenc_add_pspec(klass, pspec, member) \ + gst_xvidenc_add_pspec_full (klass, pspec, xvidenc_prop_count, \ + xvidenc_pspec_quark, GstXvidEnc, member) + +/* using the above system, property maintenance is centralized here + * (_get_property, _set_property and setting of default value in _init) + * follow automatically, it only remains to actually use it in the code + * (which may include free-ing in finalize) */ + static void gst_xvidenc_class_init (GstXvidEncClass * klass) { - GstElementClass *gstelement_class = (GstElementClass *) klass; - GObjectClass *gobject_class = (GObjectClass *) klass; - - parent_class = g_type_class_peek_parent (klass); - - gobject_class->set_property = gst_xvidenc_set_property; - gobject_class->get_property = gst_xvidenc_get_property; + GstElementClass *gstelement_class; + GObjectClass *gobject_class; + GParamSpec *pspec; - /* encoding profile */ - g_object_class_install_property (gobject_class, ARG_PROFILE, - g_param_spec_enum ("profile", "Profile", "XviD/MPEG-4 encoding profile", - GST_TYPE_XVIDENC_PROFILE, XVID_PROFILE_S_L0, G_PARAM_READWRITE)); + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); - /* bitrate */ - g_object_class_install_property (gobject_class, ARG_BITRATE, - g_param_spec_int ("bitrate", "Bitrate", - "Target video bitrate (kbps)", 0, G_MAXINT, 512, G_PARAM_READWRITE)); + parent_class = g_type_class_peek_parent (klass); - /* keyframe interval */ - g_object_class_install_property (gobject_class, ARG_MAXKEYINTERVAL, - g_param_spec_int ("max_key_interval", "Max. Key Interval", - "Maximum number of frames between two keyframes", - -1, G_MAXINT, -1, G_PARAM_READWRITE)); + GST_DEBUG_CATEGORY_INIT (xvidenc_debug, "xvidenc", 0, "XviD encoder"); - g_object_class_install_property (gobject_class, ARG_BUFSIZE, - g_param_spec_ulong ("buffer_size", "Buffer Size", - "Size of the video buffers", 0, G_MAXULONG, 0, G_PARAM_READWRITE)); + gobject_class->finalize = gst_xvidenc_finalize; - gstelement_class->change_state = gst_xvidenc_change_state; + gobject_class->set_property = gst_xvidenc_set_property; + gobject_class->get_property = gst_xvidenc_get_property; + /* prop handling */ + xvidenc_prop_count = 0; + xvidenc_pspec_quark = g_quark_from_static_string ("xvid-enc-param-spec-data"); + + pspec = g_param_spec_enum ("profile", "Profile", + "XviD/MPEG-4 encoding profile", + GST_TYPE_XVIDENC_PROFILE, 0, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, profile); + + pspec = g_param_spec_enum ("quant-type", "Quantizer Type", + "Quantizer type", GST_TYPE_XVIDENC_QUANT_TYPE, 0, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, quant_type); + + pspec = g_param_spec_enum ("pass", "Encoding pass/type", + "Encoding pass/type", + GST_TYPE_XVIDENC_PASS, XVIDENC_CBR, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, pass); + + pspec = g_param_spec_int ("bitrate", "Bitrate", + "[CBR|PASS2] Target video bitrate (bps)", + 0, G_MAXINT, 1800000, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, bitrate); + + pspec = g_param_spec_int ("quantizer", "Quantizer", + "[QUANT] Quantizer to apply for constant quantizer mode", + 2, 31, 2, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, quant); + + pspec = g_param_spec_string ("statsfile", "Statistics Filename", + "[PASS1|PASS2] Filename to store data for 2-pass encoding", + "xvid-stats.log", G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, filename); + + pspec = g_param_spec_int ("max-key-interval", "Max. Key Interval", + "Maximum number of frames between two keyframes (< 0 is in sec)", + -100, G_MAXINT, -10, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, max_key_interval); + + pspec = g_param_spec_boolean ("closed-gop", "Closed GOP", + "Closed GOP", FALSE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, closed_gop); + + pspec = g_param_spec_int ("motion", "ME Quality", + "Quality of Motion Estimation", 0, 6, 6, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, motion); + + pspec = g_param_spec_boolean ("me-chroma", "ME Chroma", + "Enable use of Chroma planes for Motion Estimation", + TRUE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, me_chroma); + + pspec = g_param_spec_int ("me-vhq", "ME DCT/Frequency", + "Extent in which to use DCT to minimize encoding length", + 0, 4, 1, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, me_vhq); + + pspec = g_param_spec_boolean ("me-quarterpel", "ME Quarterpel", + "Use quarter pixel precision for motion vector search", + FALSE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, me_quarterpel); + + pspec = g_param_spec_boolean ("lumimasking", "Lumimasking", + "Enable lumimasking - apply more compression to dark or bright areas", + FALSE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, lumimasking); + + pspec = g_param_spec_int ("max-bframes", "Max B-Frames", + "Maximum B-frames in a row", 0, G_MAXINT, 1, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, max_bframes); + + pspec = g_param_spec_int ("bquant-ratio", "B-quantizer ratio", + "Ratio in B-frame quantizer computation", 0, 200, 150, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, bquant_ratio); + + pspec = g_param_spec_int ("bquant-offset", "B-quantizer offset", + "Offset in B-frame quantizer computation", + 0, 200, 100, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, bquant_offset); + + pspec = g_param_spec_int ("bframe-threshold", "B-Frame Threshold", + "Higher threshold yields more chance that B-frame is used", + -255, 255, 0, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, bframe_threshold); + + pspec = g_param_spec_boolean ("gmc", "Global Motion Compensation", + "Allow generation of Sprite Frames for Pan/Zoom/Rotating images", + FALSE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, gmc); + + pspec = g_param_spec_boolean ("trellis", "Trellis Quantization", + "Enable Trellis Quantization", FALSE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, trellis); + + pspec = g_param_spec_boolean ("interlaced", "Interlaced Material", + "Enable for interlaced video material", FALSE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, interlaced); + + pspec = g_param_spec_boolean ("cartoon", "Cartoon Material", + "Adjust thresholds for flat looking cartoons", FALSE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, cartoon); + + pspec = g_param_spec_boolean ("greyscale", "Disable Chroma", + "Do not write chroma data in encoded video", FALSE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, greyscale); + + pspec = g_param_spec_boolean ("hqacpred", "High quality AC prediction", + "Enable high quality AC prediction", TRUE, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, hqacpred); + + pspec = g_param_spec_int ("max-iquant", "Max Quant I-Frames", + "Upper bound for I-frame quantization", 0, 31, 31, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, max_iquant); + + pspec = g_param_spec_int ("min-iquant", "Min Quant I-Frames", + "Lower bound for I-frame quantization", 0, 31, 2, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, min_iquant); + + pspec = g_param_spec_int ("max-pquant", "Max Quant P-Frames", + "Upper bound for P-frame quantization", 0, 31, 31, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, max_pquant); + + pspec = g_param_spec_int ("min-pquant", "Min Quant P-Frames", + "Lower bound for P-frame quantization", 0, 31, 2, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, min_pquant); + + pspec = g_param_spec_int ("max-bquant", "Max Quant B-Frames", + "Upper bound for B-frame quantization", 0, 31, 31, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, max_bquant); + + pspec = g_param_spec_int ("min-bquant", "Min Quant B-Frames", + "Lower bound for B-frame quantization", 0, 31, 2, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, min_bquant); + + pspec = g_param_spec_int ("reaction-delay-factor", "Reaction Delay Factor", + "[CBR] Reaction delay factor", -1, 100, -1, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, reaction_delay_factor); + + pspec = g_param_spec_int ("averaging-period", "Averaging Period", + "[CBR] Number of frames for which XviD averages bitrate", + -1, 100, -1, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, averaging_period); + + pspec = g_param_spec_int ("buffer", "Buffer Size", + "[CBR] Size of the video buffers", -1, G_MAXINT, -1, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, buffer); + + pspec = g_param_spec_int ("keyframe-boost", "Keyframe boost", + "[PASS2] Bitrate boost for keyframes", 0, 100, 0, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, keyframe_boost); + + pspec = g_param_spec_int ("curve-compression-high", "Curve Compression High", + "[PASS2] Shrink factor for upper part of bitrate curve", + 0, 100, 0, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, curve_compression_high); + + pspec = g_param_spec_int ("curve-compression-low", "Curve Compression Low", + "[PASS2] Growing factor for lower part of bitrate curve", + 0, 100, 0, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, curve_compression_low); + + pspec = g_param_spec_int ("flow-control-strength", "Flow Control Strength", + "[PASS2] Overflow control strength per frame", + -1, 100, 5, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, overflow_control_strength); + + pspec = + g_param_spec_int ("max-overflow-improvement", "Max Overflow Improvement", + "[PASS2] Amount in % that flow control can increase frame size compared to ideal curve", + -1, 100, 5, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, max_overflow_improvement); + + pspec = + g_param_spec_int ("max-overflow-degradation", "Max Overflow Degradation", + "[PASS2] Amount in % that flow control can decrease frame size compared to ideal curve", + -1, 100, 5, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, max_overflow_degradation); + + pspec = g_param_spec_int ("keyframe-reduction", "Keyframe Reduction", + "[PASS2] Keyframe size reduction in % of those within threshold", + -1, 100, 20, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, kfreduction); + + pspec = g_param_spec_int ("keyframe-threshold", "Keyframe Threshold", + "[PASS2] Distance between keyframes not to be subject to reduction", + -1, 100, 1, G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, kfthreshold); + + pspec = + g_param_spec_int ("container-frame-overhead", "Container Frame Overhead", + "[PASS2] Average container overhead per frame", -1, 100, -1, + G_PARAM_READWRITE); + gst_xvidenc_add_pspec (gobject_class, pspec, container_frame_overhead); + + /* signals */ gst_xvidenc_signals[FRAME_ENCODED] = g_signal_new ("frame-encoded", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstXvidEncClass, frame_encoded), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_xvidenc_change_state); } static void gst_xvidenc_init (GstXvidEnc * xvidenc) { - gst_xvid_init (); + GParamSpec **pspecs; + gint i, num_props; /* create the sink pad */ xvidenc->sinkpad = gst_pad_new_from_template (gst_static_pad_template_get (&sink_template), "sink"); - gst_pad_set_chain_function (xvidenc->sinkpad, gst_xvidenc_chain); - gst_pad_set_setcaps_function (xvidenc->sinkpad, gst_xvidenc_setcaps); gst_element_add_pad (GST_ELEMENT (xvidenc), xvidenc->sinkpad); + gst_pad_set_chain_function (xvidenc->sinkpad, + GST_DEBUG_FUNCPTR (gst_xvidenc_chain)); + gst_pad_set_setcaps_function (xvidenc->sinkpad, + GST_DEBUG_FUNCPTR (gst_xvidenc_setcaps)); + gst_pad_set_event_function (xvidenc->sinkpad, + GST_DEBUG_FUNCPTR (gst_xvidenc_handle_sink_event)); + /* create the src pad */ xvidenc->srcpad = gst_pad_new_from_template (gst_static_pad_template_get (&src_template), "src"); gst_element_add_pad (GST_ELEMENT (xvidenc), xvidenc->srcpad); + gst_pad_use_fixed_caps (xvidenc->srcpad); + + /* init properties. */ + xvidenc->width = xvidenc->height = xvidenc->csp = -1; + xvidenc->par_width = xvidenc->par_height = 1; + + /* set defaults for user properties */ + pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (xvidenc), + &num_props); + + for (i = 0; i < num_props; ++i) { + GValue val = { 0, }; + GParamSpec *pspec = pspecs[i]; + + /* only touch those that are really ours; i.e. should have some qdata */ + if (!g_param_spec_get_qdata (pspec, xvidenc_pspec_quark)) + continue; + g_value_init (&val, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_param_value_set_default (pspec, &val); + g_object_set_property (G_OBJECT (xvidenc), g_param_spec_get_name (pspec), + &val); + } - /* bitrate, etc. */ - xvidenc->width = xvidenc->height = xvidenc->csp = xvidenc->stride = -1; - xvidenc->profile = XVID_PROFILE_S_L0; - xvidenc->bitrate = 512; - xvidenc->max_b_frames = 0; - xvidenc->max_key_interval = -1; /* default - 2*fps */ - xvidenc->buffer_size = 512; + g_free (pspecs); /* set xvid handle to NULL */ xvidenc->handle = NULL; + + /* get a queue to keep time info if frames get delayed */ + xvidenc->delay = NULL; + + /* cache some xvid data so need not rebuild for each frame */ + xvidenc->xframe_cache = NULL; } +static void +gst_xvidenc_finalize (GObject * object) +{ + + GstXvidEnc *xvidenc = GST_XVIDENC (object); + + g_free (xvidenc->filename); +} + +static gboolean +gst_xvidenc_handle_sink_event (GstPad * pad, GstEvent * event) +{ + GstXvidEnc *xvidenc = GST_XVIDENC (GST_PAD_PARENT (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + gst_xvidenc_flush_buffers (xvidenc, TRUE); + break; + /* no flushing if flush received, + buffers in encoder are considered (in the) past */ + default: + break; + } + + return gst_pad_push_event (xvidenc->srcpad, event); +} + static gboolean gst_xvidenc_setup (GstXvidEnc * xvidenc) { xvid_enc_create_t xenc; - xvid_enc_plugin_t xplugin; - xvid_plugin_single_t xsingle; + xvid_enc_plugin_t xplugin[2]; gint ret; /* see xvid.h for the meaning of all this. */ gst_xvid_init_struct (xenc); + xenc.profile = xvidenc->profile; xenc.width = xvidenc->width; xenc.height = xvidenc->height; - xenc.max_bframes = xvidenc->max_b_frames; - xenc.global = XVID_GLOBAL_PACKED; - - /* frame duration = fincr/fbase, is inverse of framerate */ - xenc.fincr = xvidenc->fps_d; - xenc.fbase = xvidenc->fps_n; - xenc.max_key_interval = (xvidenc->max_key_interval == -1) ? - (2 * xenc.fbase / xenc.fincr) : xvidenc->max_key_interval; + xenc.max_bframes = xvidenc->max_bframes; + xenc.global = XVID_GLOBAL_PACKED + | (xvidenc->closed_gop ? XVID_GLOBAL_CLOSED_GOP : 0); + + xenc.bquant_ratio = xvidenc->bquant_ratio; + xenc.bquant_offset = xvidenc->bquant_offset; + + xenc.fbase = xvidenc->fbase; + xenc.fincr = xvidenc->fincr; + xenc.max_key_interval = (xvidenc->max_key_interval < 0) ? + (-xvidenc->max_key_interval * xenc.fbase / + xenc.fincr) : xvidenc->max_key_interval; xenc.handle = NULL; - /* CBR bitrate/quant for now */ - gst_xvid_init_struct (xsingle); - xsingle.bitrate = xvidenc->bitrate << 10; - xsingle.reaction_delay_factor = -1; - xsingle.averaging_period = -1; - xsingle.buffer = -1; + /* quantizer ranges */ + xenc.min_quant[0] = xvidenc->min_iquant; + xenc.min_quant[1] = xvidenc->min_pquant; + xenc.min_quant[2] = xvidenc->min_bquant; + xenc.max_quant[0] = xvidenc->max_iquant; + xenc.max_quant[1] = xvidenc->max_pquant; + xenc.max_quant[2] = xvidenc->max_bquant; - /* set CBR plugin */ + /* cbr, vbr or constant quantizer */ xenc.num_plugins = 1; - xenc.plugins = &xplugin; - xenc.plugins[0].func = xvid_plugin_single; - xenc.plugins[0].param = &xsingle; + xenc.plugins = xplugin; + switch (xvidenc->pass) { + case XVIDENC_CBR: + case XVIDENC_QUANT: + { + xvid_plugin_single_t xsingle; + xvid_enc_zone_t xzone; + + gst_xvid_init_struct (xsingle); + + xenc.plugins[0].func = xvid_plugin_single; + xenc.plugins[0].param = &xsingle; + + xsingle.bitrate = xvidenc->bitrate; + xsingle.reaction_delay_factor = MAX (0, xvidenc->reaction_delay_factor); + xsingle.averaging_period = MAX (0, xvidenc->averaging_period); + xsingle.buffer = MAX (0, xvidenc->buffer); + + if (xvidenc->pass == XVIDENC_CBR) + break; + + /* set up a const quantizer zone */ + xzone.mode = XVID_ZONE_QUANT; + xzone.frame = 0; + xzone.increment = xvidenc->quant; + xzone.base = 1; + xenc.zones = &xzone; + xenc.num_zones++; - if ((ret = xvid_encore (NULL, XVID_ENC_CREATE, &xenc, NULL)) < 0) { - GST_ELEMENT_ERROR (xvidenc, LIBRARY, INIT, (NULL), - ("Error setting up xvid encoder: %s (%d)", gst_xvid_error (ret), ret)); - return FALSE; - } + break; + } + case XVIDENC_VBR_PASS1: + { + xvid_plugin_2pass1_t xpass; - xvidenc->handle = xenc.handle; + gst_xvid_init_struct (xpass); - return TRUE; -} + xenc.plugins[0].func = xvid_plugin_2pass1; + xenc.plugins[0].param = &xpass; - -static GstFlowReturn -gst_xvidenc_chain (GstPad * pad, GstBuffer * buf) -{ - GstXvidEnc *xvidenc = GST_XVIDENC (gst_pad_get_parent (pad)); - GstBuffer *outbuf; - xvid_enc_frame_t xframe; - xvid_enc_stats_t xstats; - gint res; - GstFlowReturn ret = GST_FLOW_OK; - - outbuf = gst_buffer_new_and_alloc (xvidenc->buffer_size << 10); - GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buf); - GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buf); - - /* encode and so ... */ - gst_xvid_init_struct (xframe); - xframe.vol_flags = XVID_VOL_MPEGQUANT | XVID_VOL_GMC; - xframe.par = XVID_PAR_11_VGA; - xframe.vop_flags = XVID_VOP_TRELLISQUANT; - xframe.motion = 0; - xframe.input.csp = xvidenc->csp; - if (xvidenc->width == xvidenc->stride) { - xframe.input.plane[0] = GST_BUFFER_DATA (buf); - xframe.input.plane[1] = - xframe.input.plane[0] + (xvidenc->width * xvidenc->height); - xframe.input.plane[2] = - xframe.input.plane[1] + (xvidenc->width * xvidenc->height / 4); - xframe.input.stride[0] = xvidenc->width; - xframe.input.stride[1] = xvidenc->width / 2; - xframe.input.stride[2] = xvidenc->width / 2; - } else { - xframe.input.plane[0] = GST_BUFFER_DATA (buf); - xframe.input.stride[0] = xvidenc->stride; + xpass.filename = xvidenc->filename; + break; + } + case XVIDENC_VBR_PASS2: + { + xvid_plugin_2pass2_t xpass; + + gst_xvid_init_struct (xpass); + + xenc.plugins[0].func = xvid_plugin_2pass2; + xenc.plugins[0].param = &xpass; + + xpass.bitrate = xvidenc->bitrate; + xpass.filename = xvidenc->filename; + xpass.keyframe_boost = xvidenc->keyframe_boost; + xpass.curve_compression_high = xvidenc->curve_compression_high; + xpass.curve_compression_low = xvidenc->curve_compression_low; + xpass.overflow_control_strength = + MAX (0, xvidenc->overflow_control_strength); + xpass.max_overflow_improvement = + MAX (0, xvidenc->max_overflow_improvement); + xpass.max_overflow_degradation = + MAX (0, xvidenc->max_overflow_degradation); + xpass.kfreduction = MAX (0, xvidenc->kfreduction); + xpass.kfthreshold = MAX (0, xvidenc->kfthreshold); + xpass.container_frame_overhead = + MAX (0, xvidenc->container_frame_overhead); + break; + } } - xframe.type = XVID_TYPE_AUTO; - xframe.bitstream = (void *) GST_BUFFER_DATA (outbuf); - xframe.length = GST_BUFFER_SIZE (outbuf); /* GST_BUFFER_MAXSIZE */ - gst_xvid_init_struct (xstats); - if ((res = xvid_encore (xvidenc->handle, XVID_ENC_ENCODE, - &xframe, &xstats)) < 0) { - GST_ELEMENT_ERROR (xvidenc, LIBRARY, ENCODE, (NULL), - ("Error encoding xvid frame: %s (%d)", gst_xvid_error (res), res)); - gst_buffer_unref (outbuf); - ret = GST_FLOW_ERROR; - goto cleanup; + if (xvidenc->lumimasking) { + xenc.plugins[1].func = xvid_plugin_lumimasking; + xenc.plugins[1].param = NULL; + xenc.num_plugins++; } - GST_BUFFER_SIZE (outbuf) = xstats.length; - - /* mark whether key-frame = !delta-unit or not */ - if (!(xframe.out_flags & XVID_KEYFRAME)) - GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); - - /* go out, multiply! */ - gst_buffer_set_caps (outbuf, GST_PAD_CAPS (xvidenc->srcpad)); - ret = gst_pad_push (xvidenc->srcpad, outbuf); - - /* proclaim destiny */ - g_signal_emit (G_OBJECT (xvidenc), gst_xvidenc_signals[FRAME_ENCODED], 0); - - /* until the final judgement */ + if ((ret = xvid_encore (NULL, XVID_ENC_CREATE, &xenc, NULL)) < 0) { + GST_DEBUG_OBJECT (xvidenc, "Error setting up xvid encoder: %s (%d)", + gst_xvid_error (ret), ret); + return FALSE; + } -cleanup: + xvidenc->handle = xenc.handle; - gst_buffer_unref (buf); - gst_object_unref (xvidenc); - return ret; + return TRUE; } - static gboolean -gst_xvidenc_setcaps (GstPad * pad, GstCaps * caps) +gst_xvidenc_setcaps (GstPad * pad, GstCaps * vscaps) { - GstCaps *new_caps = NULL; GstXvidEnc *xvidenc; - GstStructure *structure = gst_caps_get_structure (caps, 0); - const gchar *mime; + GstStructure *structure; gint w, h; - const GValue *fps; - gint xvid_cs = -1, stride = -1; - gboolean ret = FALSE; + const GValue *fps, *par; + gint xvid_cs = -1; - xvidenc = GST_XVIDENC (gst_pad_get_parent (pad)); + xvidenc = GST_XVIDENC (GST_PAD_PARENT (pad)); /* if there's something old around, remove it */ if (xvidenc->handle) { + gst_xvidenc_flush_buffers (xvidenc, TRUE); xvid_encore (xvidenc->handle, XVID_ENC_DESTROY, NULL, NULL); xvidenc->handle = NULL; } - gst_structure_get_int (structure, "width", &w); - gst_structure_get_int (structure, "height", &h); + structure = gst_caps_get_structure (vscaps, 0); + g_return_val_if_fail (gst_structure_get_int (structure, "width", &w), FALSE); + g_return_val_if_fail (gst_structure_get_int (structure, "height", &h), FALSE); fps = gst_structure_get_value (structure, "framerate"); - if (fps != NULL) { - xvidenc->fps_n = gst_value_get_fraction_numerator (fps); - xvidenc->fps_d = gst_value_get_fraction_denominator (fps); - } else { - xvidenc->fps_n = -1; + g_return_val_if_fail (w > 0 && h > 0 + && fps != NULL && GST_VALUE_HOLDS_FRACTION (fps), FALSE); + /* optional par info */ + par = gst_structure_get_value (structure, "pixel-aspect-ratio"); + + xvid_cs = gst_xvid_structure_to_csp (structure); + if (xvid_cs == -1) { + gchar *sstr; + + sstr = gst_structure_to_string (structure); + GST_DEBUG_OBJECT (xvidenc, "Did not find xvid colourspace for caps %s", + sstr); + g_free (sstr); + return FALSE; } - mime = gst_structure_get_name (structure); - xvid_cs = gst_xvid_structure_to_csp (structure, w, &stride, NULL); xvidenc->csp = xvid_cs; xvidenc->width = w; xvidenc->height = h; - xvidenc->stride = stride; + xvidenc->fbase = gst_value_get_fraction_numerator (fps); + xvidenc->fincr = gst_value_get_fraction_denominator (fps); + if ((par != NULL) && GST_VALUE_HOLDS_FRACTION (par)) { + xvidenc->par_width = gst_value_get_fraction_numerator (par); + xvidenc->par_height = gst_value_get_fraction_denominator (par); + } else { + xvidenc->par_width = 1; + xvidenc->par_height = 1; + } + + /* wipe xframe cache given possible change caps properties */ + g_free (xvidenc->xframe_cache); + xvidenc->xframe_cache = NULL; if (gst_xvidenc_setup (xvidenc)) { + GstPadLinkReturn ret; + GstCaps *new_caps; + GstPad *peer; new_caps = gst_caps_new_simple ("video/x-xvid", - "width", G_TYPE_INT, w, - "height", G_TYPE_INT, h, - "framerate", GST_TYPE_FRACTION, xvidenc->fps_n, xvidenc->fps_d, NULL); - - if (new_caps) { - - if (!gst_pad_set_caps (xvidenc->srcpad, new_caps)) { - if (xvidenc->handle) { - xvid_encore (xvidenc->handle, XVID_ENC_DESTROY, NULL, NULL); - xvidenc->handle = NULL; - } - ret = FALSE; - goto cleanup; - } - ret = TRUE; - goto cleanup; + "width", G_TYPE_INT, w, "height", G_TYPE_INT, h, + "framerate", GST_TYPE_FRACTION, xvidenc->fbase, xvidenc->fincr, + "pixel-aspect-ratio", GST_TYPE_FRACTION, + xvidenc->par_width, xvidenc->par_height, NULL); + /* src pad should accept anyway */ + ret = gst_pad_set_caps (xvidenc->srcpad, new_caps); + if (!ret) + goto exit; + /* will peer accept */ + peer = gst_pad_get_peer (xvidenc->srcpad); + if (peer) + ret &= gst_pad_accept_caps (peer, new_caps); + gst_object_unref (peer); + + exit: + if (!ret && xvidenc->handle) { + xvid_encore (xvidenc->handle, XVID_ENC_DESTROY, NULL, NULL); + xvidenc->handle = NULL; + } + gst_caps_unref (new_caps); + return ret; + } else /* setup did not work out */ + return FALSE; +} + +/* encodes frame according to info in xframe; + - buf is input buffer, can be NULL if dummy + - buf is disposed of prior to exit + - resulting buffer is returned, NULL if no encoder output or error +*/ +static inline GstBuffer * +gst_xvidenc_encode (GstXvidEnc * xvidenc, GstBuffer * buf, + xvid_enc_frame_t xframe) +{ + GstBuffer *outbuf; + gint ret; + + /* compressed frame should fit in the rough size of an uncompressed one */ + outbuf = gst_buffer_new_and_alloc (gst_xvid_image_get_size (xvidenc->csp, + xvidenc->width, xvidenc->height)); + + xframe.bitstream = (void *) GST_BUFFER_DATA (outbuf); + xframe.length = GST_BUFFER_SIZE (outbuf); + + /* now provide input image data where-abouts, if needed */ + if (buf) + gst_xvid_image_fill (&xframe.input, GST_BUFFER_DATA (buf), xvidenc->csp, + xvidenc->width, xvidenc->height); + + GST_DEBUG_OBJECT (xvidenc, "encoding frame into buffer of size %d", + GST_BUFFER_SIZE (outbuf)); + ret = xvid_encore (xvidenc->handle, XVID_ENC_ENCODE, &xframe, NULL); + + if (ret < 0) { + /* things can be nasty if we are trying to flush, so don't signal error then */ + if (buf) { + GST_ELEMENT_WARNING (xvidenc, LIBRARY, ENCODE, (NULL), + ("Error encoding xvid frame: %s (%d)", gst_xvid_error (ret), ret)); + gst_buffer_unref (buf); } + gst_buffer_unref (outbuf); + return NULL; + } else if (ret > 0) { /* make sub-buffer */ + GST_DEBUG_OBJECT (xvidenc, "xvid produced output of size %d", ret); + GstBuffer *sub = gst_buffer_create_sub (outbuf, 0, ret); + /* parent no longer needed, will go away with child buffer */ + gst_buffer_unref (outbuf); + outbuf = sub; + } else { /* encoder did not yet produce something */ + GST_DEBUG_OBJECT (xvidenc, "xvid produced no output"); + gst_buffer_unref (outbuf); + g_queue_push_tail (xvidenc->delay, buf); + return NULL; } - /* if we got here - it's not good */ - ret = FALSE; + /* finish decoration and return */ + if (!(xframe.out_flags & XVID_KEYFRAME)) + GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); + gst_buffer_set_caps (outbuf, GST_PAD_CAPS (xvidenc->srcpad)); -cleanup: + /* now we need the right buf to take timestamps from; + note that timestamps from a display order input buffer can end up with + another encode order output buffer, but other than this permutation, + the overall time progress is tracked, + and keyframes should have the correct stamp */ + if (!g_queue_is_empty (xvidenc->delay)) { + if (buf) + g_queue_push_tail (xvidenc->delay, buf); + buf = g_queue_pop_head (xvidenc->delay); + } + if (buf) { + GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buf); + GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buf); + gst_buffer_unref (buf); + } - if (new_caps) { - gst_caps_unref (new_caps); + return outbuf; +} + +static GstFlowReturn +gst_xvidenc_chain (GstPad * pad, GstBuffer * buf) +{ + GstXvidEnc *xvidenc = GST_XVIDENC (GST_PAD_PARENT (pad)); + GstBuffer *outbuf; + xvid_enc_frame_t xframe; + + const gint motion_presets[] = { + 0, 0, 0, 0, + XVID_ME_HALFPELREFINE16, + XVID_ME_HALFPELREFINE16 | XVID_ME_ADVANCEDDIAMOND16, + XVID_ME_HALFPELREFINE16 | XVID_ME_EXTSEARCH16 + | XVID_ME_HALFPELREFINE8 | XVID_ME_USESQUARES16 + }; + + if (!xvidenc->handle) { + GST_ELEMENT_ERROR ("xvidenc", CORE, NEGOTIATION, (NULL), + ("format wasn't negotiated before chain function")); + gst_buffer_unref (buf); + return GST_FLOW_NOT_NEGOTIATED; } - gst_object_unref (xvidenc); - return ret; + GST_DEBUG_OBJECT (xvidenc, + "Received buffer of time %" GST_TIME_FORMAT ", size %d", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_SIZE (buf)); + + if (xvidenc->xframe_cache) + memcpy (&xframe, xvidenc->xframe_cache, sizeof (xframe)); + else { /* need to build some inital xframe to be cached */ + /* encode and so ... */ + gst_xvid_init_struct (xframe); + + if (xvidenc->par_width == xvidenc->par_height) + xframe.par = XVID_PAR_11_VGA; + else { + xframe.par = XVID_PAR_EXT; + xframe.par_width = xvidenc->par_height; + xframe.par_height = xvidenc->par_width; + } + + /* handle options */ + xframe.vol_flags |= xvidenc->quant_type; + xframe.vop_flags = XVID_VOP_HALFPEL; + xframe.motion = motion_presets[xvidenc->motion]; + + if (xvidenc->me_chroma) { + xframe.motion |= XVID_ME_CHROMA_PVOP; + xframe.motion |= XVID_ME_CHROMA_BVOP; + } + + if (xvidenc->me_vhq >= 1) { + xframe.vop_flags |= XVID_VOP_MODEDECISION_RD; + } + if (xvidenc->me_vhq >= 2) { + xframe.motion |= XVID_ME_HALFPELREFINE16_RD; + xframe.motion |= XVID_ME_QUARTERPELREFINE16_RD; + } + if (xvidenc->me_vhq >= 3) { + xframe.motion |= XVID_ME_HALFPELREFINE8_RD; + xframe.motion |= XVID_ME_QUARTERPELREFINE8_RD; + xframe.motion |= XVID_ME_CHECKPREDICTION_RD; + } + if (xvidenc->me_vhq >= 4) { + xframe.motion |= XVID_ME_EXTSEARCH_RD; + } + + /* no motion estimation, then only intra */ + if (xvidenc->motion == 0) { + xframe.type = XVID_TYPE_IVOP; + } else { + xframe.type = XVID_TYPE_AUTO; + } + + if (xvidenc->motion > 4) { + xframe.vop_flags |= XVID_VOP_INTER4V; + } + + if (xvidenc->me_quarterpel) { + xframe.vol_flags |= XVID_VOL_QUARTERPEL; + xframe.motion |= XVID_ME_QUARTERPELREFINE16; + xframe.motion |= XVID_ME_QUARTERPELREFINE8; + } + + if (xvidenc->gmc) { + xframe.vol_flags |= XVID_VOL_GMC; + xframe.motion |= XVID_ME_GME_REFINE; + } + if (xvidenc->interlaced) { + xframe.vol_flags |= XVID_VOL_INTERLACING; + } + + if (xvidenc->trellis) { + xframe.vop_flags |= XVID_VOP_TRELLISQUANT; + } + + if (xvidenc->hqacpred) { + xframe.vop_flags |= XVID_VOP_HQACPRED; + } + + if (xvidenc->greyscale) { + xframe.vop_flags |= XVID_VOP_GREYSCALE; + } + + if (xvidenc->cartoon) { + xframe.vop_flags |= XVID_VOP_CARTOON; + xframe.motion |= XVID_ME_DETECT_STATIC_MOTION; + } + + xframe.bframe_threshold = xvidenc->bframe_threshold; + xframe.input.csp = xvidenc->csp; + + /* save in cache */ + xvidenc->xframe_cache = g_memdup (&xframe, sizeof (xframe)); + } + + outbuf = gst_xvidenc_encode (xvidenc, buf, xframe); + + if (!outbuf) /* error or no data yet */ + return GST_FLOW_OK; + + /* proclaim destiny */ + g_signal_emit (G_OBJECT (xvidenc), gst_xvidenc_signals[FRAME_ENCODED], 0); + + /* go out, multiply! */ + return gst_pad_push (xvidenc->srcpad, outbuf); } +/* flush xvid encoder buffers caused by bframe usage */ +static void +gst_xvidenc_flush_buffers (GstXvidEnc * xvidenc, gboolean send) +{ + GstBuffer *outbuf; + xvid_enc_frame_t xframe; + + /* no need to flush if no handle */ + if (!xvidenc->handle) + return; + + gst_xvid_init_struct (xframe); + + /* init a fake frame to force flushing */ + xframe.input.csp = XVID_CSP_NULL; + xframe.input.plane[0] = NULL; + xframe.input.plane[1] = NULL; + xframe.input.plane[2] = NULL; + xframe.input.stride[0] = 0; + xframe.input.stride[1] = 0; + xframe.input.stride[2] = 0; + xframe.quant = 0; + + GST_DEBUG ("flushing buffers with sending %d", send); + + while (!g_queue_is_empty (xvidenc->delay)) { + outbuf = gst_xvidenc_encode (xvidenc, NULL, xframe); + + if (outbuf) { + if (send) + gst_pad_push (xvidenc->srcpad, outbuf); + else + gst_buffer_unref (outbuf); + } else /* hm, there should have been something in there */ + break; + } + + /* our queue should be empty anyway if we did not have to break out ... */ + while (!g_queue_is_empty (xvidenc->delay)) + gst_buffer_unref (g_queue_pop_head (xvidenc->delay)); +} static void gst_xvidenc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { - GstXvidEnc *xvidenc = GST_XVIDENC (object); + GstXvidEnc *xvidenc; + guint offset; - GST_OBJECT_LOCK (xvidenc); + g_return_if_fail (GST_IS_XVIDENC (object)); + xvidenc = GST_XVIDENC (object); - switch (prop_id) { - case ARG_PROFILE: - xvidenc->profile = g_value_get_enum (value); - break; - case ARG_BITRATE: - xvidenc->bitrate = g_value_get_int (value); + if (prop_id > xvidenc_prop_count) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } + + /* our param specs should have such qdata */ + offset = + GPOINTER_TO_UINT (g_param_spec_get_qdata (pspec, xvidenc_pspec_quark)); + g_return_if_fail (offset != 0); + + switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) { + case G_TYPE_BOOLEAN: + G_STRUCT_MEMBER (gboolean, xvidenc, offset) = g_value_get_boolean (value); break; - case ARG_BUFSIZE: - xvidenc->buffer_size = g_value_get_ulong (value); + case G_TYPE_INT: + G_STRUCT_MEMBER (gint, xvidenc, offset) = g_value_get_int (value); break; - case ARG_MAXKEYINTERVAL: - xvidenc->max_key_interval = g_value_get_int (value); + case G_TYPE_STRING: + g_free (G_STRUCT_MEMBER (gchar *, xvidenc, offset)); + G_STRUCT_MEMBER (gchar *, xvidenc, offset) = g_value_dup_string (value); break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + default: /* must be enum, given the check above */ + if (G_IS_PARAM_SPEC_ENUM (pspec)) { + G_STRUCT_MEMBER (gint, xvidenc, offset) = g_value_get_enum (value); + } else { + G_STRUCT_MEMBER (guint, xvidenc, offset) = g_value_get_flags (value); + } break; } - - GST_OBJECT_UNLOCK (xvidenc); - } static void gst_xvidenc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { - GstXvidEnc *xvidenc = GST_XVIDENC (object); + GstXvidEnc *xvidenc; - GST_OBJECT_LOCK (xvidenc); + g_return_if_fail (GST_IS_XVIDENC (object)); + xvidenc = GST_XVIDENC (object); + guint offset; - switch (prop_id) { - case ARG_PROFILE: - g_value_set_enum (value, xvidenc->profile); - break; - case ARG_BITRATE: - g_value_set_int (value, xvidenc->bitrate); + if (prop_id > xvidenc_prop_count) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } + + /* our param specs should have such qdata */ + offset = + GPOINTER_TO_UINT (g_param_spec_get_qdata (pspec, xvidenc_pspec_quark)); + g_return_if_fail (offset != 0); + + switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) { + case G_TYPE_BOOLEAN: + g_value_set_boolean (value, G_STRUCT_MEMBER (gboolean, xvidenc, offset)); break; - case ARG_BUFSIZE: - g_value_set_ulong (value, xvidenc->buffer_size); + case G_TYPE_INT: + g_value_set_int (value, G_STRUCT_MEMBER (gint, xvidenc, offset)); break; - case ARG_MAXKEYINTERVAL: - g_value_set_int (value, xvidenc->max_key_interval); + case G_TYPE_STRING: + g_value_take_string (value, + g_strdup (G_STRUCT_MEMBER (gchar *, xvidenc, offset))); break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + default: /* must be enum, given the check above */ + if (G_IS_PARAM_SPEC_ENUM (pspec)) { + g_value_set_enum (value, G_STRUCT_MEMBER (gint, xvidenc, offset)); + } else if (G_IS_PARAM_SPEC_FLAGS (pspec)) { + g_value_set_flags (value, G_STRUCT_MEMBER (guint, xvidenc, offset)); + } else { /* oops, bit lazy we don't cover this case yet */ + g_critical ("%s does not yet support type %s", GST_FUNCTION, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); + } break; } - - GST_OBJECT_UNLOCK (xvidenc); } static GstStateChangeReturn @@ -505,29 +1106,37 @@ gst_xvidenc_change_state (GstElement * element, GstStateChange transition) switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_xvid_init ()) + return GST_STATE_CHANGE_FAILURE; break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + case GST_STATE_CHANGE_READY_TO_PAUSED: + xvidenc->delay = g_queue_new (); break; default: break; } - ret = parent_class->change_state (element, transition); + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + goto done; switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; case GST_STATE_CHANGE_PAUSED_TO_READY: if (xvidenc->handle) { + gst_xvidenc_flush_buffers (xvidenc, FALSE); xvid_encore (xvidenc->handle, XVID_ENC_DESTROY, NULL, NULL); xvidenc->handle = NULL; } - break; - case GST_STATE_CHANGE_READY_TO_NULL: + g_queue_free (xvidenc->delay); + xvidenc->delay = NULL; + g_free (xvidenc->xframe_cache); + xvidenc->xframe_cache = NULL; break; default: break; } +done: return ret; } diff --git a/ext/xvid/gstxvidenc.h b/ext/xvid/gstxvidenc.h index f016c629..9bdac5cb 100644 --- a/ext/xvid/gstxvidenc.h +++ b/ext/xvid/gstxvidenc.h @@ -23,10 +23,7 @@ #include #include "gstxvid.h" -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - +G_BEGIN_DECLS #define GST_TYPE_XVIDENC \ (gst_xvidenc_get_type()) @@ -48,24 +45,84 @@ struct _GstXvidEnc { /* pads */ GstPad *sinkpad, *srcpad; + /* xvid handle */ + void *handle; + + /* cache in place */ + xvid_enc_frame_t *xframe_cache; + + /* caps information */ + gint csp; + gint width, height; + gint fbase; + gint fincr; + gint par_width; + gint par_height; + + /* delayed buffers if bframe usage */ + GQueue *delay; + /* encoding profile */ gint profile; + /* quantizer type; h263, MPEG */ + gint quant_type; + + /* encoding type; cbr, vbr, quant */ + gint pass; + /* quality of encoded image */ gint bitrate; - gint buffer_size; + gint quant; - /* max number of B frames between I/P */ - gint max_b_frames; - - /* max key interval */ + /* gop */ gint max_key_interval; - - /* xvid handle */ - void *handle; - gint csp; - gint width, height, stride; - gint fps_n, fps_d; + gboolean closed_gop; + + /* motion estimation */ + gint motion; + gboolean me_chroma; + gint me_vhq; + gboolean me_quarterpel; + + /* lumimasking */ + gboolean lumimasking; + + /* b-frames */ + gint max_bframes; + gint bquant_ratio; + gint bquant_offset; + gint bframe_threshold; + + /* misc */ + gboolean gmc; + gboolean trellis; + gboolean interlaced; + gboolean cartoon; + gboolean greyscale; + gboolean hqacpred; + + /* quantizer ranges */ + gint max_iquant, min_iquant; + gint max_pquant, min_pquant; + gint max_bquant, min_bquant; + + /* cbr (single pass) encoding */ + gint reaction_delay_factor; + gint averaging_period; + gint buffer; + + /* vbr (2pass) encoding */ + gchar *filename; + gint keyframe_boost; + gint curve_compression_high; + gint curve_compression_low; + gint overflow_control_strength; + gint max_overflow_improvement; + gint max_overflow_degradation; + gint kfreduction; + gint kfthreshold; + gint container_frame_overhead; }; struct _GstXvidEncClass { @@ -77,8 +134,6 @@ struct _GstXvidEncClass { GType gst_xvidenc_get_type(void); -#ifdef __cplusplus -} -#endif /* __cplusplus */ +G_END_DECLS #endif /* __GST_XVIDENC_H__ */ -- cgit v1.2.1