diff --git a/ChangeLog b/ChangeLog index a434c93f10..1aedc560bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2006-07-13 Tim-Philipp Müller + + Patch by: Mark Nauwelaerts + + * configure.ac: + * ext/Makefile.am: + * ext/mpeg2enc/Makefile.am: + * ext/mpeg2enc/gstmpeg2enc.cc: + * ext/mpeg2enc/gstmpeg2enc.hh: + * ext/mpeg2enc/gstmpeg2encoder.cc: + * ext/mpeg2enc/gstmpeg2encoder.hh: + * ext/mpeg2enc/gstmpeg2encoptions.cc: + * ext/mpeg2enc/gstmpeg2encpicturereader.cc: + * ext/mpeg2enc/gstmpeg2encpicturereader.hh: + * ext/mpeg2enc/gstmpeg2encstreamwriter.cc: + * ext/mpeg2enc/gstmpeg2encstreamwriter.hh: + Port mpeg2enc to 0.10 (#343184). + + * tests/check/Makefile.am: + * tests/check/elements/.cvsignore: + * tests/check/elements/mpeg2enc.c: + Add unit test for mpeg2enc. + + * tests/icles/.cvsignore: + Ignore pitch-test. + 2006-07-12 Tim-Philipp Müller * gst/spectrum/gstspectrum.c: (gst_spectrum_class_init): diff --git a/configure.ac b/configure.ac index 0150a7af52..d8328aae09 100644 --- a/configure.ac +++ b/configure.ac @@ -542,6 +542,84 @@ GST_CHECK_FEATURE(LIBMMS, [mms protocol library], libmms, [ ]) AC_SUBST(LIBMMS_LIBS) +dnl *** mjpegtools version info *** +dnl some may prefer older version (given quirks above) +dnl hm, no version info seems available within mjpegtools headers +PKG_CHECK_EXISTS(mjpegtools >= 1.8.0 mjpegtools < 1.9.0, [ + AC_DEFINE(GST_MJPEGTOOLS_18x, 1, [mjpegtools >= 1.8.0 is used]) + have_mpjegtools_18x=yes + ], [ + have_mpjegtools_18x=no]) + +dnl *** mpeg2enc *** +translit(dnm, m, l) AM_CONDITIONAL(USE_MPEG2ENC, true) +GST_CHECK_FEATURE(MPEG2ENC, [mpeg2enc], mpeg2enc, [ + HAVE_MPEG2ENC="no" + dnl we require a c++ compiler for this one + if [ test x$HAVE_CXX = xyes ]; then + dnl libmpeg2enc was first included in mjpegtools-1.6.2-rc3 (1.6.1.92) + dnl since many distros include mjpegtools specifically without mplex + dnl and mpeg2enc, we check for mpeg2enc on its own, too. + dnl HACK because mpeg2enc 1.8.0 header files have a spurious 'include config.h' + touch config.h + PKG_CHECK_MODULES(MPEG2ENC, mjpegtools >= 1.6.1.93, [ + dnl switch over to c++ to test things + AC_LANG_CPLUSPLUS + OLD_CPPFLAGS="$CPPFLAGS" + dnl HACK as above + CPPFLAGS_GOOD="$CPPFLAGS $MPEG2ENC_CFLAGS" + CPPFLAGS="$CPPFLAGS_GOOD -I." + dnl check headers + mpeg2enc_headers_ok=no + AC_CHECK_HEADER([mpeg2encoder.hh], [ + MPEG2ENC_LIBS="$MPEG2ENC_LIBS -lmpeg2encpp -lm -lpthread" + OLD_LIBS="$LIBS" + LIBS="$LIBS $MPEG2ENC_LIBS" + AC_MSG_CHECKING([for valid mpeg2enc objects]) + AC_TRY_RUN([ + #include + #include + + int + main (int argc, + char *argv[]) + { + MPEG2EncOptions *options = new MPEG2EncOptions (); + MPEG2Encoder *encoder = new MPEG2Encoder (*options); + return 0; + } + ],[ + AC_MSG_RESULT(yes) + dnl so far so good, let's check more things: + dnl mjpegtools-1.8.0 does not install the required + dnl mpeg2syntaxcodes.h header by default, and a new release + dnl is not in sight, so check for this oversight in case + dnl distros or folks have fixed this themselves + if test "x$have_mpjegtools_18x" = "xyes"; then + AC_CHECK_HEADER([mpeg2syntaxcodes.h], [ + mpeg2enc_headers_ok=yes + ], [ + mpeg2enc_headers_ok=no + ]) + fi + if test "x$mpeg2enc_headers_ok" = "xyes"; then + HAVE_MPEG2ENC="yes" + fi + CPP_FLAGS="$CPPFLAGS_GOOD" + AC_SUBST(MPEG2ENC_CFLAGS) + AC_SUBST(MPEG2ENC_LIBS) + ], [ + AC_MSG_RESULT(no) + ]) + LIBS="$OLD_LIBS" + ]) + + CPPFLAGS="$OLD_CPPFLAGS" + AC_LANG_C + ]) + fi +]) + dnl *** musepack *** translit(dnm, m, l) AM_CONDITIONAL(USE_MUSEPACK, true) GST_CHECK_FEATURE(MUSEPACK, [musepackdec], musepack, [ @@ -767,6 +845,7 @@ ext/gsm/Makefile ext/ivorbis/Makefile ext/libmms/Makefile ext/Makefile +ext/mpeg2enc/Makefile ext/musepack/Makefile ext/musicbrainz/Makefile ext/neon/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index 43b0990ef7..42765f1116 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -130,11 +130,11 @@ else LIBMMS_DIR= endif -# if USE_MPEG2ENC -# MPEG2ENC_DIR=mpeg2enc -# else +if USE_MPEG2ENC +MPEG2ENC_DIR=mpeg2enc +else MPEG2ENC_DIR= -# endif +endif # if USE_MPLEX # MPLEX_DIR=mplex @@ -286,6 +286,7 @@ DIST_SUBDIRS= \ libmms \ dts \ divx \ + mpeg2enc \ musepack \ musicbrainz \ neon \ diff --git a/ext/mpeg2enc/Makefile.am b/ext/mpeg2enc/Makefile.am index 15174006e9..7d5d34686b 100644 --- a/ext/mpeg2enc/Makefile.am +++ b/ext/mpeg2enc/Makefile.am @@ -6,8 +6,10 @@ libgstmpeg2enc_la_SOURCES = \ gstmpeg2encoder.cc \ gstmpeg2encstreamwriter.cc \ gstmpeg2encpicturereader.cc -libgstmpeg2enc_la_CXXFLAGS = $(MPEG2ENC_CFLAGS) $(GST_CFLAGS) -libgstmpeg2enc_la_LIBADD = $(MPEG2ENC_LIBS) $(GST_PLUGIN_LDFLAGS) + +libgstmpeg2enc_la_CXXFLAGS = $(MPEG2ENC_CFLAGS) $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) -Wno-non-virtual-dtor +libgstmpeg2enc_la_LIBADD = $(MPEG2ENC_LIBS) $(GST_PLUGINS_BASE_LIBS) $(GST_LIBS) +libgstmpeg2enc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) noinst_HEADERS = \ gstmpeg2enc.hh \ diff --git a/ext/mpeg2enc/gstmpeg2enc.cc b/ext/mpeg2enc/gstmpeg2enc.cc index d53c28f495..b91d942653 100644 --- a/ext/mpeg2enc/gstmpeg2enc.cc +++ b/ext/mpeg2enc/gstmpeg2enc.cc @@ -1,7 +1,8 @@ /* GStreamer mpeg2enc (mjpegtools) wrapper * (c) 2003 Ronald Bultje + * (c) 2006 Mark Nauwelaerts * - * gstmpeg2enc.cc: gstreamer wrapping + * gstmpeg2enc.cc: gstreamer mpeg2enc wrapping * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -19,91 +20,92 @@ * Boston, MA 02111-1307, USA. */ +/** + * SECTION:element-mpeg2enc + * @see_also: mpeg2dec + * + * + * + * This element encodes raw video into an MPEG ?? stream using the + * mjpegtools library. + * Documentation on MPEG encoding in general can be found in the + * MJPEG Howto + * and on the various available parameters in the documentation + * of the mpeg2enc tool in particular, which shares options with this element. + * + * Example pipeline + * + * + * gst-launch-0.10 videotestsrc num-buffers=1000 ! mpeg2enc ! filesink location=videotestsrc.m1v + * + * This example pipeline will encode a test video source to a an + * MPEG1 elementary stream (with Generic MPEG1 profile). + * + * + * Likely, the format property + * is most important, as it selects the type of MPEG stream that is produced. + * In particular, default property values are dependent on the format, + * and can even be forcibly restrained to certain pre-sets (and thereby ignored). + * Note that the (S)VCD profiles also restrict the image size, so some scaling + * may be needed to accomodate this. The so-called generic profiles (as used + * in the example above) allow most parameters to be adjusted. + * + * gst-launch-0.10 videotestsrc num-buffers=1000 ! videoscale \ + * ! mpeg2enc format=1 norm=p ! filesink location=videotestsrc.m1v + * + * (write everything in one line, without the backslash characters) + * This will produce an MPEG1 profile stream according to VCD2.0 specifications + * for PAL norm (as the image height + * is dependent on video norm). + * + * + */ + #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstmpeg2enc.hh" -/* - * We can't use fractions in static pad templates, so - * we do something manual... - */ -static void -add_fps (GstCaps * caps) -{ - GstStructure *structure = gst_caps_get_structure (caps, 0); - GValue list = { 0 }, fps = { - 0}; - gdouble fpss[] = { 24.0 / 1.001, 24.0, 25.0, - 30.0 / 1.001, 30.0, 50.0, - 60.0 / 1.001, 60.0, 0 - }; - guint n; +GST_DEBUG_CATEGORY (mpeg2enc_debug); - g_value_init (&list, GST_TYPE_LIST); - g_value_init (&fps, G_TYPE_DOUBLE); - for (n = 0; fpss[n] != 0; n++) { - g_value_set_double (&fps, fpss[n]); - gst_value_list_append_value (&list, &fps); - } - gst_structure_set_value (structure, "framerate", &list); - g_value_unset (&list); - g_value_unset (&fps); -} +static GstElementDetails gst_mpeg2enc_details = +GST_ELEMENT_DETAILS ("mpeg2enc video encoder", + "Codec/Encoder/Video", + "High-quality MPEG-1/2 video encoder", + "Andrew Stevens \n" + "Ronald Bultje "); -static GstPadTemplate * -sink_templ (void) -{ - static GstPadTemplate *templ = NULL; +#define COMMON_VIDEO_CAPS \ + "width = (int) [ 16, 4096 ], " \ + "height = (int) [ 16, 4096 ], " \ + "framerate = " \ + " (fraction) { 24000/1001, 24/1, 25/1, 30000/1001, 30/1, 50/1, 60000/1001 }" - if (!templ) { - GstCaps *caps; +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-yuv, " + "format = (fourcc) { I420 }, " COMMON_VIDEO_CAPS) + ); - caps = gst_caps_new_simple ("video/x-raw-yuv", - "format", GST_TYPE_FOURCC, - GST_MAKE_FOURCC ('I', '4', '2', '0'), - "width", GST_TYPE_INT_RANGE, 16, 4096, - "height", GST_TYPE_INT_RANGE, 16, 4096, NULL); - add_fps (caps); +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/mpeg, " + "systemstream = (boolean) false, " + "mpegversion = (int) { 1, 2 }, " COMMON_VIDEO_CAPS) + ); - templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps); - } - - return templ; -} - -static GstPadTemplate * -src_templ (void) -{ - static GstPadTemplate *templ = NULL; - - if (!templ) { - GstCaps *caps; - - caps = gst_caps_new_simple ("video/mpeg", - "systemstream", G_TYPE_BOOLEAN, FALSE, - "mpegversion", GST_TYPE_INT_RANGE, 1, 2, - "width", GST_TYPE_INT_RANGE, 16, 4096, - "height", GST_TYPE_INT_RANGE, 16, 4096, NULL); - add_fps (caps); - - templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, caps); - } - - return templ; -} - -static void gst_mpeg2enc_base_init (GstMpeg2encClass * klass); -static void gst_mpeg2enc_class_init (GstMpeg2encClass * klass); -static void gst_mpeg2enc_init (GstMpeg2enc * enc); -static void gst_mpeg2enc_dispose (GObject * object); - -static void gst_mpeg2enc_loop (GstElement * element); - -static GstPadLinkReturn -gst_mpeg2enc_sink_link (GstPad * pad, const GstCaps * caps); +static void gst_mpeg2enc_finalize (GObject * object); +static void gst_mpeg2enc_reset (GstMpeg2enc * enc); +static gboolean gst_mpeg2enc_setcaps (GstPad * pad, GstCaps * caps); +static GstCaps *gst_mpeg2enc_getcaps (GstPad * pad); +static gboolean gst_mpeg2enc_sink_event (GstPad * pad, GstEvent * event); +static void gst_mpeg2enc_loop (GstMpeg2enc * enc); +static GstFlowReturn gst_mpeg2enc_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_mpeg2enc_src_activate_push (GstPad * pad, gboolean active); static GstStateChangeReturn gst_mpeg2enc_change_state (GstElement * element, GstStateChange transition); @@ -112,49 +114,19 @@ static void gst_mpeg2enc_get_property (GObject * object, static void gst_mpeg2enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); -static GstElementClass *parent_class = NULL; - -GType -gst_mpeg2enc_get_type (void) -{ - static GType gst_mpeg2enc_type = 0; - - if (!gst_mpeg2enc_type) { - static const GTypeInfo gst_mpeg2enc_info = { - sizeof (GstMpeg2encClass), - (GBaseInitFunc) gst_mpeg2enc_base_init, - NULL, - (GClassInitFunc) gst_mpeg2enc_class_init, - NULL, - NULL, - sizeof (GstMpeg2enc), - 0, - (GInstanceInitFunc) gst_mpeg2enc_init, - }; - - gst_mpeg2enc_type = - g_type_register_static (GST_TYPE_ELEMENT, - "GstMpeg2enc", &gst_mpeg2enc_info, (GTypeFlags) 0); - } - - return gst_mpeg2enc_type; -} +GST_BOILERPLATE (GstMpeg2enc, gst_mpeg2enc, GstElement, GST_TYPE_ELEMENT); static void -gst_mpeg2enc_base_init (GstMpeg2encClass * klass) +gst_mpeg2enc_base_init (gpointer klass) { - static GstElementDetails gst_mpeg2enc_details = { - "mpeg2enc video encoder", - "Codec/Encoder/Video", - "High-quality MPEG-1/2 video encoder", - "Andrew Stevens \n" - "Ronald Bultje " - }; GstElementClass *element_class = GST_ELEMENT_CLASS (klass); - gst_element_class_add_pad_template (element_class, src_templ ()); - gst_element_class_add_pad_template (element_class, sink_templ ()); gst_element_class_set_details (element_class, &gst_mpeg2enc_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); } static void @@ -163,22 +135,21 @@ gst_mpeg2enc_class_init (GstMpeg2encClass * klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); - parent_class = GST_ELEMENT_CLASS (g_type_class_ref (GST_TYPE_ELEMENT)); - - /* register arguments */ - mjpeg_default_handler_verbosity (0); - GstMpeg2EncOptions::initProperties (object_class); + GST_DEBUG_CATEGORY_INIT (mpeg2enc_debug, "mpeg2enc", 0, "MPEG1/2 encoder"); object_class->set_property = gst_mpeg2enc_set_property; object_class->get_property = gst_mpeg2enc_get_property; - object_class->dispose = gst_mpeg2enc_dispose; + /* register properties */ + GstMpeg2EncOptions::initProperties (object_class); - element_class->change_state = gst_mpeg2enc_change_state; + object_class->finalize = GST_DEBUG_FUNCPTR (gst_mpeg2enc_finalize); + + element_class->change_state = GST_DEBUG_FUNCPTR (gst_mpeg2enc_change_state); } static void -gst_mpeg2enc_dispose (GObject * object) +gst_mpeg2enc_finalize (GObject * object) { GstMpeg2enc *enc = GST_MPEG2ENC (object); @@ -189,96 +160,431 @@ gst_mpeg2enc_dispose (GObject * object) } delete enc->options; - G_OBJECT_CLASS (parent_class)->dispose (object); + g_mutex_free (enc->tlock); + g_cond_free (enc->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); } static void -gst_mpeg2enc_init (GstMpeg2enc * enc) +gst_mpeg2enc_init (GstMpeg2enc * enc, GstMpeg2encClass * g_class) { GstElement *element = GST_ELEMENT (enc); - GstElementClass *klass = GST_ELEMENT_GET_CLASS (element); - - GST_FLAG_SET (element, GST_ELEMENT_EVENT_AWARE); + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); enc->sinkpad = - gst_pad_new_from_template (gst_element_class_get_pad_template (klass, - "sink"), "sink"); - gst_pad_set_link_function (enc->sinkpad, gst_mpeg2enc_sink_link); + gst_pad_new_from_template (gst_element_class_get_pad_template + (element_class, "sink"), "sink"); + gst_pad_set_setcaps_function (enc->sinkpad, + GST_DEBUG_FUNCPTR (gst_mpeg2enc_setcaps)); + gst_pad_set_getcaps_function (enc->sinkpad, + GST_DEBUG_FUNCPTR (gst_mpeg2enc_getcaps)); + gst_pad_set_event_function (enc->sinkpad, + GST_DEBUG_FUNCPTR (gst_mpeg2enc_sink_event)); + gst_pad_set_chain_function (enc->sinkpad, + GST_DEBUG_FUNCPTR (gst_mpeg2enc_chain)); gst_element_add_pad (element, enc->sinkpad); enc->srcpad = - gst_pad_new_from_template (gst_element_class_get_pad_template (klass, - "src"), "src"); - gst_pad_use_explicit_caps (enc->srcpad); + gst_pad_new_from_template (gst_element_class_get_pad_template + (element_class, "src"), "src"); + gst_pad_use_fixed_caps (enc->srcpad); + gst_pad_set_activatepush_function (enc->srcpad, + GST_DEBUG_FUNCPTR (gst_mpeg2enc_src_activate_push)); gst_element_add_pad (element, enc->srcpad); enc->options = new GstMpeg2EncOptions (); - - gst_element_set_loop_function (element, gst_mpeg2enc_loop); - enc->encoder = NULL; + + enc->buffer = NULL; + enc->tlock = g_mutex_new (); + enc->cond = g_cond_new (); + + gst_mpeg2enc_reset (enc); } static void -gst_mpeg2enc_loop (GstElement * element) +gst_mpeg2enc_reset (GstMpeg2enc * enc) { - GstMpeg2enc *enc = GST_MPEG2ENC (element); + enc->eos = FALSE; + enc->srcresult = GST_FLOW_OK; - if (!enc->encoder) { - const GstCaps *caps; - GstCaps *othercaps; - GstData *data; - - /* make sure we've had data */ - data = gst_pad_pull (enc->sinkpad); - /* forward any events */ - if (GST_IS_EVENT (data)) { - gst_pad_event_default (enc->sinkpad, GST_EVENT (data)); - return; - } - - gst_pad_set_element_private (enc->sinkpad, data); - - if (!(caps = GST_PAD_CAPS (enc->sinkpad))) { - GST_ELEMENT_ERROR (element, CORE, NEGOTIATION, (NULL), - ("format wasn't negotiated before loop function")); - return; - } - - /* create new encoder with these settings */ - enc->encoder = new GstMpeg2Encoder (enc->options, enc->sinkpad, - caps, enc->srcpad); - - /* and set caps on other side */ - othercaps = enc->encoder->getFormat (); - if (gst_pad_set_explicit_caps (enc->srcpad, othercaps) <= 0) { - GST_ELEMENT_ERROR (element, CORE, NEGOTIATION, (NULL), (NULL)); - delete enc->encoder; - - enc->encoder = NULL; - return; - } - } - - enc->encoder->encodePicture (); - gst_pad_event_default (enc->sinkpad, gst_event_new (GST_EVENT_EOS)); -} - -static GstPadLinkReturn -gst_mpeg2enc_sink_link (GstPad * pad, const GstCaps * caps) -{ - GstMpeg2enc *enc = GST_MPEG2ENC (gst_pad_get_parent (pad)); - - if (!gst_caps_is_fixed (caps)) - return GST_PAD_LINK_DELAYED; + /* in case of error'ed ending */ + if (enc->buffer) + gst_buffer_unref (enc->buffer); + enc->buffer = NULL; if (enc->encoder) { delete enc->encoder; enc->encoder = NULL; } +} - return GST_PAD_LINK_OK; +/* some (!) coding to get caps depending on the video norm and chosen format */ +static void +gst_mpeg2enc_add_fps (GstStructure * structure, gint fpss[]) +{ + GValue list = { 0, }, fps = { + 0,}; + guint n; + + g_value_init (&list, GST_TYPE_LIST); + g_value_init (&fps, GST_TYPE_FRACTION); + for (n = 0; fpss[n] != 0; n++) { + gst_value_set_fraction (&fps, fpss[n], fpss[n + 1]); + gst_value_list_append_value (&list, &fps); + n++; + } + gst_structure_set_value (structure, "framerate", &list); + g_value_unset (&list); + g_value_unset (&fps); +} + +static inline gint * +gst_mpeg2enc_get_fps (GstMpeg2enc * enc) +{ + static gint fps_pal[] + = { 24, 1, 25, 1, 50, 1, 0 }; + static gint fps_ntsc[] + = { 24000, 1001, 24, 1, 30000, 1001, 30, 1, 60000, 1001, 0 }; + static gint fps_all[] + = { 24000, 1001, 24, 1, 30000, 1001, 30, 1, 60000, 1001, 25, 1, 50, 1, 0 }; + + if (enc->options->norm == 'n') + return fps_ntsc; + else if (enc->options->norm == 0) + return fps_all; + else + return fps_pal; +} + +static GstStructure * +gst_mpeg2enc_structure_from_norm (GstMpeg2enc * enc, gint horiz, + gint pal_v, gint ntsc_v) +{ + GstStructure *structure; + + structure = gst_structure_new ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'), NULL); + + switch (enc->options->norm) { + case 0: + { + GValue list = { 0, } + , val = { + 0,}; + + g_value_init (&list, GST_TYPE_LIST); + g_value_init (&val, G_TYPE_INT); + g_value_set_int (&val, pal_v); + gst_value_list_append_value (&list, &val); + g_value_set_int (&val, ntsc_v); + gst_value_list_append_value (&list, &val); + gst_structure_set_value (structure, "height", &list); + g_value_unset (&list); + g_value_unset (&val); + break; + } + case 'n': + gst_structure_set (structure, "height", G_TYPE_INT, ntsc_v, NULL); + break; + default: + gst_structure_set (structure, "height", G_TYPE_INT, pal_v, NULL); + break; + } + gst_structure_set (structure, "width", G_TYPE_INT, horiz, NULL); + gst_mpeg2enc_add_fps (structure, gst_mpeg2enc_get_fps (enc)); + + return structure; +} + +static GstCaps * +gst_mpeg2enc_getcaps (GstPad * pad) +{ + GstMpeg2enc *enc = GST_MPEG2ENC (GST_PAD_PARENT (pad)); + GstCaps *caps; + + caps = GST_PAD_CAPS (pad); + if (caps) { + gst_caps_ref (caps); + return caps; + } + + switch (enc->options->format) { + case 1: /* vcd */ + case 2: /* user vcd */ + caps = gst_caps_new_full (gst_mpeg2enc_structure_from_norm (enc, + 352, 288, 240), NULL); + break; + case 4: /* svcd */ + case 5: /* user svcd */ + caps = gst_caps_new_full (gst_mpeg2enc_structure_from_norm (enc, + 480, 576, 480), NULL); + break; + case 6: /* vcd stills */ + /* low resolution */ + caps = gst_caps_new_full (gst_mpeg2enc_structure_from_norm (enc, + 352, 288, 240), NULL); + /* high resolution */ + gst_caps_append_structure (caps, + gst_mpeg2enc_structure_from_norm (enc, 704, 576, 480)); + break; + case 7: /* svcd stills */ + /* low resolution */ + caps = gst_caps_new_full (gst_mpeg2enc_structure_from_norm (enc, + 480, 576, 480), NULL); + /* high resolution */ + gst_caps_append_structure (caps, + gst_mpeg2enc_structure_from_norm (enc, 704, 576, 480)); + break; + case 0: + case 3: + case 8: + case 9: + default: + caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + gst_mpeg2enc_add_fps (gst_caps_get_structure (caps, 0), + gst_mpeg2enc_get_fps (enc)); + break; + } + + GST_DEBUG_OBJECT (enc, "returned caps %" GST_PTR_FORMAT, caps); + return caps; +} + +static gboolean +gst_mpeg2enc_setcaps (GstPad * pad, GstCaps * caps) +{ + GstMpeg2enc *enc = GST_MPEG2ENC (GST_PAD_PARENT (pad)); + GstCaps *othercaps = NULL, *mycaps; + gboolean ret; + + /* does not go well to restart stream mid-way */ + if (enc->encoder) + goto refuse_renegotiation; + + /* since mpeg encoder does not really check, let's check caps */ + mycaps = gst_pad_get_caps (pad); + othercaps = gst_caps_intersect (caps, mycaps); + gst_caps_unref (mycaps); + if (!othercaps || gst_caps_is_empty (othercaps)) + goto refuse_caps; + gst_caps_unref (othercaps); + othercaps = NULL; + + /* create new encoder with these settings */ + enc->encoder = new GstMpeg2Encoder (enc->options, GST_ELEMENT (enc), caps); + + if (!enc->encoder->setup ()) + goto refuse_caps; + + /* and set caps on other side, which should accept anyway */ + othercaps = enc->encoder->getFormat (); + ret = gst_pad_set_caps (enc->srcpad, othercaps); + gst_caps_unref (othercaps); + othercaps = NULL; + if (!ret) + goto refuse_caps; + + /* now that we have all the setup and buffers are expected incoming; + * task can get going */ + gst_pad_start_task (enc->srcpad, (GstTaskFunction) gst_mpeg2enc_loop, enc); + + return TRUE; + +refuse_caps: + { + GST_WARNING_OBJECT (enc, "refused caps %" GST_PTR_FORMAT, caps); + + if (othercaps) + gst_caps_unref (othercaps); + + if (enc->encoder) { + delete enc->encoder; + + enc->encoder = NULL; + } + + return FALSE; + } +refuse_renegotiation: + { + GST_WARNING_OBJECT (enc, "refused renegotiation (to %" GST_PTR_FORMAT ")", + caps); + + return FALSE; + } +} + +static gboolean +gst_mpeg2enc_sink_event (GstPad * pad, GstEvent * event) +{ + GstMpeg2enc *enc; + gboolean result = TRUE; + + enc = GST_MPEG2ENC (GST_PAD_PARENT (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + /* forward event */ + result = gst_pad_push_event (enc->srcpad, event); + + /* no special action as there is not much to flush; + * neither is it possible to halt the mpeg encoding loop */ + goto done; + break; + case GST_EVENT_FLUSH_STOP: + /* forward event */ + result = gst_pad_push_event (enc->srcpad, event); + if (!result) + goto done; + + /* this clears the error state in case of a failure in encoding task; + * so chain function can carry on again */ + GST_MPEG2ENC_MUTEX_LOCK (enc); + enc->srcresult = GST_FLOW_OK; + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + goto done; + break; + case GST_EVENT_EOS: + /* inform the encoding task that it can stop now */ + GST_MPEG2ENC_MUTEX_LOCK (enc); + enc->eos = TRUE; + GST_MPEG2ENC_SIGNAL (enc); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + + /* eat this event for now, task will send eos when finished */ + gst_event_unref (event); + goto done; + break; + default: + /* for a serialized event, wait until an earlier buffer is gone, + * though this is no guarantee as to when the encoder is done with it */ + if (GST_EVENT_IS_SERIALIZED (event)) { + GST_MPEG2ENC_MUTEX_LOCK (enc); + while (enc->buffer) + GST_MPEG2ENC_WAIT (enc); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + } + break; + } + + result = gst_pad_push_event (enc->srcpad, event); + +done: + return result; +} + +static void +gst_mpeg2enc_loop (GstMpeg2enc * enc) +{ + /* do not try to resume or start when output problems; + * also ensures a proper (forced) state change */ + if (enc->srcresult != GST_FLOW_OK) + goto ignore; + + if (enc->encoder) { + /* note that init performs a pre-fill and therefore needs buffers */ + enc->encoder->init (); + /* task will stay in here during all of the encoding */ + enc->encoder->encode (); + + /* if not well and truly eos, something strange happened */ + if (!enc->eos) { + GST_ERROR_OBJECT (enc, "encoding task ended without being eos"); + /* notify the chain function that it's over */ + GST_MPEG2ENC_MUTEX_LOCK (enc); + enc->srcresult = GST_FLOW_ERROR; + GST_MPEG2ENC_SIGNAL (enc); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + } else { + /* send eos if this was not a forced stop or other problem */ + if (enc->srcresult == GST_FLOW_OK) + gst_pad_push_event (enc->srcpad, gst_event_new_eos ()); + goto eos; + } + } else { + GST_WARNING_OBJECT (enc, "task started without Mpeg2Encoder"); + } + + /* fall-through */ +done: + { + /* no need to run wildly, stopped elsewhere, e.g. state change */ + GST_DEBUG_OBJECT (enc, "pausing encoding task"); + gst_pad_pause_task (enc->srcpad); + + return; + } +eos: + { + GST_DEBUG_OBJECT (enc, "encoding task reached eos"); + goto done; + } +ignore: + { + GST_DEBUG_OBJECT (enc, "not looping because encoding task encountered %s", + gst_flow_get_name (enc->srcresult)); + goto done; + } +} + +static GstFlowReturn +gst_mpeg2enc_chain (GstPad * pad, GstBuffer * buffer) +{ + GstMpeg2enc *enc; + + enc = GST_MPEG2ENC (GST_PAD_PARENT (pad)); + + if (G_UNLIKELY (!enc->encoder)) + goto not_negotiated; + + GST_MPEG2ENC_MUTEX_LOCK (enc); + + if (G_UNLIKELY (enc->eos)) + goto eos; + + if (G_UNLIKELY (enc->srcresult != GST_FLOW_OK)) + goto ignore; + + /* things look good, now inform the encoding task that a buffer is ready */ + while (enc->buffer) + GST_MPEG2ENC_WAIT (enc); + enc->buffer = buffer; + GST_MPEG2ENC_SIGNAL (enc); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + + /* buffer will be released by task */ + return GST_FLOW_OK; + + /* special cases */ +not_negotiated: + { + GST_ELEMENT_ERROR (enc, CORE, NEGOTIATION, (NULL), + ("format wasn't negotiated before chain function")); + + gst_buffer_unref (buffer); + return GST_FLOW_NOT_NEGOTIATED; + } +eos: + { + GST_DEBUG_OBJECT (enc, "ignoring buffer at end-of-stream"); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + + gst_buffer_unref (buffer); + return GST_FLOW_UNEXPECTED; + } +ignore: + { + GST_DEBUG_OBJECT (enc, + "ignoring buffer because encoding task encountered %s", + gst_flow_get_name (enc->srcresult)); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + + gst_buffer_unref (buffer); + return enc->srcresult; + } } static void @@ -295,29 +601,101 @@ gst_mpeg2enc_set_property (GObject * object, GST_MPEG2ENC (object)->options->setProperty (prop_id, value); } +static gboolean +gst_mpeg2enc_src_activate_push (GstPad * pad, gboolean active) +{ + gboolean result = TRUE; + GstMpeg2enc *enc; + + enc = GST_MPEG2ENC (GST_PAD_PARENT (pad)); + + if (active) { + /* setcaps will start task once encoder is setup */ + } else { + /* can only end the encoding loop by forcing eos */ + GST_MPEG2ENC_MUTEX_LOCK (enc); + enc->eos = TRUE; + enc->srcresult = GST_FLOW_WRONG_STATE; + GST_MPEG2ENC_SIGNAL (enc); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + + /* encoding loop should have ended now and can be joined */ + result = gst_pad_stop_task (pad); + } + + return result; +} + static GstStateChangeReturn gst_mpeg2enc_change_state (GstElement * element, GstStateChange transition) { GstMpeg2enc *enc = GST_MPEG2ENC (element); + GstStateChangeReturn ret; + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + goto done; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: - delete enc->encoder; - enc->encoder = NULL; + gst_mpeg2enc_reset (enc); break; default: break; } - if (parent_class->change_state) - return parent_class->change_state (element, transition); - - return GST_STATE_CHANGE_SUCCESS; +done: + return ret; } +#ifndef GST_DISABLE_GST_DEBUG + +static mjpeg_log_handler_t old_handler = NULL; + +/* note that this will affect all mjpegtools elements/threads */ +static void +gst_mpeg2enc_log_callback (log_level_t level, const char *message) +{ + GstDebugLevel gst_level; + + switch (level) { + case LOG_NONE: + gst_level = GST_LEVEL_NONE; + break; + case LOG_ERROR: + gst_level = GST_LEVEL_ERROR; + break; + case LOG_INFO: + gst_level = GST_LEVEL_INFO; + break; + case LOG_DEBUG: + gst_level = GST_LEVEL_DEBUG; + break; + default: + gst_level = GST_LEVEL_INFO; + break; + } + + gst_debug_log (mpeg2enc_debug, gst_level, "", "", 0, NULL, message); + + /* chain up to the old handler; + * this could actually be a handler from another mjpegtools based + * plugin; in which case messages can come out double or from + * the wrong plugin (element)... */ + old_handler (level, message); +} +#endif + static gboolean plugin_init (GstPlugin * plugin) { +#ifndef GST_DISABLE_GST_DEBUG + old_handler = mjpeg_log_set_handler (gst_mpeg2enc_log_callback); + g_assert (old_handler != NULL); +#endif + /* in any case, we do not want default handler output */ + mjpeg_default_handler_verbosity (0); + return gst_element_register (plugin, "mpeg2enc", GST_RANK_NONE, GST_TYPE_MPEG2ENC); } @@ -326,4 +704,4 @@ GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "mpeg2enc", "High-quality MPEG-1/2 video encoder", - plugin_init, VERSION, "GPL", GST_PACKAGE, GST_ORIGIN) + plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/mpeg2enc/gstmpeg2enc.hh b/ext/mpeg2enc/gstmpeg2enc.hh index 25c61e61a5..66e799a0f6 100644 --- a/ext/mpeg2enc/gstmpeg2enc.hh +++ b/ext/mpeg2enc/gstmpeg2enc.hh @@ -1,5 +1,6 @@ /* GStreamer mpeg2enc (mjpegtools) wrapper * (c) 2003 Ronald Bultje + * (c) 2006 Mark Nauwelaerts * * gstmpeg2enc.hh: object definition * @@ -39,6 +40,30 @@ G_BEGIN_DECLS #define GST_IS_MPEG2ENC_CLASS(obj) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MPEG2ENC)) +GST_DEBUG_CATEGORY_EXTERN (mpeg2enc_debug); +#define GST_CAT_DEFAULT mpeg2enc_debug + +#define GST_MPEG2ENC_MUTEX_LOCK(m) G_STMT_START { \ + GST_LOG_OBJECT (m, "locking tlock from thread %p", g_thread_self ()); \ + g_mutex_lock (m->tlock); \ + GST_LOG_OBJECT (m, "locked tlock from thread %p", g_thread_self ()); \ +} G_STMT_END + +#define GST_MPEG2ENC_MUTEX_UNLOCK(m) G_STMT_START { \ + GST_LOG_OBJECT (m, "unlocking tlock from thread %p", g_thread_self ()); \ + g_mutex_unlock (m->tlock); \ +} G_STMT_END + +#define GST_MPEG2ENC_WAIT(m) G_STMT_START { \ + GST_LOG_OBJECT (m, "thread %p waiting", g_thread_self ()); \ + g_cond_wait (m->cond, m->tlock); \ +} G_STMT_END + +#define GST_MPEG2ENC_SIGNAL(m) G_STMT_START { \ + GST_LOG_OBJECT (m, "signalling from thread %p", g_thread_self ()); \ + g_cond_signal (m->cond); \ +} G_STMT_END + typedef struct _GstMpeg2enc { GstElement parent; @@ -50,6 +75,20 @@ typedef struct _GstMpeg2enc { /* general encoding object (contains rest) */ GstMpeg2Encoder *encoder; + + /* lock for syncing with encoding task */ + GMutex *tlock; + /* with TLOCK */ + /* signals counterpart thread that something changed; + * buffer ready for task or buffer has been processed */ + GCond *cond; + /* seen eos */ + gboolean eos; + /* flowreturn obtained by encoding task */ + GstFlowReturn srcresult; + /* buffer for encoding task */ + GstBuffer *buffer; + } GstMpeg2enc; typedef struct _GstMpeg2encClass { diff --git a/ext/mpeg2enc/gstmpeg2encoder.cc b/ext/mpeg2enc/gstmpeg2encoder.cc index 5a77b58b48..866f63d8d7 100644 --- a/ext/mpeg2enc/gstmpeg2encoder.cc +++ b/ext/mpeg2enc/gstmpeg2encoder.cc @@ -1,5 +1,6 @@ /* GStreamer mpeg2enc (mjpegtools) wrapper * (c) 2003 Ronald Bultje + * (c) 2006 Mark Nauwelaerts * * gstmpeg2encoder.cc: gstreamer/mpeg2enc encoder class * @@ -29,49 +30,94 @@ #include #include +#include "gstmpeg2enc.hh" #include "gstmpeg2encoder.hh" /* * Class init stuff. */ -GstMpeg2Encoder::GstMpeg2Encoder (GstMpeg2EncOptions * options, - GstPad * sinkpad, const GstCaps * caps, GstPad * srcpad): +GstMpeg2Encoder::GstMpeg2Encoder (GstMpeg2EncOptions * options, GstElement * in_element, GstCaps * in_caps): MPEG2Encoder (*options) { - MPEG2EncInVidParams strm; + element = in_element; + gst_object_ref (element); + caps = in_caps; + gst_caps_ref (in_caps); + init_done = FALSE; +} + +GstMpeg2Encoder::~GstMpeg2Encoder () +{ + gst_caps_unref (caps); + gst_object_unref (element); +} + +gboolean GstMpeg2Encoder::setup () +{ + MPEG2EncInVidParams + strm; + GstMpeg2enc * + enc; + + enc = GST_MPEG2ENC (element); /* I/O */ - reader = new GstMpeg2EncPictureReader (sinkpad, caps, &parms); + reader = new GstMpeg2EncPictureReader (element, caps, &parms); reader->StreamPictureParams (strm); - if (options->SetFormatPresets (strm)) { - g_warning ("Eek! Format presets failed. This is really bad!"); +#ifdef GST_MJPEGTOOLS_18x + /* chain thread caters for reading, do not need another thread for this */ + options.allow_parallel_read = FALSE; +#endif + if (options.SetFormatPresets (strm)) { + return FALSE; } - writer = new GstMpeg2EncStreamWriter (srcpad, &parms); + writer = new GstMpeg2EncStreamWriter (enc->srcpad, &parms); /* encoding internals */ quantizer = new Quantizer (parms); - coder = new MPEG2Coder (parms, *writer); bitrate_controller = new OnTheFlyRateCtl (parms); - +#ifdef GST_MJPEGTOOLS_18x + /* sequencer */ + seqencoder = new SeqEncoder (parms, *reader, *quantizer, + *writer, *bitrate_controller); +#else + coder = new MPEG2Coder (parms, *writer); /* sequencer */ seqencoder = new SeqEncoder (parms, *reader, *quantizer, *writer, *coder, *bitrate_controller); +#endif - parms.Init (*options); - reader->Init (); - quantizer->Init (); + return TRUE; +} + +void +GstMpeg2Encoder::init () +{ + if (!init_done) { + parms.Init (options); + reader->Init (); + quantizer->Init (); +#ifdef GST_MJPEGTOOLS_18x + seqencoder->Init (); +#endif + init_done = TRUE; + } } /* - * One image. + * Process all input provided by the reader until it signals eos. */ void -GstMpeg2Encoder::encodePicture () +GstMpeg2Encoder::encode () { /* hm, this is all... eek! */ +#ifdef GST_MJPEGTOOLS_18x + seqencoder->EncodeStream (); +#else seqencoder->Encode (); +#endif } /* @@ -81,12 +127,12 @@ GstMpeg2Encoder::encodePicture () GstCaps * GstMpeg2Encoder::getFormat () { - gdouble fps = Y4M_RATIO_DBL (mpeg_framerate (options.frame_rate)); + y4m_ratio_t fps = mpeg_framerate (options.frame_rate); return gst_caps_new_simple ("video/mpeg", "systemstream", G_TYPE_BOOLEAN, FALSE, "mpegversion", G_TYPE_INT, options.mpeg, "width", G_TYPE_INT, options.in_img_width, "height", G_TYPE_INT, options.in_img_height, - "framerate", G_TYPE_DOUBLE, fps, NULL); + "framerate", GST_TYPE_FRACTION, fps.n, fps.d, NULL); } diff --git a/ext/mpeg2enc/gstmpeg2encoder.hh b/ext/mpeg2enc/gstmpeg2encoder.hh index df034e6b86..94cf57649f 100644 --- a/ext/mpeg2enc/gstmpeg2encoder.hh +++ b/ext/mpeg2enc/gstmpeg2encoder.hh @@ -1,5 +1,6 @@ /* GStreamer mpeg2enc (mjpegtools) wrapper * (c) 2003 Ronald Bultje + * (c) 2006 Mark Nauwelaerts * * gstmpeg2encoder.hh: gstreamer/mpeg2enc encoder class * @@ -22,6 +23,7 @@ #ifndef __GST_MPEG2ENCODER_H__ #define __GST_MPEG2ENCODER_H__ + #include #include "gstmpeg2encoptions.hh" #include "gstmpeg2encpicturereader.hh" @@ -29,16 +31,23 @@ class GstMpeg2Encoder : public MPEG2Encoder { public: - GstMpeg2Encoder (GstMpeg2EncOptions *options, - GstPad *sinkpad, - const GstCaps *caps, - GstPad *srcpad); + GstMpeg2Encoder (GstMpeg2EncOptions *options, + GstElement *element, GstCaps *caps); + ~GstMpeg2Encoder (); - /* one image */ - void encodePicture (); + gboolean setup (); + void init (); + + /* process stream */ + void encode (); /* get current output format */ GstCaps *getFormat (); + +private: + GstElement *element; + GstCaps *caps; + gboolean init_done; }; #endif /* __GST_MPEG2ENCODER_H__ */ diff --git a/ext/mpeg2enc/gstmpeg2encoptions.cc b/ext/mpeg2enc/gstmpeg2encoptions.cc index 0d48ed29ae..4635b81e01 100644 --- a/ext/mpeg2enc/gstmpeg2encoptions.cc +++ b/ext/mpeg2enc/gstmpeg2encoptions.cc @@ -64,14 +64,20 @@ enum ARG_DUMMY_SVCD_SOF, ARG_CORRECT_SVCD_HDS, ARG_ALTSCAN_MPEG2, - ARG_CONSTRAINTS + ARG_CONSTRAINTS, + ARG_DUALPRIME_MPEG2 /* FILL ME */ }; +/* MPEG1 VCD bitrate is used as default (also by mpeg2enc) */ +#define DEFAULT_BITRATE 1125 + /* * Property enumeration types. */ +/* FIXME: nick/name perhaps to be reversed (?) */ + #define GST_TYPE_MPEG2ENC_FORMAT \ (gst_mpeg2enc_format_get_type ()) @@ -276,6 +282,9 @@ MPEG2EncOptions () num_cpus = 1; if (num_cpus > 32) num_cpus = 32; + + /* set some default(s) not set in base class */ + bitrate = DEFAULT_BITRATE * 1024; } /* @@ -306,7 +315,7 @@ GstMpeg2EncOptions::initProperties (GObjectClass * klass) /* general encoding stream options */ g_object_class_install_property (klass, ARG_BITRATE, g_param_spec_int ("bitrate", "Bitrate", "Compressed video bitrate (kbps)", - 0, 10 * 1024, 1125, (GParamFlags) G_PARAM_READWRITE)); + 0, 10 * 1024, DEFAULT_BITRATE, (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (klass, ARG_NONVIDEO_BITRATE, g_param_spec_int ("non-video-bitrate", "Non-video bitrate", "Assumed bitrate of non-video for sequence splitting (kbps)", @@ -346,11 +355,11 @@ GstMpeg2EncOptions::initProperties (GObjectClass * klass) g_object_class_install_property (klass, ARG_MIN_GOP_SIZE, g_param_spec_int ("min-gop-size", "Min. GOP size", "Minimal size per Group-of-Pictures (-1=default)", - -1, 250, 0, (GParamFlags) G_PARAM_READWRITE)); + -1, 250, -1, (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (klass, ARG_MAX_GOP_SIZE, g_param_spec_int ("max-gop-size", "Max. GOP size", "Maximal size per Group-of-Pictures (-1=default)", - -1, 250, 0, (GParamFlags) G_PARAM_READWRITE)); + -1, 250, -1, (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (klass, ARG_CLOSED_GOP, g_param_spec_boolean ("closed-gop", "Closed GOP", "All Group-of-Pictures are closed (for multi-angle DVDs)", @@ -362,7 +371,7 @@ GstMpeg2EncOptions::initProperties (GObjectClass * klass) g_object_class_install_property (klass, ARG_B_PER_REFFRAME, g_param_spec_int ("b-per-refframe", "B per ref. frame", "Number of B frames between each I/P frame", - 0, 2, 2, (GParamFlags) G_PARAM_READWRITE)); + 0, 2, 0, (GParamFlags) G_PARAM_READWRITE)); /* quantisation options */ g_object_class_install_property (klass, ARG_QUANTISATION_REDUCTION, @@ -372,12 +381,12 @@ GstMpeg2EncOptions::initProperties (GObjectClass * klass) g_object_class_install_property (klass, ARG_QUANT_REDUCTION_MAX_VAR, g_param_spec_float ("quant-reduction-max-var", "Max. quant. reduction variance", - "Maximal luma variance below which quantisation boost is used", 0., - 2500., 0., (GParamFlags) G_PARAM_READWRITE)); + "Maximal luma variance below which quantisation boost is used", + 0., 2500., 100., (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (klass, ARG_INTRA_DC_PRECISION, g_param_spec_int ("intra-dc-prec", "Intra. DC precision", - "Number of bits precision for DC (base colour) in MPEG-2 blocks", 8, - 11, 9, (GParamFlags) G_PARAM_READWRITE)); + "Number of bits precision for DC (base colour) in MPEG-2 blocks", + 8, 11, 9, (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (klass, ARG_REDUCE_HF, g_param_spec_float ("reduce-hf", "Reduce HF", "How much to reduce high-frequency resolution (by increasing quantisation)", @@ -395,7 +404,7 @@ GstMpeg2EncOptions::initProperties (GObjectClass * klass) /* general options */ g_object_class_install_property (klass, ARG_BUFSIZE, g_param_spec_int ("bufsize", "Decoder buf. size", - "Target decoders video buffer size (kB)", + "Target decoders video buffer size (kB) (default depends on format)", 20, 4000, 46, (GParamFlags) G_PARAM_READWRITE)); /* header flag settings */ @@ -434,14 +443,19 @@ GstMpeg2EncOptions::initProperties (GObjectClass * klass) "Alternate MPEG-2 block scanning. Disabling this might " "make buggy players play SVCD streams", TRUE, (GParamFlags) G_PARAM_READWRITE)); -#if 0 - "--dxr2-hack" -#endif - /* dangerous/experimental stuff */ - g_object_class_install_property (klass, ARG_CONSTRAINTS, + + /* dangerous/experimental stuff */ + g_object_class_install_property (klass, ARG_CONSTRAINTS, g_param_spec_boolean ("constraints", "Constraints", "Use strict video resolution and bitrate checks", TRUE, (GParamFlags) G_PARAM_READWRITE)); +#ifdef GST_MJPEGTOOLS_18x + g_object_class_install_property (klass, ARG_DUALPRIME_MPEG2, + g_param_spec_boolean ("dualprime", "Dual Prime Motion Estimation", + "Dual Prime Motion Estimation Mode for MPEG-2 I/P-frame only " + "streams. Quite some players do not support this.", + FALSE, (GParamFlags) G_PARAM_READWRITE)); +#endif } /* @@ -510,7 +524,7 @@ GstMpeg2EncOptions::getProperty (guint prop_id, GValue * value) g_value_set_float (value, boost_var_ceil); break; case ARG_INTRA_DC_PRECISION: - g_value_set_int (value, mpeg2_dc_prec - 8); + g_value_set_int (value, mpeg2_dc_prec + 8); break; case ARG_REDUCE_HF: g_value_set_float (value, hf_q_boost); @@ -564,6 +578,11 @@ GstMpeg2EncOptions::getProperty (guint prop_id, GValue * value) case ARG_CONSTRAINTS: g_value_set_boolean (value, !ignore_constraints); break; +#ifdef GST_MJPEGTOOLS_18x + case ARG_DUALPRIME_MPEG2: + g_value_set_boolean (value, hack_dualprime); + break; +#endif default: break; } @@ -631,7 +650,7 @@ GstMpeg2EncOptions::setProperty (guint prop_id, const GValue * value) boost_var_ceil = g_value_get_float (value); break; case ARG_INTRA_DC_PRECISION: - mpeg2_dc_prec = g_value_get_int (value) + 8; + mpeg2_dc_prec = g_value_get_int (value) - 8; break; case ARG_REDUCE_HF: hf_q_boost = g_value_get_float (value); @@ -688,6 +707,11 @@ GstMpeg2EncOptions::setProperty (guint prop_id, const GValue * value) case ARG_CONSTRAINTS: ignore_constraints = !g_value_get_boolean (value); break; +#ifdef GST_MJPEGTOOLS_18x + case ARG_DUALPRIME_MPEG2: + hack_dualprime = g_value_get_boolean (value); + break; +#endif default: break; } diff --git a/ext/mpeg2enc/gstmpeg2encpicturereader.cc b/ext/mpeg2enc/gstmpeg2encpicturereader.cc index 9ed8afc056..9bc59edf7f 100644 --- a/ext/mpeg2enc/gstmpeg2encpicturereader.cc +++ b/ext/mpeg2enc/gstmpeg2encpicturereader.cc @@ -25,23 +25,26 @@ #include +#include "gstmpeg2enc.hh" #include "gstmpeg2encpicturereader.hh" /* * Class init stuff. */ -GstMpeg2EncPictureReader::GstMpeg2EncPictureReader (GstPad * in_pad, - const GstCaps * in_caps, EncoderParams * params): +GstMpeg2EncPictureReader::GstMpeg2EncPictureReader (GstElement * in_element, GstCaps * in_caps, EncoderParams * params): PictureReader (*params) { - pad = in_pad; - caps = gst_caps_copy (in_caps); + element = in_element; + gst_object_ref (element); + caps = in_caps; + gst_caps_ref (caps); } GstMpeg2EncPictureReader::~GstMpeg2EncPictureReader () { - gst_caps_free (caps); + gst_caps_unref (caps); + gst_object_unref (element); } /* @@ -53,83 +56,50 @@ GstMpeg2EncPictureReader::StreamPictureParams (MPEG2EncInVidParams & strm) { GstStructure *structure = gst_caps_get_structure (caps, 0); gint width, height; - gdouble fps; + const GValue *fps_val; + y4m_ratio_t fps; gst_structure_get_int (structure, "width", &width); gst_structure_get_int (structure, "height", &height); - gst_structure_get_double (structure, "framerate", &fps); + fps_val = gst_structure_get_value (structure, "framerate"); + fps.n = gst_value_get_fraction_numerator (fps_val); + fps.d = gst_value_get_fraction_denominator (fps_val); strm.horizontal_size = width; strm.vertical_size = height; - strm.frame_rate_code = mpeg_framerate_code (mpeg_conform_framerate (fps)); + strm.frame_rate_code = mpeg_framerate_code (fps); strm.interlacing_code = Y4M_ILACE_NONE; + /* FIXME perhaps involve pixel-aspect-ratio for 'better' sar */ strm.aspect_ratio_code = mpeg_guess_mpeg_aspect_code (2, y4m_sar_SQUARE, strm.horizontal_size, strm.vertical_size); - - /* FIXME: - * strm.interlacing_code = y4m_si_get_interlace(&si); - * sar = y4m_si_get_sampleaspect(&si); - * strm.aspect_ratio_code = - * mpeg_guess_mpeg_aspect_code(2, sar, - * strm.horizontal_size, - * strm.vertical_size); - */ } /* * Read a frame. Return true means EOS or error. */ -bool GstMpeg2EncPictureReader::LoadFrame () +bool +GstMpeg2EncPictureReader::LoadFrame () { - GstData * - data; - GstBuffer * - buf = - NULL; + gint i, x, y, n; + guint8 *frame; + GstMpeg2enc *enc; - gint - i, - x, - y, - n; - guint8 * - frame; + enc = GST_MPEG2ENC (element); - GstFormat - fmt = - GST_FORMAT_DEFAULT; - gint64 - pos = - 0, - tot = - 0; + GST_MPEG2ENC_MUTEX_LOCK (enc); - gst_pad_query (GST_PAD_PEER (pad), GST_QUERY_POSITION, &fmt, &pos); - gst_pad_query (GST_PAD_PEER (pad), GST_QUERY_TOTAL, &fmt, &tot); - - do { - if ((data = (GstData *) gst_pad_get_element_private (pad))) { - gst_pad_set_element_private (pad, NULL); - } else if (!(data = gst_pad_pull (pad))) { - GST_ELEMENT_ERROR (gst_pad_get_parent (pad), RESOURCE, READ, - (NULL), (NULL)); - return true; + /* hang around until the element provides us with a buffer */ + while (!enc->buffer) { + if (enc->eos) { + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + /* inform the mpeg encoding loop that it can give up */ + return TRUE; } + GST_MPEG2ENC_WAIT (enc); + } - if (GST_IS_EVENT (data)) { - if (GST_EVENT_TYPE (data) == GST_EVENT_EOS) { - gst_event_unref (GST_EVENT (data)); - return true; - } else { - gst_pad_event_default (pad, GST_EVENT (data)); - } - } else { - buf = GST_BUFFER (data); - } - } while (!buf); - - frame = GST_BUFFER_DATA (buf); + frame = GST_BUFFER_DATA (enc->buffer); n = frames_read % input_imgs_buf_size; x = encparams.horizontal_size; y = encparams.vertical_size; @@ -149,7 +119,12 @@ bool GstMpeg2EncPictureReader::LoadFrame () memcpy (input_imgs_buf[n][2] + i * encparams.phy_chrom_width, frame, x); frame += x; } - gst_buffer_unref (buf); + gst_buffer_unref (enc->buffer); + enc->buffer = NULL; - return false; + /* inform the element the buffer has been processed */ + GST_MPEG2ENC_SIGNAL (enc); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); + + return FALSE; } diff --git a/ext/mpeg2enc/gstmpeg2encpicturereader.hh b/ext/mpeg2enc/gstmpeg2encpicturereader.hh index 92d01f6d29..7f6c9c3c33 100644 --- a/ext/mpeg2enc/gstmpeg2encpicturereader.hh +++ b/ext/mpeg2enc/gstmpeg2encpicturereader.hh @@ -25,13 +25,11 @@ #include #include -#include "gstmpeg2encoptions.hh" class GstMpeg2EncPictureReader : public PictureReader { public: - GstMpeg2EncPictureReader (GstPad *pad, - const GstCaps *caps, - EncoderParams *params); + GstMpeg2EncPictureReader (GstElement *element, GstCaps *caps, + EncoderParams *params); ~GstMpeg2EncPictureReader (); /* get input picture parameters (width/height etc.) */ @@ -42,7 +40,7 @@ protected: bool LoadFrame (); private: - GstPad *pad; + GstElement *element; GstCaps *caps; }; diff --git a/ext/mpeg2enc/gstmpeg2encstreamwriter.cc b/ext/mpeg2enc/gstmpeg2encstreamwriter.cc index 39c705ace2..ee3fefd6ac 100644 --- a/ext/mpeg2enc/gstmpeg2encstreamwriter.cc +++ b/ext/mpeg2enc/gstmpeg2encstreamwriter.cc @@ -1,5 +1,6 @@ /* GStreamer mpeg2enc (mjpegtools) wrapper * (c) 2003 Ronald Bultje + * (c) 2006 Mark Nauwelaerts * * gstmpeg2encstreamwriter.cc: GStreamer/mpeg2enc output wrapper * @@ -23,7 +24,55 @@ #include "config.h" #endif +#include "gstmpeg2enc.hh" #include "gstmpeg2encstreamwriter.hh" +#include + +#ifdef GST_MJPEGTOOLS_18x + +/* + * Class init stuff. + */ + +GstMpeg2EncStreamWriter::GstMpeg2EncStreamWriter (GstPad * in_pad, + EncoderParams * params) +{ + pad = in_pad; + gst_object_ref (pad); + buf = NULL; +} + +GstMpeg2EncStreamWriter::~GstMpeg2EncStreamWriter () +{ + gst_object_unref (pad); +} + +void +GstMpeg2EncStreamWriter::WriteOutBufferUpto (const guint8 * buffer, + const guint32 flush_upto) +{ + GstBuffer *buf; + GstMpeg2enc *enc = GST_MPEG2ENC (GST_PAD_PARENT (pad)); + + buf = gst_buffer_new_and_alloc (flush_upto); + + memcpy (GST_BUFFER_DATA (buf), buffer, flush_upto); + flushed += flush_upto; + + /* this should not block anything else (e.g. chain), but if it does, + * it's ok as mpeg2enc is not really a loop-based element, but push-based */ + GST_MPEG2ENC_MUTEX_LOCK (enc); + gst_buffer_set_caps (buf, GST_PAD_CAPS (pad)); + enc->srcresult = gst_pad_push (pad, buf); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); +} + +guint64 GstMpeg2EncStreamWriter::BitCount () +{ + return flushed * 8ll; +} + +#else #define BUFSIZE (128*1024) @@ -35,9 +84,15 @@ GstMpeg2EncStreamWriter::GstMpeg2EncStreamWriter (GstPad * in_pad, EncoderParams ElemStrmWriter (*params) { pad = in_pad; + gst_object_ref (pad); buf = NULL; } +GstMpeg2EncStreamWriter::~GstMpeg2EncStreamWriter () +{ + gst_object_unref (pad); +} + /* * Output functions. */ @@ -66,7 +121,7 @@ GstMpeg2EncStreamWriter::PutBits (guint32 val, gint n) outcnt = 8; bytecnt++; - if (GST_BUFFER_SIZE (buf) >= GST_BUFFER_MAXSIZE (buf)) + if (GST_BUFFER_SIZE (buf) >= BUFSIZE) FrameFlush (); } @@ -85,8 +140,15 @@ GstMpeg2EncStreamWriter::FrameBegin () void GstMpeg2EncStreamWriter::FrameFlush () { + GstMpeg2enc *enc = GST_MPEG2ENC (GST_PAD_PARENT (pad)); + if (buf) { - gst_pad_push (pad, GST_DATA (buf)); + /* this should not block anything else (e.g. chain), but if it does, + * it's ok as mpeg2enc is not really a loop-based element, but push-based */ + GST_MPEG2ENC_MUTEX_LOCK (enc); + gst_buffer_set_caps (buf, GST_PAD_CAPS (pad)); + enc->srcresult = gst_pad_push (pad, buf); + GST_MPEG2ENC_MUTEX_UNLOCK (enc); buf = NULL; } } @@ -95,3 +157,4 @@ void GstMpeg2EncStreamWriter::FrameDiscard () { } +#endif /* GST_MJPEGTOOLS_18x */ diff --git a/ext/mpeg2enc/gstmpeg2encstreamwriter.hh b/ext/mpeg2enc/gstmpeg2encstreamwriter.hh index 708b942e06..766d7bd12f 100644 --- a/ext/mpeg2enc/gstmpeg2encstreamwriter.hh +++ b/ext/mpeg2enc/gstmpeg2encstreamwriter.hh @@ -1,5 +1,6 @@ /* GStreamer mpeg2enc (mjpegtools) wrapper * (c) 2003 Ronald Bultje + * (c) 2006 Mark Nauwelaerts * * gstmpeg2encstreamwriter.hh: GStreamer/mpeg2enc output wrapper * @@ -26,10 +27,29 @@ #include +#ifdef GST_MJPEGTOOLS_18x + +class GstMpeg2EncStreamWriter : public ElemStrmWriter { + public: + GstMpeg2EncStreamWriter (GstPad *pad, EncoderParams *params); + ~GstMpeg2EncStreamWriter (); + + /* output functions */ + void WriteOutBufferUpto (const guint8 * buffer, + const guint32 flush_upto); + guint64 BitCount (); + + private: + GstPad *pad; + GstBuffer *buf; +}; + +#else + class GstMpeg2EncStreamWriter : public ElemStrmWriter { public: - GstMpeg2EncStreamWriter (GstPad *pad, - EncoderParams *params); + GstMpeg2EncStreamWriter (GstPad *pad, EncoderParams *params); + ~GstMpeg2EncStreamWriter (); /* output functions */ void PutBits (guint32 val, gint n); @@ -41,5 +61,6 @@ private: GstPad *pad; GstBuffer *buf; }; +#endif /* GST_MJPEGTOOLS_18x */ #endif /* __GST_MPEG2ENCSTREAMWRITER_H__ */ diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 02b03380e6..3df83e7b99 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -28,7 +28,8 @@ clean-local: clean-local-check check_PROGRAMS = \ elements/gdppay \ - elements/gdpdepay + elements/gdpdepay \ + elements/mpeg2enc TESTS = $(check_PROGRAMS) diff --git a/tests/check/elements/.gitignore b/tests/check/elements/.gitignore index 2ccaf2a099..83383ce749 100644 --- a/tests/check/elements/.gitignore +++ b/tests/check/elements/.gitignore @@ -3,3 +3,4 @@ tagid3v2mux gdpdepay gdppay +mpeg2enc diff --git a/tests/check/elements/mpeg2enc.c b/tests/check/elements/mpeg2enc.c new file mode 100644 index 0000000000..07151cefff --- /dev/null +++ b/tests/check/elements/mpeg2enc.c @@ -0,0 +1,203 @@ +/* GStreamer + * + * unit test for mpeg2enc + * + * Copyright (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 + * 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. + */ + +#include + +#include + +/* For ease of programming we use globals to keep refs for our floating + * src and sink pads we create; otherwise we always have to do get_pad, + * get_peer, and then remove references in every test function */ +static GstPad *mysrcpad, *mysinkpad; + +#define VIDEO_CAPS_STRING "video/x-raw-yuv, " \ + "width = (int) 384, " \ + "height = (int) 288, " \ + "framerate = (fraction) 25/1" + +#define MPEG_CAPS_STRING "video/mpeg, " \ + "mpegversion = (int) { 1, 2 }, " \ + "systemstream = (bool) false, " \ + "height = (int) 288, " \ + "framerate = (fraction) 25/1" + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (MPEG_CAPS_STRING)); + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS_STRING)); + + +/* some global vars, makes it easy as for the ones above */ +static GMutex *mpeg2enc_mutex; +static GCond *mpeg2enc_cond; +static gboolean arrived_eos; + +gboolean +test_sink_event (GstPad * pad, GstEvent * event) +{ + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + g_mutex_lock (mpeg2enc_mutex); + arrived_eos = TRUE; + g_cond_signal (mpeg2enc_cond); + g_mutex_unlock (mpeg2enc_mutex); + break; + default: + break; + } + + return gst_pad_event_default (pad, event); +} + +GstElement * +setup_mpeg2enc () +{ + GstElement *mpeg2enc; + + GST_DEBUG ("setup_mpeg2enc"); + mpeg2enc = gst_check_setup_element ("mpeg2enc"); + mysrcpad = gst_check_setup_src_pad (mpeg2enc, &srctemplate, NULL); + mysinkpad = gst_check_setup_sink_pad (mpeg2enc, &sinktemplate, NULL); + + /* need to know when we are eos */ + gst_pad_set_event_function (mysinkpad, test_sink_event); + + /* and notify the test run */ + mpeg2enc_mutex = g_mutex_new (); + mpeg2enc_cond = g_cond_new (); + + return mpeg2enc; +} + +void +cleanup_mpeg2enc (GstElement * mpeg2enc) +{ + GST_DEBUG ("cleanup_mpeg2enc"); + gst_element_set_state (mpeg2enc, GST_STATE_NULL); + + gst_check_teardown_src_pad (mpeg2enc); + gst_check_teardown_sink_pad (mpeg2enc); + gst_check_teardown_element (mpeg2enc); + + g_mutex_free (mpeg2enc_mutex); + g_cond_free (mpeg2enc_cond); +} + +GST_START_TEST (test_video_pad) +{ + GstElement *mpeg2enc; + GstBuffer *inbuffer, *outbuffer; + GstCaps *caps; + int i, num_buffers; + guint8 data0[] = { 0x00, 0x00, 0x01, 0xb3 }; + + + mpeg2enc = setup_mpeg2enc (); + fail_unless (gst_element_set_state (mpeg2enc, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + /* corresponds to I420 buffer for the size mentioned in the caps */ + inbuffer = gst_buffer_new_and_alloc (384 * 288 * 3 / 2); + /* makes valgrind's memcheck happier */ + memset (GST_BUFFER_DATA (inbuffer), 0, GST_BUFFER_SIZE (inbuffer)); + caps = gst_caps_from_string (VIDEO_CAPS_STRING); + gst_buffer_set_caps (inbuffer, caps); + gst_caps_unref (caps); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + + /* need to force eos and state change to make sure the encoding task ends */ + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()) == TRUE); + /* need to wait a bit to make sure mpeg2enc task digested all this */ + g_mutex_lock (mpeg2enc_mutex); + while (!arrived_eos) + g_cond_wait (mpeg2enc_cond, mpeg2enc_mutex); + g_mutex_unlock (mpeg2enc_mutex); + + num_buffers = g_list_length (buffers); + /* well, we do not really know much with mpeg, but at least something ... */ + fail_unless (num_buffers >= 1); + + /* clean up buffers */ + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + + switch (i) { + case 0: + fail_unless (GST_BUFFER_SIZE (outbuffer) >= sizeof (data0)); + fail_unless (memcmp (data0, GST_BUFFER_DATA (outbuffer), + sizeof (data0)) == 0); + break; + default: + break; + } + buffers = g_list_remove (buffers, outbuffer); + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + cleanup_mpeg2enc (mpeg2enc); + g_list_free (buffers); + buffers = NULL; +} + +GST_END_TEST; + +Suite * +mpeg2enc_suite (void) +{ + Suite *s = suite_create ("mpeg2enc"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_video_pad); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = mpeg2enc_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} diff --git a/tests/icles/.gitignore b/tests/icles/.gitignore index f931c20340..3ee2a6563b 100644 --- a/tests/icles/.gitignore +++ b/tests/icles/.gitignore @@ -1,2 +1,2 @@ +pitch-test v4l2src-test -ximagesrc-test