| /* |
| * cmmlenc.c - GStreamer CMML decoder test suite |
| * Copyright (C) 2005 Alessandro Decina |
| * |
| * Authors: |
| * Alessandro Decina <alessandro@nnva.org> |
| * |
| * 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 <gst/check/gstcheck.h> |
| |
| #include <gst/tag/tag.h> |
| |
| #define SINK_CAPS "text/x-cmml" |
| #define SRC_CAPS "text/x-cmml,encoded=(boolean)FALSE" |
| |
| #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 XML_PREAMBLE \ |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"\ |
| "<!DOCTYPE cmml SYSTEM \"cmml.dtd\">\n" |
| |
| #define START_TAG \ |
| "<cmml>" |
| |
| #define PROCESSING_INSTRUCTION \ |
| "<?cmml ?>" |
| |
| #define PREAMBLE \ |
| XML_PREAMBLE START_TAG |
| |
| #define PREAMBLE_ENCODED \ |
| XML_PREAMBLE PROCESSING_INSTRUCTION |
| |
| #define STREAM_TAG \ |
| "<stream timebase=\"10\">"\ |
| "<import src=\"test.ogg\"/>"\ |
| "<import src=\"test1.ogg\"/>"\ |
| "</stream>" |
| |
| #define STREAM_TAG_ENCODED STREAM_TAG |
| |
| #define HEAD_TAG \ |
| "<head>"\ |
| "<title>The Research Hunter</title>"\ |
| "<meta name=\"DC.audience\" content=\"General\"/>"\ |
| "<meta name=\"DC.author\" content=\"CSIRO Publishing\"/>"\ |
| "<meta name=\"DC.format\" content=\"video\"/>"\ |
| "<meta name=\"DC.language\" content=\"English\"/>"\ |
| "<meta name=\"DC.publisher\" content=\"CSIRO Australia\"/>"\ |
| "</head>" |
| |
| #define HEAD_TAG_ENCODED HEAD_TAG |
| |
| #define CLIP_TEMPLATE \ |
| "<clip id=\"%s\" track=\"%s\" start=\"%s\">"\ |
| "<a href=\"http://www.annodex.org/\">http://www.annodex.org</a>"\ |
| "<img src=\"images/index.jpg\"/>"\ |
| "<desc>Annodex Foundation</desc>"\ |
| "<meta name=\"test\" content=\"test content\"/>"\ |
| "</clip>" |
| |
| #define ENDED_CLIP_TEMPLATE \ |
| "<clip id=\"%s\" track=\"%s\" start=\"%s\" end=\"%s\">"\ |
| "<a href=\"http://www.annodex.org/\">http://www.annodex.org</a>"\ |
| "<img src=\"images/index.jpg\"/>"\ |
| "<desc>Annodex Foundation</desc>"\ |
| "<meta name=\"test\" content=\"test content\"/>"\ |
| "</clip>" |
| |
| #define CLIP_TEMPLATE_ENCODED \ |
| "<clip id=\"%s\" track=\"%s\">"\ |
| "<a href=\"http://www.annodex.org/\">http://www.annodex.org</a>"\ |
| "<img src=\"images/index.jpg\"/>"\ |
| "<desc>Annodex Foundation</desc>"\ |
| "<meta name=\"test\" content=\"test content\"/>"\ |
| "</clip>" |
| |
| #define EMPTY_CLIP_TEMPLATE_ENCODED \ |
| "<clip track=\"%s\"/>" |
| |
| #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 GList *current_buf; |
| static guint64 granulerate; |
| static guint8 granuleshift; |
| static GstElement *cmmlenc; |
| static GstBus *bus; |
| static GstFlowReturn flow; |
| 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) |
| { |
| gst_buffer_unref (GST_BUFFER (buffer)); |
| } |
| |
| static void |
| setup_cmmlenc (void) |
| { |
| guint64 granulerate_n, granulerate_d; |
| |
| GST_DEBUG ("setup_cmmlenc"); |
| |
| cmmlenc = gst_check_setup_element ("cmmlenc"); |
| srcpad = gst_check_setup_src_pad (cmmlenc, &srctemplate); |
| sinkpad = gst_check_setup_sink_pad (cmmlenc, &sinktemplate); |
| gst_pad_set_active (srcpad, TRUE); |
| gst_pad_set_active (sinkpad, TRUE); |
| |
| bus = gst_bus_new (); |
| gst_element_set_bus (cmmlenc, bus); |
| |
| fail_unless (gst_element_set_state (cmmlenc, |
| GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE, |
| "could not set to playing"); |
| |
| g_object_get (cmmlenc, "granule-rate-numerator", &granulerate_n, |
| "granule-rate-denominator", &granulerate_d, |
| "granule-shift", &granuleshift, NULL); |
| |
| granulerate = GST_SECOND * granulerate_d / granulerate_n; |
| } |
| |
| static void |
| teardown_cmmlenc (void) |
| { |
| /* free encoded buffers */ |
| 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_cmmlenc"); |
| gst_pad_set_active (srcpad, FALSE); |
| gst_pad_set_active (sinkpad, FALSE); |
| gst_check_teardown_src_pad (cmmlenc); |
| gst_check_teardown_sink_pad (cmmlenc); |
| gst_check_teardown_element (cmmlenc); |
| } |
| |
| static void |
| check_output_buffer_is_equal (const gchar * name, |
| const gchar * data, gint refcount) |
| { |
| GstBuffer *buffer; |
| GstMapInfo map; |
| |
| 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); |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| |
| ASSERT_OBJECT_REFCOUNT (buffer, name, refcount); |
| fail_unless (memcmp (map.data, data, map.size) == 0, |
| "'%s' (%s) is not equal to (%s)", name, map.data, data); |
| |
| gst_buffer_unmap (buffer, &map); |
| } |
| |
| static GstFlowReturn |
| push_data (const gchar * name, const gchar * data, gint size) |
| { |
| GstBuffer *buffer; |
| GstFlowReturn res; |
| |
| buffer = buffer_new (data, size); |
| res = gst_pad_push (srcpad, buffer); |
| |
| return res; |
| } |
| |
| 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 void |
| check_headers (void) |
| { |
| push_caps (); |
| |
| /* push the cmml start tag */ |
| flow = push_data ("preamble", PREAMBLE, strlen (PREAMBLE)); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| |
| /* push the stream tag */ |
| flow = push_data ("stream", STREAM_TAG, strlen (STREAM_TAG)); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| /* push the head tag */ |
| flow = push_data ("head", HEAD_TAG, strlen (HEAD_TAG)); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| |
| /* should output 3 buffers: the ident, preamble and head headers */ |
| fail_unless_equals_int (g_list_length (buffers), 3); |
| |
| /* check the ident header */ |
| check_output_buffer_is_equal ("cmml-ident-buffer", IDENT_HEADER, 1); |
| |
| /* check the cmml processing instruction */ |
| check_output_buffer_is_equal ("cmml-preamble-buffer", PREAMBLE_ENCODED, 1); |
| |
| /* check the encoded head tag */ |
| check_output_buffer_is_equal ("head-tag-buffer", HEAD_TAG_ENCODED, 1); |
| } |
| |
| static GstFlowReturn |
| push_clip (const gchar * name, const gchar * track, |
| const gchar * start, const gchar * end) |
| { |
| gchar *clip; |
| GstFlowReturn res; |
| |
| if (end != NULL) |
| clip = g_strdup_printf (ENDED_CLIP_TEMPLATE, name, track, start, end); |
| else |
| clip = g_strdup_printf (CLIP_TEMPLATE, name, track, start); |
| res = push_data (name, clip, strlen (clip)); |
| g_free (clip); |
| |
| return res; |
| } |
| |
| static void |
| check_clip_times (GstBuffer * buffer, GstClockTime start, GstClockTime prev) |
| { |
| guint64 keyindex, keyoffset, granulepos; |
| |
| granulepos = GST_BUFFER_OFFSET_END (buffer); |
| if (granuleshift == 0 || granuleshift == 64) |
| keyindex = 0; |
| else |
| keyindex = granulepos >> granuleshift; |
| keyoffset = granulepos - (keyindex << granuleshift); |
| fail_unless_equals_uint64 (keyindex * granulerate, prev); |
| fail_unless_equals_uint64 ((keyindex + keyoffset) * granulerate, start); |
| } |
| |
| static void |
| check_clip (const gchar * name, const gchar * track, |
| GstClockTime start, GstClockTime prev) |
| { |
| gchar *encoded_clip; |
| GstBuffer *buffer; |
| |
| encoded_clip = g_strdup_printf (CLIP_TEMPLATE_ENCODED, name, track); |
| check_output_buffer_is_equal (name, encoded_clip, 1); |
| g_free (encoded_clip); |
| buffer = GST_BUFFER (current_buf->data); |
| check_clip_times (buffer, start, prev); |
| } |
| |
| static void |
| check_empty_clip (const gchar * name, const gchar * track, |
| GstClockTime start, GstClockTime prev) |
| { |
| gchar *encoded_clip; |
| GstBuffer *buffer; |
| |
| encoded_clip = g_strdup_printf (EMPTY_CLIP_TEMPLATE_ENCODED, track); |
| check_output_buffer_is_equal (name, encoded_clip, 1); |
| g_free (encoded_clip); |
| buffer = GST_BUFFER (current_buf->data); |
| check_clip_times (buffer, start, prev); |
| } |
| |
| GST_START_TEST (test_enc) |
| { |
| check_headers (); |
| |
| flow = push_clip ("clip-1", "default", "1.234", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("clip-1", "default", 1 * GST_SECOND + 234 * GST_MSECOND, 0); |
| |
| flow = push_clip ("clip-2", "default", "5.678", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("clip-2", "default", |
| 5 * GST_SECOND + 678 * GST_MSECOND, 1 * GST_SECOND + 234 * GST_MSECOND); |
| |
| flow = push_clip ("clip-3", "othertrack", "9.123", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("clip-3", "othertrack", 9 * GST_SECOND + 123 * GST_MSECOND, 0); |
| |
| flow = push_data ("end-tag", "</cmml>", strlen ("</cmml>")); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_output_buffer_is_equal ("cmml-eos", NULL, 1); |
| } |
| |
| GST_END_TEST; |
| |
| GST_START_TEST (test_clip_end_time) |
| { |
| check_headers (); |
| |
| /* push a clip that starts at 1.234 an ends at 2.234 */ |
| flow = push_clip ("clip-1", "default", "1.234", "2.234"); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("clip-1", "default", 1 * GST_SECOND + 234 * GST_MSECOND, 0); |
| |
| /* now check that the encoder created an empty clip starting at 2.234 to mark |
| * the end of clip-1 */ |
| check_empty_clip ("clip-1-end", "default", |
| 2 * GST_SECOND + 234 * GST_MSECOND, 1 * GST_SECOND + 234 * GST_MSECOND); |
| |
| /* now push another clip on the same track and check that the keyindex part of |
| * the granulepos points to clip-1 and not to the empty clip */ |
| flow = push_clip ("clip-2", "default", "5", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("clip-2", "default", |
| 5 * GST_SECOND, 1 * GST_SECOND + 234 * GST_MSECOND); |
| } |
| |
| GST_END_TEST; |
| |
| GST_START_TEST (test_time_order) |
| { |
| check_headers (); |
| |
| /* clips belonging to the same track must have start times in non decreasing |
| * order */ |
| flow = push_clip ("clip-1", "default", "1000:00:00.000", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("clip-1", "default", 3600 * 1000 * GST_SECOND, 0); |
| |
| /* this will make the encoder throw an error message */ |
| flow = push_clip ("clip-2", "default", "5.678", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| |
| flow = push_clip ("clip-3", "default", "1000:00:00.001", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("clip-3", "default", |
| 3600 * 1000 * GST_SECOND + 1 * GST_MSECOND, 3600 * 1000 * GST_SECOND); |
| |
| /* tracks don't interfere with each other */ |
| flow = push_clip ("clip-4", "othertrack", "9.123", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("clip-4", "othertrack", 9 * GST_SECOND + 123 * GST_MSECOND, 0); |
| } |
| |
| GST_END_TEST; |
| |
| GST_START_TEST (test_time_parsing) |
| { |
| check_headers (); |
| |
| flow = push_clip ("bad-msecs", "default", "0.1000", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| |
| flow = push_clip ("bad-secs", "default", "00:00:60.123", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| |
| flow = push_clip ("bad-minutes", "default", "00:60:12.345", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| |
| /* this fails since we can't store 5124096 * 3600 * GST_SECOND in a |
| * GstClockTime */ |
| flow = push_clip ("bad-hours", "default", "5124096:00:00.000", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| } |
| |
| GST_END_TEST; |
| |
| GST_START_TEST (test_time_limits) |
| { |
| check_headers (); |
| |
| /* ugly hack to make sure that the following checks actually overflow parsing |
| * the times in gst_cmml_clock_time_from_npt rather than converting them to |
| * granulepos in gst_cmml_clock_time_to_granule */ |
| granuleshift = 64; |
| g_object_set (cmmlenc, "granule-shift", granuleshift, NULL); |
| |
| /* 5124095:34:33.709 is the max npt-hhmmss time representable with |
| * GstClockTime */ |
| flow = push_clip ("max-npt-hhmmss", "foo", "5124095:34:33.709", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("max-npt-hhmmss", "foo", |
| (GstClockTime) 5124095 * 3600 * GST_SECOND + 34 * 60 * GST_SECOND + |
| 33 * GST_SECOND + 709 * GST_MSECOND, 0); |
| |
| flow = push_clip ("overflow-max-npt-hhmmss", "overflows", |
| "5124095:34:33.710", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| |
| /* 18446744073.709 is the max ntp-sec time */ |
| flow = push_clip ("max-npt-secs", "bar", "18446744073.709", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("max-npt-secs", "bar", |
| (GstClockTime) 5124095 * 3600 * GST_SECOND + 34 * 60 * GST_SECOND + |
| 33 * GST_SECOND + 709 * GST_MSECOND, 0); |
| |
| /* overflow doing 18446744074 * GST_SECOND */ |
| flow = push_clip ("overflow-max-npt-secs", "overflows", |
| "18446744074.000", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| |
| /* overflow doing seconds + milliseconds */ |
| flow = push_clip ("overflow-max-npt-secs-msecs", "overflows", |
| "18446744073.710", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| |
| /* reset granuleshift to 32 to check keyoffset overflows in |
| * gst_cmml_clock_time_to_granule */ |
| granuleshift = 32; |
| g_object_set (cmmlenc, "granule-shift", granuleshift, NULL); |
| |
| /* 1193:02:47.295 is the max time we can encode in the keyoffset part of a |
| * granulepos given a granuleshift of 32 */ |
| flow = push_clip ("max-granule-keyoffset", "baz", "1193:02:47.295", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_OK); |
| check_clip ("max-granule-keyoffset", "baz", |
| 1193 * 3600 * GST_SECOND + 2 * 60 * GST_SECOND + |
| 47 * GST_SECOND + 295 * GST_MSECOND, 0); |
| |
| flow = push_clip ("overflow-max-granule-keyoffset", "overflows", |
| "1193:02:47.296", NULL); |
| fail_unless_equals_flow_return (flow, GST_FLOW_ERROR); |
| } |
| |
| GST_END_TEST; |
| |
| static Suite * |
| cmmlenc_suite (void) |
| { |
| Suite *s = suite_create ("cmmlenc"); |
| TCase *tc_general = tcase_create ("general"); |
| |
| suite_add_tcase (s, tc_general); |
| tcase_add_checked_fixture (tc_general, setup_cmmlenc, teardown_cmmlenc); |
| tcase_add_test (tc_general, test_enc); |
| tcase_add_test (tc_general, test_clip_end_time); |
| tcase_add_test (tc_general, test_time_order); |
| tcase_add_test (tc_general, test_time_parsing); |
| tcase_add_test (tc_general, test_time_limits); |
| |
| return s; |
| } |
| |
| GST_CHECK_MAIN (cmmlenc); |