diff --git a/.gitlab-image-tags.yml b/.gitlab-image-tags.yml index 87c5d4726a..20dcaf5cfe 100644 --- a/.gitlab-image-tags.yml +++ b/.gitlab-image-tags.yml @@ -11,6 +11,6 @@ variables: CHECKS_TAG: '2025-02-04.0' - ABI_CHECK_TAG: '2025-03-13.0' + ABI_CHECK_TAG: '2025-04-01.0' WINDOWS_TAG: '2025-02-23.0' diff --git a/girs/Gst-1.0.gir b/girs/Gst-1.0.gir index 6b5482e9ed..22cc9724ef 100644 --- a/girs/Gst-1.0.gir +++ b/girs/Gst-1.0.gir @@ -51890,6 +51890,24 @@ or GST_VALUE_UNORDERED + + Used by gst_value_hash() to calculate a hash of @value. + + + %TRUE, or %FALSE if @value cannot be hashed. + + + + + a #GValue + + + + a location to store the hash value + + + + A fundamental type that describes an unordered list of #GValue @@ -52083,8 +52101,12 @@ Free-function: g_free a #GstValueDeserializeWithPSpecFunc + + a #GstValueHashFunc + + - + @@ -58598,6 +58620,25 @@ a reference to @v. + + Compute a hash value of @value. +#GValue considered as equals by gst_value_compare() will have the same hash value. + + + %TRUE, or %FALSE if @value cannot be hashed. + + + + + a #GValue to hash + + + + a location to store the hash value + + + + Initialises the target value to be of the same type as source and then copies the contents from source to target. diff --git a/subprojects/gstreamer/gst/gstvalue.c b/subprojects/gstreamer/gst/gstvalue.c index 1aeb012c16..d8e94a72ec 100644 --- a/subprojects/gstreamer/gst/gstvalue.c +++ b/subprojects/gstreamer/gst/gstvalue.c @@ -3219,6 +3219,7 @@ gst_value_serialize_tag_list (const GValue * value) static gint compare_buffer (GstBuffer * buf1, GstBuffer * buf2) { + /* keep this implementation synced with gst_hash_buffer() */ gsize size1, size2; GstMapInfo info1, info2; gint result, mret; @@ -3355,6 +3356,7 @@ wrong_char: static gint gst_value_compare_sample (const GValue * value1, const GValue * value2) { + /* keep this implementation synced with gst_value_hash_sample() */ GstBuffer *buf1 = gst_sample_get_buffer (gst_value_get_sample (value1)); GstBuffer *buf2 = gst_sample_get_buffer (gst_value_get_sample (value2)); @@ -6139,7 +6141,8 @@ gst_value_compare (const GValue * value1, const GValue * value2) value2_is_list = G_VALUE_TYPE (value2) == GST_TYPE_LIST; /* Special cases: lists and scalar values ("{ 1 }" and "1" are equal), - as well as lists and ranges ("{ 1, 2 }" and "[ 1, 2 ]" are equal) */ + as well as lists and ranges ("{ 1, 2 }" and "[ 1, 2 ]" are equal). + Make sure to update gst_value_hash() when adding new special cases. */ if (value1_is_list && !value2_is_list) { gint i, n, ret; @@ -8059,6 +8062,548 @@ gst_value_compare_flagset (const GValue * value1, const GValue * value2) return GST_VALUE_UNORDERED; } +static gboolean +gst_value_hash_int (const GValue * v, guint * res) +{ + gint i = g_value_get_int (v); + *res = g_int_hash ((gconstpointer) & i); + return TRUE; +} + +static gboolean +gst_value_hash_int64 (const GValue * v, guint * res) +{ + gint64 i = g_value_get_int64 (v); + *res = g_int64_hash ((gconstpointer) & i); + return TRUE; +} + +static gboolean +gst_value_hash_long (const GValue * v, guint * res) +{ + glong l = g_value_get_long (v); + +#if GLIB_SIZEOF_LONG == 4 + *res = g_int_hash ((gconstpointer) & l); +#else + *res = g_int64_hash ((gconstpointer) & l); +#endif + + return TRUE; +} + +static gboolean +gst_value_hash_uint (const GValue * v, guint * res) +{ + guint u = g_value_get_uint (v); + *res = g_int_hash ((gconstpointer) & u); + return TRUE; +} + +static gboolean +gst_value_hash_uint64 (const GValue * v, guint * res) +{ + guint64 u = g_value_get_uint64 (v); + *res = g_int64_hash ((gconstpointer) & u); + return TRUE; +} + +static gboolean +gst_value_hash_ulong (const GValue * v, guint * res) +{ + gulong u = g_value_get_ulong (v); + *res = g_double_hash ((gconstpointer) & u); + return TRUE; +} + +static gboolean +gst_value_hash_uchar (const GValue * v, guint * res) +{ + guchar u = g_value_get_uchar (v); + gint i = u; + *res = g_int_hash ((gconstpointer) & i); + return TRUE; +} + +static gboolean +gst_value_hash_gtype (const GValue * v, guint * res) +{ + GType t = g_value_get_gtype (v); + +#if GLIB_SIZEOF_SIZE_T == 4 + *res = g_int_hash ((gconstpointer) & t); +#else + *res = g_int64_hash ((gconstpointer) & t); +#endif + + return TRUE; +} + +static gboolean +gst_value_hash_double (const GValue * v, guint * res) +{ + gdouble d = g_value_get_double (v); + *res = g_double_hash ((gconstpointer) & d); + return TRUE; +} + +static gboolean +gst_value_hash_float (const GValue * v, guint * res) +{ + gfloat f = g_value_get_float (v); + *res = g_int_hash ((gconstpointer) & f); + return TRUE; +} + +static gboolean +gst_value_hash_string (const GValue * v, guint * res) +{ + const gchar *s = g_value_get_string (v); + *res = g_str_hash (s); + return TRUE; +} + +static gboolean +gst_value_hash_boolean (const GValue * v, guint * res) +{ + gboolean b = g_value_get_boolean (v); + *res = b ? 1 : 0; + return TRUE; +} + +static gboolean +gst_value_hash_enum (const GValue * v, guint * res) +{ + gint e = g_value_get_enum (v); + *res = g_int_hash ((gconstpointer) & e); + return TRUE; +} + +static gboolean +gst_value_hash_gflags (const GValue * v, guint * res) +{ + guint f = g_value_get_flags (v); + *res = g_int_hash ((gconstpointer) & f); + return TRUE; +} + +/* helper to combine hash when hashing ordered container */ +static void +combine_hash_ordered (guint * hash, guint value_hash) +{ + /* perform a rotating shift to account for the position in the container */ + *hash = ((*hash << 5) | (*hash >> 27)) ^ value_hash; +} + +static gboolean +gst_value_hash_int_range (const GValue * v, guint * res) +{ + gint min = gst_value_get_int_range_min (v); + gint max = gst_value_get_int_range_max (v); + gint step = gst_value_get_int_range_step (v); + gint i; + + *res = 0; + /* range and list are considered as equal if they contain the same elements, + so they should have the same hash. Lists are not ordered so do not account + for element position when hashing. */ + for (i = min; i <= max; i += step) { + *res ^= g_int_hash ((gconstpointer) & i); + } + + return TRUE; +} + +static gboolean +gst_value_hash_int64_range (const GValue * v, guint * res) +{ + gint64 min = gst_value_get_int64_range_min (v); + gint64 max = gst_value_get_int64_range_max (v); + gint64 step = gst_value_get_int64_range_step (v); + gint64 i; + + *res = 0; + for (i = min; i <= max; i += step) { + *res ^= g_int64_hash ((gconstpointer) & i); + } + + return TRUE; +} + +static gboolean +gst_value_hash_double_range (const GValue * v, guint * res) +{ + gdouble min = gst_value_get_double_range_min (v); + gdouble max = gst_value_get_double_range_max (v); + + *res = g_double_hash ((gconstpointer) & min) ^ g_double_hash ((gconstpointer) + & max); + + return TRUE; +} + +static gboolean +gst_value_hash_g_value_array (const GValue * v, guint * res) +{ + guint i = 0; + GValueArray *array = g_value_get_boxed (v); + + *res = 0; + for (i = 0; i < array->n_values; i++) { + guint value_hash; + if (!gst_value_hash (&array->values[i], &value_hash)) + return FALSE; + combine_hash_ordered (res, value_hash); + } + + return TRUE; +} + +static gboolean +gst_hash_buffer (GstBuffer * buf, guint * res) +{ + GstBufferMapInfo map; + guint i; + + *res = 0; + + if (!gst_buffer_map (buf, &map, GST_MAP_READ)) { + return FALSE; + } + + for (i = 0; i < map.size; i++) { + combine_hash_ordered (res, map.data[i]); + } + + gst_buffer_unmap (buf, &map); + return TRUE; +} + +static gboolean +gst_value_hash_buffer (const GValue * v, guint * res) +{ + GstBuffer *buf = gst_value_get_buffer (v); + + /* compare_buffer() only compares the buffer content */ + return gst_hash_buffer (buf, res); +} + +static gboolean +gst_value_hash_sample (const GValue * v, guint * res) +{ + GstSample *sample = gst_value_get_sample (v); + GstBuffer *buf = gst_sample_get_buffer (sample); + + /* gst_value_compare_sample() only compares buffers */ + return gst_hash_buffer (buf, res); +} + +static gboolean +gst_value_hash_fraction (const GValue * v, guint * res) +{ + gint num = gst_value_get_fraction_numerator (v); + gint den = gst_value_get_fraction_denominator (v); + gfloat div = ((gfloat) num) / ((gfloat) den); + + *res = g_int_hash ((gconstpointer) & div); + return TRUE; +} + +static gboolean +gst_value_hash_fraction_range (const GValue * v, guint * res) +{ + const GValue *min = gst_value_get_fraction_range_min (v); + const GValue *max = gst_value_get_fraction_range_max (v); + gint num, den; + gfloat div; + + num = gst_value_get_fraction_numerator (min); + den = gst_value_get_fraction_denominator (min); + div = ((gfloat) num) / ((gfloat) den); + *res = g_int_hash ((gconstpointer) & div); + + num = gst_value_get_fraction_numerator (max); + den = gst_value_get_fraction_denominator (max); + div = ((gfloat) num) / ((gfloat) den); + *res ^= g_int_hash ((gconstpointer) & div); + + return TRUE; +} + +static gboolean +hash_structure_foreach (const GstIdStr * fieldname, + const GValue * value, gpointer user_data) +{ + gint *hash = user_data; + guint value_hash; + + *hash ^= g_str_hash (gst_id_str_as_str (fieldname)); + if (!gst_value_hash (value, &value_hash)) + return FALSE; + *hash ^= value_hash; + + return TRUE; +} + +static gboolean +gst_hash_structure (const GstStructure * s, guint * res) +{ + *res = g_str_hash (gst_structure_get_name (s)); + return gst_structure_foreach_id_str (s, hash_structure_foreach, res); +} + +static gboolean +gst_value_hash_structure (const GValue * v, guint * res) +{ + const GstStructure *s = gst_value_get_structure (v); + + return gst_hash_structure (s, res); +} + +static gboolean +gst_hash_caps_features (const GstCapsFeatures * features, guint * res) +{ + guint i; + + *res = 0; + + for (i = 0; i < gst_caps_features_get_size (features); i++) { + const gchar *feature = gst_caps_features_get_nth (features, i); + *res ^= g_str_hash (feature); + } + + return TRUE; +} + +static gboolean +gst_value_hash_caps_features (const GValue * v, guint * res) +{ + const GstCapsFeatures *features = gst_value_get_caps_features (v); + return gst_hash_caps_features (features, res); +} + +static gboolean +gst_value_hash_caps (const GValue * v, guint * res) +{ + const GstCaps *caps = gst_value_get_caps (v); + guint i; + guint n = gst_caps_get_size (caps); + + *res = 0; + for (i = 0; i < n; i++) { + GstCapsFeatures *features = gst_caps_get_features (caps, i); + guint hash; + + if (!gst_hash_structure (gst_caps_get_structure (caps, i), &hash)) + return FALSE; + *res ^= hash; + + if (!gst_hash_caps_features (features, &hash)) + return FALSE; + *res ^= hash; + } + + return TRUE; +} + +static gboolean +gst_value_hash_tag_list (const GValue * v, guint * res) +{ + const GstTagList *tags = g_value_get_boxed (v); + const GstStructure *s; + + s = _gst_tag_list_structure (tags); + return gst_hash_structure (s, res); +} + +static gboolean +gst_value_hash_date (const GValue * v, guint * res) +{ + const GDate *date = g_value_get_boxed (v); + guint32 julian, year, month, day; + + julian = g_date_get_julian (date); + if (julian) { + *res = g_int_hash ((gconstpointer) & julian); + return TRUE; + } + + year = g_date_get_year (date); + month = g_date_get_month (date); + day = g_date_get_day (date); + + *res = g_int_hash ((gconstpointer) & year); + combine_hash_ordered (res, g_int_hash ((gconstpointer) & month)); + combine_hash_ordered (res, g_int_hash ((gconstpointer) & day)); + + return TRUE; +} + +static gboolean +gst_hash_g_date_time (const GDateTime * dt, guint * res) +{ + gint64 unix_time; + + unix_time = g_date_time_to_unix ((GDateTime *) dt); + *res = g_int64_hash ((gconstpointer) & unix_time); + return TRUE; +} + +static gboolean +gst_value_hash_g_date_time (const GValue * v, guint * res) +{ + GDateTime *dt = g_value_get_boxed (v); + return gst_hash_g_date_time (dt, res); +} + +static gboolean +gst_value_hash_date_time (const GValue * v, guint * res) +{ + GstDateTime *dt = g_value_get_boxed (v); + GDateTime *gdt; + gboolean ret_val; + + gdt = gst_date_time_to_g_date_time (dt); + ret_val = gst_hash_g_date_time (gdt, res); + + g_date_time_unref (gdt); + return ret_val; +} + +static gboolean +gst_value_hash_bytes (const GValue * v, guint * res) +{ + GBytes *bytes = g_value_get_boxed (v); + const guint *data; + gsize size, i; + + *res = 0; + data = g_bytes_get_data (bytes, &size); + + for (i = 0; i < size; i++) { + combine_hash_ordered (res, data[i]); + } + + return TRUE; +} + +static gboolean +gst_value_hash_bitmask (const GValue * v, guint * res) +{ + guint64 mask = gst_value_get_bitmask (v); + *res = g_int64_hash ((gconstpointer) & mask); + return TRUE; +} + +static gboolean +gst_value_hash_flagset (const GValue * v, guint * res) +{ + guint flags = gst_value_get_flagset_flags (v); + guint mask = gst_value_get_flagset_mask (v); + guint value = flags & mask; + *res = g_int_hash ((gconstpointer) & value); + return TRUE; +} + +static gboolean +gst_value_hash_segment (const GValue * v, guint * res) +{ + GstSegment *segment = g_value_get_boxed (v); + + *res = g_int_hash ((gconstpointer) & segment->flags); + combine_hash_ordered (res, g_double_hash ((gconstpointer) & segment->rate)); + combine_hash_ordered (res, + g_double_hash ((gconstpointer) & segment->applied_rate)); + combine_hash_ordered (res, g_int_hash ((gconstpointer) & segment->format)); + combine_hash_ordered (res, g_int64_hash ((gconstpointer) & segment->base)); + combine_hash_ordered (res, g_int64_hash ((gconstpointer) & segment->offset)); + combine_hash_ordered (res, g_int64_hash ((gconstpointer) & segment->start)); + combine_hash_ordered (res, g_int64_hash ((gconstpointer) & segment->stop)); + combine_hash_ordered (res, g_int64_hash ((gconstpointer) & segment->time)); + combine_hash_ordered (res, + g_int64_hash ((gconstpointer) & segment->position)); + combine_hash_ordered (res, + g_int64_hash ((gconstpointer) & segment->duration)); + + return TRUE; +} + +static gboolean +gst_value_hash_allocation_params (const GValue * v, guint * res) +{ + GstAllocationParams *params = g_value_get_boxed (v); + + *res = g_int_hash ((gconstpointer) & params->flags); + combine_hash_ordered (res, g_int64_hash ((gconstpointer) & params->align)); + combine_hash_ordered (res, g_int64_hash ((gconstpointer) & params->prefix)); + combine_hash_ordered (res, g_int64_hash ((gconstpointer) & params->padding)); + + return TRUE; +} + +static gboolean +gst_value_hash_object (const GValue * v, guint * res) +{ + GObject *obj = g_value_get_object (v); + *res = g_direct_hash (obj); + return TRUE; +} + +static gboolean +gst_value_hash_value_list (const GValue * v, guint * res) +{ + guint i, n; + + *res = 0; + /* value list not ordered */ + n = gst_value_list_get_size (v); + for (i = 0; i < n; i++) { + guint value_hash; + + if (!gst_value_hash (gst_value_list_get_value (v, i), &value_hash)) + return FALSE; + *res ^= value_hash; + } + + return TRUE; +} + +static gboolean +gst_value_hash_value_array (const GValue * v, guint * res) +{ + guint i, n; + + *res = 0; + /* value array is ordered */ + n = gst_value_array_get_size (v); + for (i = 0; i < n; i++) { + guint value_hash; + + if (!gst_value_hash (gst_value_array_get_value (v, i), &value_hash)) + return FALSE; + combine_hash_ordered (res, value_hash); + } + + return TRUE; +} + +static gboolean +gst_value_hash_strv (const GValue * v, guint * res) +{ + GStrv strv = g_value_get_boxed (v); + guint i, n; + + *res = 0; + + /* GStrv is ordered */ + n = g_strv_length (strv); + for (i = 0; i < n; i++) { + combine_hash_ordered (res, g_str_hash (strv[i])); + } + + return TRUE; +} + /*********************** * GstAllocationParams * ***********************/ @@ -8411,14 +8956,15 @@ gst_g_thread_get_type (void) return G_TYPE_THREAD; } -#define SERIAL_VTABLE(t,c,s,d) { t, c, s, d, NULL } -#define SERIAL_VTABLE_PSPEC(t,c,s,d) { t, c, s, NULL, d } +#define SERIAL_VTABLE(t,c,s,d,h) { t, c, s, d, NULL, h } +#define SERIAL_VTABLE_PSPEC(t,c,s,d,h) { t, c, s, NULL, d, h } #define REGISTER_SERIALIZATION_CONST(_gtype, _type) \ G_STMT_START { \ static const GstValueTable gst_value = \ SERIAL_VTABLE (_gtype, gst_value_compare_ ## _type, \ - gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type); \ + gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type, \ + gst_value_hash_ ## _type); \ gst_value_register (&gst_value); \ } G_STMT_END @@ -8426,7 +8972,8 @@ G_STMT_START { \ G_STMT_START { \ static GstValueTable gst_value = \ SERIAL_VTABLE (0, gst_value_compare_ ## _type, \ - gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type); \ + gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type, \ + gst_value_hash_ ## _type); \ gst_value.type = _gtype; \ gst_value_register (&gst_value); \ } G_STMT_END @@ -8435,7 +8982,8 @@ G_STMT_START { \ G_STMT_START { \ static GstValueTable gst_value = \ SERIAL_VTABLE_PSPEC (0, gst_value_compare_ ## _type, \ - gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type); \ + gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type, \ + gst_value_hash_ ## _type); \ gst_value.type = _gtype; \ gst_value_register (&gst_value); \ } G_STMT_END @@ -8444,7 +8992,8 @@ G_STMT_START { \ G_STMT_START { \ static GstValueTable gst_value = \ SERIAL_VTABLE (0, NULL, \ - gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type); \ + gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type, \ + gst_value_hash_ ## _type); \ gst_value.type = _gtype; \ gst_value_register (&gst_value); \ } G_STMT_END @@ -8453,7 +9002,7 @@ G_STMT_START { \ G_STMT_START { \ static GstValueTable gst_value = \ SERIAL_VTABLE (0, gst_value_compare_ ## _type, \ - NULL, NULL); \ + NULL, NULL, gst_value_hash_ ## _type); \ gst_value.type = _gtype; \ gst_value_register (&gst_value); \ } G_STMT_END @@ -8703,3 +9252,58 @@ gst_flagset_register (GType flags_type) return t; } + +/** + * gst_value_hash: + * @value: a #GValue to hash + * @res: (out): a location to store the hash value + * + * Compute a hash value of @value. + * #GValue considered as equals by gst_value_compare() will have the same hash value. + * + * Returns: %TRUE, or %FALSE if @value cannot be hashed. + * + * Since: 1.28 + */ +gboolean +gst_value_hash (const GValue * value, guint * res) +{ + GType type; + GstValueTable *table; + + g_return_val_if_fail (res, FALSE); + *res = 0; + + type = G_VALUE_TYPE (value); + + table = gst_value_hash_lookup_type (type); + + if (G_LIKELY (table && table->hash)) { + if (type == GST_TYPE_LIST && gst_value_list_get_size (value) == 1) { + /* { 1 } and 1 are considered as equals so hash the single element contained in the list */ + const GValue *v = gst_value_list_get_value (value, 0); + return gst_value_hash (v, res); + } else if (type == GST_TYPE_INT_RANGE || type == GST_TYPE_INT64_RANGE) { + /* hash the list type as list and ranges of the same values are considered as equals */ + type = GST_TYPE_LIST; + } + guint hash; + + *res = g_int_hash ((gconstpointer) & type); + if (!table->hash (value, &hash)) + return FALSE; + *res ^= hash; + return TRUE; + } else { + /* custom enums and flags are not registered through gst_value_register() and so don't have a table entry */ + if G_VALUE_HOLDS_ENUM + (value) { + return gst_value_hash_enum (value, res); + } else if G_VALUE_HOLDS_FLAGS + (value) { + return gst_value_hash_gflags (value, res); + } + } + + return FALSE; +} diff --git a/subprojects/gstreamer/gst/gstvalue.h b/subprojects/gstreamer/gst/gstvalue.h index 1802904f3d..13caf6451e 100644 --- a/subprojects/gstreamer/gst/gstvalue.h +++ b/subprojects/gstreamer/gst/gstvalue.h @@ -487,6 +487,19 @@ typedef gboolean (* GstValueDeserializeWithPSpecFunc) (GValue *dest, const gchar *s, GParamSpec *pspec); +/** + * GstValueHashFunc: + * @value: a #GValue + * @res: (out): a location to store the hash value + * + * Used by gst_value_hash() to calculate a hash of @value. + * + * Returns: %TRUE, or %FALSE if @value cannot be hashed. + * + * Since: 1.28 + */ +typedef gboolean (* GstValueHashFunc) (const GValue *value, + guint *res); typedef struct _GstValueTable GstValueTable; /** @@ -514,8 +527,17 @@ struct _GstValueTable { */ GstValueDeserializeWithPSpecFunc deserialize_with_pspec; + /** + * GstValueTable.hash: + * + * a #GstValueHashFunc + * + * Since: 1.28 + */ + GstValueHashFunc hash; + /*< private >*/ - gpointer _gst_reserved [GST_PADDING - 1]; + gpointer _gst_reserved [GST_PADDING - 2]; }; GST_API @@ -569,6 +591,10 @@ gboolean gst_value_deserialize_with_pspec (GValue *dest, const gchar *src, GParamSpec *pspec); +GST_API +gboolean gst_value_hash (const GValue * value, + guint * res); + /* list */ GST_API diff --git a/subprojects/gstreamer/tests/check/gst/gstvalue.c b/subprojects/gstreamer/tests/check/gst/gstvalue.c index 72bc4848a9..9e6e8d0abf 100644 --- a/subprojects/gstreamer/tests/check/gst/gstvalue.c +++ b/subprojects/gstreamer/tests/check/gst/gstvalue.c @@ -4063,6 +4063,435 @@ GST_START_TEST (test_serialize_deserialize_strv) GST_END_TEST; +/* take ownership of @a and @b */ +static void +test_hash_value_diff (GValue * a, GValue * b, gboolean compare) +{ + guint hash_a, hash_b; + + if (compare) { + fail_unless (gst_value_compare (a, a) == GST_VALUE_EQUAL, + "[%s] value should be equal: \"%s\" \"%s\"", + G_VALUE_TYPE_NAME (a), gst_value_serialize (a), + gst_value_serialize (a)); + fail_unless (gst_value_compare (b, b) == GST_VALUE_EQUAL, + "[%s] value should be equal: \"%s\" \"%s\"", G_VALUE_TYPE_NAME (a), + gst_value_serialize (b), gst_value_serialize (b)); + fail_unless (gst_value_compare (a, b) != GST_VALUE_EQUAL, + "[%s] value should be different: \"%s\" \"%s\"", G_VALUE_TYPE_NAME (a), + gst_value_serialize (a), gst_value_serialize (b)); + } + + fail_unless (gst_value_hash (a, &hash_a), + "[%s] failed to hash: \"%s\"", + G_VALUE_TYPE_NAME (a), gst_value_serialize (a)); + fail_unless (gst_value_hash (b, &hash_b), + "[%s] failed to hash: \"%s\"", + G_VALUE_TYPE_NAME (b), gst_value_serialize (b)); + fail_unless (hash_a != hash_b, + "[%s] hash should be different: \"%s\" \"%s\"", + G_VALUE_TYPE_NAME (a), gst_value_serialize (a), gst_value_serialize (b)); + + g_value_unset (a); + g_value_unset (b); +} + +/* take ownership of @a and @b */ +static void +test_hash_value_equal (GValue * a, GValue * b, gboolean compare) +{ + guint hash_a, hash_b; + + if (compare) { + fail_unless (gst_value_compare (a, a) == GST_VALUE_EQUAL, + "[%s] value should be equal: \"%s\" \"%s\"", + G_VALUE_TYPE_NAME (a), gst_value_serialize (a), + gst_value_serialize (a)); + fail_unless (gst_value_compare (b, b) == GST_VALUE_EQUAL, + "[%s] value should be equal: \"%s\" \"%s\"", G_VALUE_TYPE_NAME (a), + gst_value_serialize (b), gst_value_serialize (b)); + fail_unless (gst_value_compare (a, b) == GST_VALUE_EQUAL, + "[%s] value should be equal: \"%s\" \"%s\"", G_VALUE_TYPE_NAME (a), + gst_value_serialize (a), gst_value_serialize (b)); + } + + fail_unless (gst_value_hash (a, &hash_a), + "[%s] failed to hash: \"%s\"", + G_VALUE_TYPE_NAME (a), gst_value_serialize (a)); + fail_unless (gst_value_hash (b, &hash_b), + "[%s] failed to hash: \"%s\"", + G_VALUE_TYPE_NAME (b), gst_value_serialize (b)); + fail_unless (hash_a == hash_b, + "[%s] hash should be equal: \"%s\" \"%s\"", + G_VALUE_TYPE_NAME (a), gst_value_serialize (a), gst_value_serialize (b)); + + g_value_unset (a); + g_value_unset (b); +} + +GST_START_TEST (test_hash) +{ + guint i; + /* tests where we can rely on gst_value_deserialize() to set GValue */ + struct HashTest + { + GType gtype; + const gchar *a; + const gchar *b; + gboolean compare; + }; + + /* check that a and b are different */ + struct HashTest tests_diff[] = { + {G_TYPE_INT, "1", "2", TRUE}, + {G_TYPE_INT64, "1", "2", TRUE}, + {G_TYPE_LONG, "1", "2", TRUE}, + {G_TYPE_UINT, "1", "2", TRUE}, + {G_TYPE_UINT64, "1", "2", TRUE}, + {G_TYPE_ULONG, "1", "2", TRUE}, + {G_TYPE_UCHAR, "1", "2", TRUE}, + {G_TYPE_GTYPE, "gint", "gint64", TRUE}, + {G_TYPE_DOUBLE, "1.0", "2.0", TRUE}, + {G_TYPE_FLOAT, "1.0", "2.0", TRUE}, + {G_TYPE_STRING, "a", "b", TRUE}, + {G_TYPE_BOOLEAN, "TRUE", "FALSE", TRUE}, + {GST_TYPE_BUFFER, "ab", "abcd", TRUE}, + /* only difference is the buffer content */ + {GST_TYPE_SAMPLE, + "6275660a64617400:Y2FwcywgSW50PShpbnQpMjAsIFN0cmluZz0oc3RyaW5nKSJhXCBzdHJpbmciAA__:c2VnbWVudCwgZmxhZ3M9KEdzdFNlZ21lbnRGbGFncylHU1RfU0VHTUVOVF9GTEFHX1JFU0VULCByYXRlPShkb3VibGUpMS4yLCBhcHBsaWVkLXJhdGU9KGRvdWJsZSkxLCBmb3JtYXQ9KEdzdEZvcm1hdClkZWZhdWx0LCBiYXNlPShndWludDY0KTAsIG9mZnNldD0oZ3VpbnQ2NCkwLCBzdGFydD0oZ3VpbnQ2NCkyMCwgc3RvcD0oZ3VpbnQ2NCkzMCwgdGltZT0oZ3VpbnQ2NCkyMCwgcG9zaXRpb249KGd1aW50NjQpMjAsIGR1cmF0aW9uPShndWludDY0KTE4NDQ2NzQ0MDczNzA5NTUxNjE1OwA_:c3RydWN0dXJlLCBGbG9hdD0oZmxvYXQpLTIuNTsA", + "4255460a44415400:Y2FwcywgSW50PShpbnQpMjAsIFN0cmluZz0oc3RyaW5nKSJhXCBzdHJpbmciAA__:c2VnbWVudCwgZmxhZ3M9KEdzdFNlZ21lbnRGbGFncylHU1RfU0VHTUVOVF9GTEFHX1JFU0VULCByYXRlPShkb3VibGUpMS4yLCBhcHBsaWVkLXJhdGU9KGRvdWJsZSkxLCBmb3JtYXQ9KEdzdEZvcm1hdClkZWZhdWx0LCBiYXNlPShndWludDY0KTAsIG9mZnNldD0oZ3VpbnQ2NCkwLCBzdGFydD0oZ3VpbnQ2NCkyMCwgc3RvcD0oZ3VpbnQ2NCkzMCwgdGltZT0oZ3VpbnQ2NCkyMCwgcG9zaXRpb249KGd1aW50NjQpMjAsIGR1cmF0aW9uPShndWludDY0KTE4NDQ2NzQ0MDczNzA5NTUxNjE1OwA_:c3RydWN0dXJlLCBGbG9hdD0oZmxvYXQpLTIuNTsA", + TRUE}, + {GST_TYPE_FRACTION, "1/3", "3/1", TRUE}, + {GST_TYPE_STRUCTURE, "video/x-raw", "audio/x-raw", TRUE}, + {GST_TYPE_STRUCTURE, "video/x-raw,width=1920", "video/x-raw,width=800", + TRUE}, + {GST_TYPE_CAPS, "video/x-raw", "audio/x-raw", TRUE}, + {GST_TYPE_CAPS, "video/x-raw,width=1920", "video/x-raw,width=800", TRUE}, + {GST_TYPE_CAPS, "video/x-raw", "video/x-raw(memory:DMABuf)", TRUE}, + {GST_TYPE_TAG_LIST, "taglist, title=(string)Title, artist=(string)Artist", + "taglist, title=(string)Title2, artist=(string)Artist", TRUE}, + {G_TYPE_DATE, "1984-11-30", "1985-11-30", TRUE}, + {G_TYPE_DATE_TIME, "2011-06-23T07:40:10Z", "2011-06-23T07:40:11Z", TRUE}, + {GST_TYPE_DATE_TIME, "2011-06-23T07:40:10Z", "2011-06-23T07:40:11Z", + TRUE}, + {G_TYPE_BYTES, "YQ==", "Yg==", TRUE}, + {GST_TYPE_BITMASK, "0xaa", "0xbb", TRUE}, + {GST_TYPE_SEEK_FLAGS, "+flush", "+flush+trickmode/trickmode-no-audio", + TRUE}, + {GST_TYPE_LIST, "{1, 2}", "{1, 3}", TRUE}, + {GST_TYPE_CAPS_FEATURES, "memory:DMABuf", "memory:GLMemory", FALSE}, + }; + + /* check that a and b are equal */ + struct HashTest tests_equal[] = { + /* structures fields are not ordered */ + {GST_TYPE_STRUCTURE, "video/x-raw,width=1920,height=1080", + "video/x-raw,height=1080,width=1920", TRUE}, + /* fixed and unfixed caps are not ordered */ + {GST_TYPE_CAPS, "video/x-raw; audio/x-raw", "audio/x-raw; video/x-raw", + TRUE}, + {GST_TYPE_CAPS, + "video/x-raw,format=NV12,width=[1, 100]; video/x-raw,format=GREY8,width=[1, 100]", + "video/x-raw,format=GREY8,width=[1, 100]; video/x-raw,format=NV12,width=[1, 100]", + TRUE}, + {GST_TYPE_LIST, "{1, 2}", "{2, 1}", TRUE}, + {GST_TYPE_LIST, "{1, 2}", "{2, 1}", TRUE}, + /* caps features are not ordered */ + {GST_TYPE_CAPS_FEATURES, "memory:DMABuf, memory:GLMemory", + "memory:GLMemory, memory:DMABuf", FALSE}, + /* same actual fraction have the same hash */ + {GST_TYPE_FRACTION, "1/2", "2/4", TRUE}, + }; + + for (i = 0; i < G_N_ELEMENTS (tests_diff); i++) { + GValue a = G_VALUE_INIT; + GValue b = G_VALUE_INIT; + + g_value_init (&a, tests_diff[i].gtype); + fail_unless (gst_value_deserialize (&a, tests_diff[i].a), + "[%s] could not deserialize %s", g_type_name (tests_diff[i].gtype), + tests_diff[i].a); + g_value_init (&b, tests_diff[i].gtype); + fail_unless (gst_value_deserialize (&b, tests_diff[i].b), + "[%s] could not deserialize %s", g_type_name (tests_diff[i].gtype), + tests_diff[i].b); + + test_hash_value_diff (&a, &b, tests_diff[i].compare); + } + + for (i = 0; i < G_N_ELEMENTS (tests_equal); i++) { + GValue a = G_VALUE_INIT; + GValue b = G_VALUE_INIT; + + g_value_init (&a, tests_equal[i].gtype); + fail_unless (gst_value_deserialize (&a, tests_equal[i].a), + "[%s] could not deserialize %s", g_type_name (tests_equal[i].gtype), + tests_equal[i].a); + g_value_init (&b, tests_equal[i].gtype); + fail_unless (gst_value_deserialize (&b, tests_equal[i].b), + "[%s] could not deserialize %s", g_type_name (tests_equal[i].gtype), + tests_equal[i].b); + + test_hash_value_equal (&a, &b, tests_equal[i].compare); + } + + /* tests for types not implementing deserialize */ + { + GValue a = G_VALUE_INIT; + GValue b = G_VALUE_INIT; + GValue v = G_VALUE_INIT; + GValueArray *array; + + /* int range */ + g_value_init (&a, GST_TYPE_INT_RANGE); + g_value_init (&b, GST_TYPE_INT_RANGE); + gst_value_set_int_range (&a, 0, 2); + gst_value_set_int_range (&b, 0, 3); + test_hash_value_diff (&a, &b, TRUE); + + /* int range with step */ + g_value_init (&a, GST_TYPE_INT_RANGE); + g_value_init (&b, GST_TYPE_INT_RANGE); + gst_value_set_int_range_step (&a, 0, 12, 2); + gst_value_set_int_range_step (&b, 0, 12, 4); + test_hash_value_diff (&a, &b, TRUE); + + /* int64 range */ + g_value_init (&a, GST_TYPE_INT64_RANGE); + g_value_init (&b, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range (&a, 0, 2); + gst_value_set_int64_range (&b, 0, 3); + test_hash_value_diff (&a, &b, TRUE); + + /* int64 range with step */ + g_value_init (&a, GST_TYPE_INT64_RANGE); + g_value_init (&b, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range_step (&a, 0, 12, 2); + gst_value_set_int64_range_step (&b, 0, 12, 4); + test_hash_value_diff (&a, &b, TRUE); + + /* double range */ + g_value_init (&a, GST_TYPE_DOUBLE_RANGE); + g_value_init (&b, GST_TYPE_DOUBLE_RANGE); + gst_value_set_double_range (&a, 0, 12); + gst_value_set_double_range (&b, 0, 13); + test_hash_value_diff (&a, &b, TRUE); + + /* enum */ + static const GEnumValue test_enum_vals[] = { + {0, "Badger", "badger"}, + {1, "Mushroom", "mushroom"}, + {0, NULL, NULL}, + }; + GType test_enum_type = + g_enum_register_static ("GstTestEnum", test_enum_vals); + g_value_init (&a, test_enum_type); + g_value_set_enum (&a, 0); + g_value_init (&b, test_enum_type); + g_value_set_enum (&b, 1); + test_hash_value_diff (&a, &b, TRUE); + + /* flags */ + static const GFlagsValue test_flags_vals[] = { + {0, "Badger", "badger"}, + {1, "Mushroom", "mushroom"}, + {0, NULL, NULL}, + }; + GType test_flags_type = + g_flags_register_static ("GstTestFlags", test_flags_vals); + g_value_init (&a, test_flags_type); + g_value_set_flags (&a, 0); + g_value_init (&b, test_flags_type); + g_value_set_flags (&b, 1); + test_hash_value_diff (&a, &b, TRUE); + + /* value array */ + setup_test_value_array (&a); + setup_test_value_array (&b); + GValueArray *arr = g_value_get_boxed (&b); + g_value_init (&v, G_TYPE_INT); + g_value_set_int (&v, 4); + g_value_array_append (arr, &v); + test_hash_value_diff (&a, &b, TRUE); + g_value_unset (&v); + + g_value_init (&v, G_TYPE_INT); + g_value_init (&a, G_TYPE_VALUE_ARRAY); + array = g_value_array_new (2); + g_value_set_int (&v, 1); + g_value_array_append (array, &v); + g_value_set_int (&v, 2); + g_value_array_append (array, &v); + g_value_take_boxed (&a, array); + array = g_value_array_new (2); + g_value_init (&b, G_TYPE_VALUE_ARRAY); + g_value_set_int (&v, 2); + g_value_array_append (array, &v); + g_value_set_int (&v, 1); + g_value_array_append (array, &v); + g_value_take_boxed (&b, array); + test_hash_value_diff (&a, &b, TRUE); + g_value_unset (&v); + + /* fraction range */ + g_value_init (&a, GST_TYPE_FRACTION_RANGE); + g_value_init (&b, GST_TYPE_FRACTION_RANGE); + gst_value_set_fraction_range_full (&a, 1, 5, 4, 5); + gst_value_set_fraction_range_full (&a, 2, 5, 4, 5); + test_hash_value_diff (&a, &b, TRUE); + + /* objet */ + GstBufferPool *obj_a = gst_buffer_pool_new (); + GstBufferPool *obj_b = gst_buffer_pool_new (); + g_value_init (&a, G_TYPE_OBJECT); + g_value_take_object (&a, obj_a); + g_value_init (&b, G_TYPE_OBJECT); + g_value_take_object (&b, obj_b); + test_hash_value_diff (&a, &b, TRUE); + + /* value array */ + g_value_init (&a, GST_TYPE_ARRAY); + g_value_init (&v, G_TYPE_INT); + g_value_set_int (&v, 1); + gst_value_array_append_value (&a, &v); + g_value_set_int (&v, 2); + gst_value_array_append_value (&a, &v); + g_value_init (&b, GST_TYPE_ARRAY); + g_value_set_int (&v, 2); + gst_value_array_append_value (&b, &v); + g_value_set_int (&v, 2); + gst_value_array_append_value (&b, &v); + test_hash_value_diff (&a, &b, TRUE); + g_value_unset (&v); + + /* AllocationParams */ + GstAllocationParams params_a = { 0 }, params_b; + params_a.flags = 0; + params_a.align = 1; + params_a.prefix = 2; + params_a.padding = 3; + params_b = params_a; + params_b.padding = 4; + g_value_init (&a, GST_TYPE_ALLOCATION_PARAMS); + g_value_set_boxed (&a, ¶ms_a); + g_value_init (&b, GST_TYPE_ALLOCATION_PARAMS); + g_value_set_boxed (&b, ¶ms_b); + test_hash_value_diff (&a, &b, TRUE); + + /* float NaN */ + g_value_init (&a, G_TYPE_FLOAT); + g_value_set_float (&a, NAN); + g_value_init (&b, G_TYPE_FLOAT); + g_value_set_float (&b, 0); + test_hash_value_diff (&a, &b, FALSE); + } + + /* types with specific compare function */ + { + GValue a = G_VALUE_INIT; + GValue b = G_VALUE_INIT; + + GstSegment seg_a, seg_b; + gst_segment_init (&seg_a, GST_FORMAT_DEFAULT); + fail_unless (gst_segment_do_seek (&seg_a, 1.2, GST_FORMAT_DEFAULT, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 20, GST_SEEK_TYPE_SET, 30, + NULL)); + seg_b = seg_a; + seg_b.rate = 1.0; + fail_unless (!gst_segment_is_equal (&seg_a, &seg_b)); + g_value_init (&a, GST_TYPE_SEGMENT); + g_value_set_boxed (&a, &seg_a); + g_value_init (&b, GST_TYPE_SEGMENT); + g_value_set_boxed (&b, &seg_b); + test_hash_value_diff (&a, &b, FALSE); + + g_value_init (&a, G_TYPE_STRV); + fail_unless (gst_value_deserialize (&a, "<\"foo\",\"bar\">")); + g_value_init (&b, G_TYPE_STRV); + fail_unless (gst_value_deserialize (&b, "<\"bar\",\"foo\">")); + fail_unless (!g_strv_equal (g_value_get_boxed (&a), + g_value_get_boxed (&b))); + test_hash_value_diff (&a, &b, FALSE); + } + + // gst_value_compare() special cases + { + GValue a = G_VALUE_INIT; + GValue b = G_VALUE_INIT; + GValue v = G_VALUE_INIT; + + /* { 1 } and 1 are considered as equals */ + g_value_init (&a, GST_TYPE_LIST); + fail_unless (gst_value_deserialize (&a, "{ 1 }")); + g_value_init (&b, G_TYPE_INT); + fail_unless (gst_value_deserialize (&b, "1")); + test_hash_value_equal (&a, &b, TRUE); + + /* list and range are considered as equal if they contain the same set of elements */ + g_value_init (&a, GST_TYPE_LIST); + fail_unless (gst_value_deserialize (&a, "{ 1, 2 }")); + g_value_init (&b, GST_TYPE_INT_RANGE); + gst_value_set_int_range (&b, 1, 2); + test_hash_value_equal (&a, &b, TRUE); + + /* same set with steps */ + g_value_init (&a, GST_TYPE_LIST); + fail_unless (gst_value_deserialize (&a, "{ 2, 4, 6, 8 }")); + g_value_init (&b, GST_TYPE_INT_RANGE); + gst_value_set_int_range_step (&b, 2, 8, 2); + test_hash_value_equal (&a, &b, TRUE); + + /* different step */ + g_value_init (&a, GST_TYPE_LIST); + fail_unless (gst_value_deserialize (&a, "{ 1, 3 }")); + g_value_init (&b, GST_TYPE_INT_RANGE); + gst_value_set_int_range (&b, 1, 3); + test_hash_value_diff (&a, &b, TRUE); + + /* same for int64 range */ + g_value_init (&a, GST_TYPE_LIST); + g_value_init (&v, G_TYPE_INT64); + g_value_set_int64 (&v, 1); + gst_value_list_append_and_take_value (&a, &v); + g_value_init (&v, G_TYPE_INT64); + g_value_set_int64 (&v, 2); + gst_value_list_append_and_take_value (&a, &v); + g_value_init (&b, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range (&b, 1, 2); + test_hash_value_equal (&a, &b, TRUE); + + g_value_init (&a, GST_TYPE_LIST); + g_value_init (&v, G_TYPE_INT64); + g_value_set_int64 (&v, 2); + gst_value_list_append_and_take_value (&a, &v); + g_value_init (&v, G_TYPE_INT64); + g_value_set_int64 (&v, 4); + gst_value_list_append_and_take_value (&a, &v); + g_value_init (&v, G_TYPE_INT64); + g_value_set_int64 (&v, 6); + gst_value_list_append_and_take_value (&a, &v); + g_value_init (&v, G_TYPE_INT64); + g_value_set_int64 (&v, 8); + gst_value_list_append_and_take_value (&a, &v); + g_value_init (&b, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range_step (&b, 2, 8, 2); + test_hash_value_equal (&a, &b, TRUE); + + g_value_init (&a, GST_TYPE_LIST); + g_value_init (&v, G_TYPE_INT64); + g_value_set_int64 (&v, 1); + gst_value_list_append_and_take_value (&a, &v); + g_value_init (&v, G_TYPE_INT64); + g_value_set_int64 (&v, 3); + gst_value_list_append_and_take_value (&a, &v); + g_value_init (&b, GST_TYPE_INT64_RANGE); + gst_value_set_int64_range (&b, 1, 3); + test_hash_value_diff (&a, &b, TRUE); + } +} + +GST_END_TEST; + static Suite * gst_value_suite (void) { @@ -4126,6 +4555,7 @@ gst_value_suite (void) tcase_add_test (tc_chain, test_serialize_deserialize_tag_list); tcase_add_test (tc_chain, test_serialize_deserialize_sample); tcase_add_test (tc_chain, test_serialize_deserialize_strv); + tcase_add_test (tc_chain, test_hash); return s; }