diff options
author | Robert Swain <robert.swain@collabora.co.uk> | 2010-09-08 15:08:50 +0200 |
---|---|---|
committer | David Schleef <ds@schleef.org> | 2010-09-18 14:58:04 -0700 |
commit | 351562936c626602116eead687fcb61d5c2d9f94 (patch) | |
tree | 9364934a84dafb1c7cccefe8f0b9af7136f0be48 /gst/interlace | |
parent | c28c480eb9d3354bc2d1673aa21b346cab401375 (diff) | |
download | gstreamer-plugins-bad-351562936c626602116eead687fcb61d5c2d9f94.tar.gz |
interlace: Add telecine support
Diffstat (limited to 'gst/interlace')
-rw-r--r-- | gst/interlace/gstinterlace.c | 389 |
1 files changed, 341 insertions, 48 deletions
diff --git a/gst/interlace/gstinterlace.c b/gst/interlace/gstinterlace.c index e0eb90209..b984962c8 100644 --- a/gst/interlace/gstinterlace.c +++ b/gst/interlace/gstinterlace.c @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) 2010 David A. Schleef <ds@schleef.org> + * Copyright (C) 2010 Robert Swain <robert.swain@collabora.co.uk> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -21,7 +22,8 @@ * * The interlace element takes a non-interlaced raw video stream as input, * creates fields out of each frame, then combines fields into interlaced - * frames to output as an interlaced video stream. + * frames to output as an interlaced video stream. It can also produce + * telecined streams from progressive input. * * <refsect2> * <title>Example launch line</title> @@ -38,6 +40,15 @@ * ]| * This pipeline converts a progressive video stream into an interlaced * stream suitable for standard definition NTSC. + * |[ + * gst-launch -v videotestsrc pattern=ball ! video/x-raw-yuv, + * format=\(fourcc\)I420,width=720,height=480,framerate=24000/1001, + * pixel-aspect-ratio=11/10 ! interlace mode=telecine telecine-pattern=2:3 ! + * ... + * ]| + * This pipeline converts a 24 frames per second progressive film stream into a + * 30000/1001 2:3:2:3... pattern telecined stream suitable for displaying film + * content on NTSC. * </refsect2> */ @@ -79,27 +90,84 @@ struct _GstInterlace /* properties */ gboolean top_field_first; + gint mode; /* state */ int width; int height; GstVideoFormat format; GstBuffer *stored_frame; - + gint stored_fields_pushed; + gint telecine_pattern; + gint phase_index; + GstClockTime buffer_ts; }; struct _GstInterlaceClass { GstElementClass element_class; - }; enum { - ARG_0, - PROP_TOP_FIELD_FIRST + PROP_0, + PROP_TOP_FIELD_FIRST, + PROP_MODE, + PROP_TELECINE_PATTERN }; +typedef enum +{ + GST_INTERLACE_MODE_INTERLACE, + GST_INTERLACE_MODE_TELECINE +} GstInterlaceMode; + +#define GST_INTERLACE_MODE (gst_interlace_mode_get_type ()) +static GType +gst_interlace_mode_get_type (void) +{ + static GType interlace_mode_type = 0; + static const GEnumValue mode_types[] = { + {GST_INTERLACE_MODE_INTERLACE, "Interlace", "interlace"}, + {GST_INTERLACE_MODE_TELECINE, "Telecine", "telecine"}, + {0, NULL, NULL} + }; + + if (!interlace_mode_type) { + interlace_mode_type = + g_enum_register_static ("GstInterlaceMode", mode_types); + } + + return interlace_mode_type; +} + +typedef enum +{ + GST_INTERLACE_PATTERN_2_3, + GST_INTERLACE_PATTERN_2_3_3_2, + GST_INTERLACE_PATTERN_EURO +} GstInterlacePattern; + +#define GST_INTERLACE_PATTERN (gst_interlace_pattern_get_type ()) +static GType +gst_interlace_pattern_get_type (void) +{ + static GType interlace_pattern_type = 0; + static const GEnumValue pattern_types[] = { + {GST_INTERLACE_PATTERN_2_3, "2:3", "2:3"}, + {GST_INTERLACE_PATTERN_2_3_3_2, "2:3:3:2", "2:3:3:2"}, + {GST_INTERLACE_PATTERN_EURO, "Euro 2-11:3", "2-11:3"}, + {0, NULL, NULL} + }; + + if (!interlace_pattern_type) { + interlace_pattern_type = + g_enum_register_static ("GstInterlacePattern", pattern_types); + } + + return interlace_pattern_type; +} + static GstStaticPadTemplate gst_interlace_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, @@ -121,6 +189,7 @@ GST_STATIC_PAD_TEMPLATE ("sink", static void gst_interlace_base_init (gpointer g_class); static void gst_interlace_class_init (GstInterlaceClass * klass); static void gst_interlace_init (GstInterlace * interlace); +static gboolean gst_interlace_sink_event (GstPad * pad, GstEvent * event); static GstFlowReturn gst_interlace_chain (GstPad * pad, GstBuffer * buffer); static void gst_interlace_set_property (GObject * object, @@ -164,7 +233,6 @@ gst_interlace_get_type (void) static void gst_interlace_base_init (gpointer g_class) { - GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); gst_element_class_set_details_simple (element_class, @@ -196,6 +264,22 @@ gst_interlace_class_init (GstInterlaceClass * klass) "Interlaced stream should be top field first", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_MODE, + g_param_spec_enum ("mode", "Mode", "Type of interlacing to be performed", + GST_INTERLACE_MODE, GST_INTERLACE_MODE_INTERLACE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, PROP_TELECINE_PATTERN, + g_param_spec_enum ("telecine-pattern", "Telecine pattern", + "Pattern of fields to be used for telecine", GST_INTERLACE_PATTERN, + GST_INTERLACE_PATTERN_2_3, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_interlace_reset (GstInterlace * interlace) +{ + interlace->phase_index = 0; + interlace->buffer_ts = GST_CLOCK_TIME_NONE; } static void @@ -208,6 +292,7 @@ gst_interlace_init (GstInterlace * interlace) gst_pad_set_chain_function (interlace->sinkpad, gst_interlace_chain); gst_pad_set_setcaps_function (interlace->sinkpad, gst_interlace_setcaps); gst_pad_set_getcaps_function (interlace->sinkpad, gst_interlace_getcaps); + gst_pad_set_event_function (interlace->sinkpad, gst_interlace_sink_event); interlace->srcpad = gst_pad_new_from_static_template (&gst_interlace_src_template, "src"); @@ -216,6 +301,121 @@ gst_interlace_init (GstInterlace * interlace) gst_pad_set_getcaps_function (interlace->srcpad, gst_interlace_getcaps); interlace->top_field_first = FALSE; + interlace->mode = GST_INTERLACE_MODE_INTERLACE; + interlace->telecine_pattern = GST_INTERLACE_PATTERN_2_3; + gst_interlace_reset (interlace); +} + +typedef struct _PulldownFormat PulldownFormat; +struct _PulldownFormat +{ + const gchar *name; + int n_fields[13]; + int dur_n, dur_d; +}; + +static const PulldownFormat formats[] = { + /* 24p -> 30fps telecine */ + {"2:3", {2, 3,}, 1001, 30000}, + {"2:3:3:2", {2, 3, 3, 2,}, 1001, 30000}, + /* 24p -> 50i Euro pulldown */ + {"2-11:3", {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3,}, 1, 25} +}; + +static void +gst_interlace_telecine_decorate_buffer (GstInterlace * interlace, + GstBuffer * buf, const PulldownFormat * format) +{ + GST_BUFFER_TIMESTAMP (buf) = interlace->buffer_ts; + GST_BUFFER_DURATION (buf) = + gst_util_uint64_scale (GST_SECOND, format->dur_n, format->dur_d); + /* increment the buffer timestamp by duration for the next buffer */ + interlace->buffer_ts += + gst_util_uint64_scale (GST_SECOND, format->dur_n, format->dur_d); + gst_buffer_set_caps (buf, interlace->srccaps); + + if (interlace->top_field_first) + GST_BUFFER_FLAG_SET (buf, GST_VIDEO_BUFFER_TFF); +} + +static gboolean +gst_interlace_sink_event (GstPad * pad, GstEvent * event) +{ + gboolean ret; + GstInterlace *interlace; + + interlace = GST_INTERLACE (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + GST_DEBUG_OBJECT (interlace, "handling FLUSH_START"); + if (interlace->stored_frame) { + gst_buffer_unref (interlace->stored_frame); + interlace->stored_frame = NULL; + } + ret = gst_pad_push_event (interlace->srcpad, event); + break; + case GST_EVENT_FLUSH_STOP: + GST_DEBUG_OBJECT (interlace, "handling FLUSH_STOP"); + gst_interlace_reset (interlace); + ret = gst_pad_push_event (interlace->srcpad, event); + break; + case GST_EVENT_EOS: + if (interlace->mode == GST_INTERLACE_MODE_TELECINE) { + gint num_fields; + const PulldownFormat *format = &formats[interlace->telecine_pattern]; + + num_fields = + format->n_fields[interlace->phase_index] - + interlace->stored_fields_pushed; + interlace->stored_fields_pushed = 0; + + /* on EOS we want to push as many sane frames as are left */ + while (num_fields > 1) { + GstBuffer *output_buffer; + + /* make metadata writable before editing it */ + interlace->stored_frame = + gst_buffer_make_metadata_writable (interlace->stored_frame); + num_fields -= 2; + + gst_interlace_telecine_decorate_buffer (interlace, + interlace->stored_frame, format); + + /* ref output_buffer/stored frame because we want to keep it for now + * and pushing gives away a ref */ + output_buffer = gst_buffer_ref (interlace->stored_frame); + if (gst_pad_push (interlace->srcpad, output_buffer)) { + GST_DEBUG_OBJECT (interlace, "Failed to push buffer %p", + output_buffer); + return FALSE; + } + output_buffer = NULL; + + if (num_fields <= 1) { + gst_buffer_unref (interlace->stored_frame); + interlace->stored_frame = NULL; + break; + } + } + + /* increment the phase index */ + interlace->phase_index++; + if (!format->n_fields[interlace->phase_index]) { + interlace->phase_index = 0; + } + } + + ret = gst_pad_push_event (interlace->srcpad, event); + break; + default: + ret = gst_pad_push_event (interlace->srcpad, event); + break; + } + + g_object_unref (interlace); + + return ret; } static GstCaps * @@ -225,8 +425,6 @@ gst_interlace_getcaps (GstPad * pad) GstPad *otherpad; GstCaps *othercaps; GstCaps *icaps; - GstStructure *structure; - int i; interlace = GST_INTERLACE (gst_pad_get_parent (pad)); @@ -241,15 +439,8 @@ gst_interlace_getcaps (GstPad * pad) gst_pad_get_pad_template_caps (otherpad)); } - for (i = 0; i < gst_caps_get_size (icaps); i++) { - structure = gst_caps_get_structure (icaps, i); - - if (pad == interlace->srcpad) { - gst_structure_set (structure, "interlaced", G_TYPE_BOOLEAN, TRUE, NULL); - } else { - gst_structure_set (structure, "interlaced", G_TYPE_BOOLEAN, FALSE, NULL); - } - } + gst_caps_set_simple (icaps, "interlaced", G_TYPE_BOOLEAN, + pad == interlace->srcpad ? TRUE : FALSE, NULL); return icaps; } @@ -265,7 +456,8 @@ gst_interlace_setcaps (GstPad * pad, GstCaps * caps) int fps_n, fps_d; GstPad *otherpad; GstCaps *othercaps; - GstStructure *structure; + GString *method = NULL; + const PulldownFormat *pdformat; interlace = GST_INTERLACE (gst_pad_get_parent (pad)); @@ -281,16 +473,32 @@ gst_interlace_setcaps (GstPad * pad, GstCaps * caps) othercaps = gst_caps_copy (caps); - structure = gst_caps_get_structure (othercaps, 0); + if (interlace->mode == GST_INTERLACE_MODE_TELECINE) { + pdformat = &formats[interlace->telecine_pattern]; + method = g_string_new ("telecine-"); + g_string_append (method, pdformat->name); + } if (pad == interlace->srcpad) { - gst_structure_set (structure, - "interlaced", G_TYPE_BOOLEAN, FALSE, - "framerate", GST_TYPE_FRACTION, fps_n * 2, fps_d, NULL); + gst_caps_set_simple (othercaps, "interlaced", G_TYPE_BOOLEAN, FALSE, NULL); + if (interlace->mode == GST_INTERLACE_MODE_INTERLACE) { + gst_caps_set_simple (othercaps, "framerate", GST_TYPE_FRACTION, fps_n * 2, + fps_d, NULL); + } else { + gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, + pdformat->dur_d, pdformat->dur_n, "interlacing-method", G_TYPE_STRING, + method->str, NULL); + } } else { - gst_structure_set (structure, - "interlaced", G_TYPE_BOOLEAN, TRUE, - "framerate", GST_TYPE_FRACTION, fps_n, fps_d * 2, NULL); + gst_caps_set_simple (othercaps, "interlaced", G_TYPE_BOOLEAN, TRUE, NULL); + if (interlace->mode == GST_INTERLACE_MODE_INTERLACE) { + gst_caps_set_simple (othercaps, "framerate", GST_TYPE_FRACTION, fps_n, + fps_d * 2, NULL); + } else { + gst_caps_set_simple (othercaps, "framerate", GST_TYPE_FRACTION, + pdformat->dur_d, pdformat->dur_n, "interlacing-method", G_TYPE_STRING, + method->str, NULL); + } } ret = gst_pad_set_caps (otherpad, othercaps); @@ -422,6 +630,7 @@ gst_interlace_chain (GstPad * pad, GstBuffer * buffer) { GstInterlace *interlace = GST_INTERLACE (gst_pad_get_parent (pad)); GstFlowReturn ret; + gint num_fields = 0; GST_DEBUG ("Received buffer at %u:%02u:%02u:%09u", (guint) (GST_BUFFER_TIMESTAMP (buffer) / (GST_SECOND * 60 * 60)), @@ -439,37 +648,109 @@ gst_interlace_chain (GstPad * pad, GstBuffer * buffer) if (GST_BUFFER_FLAGS (buffer) & GST_BUFFER_FLAG_DISCONT) { GST_DEBUG ("discont"); + } + if (interlace->buffer_ts == GST_CLOCK_TIME_NONE) { + /* get the initial ts */ + interlace->buffer_ts = GST_BUFFER_TIMESTAMP (buffer); } if (interlace->stored_frame == NULL) { interlace->stored_frame = buffer; ret = GST_FLOW_OK; } else { - GstBuffer *output_buffer; - - output_buffer = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (buffer)); - - copy_field (interlace, output_buffer, interlace->stored_frame, - !interlace->top_field_first); - copy_field (interlace, output_buffer, buffer, interlace->top_field_first); - - GST_BUFFER_TIMESTAMP (output_buffer) = - GST_BUFFER_TIMESTAMP (interlace->stored_frame); - GST_BUFFER_DURATION (output_buffer) = - GST_BUFFER_DURATION (interlace->stored_frame) + - GST_BUFFER_DURATION (buffer); - gst_buffer_set_caps (output_buffer, interlace->srccaps); - - if (interlace->top_field_first) { - GST_BUFFER_FLAG_SET (output_buffer, GST_VIDEO_BUFFER_TFF); + switch (interlace->mode) { + case GST_INTERLACE_MODE_INTERLACE: + { + GstBuffer *output_buffer; + + output_buffer = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (buffer)); + + copy_field (interlace, output_buffer, interlace->stored_frame, + !interlace->top_field_first); + copy_field (interlace, output_buffer, buffer, + interlace->top_field_first); + + GST_BUFFER_TIMESTAMP (output_buffer) = + GST_BUFFER_TIMESTAMP (interlace->stored_frame); + GST_BUFFER_DURATION (output_buffer) = + GST_BUFFER_DURATION (interlace->stored_frame) + + GST_BUFFER_DURATION (buffer); + gst_buffer_set_caps (output_buffer, interlace->srccaps); + + if (interlace->top_field_first) { + GST_BUFFER_FLAG_SET (output_buffer, GST_VIDEO_BUFFER_TFF); + } + + ret = gst_pad_push (interlace->srcpad, output_buffer); + + gst_buffer_unref (buffer); + gst_buffer_unref (interlace->stored_frame); + interlace->stored_frame = NULL; + break; + } + case GST_INTERLACE_MODE_TELECINE: + { + const PulldownFormat *format = &formats[interlace->telecine_pattern]; + + num_fields = + format->n_fields[interlace->phase_index] - + interlace->stored_fields_pushed; + interlace->stored_fields_pushed = 0; + + while (num_fields > 0) { + GstBuffer *output_buffer; + + if (num_fields > 1) { + /* make metadata writable before editing it */ + interlace->stored_frame = + gst_buffer_make_metadata_writable (interlace->stored_frame); + num_fields -= 2; + gst_interlace_telecine_decorate_buffer (interlace, + interlace->stored_frame, format); + /* ref output_buffer/stored frame because we want to keep it for now + * and pushing gives away a ref */ + output_buffer = gst_buffer_ref (interlace->stored_frame); + } else { + output_buffer = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (buffer)); + + /* take the top field from the stored frame */ + copy_field (interlace, output_buffer, interlace->stored_frame, 0); + /* take the bottom field from the incoming buffer */ + copy_field (interlace, output_buffer, buffer, 1); + /* one field pushed from the stored frame, one from the incoming + * buffer that will become stored */ + num_fields--; + interlace->stored_fields_pushed = 1; + gst_interlace_telecine_decorate_buffer (interlace, output_buffer, + format); + } + + if ((ret = gst_pad_push (interlace->srcpad, output_buffer))) { + GST_DEBUG_OBJECT (interlace, "Failed to push buffer %p", + output_buffer); + return ret; + } + output_buffer = NULL; + + if (!num_fields) { + gst_buffer_unref (interlace->stored_frame); + /* if we still need one field from the incoming buffer, store it */ + interlace->stored_frame = buffer; + break; + } + } + + /* increment the phase index */ + interlace->phase_index++; + if (!format->n_fields[interlace->phase_index]) { + interlace->phase_index = 0; + } + break; + } + default: + break; } - - ret = gst_pad_push (interlace->srcpad, output_buffer); - - gst_buffer_unref (buffer); - gst_buffer_unref (interlace->stored_frame); - interlace->stored_frame = NULL; } gst_object_unref (interlace); @@ -487,6 +768,12 @@ gst_interlace_set_property (GObject * object, case PROP_TOP_FIELD_FIRST: interlace->top_field_first = g_value_get_boolean (value); break; + case PROP_MODE: + interlace->mode = g_value_get_enum (value); + break; + case PROP_TELECINE_PATTERN: + interlace->telecine_pattern = g_value_get_enum (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -503,6 +790,12 @@ gst_interlace_get_property (GObject * object, case PROP_TOP_FIELD_FIRST: g_value_set_boolean (value, interlace->top_field_first); break; + case PROP_MODE: + g_value_set_enum (value, interlace->mode); + break; + case PROP_TELECINE_PATTERN: + g_value_set_enum (value, interlace->telecine_pattern); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; |