/* * cmmldec.c - GStreamer CMML decoder test suite * Copyright (C) 2005 Alessandro Decina * * Authors: * Alessandro Decina * * 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 #define SINK_CAPS "text/x-cmml" #define SRC_CAPS "text/x-cmml, encoded=(boolean)TRUE" #define IDENT_HEADER \ "CMML\x00\x00\x00\x00"\ "\x03\x00\x00\x00"\ "\xe8\x03\x00\x00\x00\x00\x00\x00"\ "\x01\x00\x00\x00\x00\x00\x00\x00"\ "\x20" #define IDENT_HEADER_SIZE 29 #define PREAMBLE_NO_PI \ "\n"\ "\n" #define PREAMBLE PREAMBLE_NO_PI "" #define PREAMBLE_DECODED PREAMBLE_NO_PI "" #define HEAD_TAG \ ""\ "The Research Hunter"\ ""\ ""\ ""\ ""\ ""\ "" #define HEAD_TAG_DECODED HEAD_TAG #define CLIP_TEMPLATE \ ""\ "http://www.csiro.au"\ ""\ "Welcome to CSIRO"\ ""\ "" #define CLIP_TEMPLATE_DECODED \ ""\ "http://www.csiro.au"\ ""\ "Welcome to CSIRO"\ ""\ "" #define EMPTY_CLIP_TEMPLATE \ "" #define END_TAG \ "" #define fail_unless_equals_flow_return(a, b) \ G_STMT_START { \ gchar *a_up = g_ascii_strup (gst_flow_get_name (a), -1); \ gchar *b_up = g_ascii_strup (gst_flow_get_name (b), -1); \ fail_unless (a == b, \ "'" #a "' (GST_FLOW_%s) is not equal to '" #b "' (GST_FLOW_%s)", \ a_up, b_up); \ g_free (a_up); \ g_free (b_up); \ } G_STMT_END; static GstElement *cmmldec; static GstBus *bus; static GstFlowReturn flow; static GList *current_buf; static gint64 granulerate; static guint8 granuleshift; static GstPad *srcpad, *sinkpad; static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (SINK_CAPS) ); static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (SRC_CAPS) ); static GstBuffer * buffer_new (const gchar * buffer_data, guint size) { GstBuffer *buffer; guint8 *data; data = g_malloc (size); memcpy (data, buffer_data, size); buffer = gst_buffer_new_wrapped (data, size); return buffer; } static void buffer_unref (void *buffer, void *user_data) { ASSERT_OBJECT_REFCOUNT (buffer, "buf", 1); gst_buffer_unref (GST_BUFFER (buffer)); } static void setup_cmmldec (void) { GST_DEBUG ("setup_cmmldec"); cmmldec = gst_check_setup_element ("cmmldec"); srcpad = gst_check_setup_src_pad (cmmldec, &srctemplate, NULL); sinkpad = gst_check_setup_sink_pad (cmmldec, &sinktemplate, NULL); gst_pad_set_active (srcpad, TRUE); gst_pad_set_active (sinkpad, TRUE); bus = gst_bus_new (); gst_element_set_bus (cmmldec, bus); fail_unless (gst_element_set_state (cmmldec, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE, "could not set to playing"); granulerate = GST_SECOND / 1000; granuleshift = 32; buffers = NULL; } static void teardown_cmmldec (void) { g_list_foreach (buffers, buffer_unref, NULL); g_list_free (buffers); buffers = NULL; current_buf = NULL; gst_bus_set_flushing (bus, TRUE); gst_object_unref (bus); GST_DEBUG ("teardown_cmmldec"); gst_pad_set_active (srcpad, FALSE); gst_pad_set_active (sinkpad, FALSE); gst_check_teardown_src_pad (cmmldec); gst_check_teardown_sink_pad (cmmldec); gst_check_teardown_element (cmmldec); } static void check_output_buffer_is_equal (const gchar * name, const gchar * data, gint refcount) { GstBuffer *buffer; gpointer buf_data; gsize size; if (current_buf == NULL) current_buf = buffers; else current_buf = g_list_next (current_buf); fail_unless (current_buf != NULL); buffer = GST_BUFFER (current_buf->data); buf_data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ); ASSERT_OBJECT_REFCOUNT (buffer, name, refcount); fail_unless (memcmp (buf_data, data, size) == 0, "'%s' (%s) is not equal to (%s)", name, buf_data, data); gst_buffer_unmap (buffer, buf_data, size); } static GstFlowReturn push_data (const gchar * name, const gchar * data, gint size, gint64 granulepos) { GstBuffer *buffer; buffer = buffer_new (data, size); GST_BUFFER_OFFSET_END (buffer) = granulepos; return gst_pad_push (srcpad, buffer); } static void push_caps (void) { GstCaps *caps; caps = gst_caps_from_string (SRC_CAPS); fail_unless (gst_pad_set_caps (srcpad, caps)); gst_caps_unref (caps); } static GObject * cmml_tag_message_pop (GstBus * bus, const gchar * tag) { GstMessage *message; GstTagList *taglist; const GValue *value; GObject *obj; message = gst_bus_poll (bus, GST_MESSAGE_TAG, 0); if (message == NULL) return NULL; gst_message_parse_tag (message, &taglist); value = gst_tag_list_get_value_index (taglist, tag, 0); if (value == NULL) { gst_message_unref (message); gst_tag_list_free (taglist); return NULL; } obj = g_value_dup_object (value); gst_message_unref (message); gst_tag_list_free (taglist); return obj; } static void check_headers (void) { GObject *head_tag; gchar *title, *base; GValueArray *meta; push_caps (); /* push the ident header */ flow = push_data ("ident-header", IDENT_HEADER, IDENT_HEADER_SIZE, 0); fail_unless_equals_flow_return (flow, GST_FLOW_OK); /* push the cmml preamble */ flow = push_data ("preamble", PREAMBLE, strlen (PREAMBLE), 0); fail_unless_equals_flow_return (flow, GST_FLOW_OK); /* push the head tag */ flow = push_data ("head", HEAD_TAG, strlen (HEAD_TAG), 0); fail_unless_equals_flow_return (flow, GST_FLOW_OK); fail_unless_equals_int (g_list_length (buffers), 2); /* check the decoded preamble */ check_output_buffer_is_equal ("cmml-preamble-buffer", PREAMBLE_DECODED, 1); /* check the decoded head tag */ check_output_buffer_is_equal ("head-tag-buffer", HEAD_TAG_DECODED, 1); /* check the GstCmmlTagHead tag object */ head_tag = cmml_tag_message_pop (bus, GST_TAG_CMML_HEAD); fail_unless (head_tag != NULL); g_object_get (head_tag, "title", &title, "base-uri", &base, "meta", &meta, NULL); fail_unless_equals_string ("The Research Hunter", title); fail_unless (base == NULL); fail_unless (meta != NULL); fail_unless_equals_int (meta->n_values, 10); g_free (title); g_free (base); g_value_array_free (meta); g_object_unref (head_tag); } static GstFlowReturn push_clip_full (const gchar * name, const gchar * track, const gchar * template, GstClockTime prev, GstClockTime start) { gchar *clip; gint64 keyindex, keyoffset, granulepos; GstFlowReturn res; if (track == NULL) track = "default"; if (prev == GST_CLOCK_TIME_NONE) prev = 0; keyindex = prev / granulerate << granuleshift; keyoffset = (start - prev) / granulerate; granulepos = keyindex + keyoffset; clip = g_strdup_printf (template, name, track); res = push_data (name, clip, strlen (clip), granulepos); g_free (clip); return res; } static GstFlowReturn push_clip (const gchar * name, const gchar * track, GstClockTime prev, GstClockTime start) { return push_clip_full (name, track, CLIP_TEMPLATE, prev, start); } static GstFlowReturn push_empty_clip (const gchar * name, const gchar * track, GstClockTime start) { return push_clip_full (name, track, EMPTY_CLIP_TEMPLATE, GST_CLOCK_TIME_NONE, start); } static void check_output_clip (const gchar * name, const gchar * track, const gchar * start, const gchar * end) { gchar *decoded_clip; if (track == NULL) track = "default"; decoded_clip = g_strdup_printf (CLIP_TEMPLATE_DECODED, name, track, start); check_output_buffer_is_equal (name, decoded_clip, 1); g_free (decoded_clip); } GST_START_TEST (test_dec) { GstClockTime clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND; GstClockTime clip2_start = clip1_start; GstClockTime clip3_start = ((100 * 3600) + (59 * 60) + 59) * GST_SECOND + 678 * GST_MSECOND; check_headers (); flow = push_clip ("clip-1", "default", GST_CLOCK_TIME_NONE, clip1_start); fail_unless_equals_flow_return (flow, GST_FLOW_OK); flow = push_clip ("clip-2", "othertrack", GST_CLOCK_TIME_NONE, clip2_start); fail_unless_equals_flow_return (flow, GST_FLOW_OK); flow = push_clip ("clip-3", "default", clip1_start, clip3_start); fail_unless_equals_flow_return (flow, GST_FLOW_OK); /* send EOS to flush clip-2 and clip-3 */ gst_pad_send_event (GST_PAD_PEER (srcpad), gst_event_new_eos ()); check_output_clip ("clip-1", "default", "0:00:01.234", NULL); check_output_clip ("clip-2", "othertrack", "0:00:01.234", NULL); check_output_clip ("clip-3", "default", "100:59:59.678", NULL); check_output_buffer_is_equal ("cmml-end-tag", END_TAG, 1); } GST_END_TEST; GST_START_TEST (test_preamble_no_pi) { push_caps (); flow = push_data ("ident-header", IDENT_HEADER, IDENT_HEADER_SIZE, 0); fail_unless_equals_flow_return (flow, GST_FLOW_OK); fail_unless_equals_int (g_list_length (buffers), 0); flow = push_data ("preamble-no-pi", PREAMBLE_NO_PI, strlen (PREAMBLE_NO_PI), 0); fail_unless_equals_flow_return (flow, GST_FLOW_OK); fail_unless_equals_int (g_list_length (buffers), 1); check_output_buffer_is_equal ("cmml-preamble-buffer", PREAMBLE_NO_PI "", 1); } GST_END_TEST; GST_START_TEST (test_tags) { GObject *tag; gboolean empty; gchar *id, *track; gint64 start_time, end_time; gchar *anchor_href, *anchor_text; gchar *img_src, *img_alt; gchar *desc; GValueArray *meta; GstClockTime clip1_start; check_headers (); clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND; flow = push_clip ("clip-1", "default", 0, clip1_start); fail_unless_equals_flow_return (flow, GST_FLOW_OK); tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP); fail_unless (tag != NULL); g_object_get (tag, "id", &id, "empty", &empty, "track", &track, "start-time", &start_time, "end-time", &end_time, "anchor-uri", &anchor_href, "anchor-text", &anchor_text, "img-uri", &img_src, "img-alt", &img_alt, "description", &desc, "meta", &meta, NULL); fail_unless (empty == FALSE); fail_unless_equals_string (id, "clip-1"); fail_unless_equals_string (track, "default"); fail_unless_equals_int (start_time, 1 * GST_SECOND + 234 * GST_MSECOND); fail_unless_equals_uint64 (end_time, GST_CLOCK_TIME_NONE); fail_unless_equals_string (anchor_href, "http://www.csiro.au/"); fail_unless_equals_string (anchor_text, "http://www.csiro.au"); fail_unless_equals_string (img_src, "images/index1.jpg"); fail_unless (img_alt == NULL); fail_unless_equals_string (desc, "Welcome to CSIRO"); fail_unless (meta != NULL); fail_unless_equals_int (meta->n_values, 2); g_free (id); g_free (track); g_free (anchor_href); g_free (anchor_text); g_free (img_src); g_free (img_alt); g_free (desc); g_value_array_free (meta); g_object_unref (tag); } GST_END_TEST; GST_START_TEST (test_wait_clip_end) { GObject *tag; gchar *id; GstClockTime end_time = 0; GstClockTime clip1_start = 1 * GST_SECOND + 234 * GST_MSECOND; GstClockTime clip2_start = 2 * GST_SECOND + 234 * GST_MSECOND; GstClockTime clip3_start = 3 * GST_SECOND + 234 * GST_MSECOND; GstClockTime clip3_end = 4 * GST_SECOND + 234 * GST_MSECOND; GstClockTime clip4_start = 5 * GST_SECOND + 234 * GST_MSECOND; g_object_set (cmmldec, "wait-clip-end-time", TRUE, NULL); check_headers (); flow = push_clip ("clip-1", "default", 0, clip1_start); fail_unless_equals_flow_return (flow, GST_FLOW_OK); /* no tag has been posted yet */ fail_unless (cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP) == NULL); flow = push_clip ("clip-2", "default", clip1_start, clip2_start); fail_unless_equals_flow_return (flow, GST_FLOW_OK); tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP); fail_unless (tag != NULL); g_object_get (tag, "id", &id, "end-time", &end_time, NULL); /* clip-1 is posted when clip-2 is decoded. clip-1 ends when clip-2 starts */ fail_unless_equals_string (id, "clip-1"); fail_unless_equals_int (end_time, clip2_start); g_free (id); g_object_unref (tag); flow = push_clip ("clip-3", "default", clip2_start, clip3_start); fail_unless_equals_flow_return (flow, GST_FLOW_OK); tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP); fail_unless (tag != NULL); g_object_get (tag, "id", &id, "end-time", &end_time, NULL); /* clip-2 is posted when clip-3 is decoded. It ends when clip-3 starts */ fail_unless_equals_string (id, "clip-2"); fail_unless_equals_int (end_time, clip3_start); g_free (id); g_object_unref (tag); flow = push_empty_clip ("empty-clip", "default", clip3_end); tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP); fail_unless (tag != NULL); g_object_get (tag, "id", &id, "end-time", &end_time, NULL); /* clip-3 ends when empty-clip is decoded */ fail_unless_equals_string (id, "clip-3"); fail_unless_equals_int (end_time, clip3_end); g_free (id); g_object_unref (tag); flow = push_clip ("clip-4", "default", clip3_start, clip4_start); fail_unless_equals_flow_return (flow, GST_FLOW_OK); /* an empty clip just marks the end of the previous one, so no tag is posted * for empty-clip */ fail_unless (cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP) == NULL); /* send EOS to flush clip-4 */ gst_pad_send_event (GST_PAD_PEER (srcpad), gst_event_new_eos ()); tag = cmml_tag_message_pop (bus, GST_TAG_CMML_CLIP); fail_unless (tag != NULL); g_object_get (tag, "id", &id, NULL); fail_unless_equals_string (id, "clip-4"); g_free (id); g_object_unref (tag); } GST_END_TEST; GST_START_TEST (test_weird_input) { const gchar *bad_xml = ""; push_caps (); /* malformed ident header */ flow = push_data ("bad-ident-header", "CMML\0\0\0\0garbage", 15, 0); fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); /* push invalid xml */ flow = push_data ("bad-xml", bad_xml, strlen (bad_xml), 0); fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); /* and now for something completely different: an empty buffer. This is valid * as 'NIL' EOS pages are allowed */ flow = push_data ("empty-eos", NULL, 0, 0); fail_unless_equals_flow_return (flow, GST_FLOW_OK); } GST_END_TEST; GST_START_TEST (test_sink_query_convert) { guint64 keyindex, keyoffset, granulepos; GstClockTime index_time, offset_time; GstFormat dstfmt = GST_FORMAT_TIME; gint64 dstval; /* send headers to set the granulerate */ check_headers (); /* create a 1|1 granulepos */ index_time = 1 * GST_SECOND; offset_time = 1 * GST_SECOND; keyindex = (index_time / granulerate) << granuleshift; keyoffset = offset_time / granulerate; granulepos = keyindex + keyoffset; fail_unless (gst_pad_query_convert (GST_PAD_PEER (srcpad), GST_FORMAT_DEFAULT, granulepos, dstfmt, &dstval)); fail_unless (dstfmt == GST_FORMAT_TIME); /* fail unless dstval == index + offset */ fail_unless_equals_int (2 * GST_SECOND, dstval); } GST_END_TEST; static Suite * cmmldec_suite (void) { Suite *s = suite_create ("cmmldec"); TCase *tc_general = tcase_create ("general"); suite_add_tcase (s, tc_general); tcase_add_checked_fixture (tc_general, setup_cmmldec, teardown_cmmldec); tcase_add_test (tc_general, test_dec); tcase_add_test (tc_general, test_tags); tcase_add_test (tc_general, test_preamble_no_pi); tcase_add_test (tc_general, test_wait_clip_end); tcase_add_test (tc_general, test_sink_query_convert); tcase_add_test (tc_general, test_weird_input); return s; } GST_CHECK_MAIN (cmmldec);