mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-10 19:31:12 +00:00
4992249848
Various type of object should be able to do some reporting, so we have to make sure all the code to do that is in one place. Creating an interface makes it simple to share information and it avoid to have a baseclass for something that is not actually important enough to create a baseclass. Conflicts: gst/qa/gst-qa-pad-monitor.c
1547 lines
49 KiB
C
1547 lines
49 KiB
C
/* GStreamer
|
|
* Copyright (C) 2013 Thiago Santos <thiago.sousa.santos@collabora.com>
|
|
*
|
|
* gst-qa-pad-monitor.c - QA PadMonitor class
|
|
*
|
|
* 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.1 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-qa-pad-monitor.h"
|
|
#include "gst-qa-element-monitor.h"
|
|
#include "gst-qa-reporter.h"
|
|
#include <gst/gst.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
/**
|
|
* SECTION:gst-qa-pad-monitor
|
|
* @short_description: Class that wraps a #GstPad for QA checks
|
|
*
|
|
* TODO
|
|
*/
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_qa_pad_monitor_debug);
|
|
#define GST_CAT_DEFAULT gst_qa_pad_monitor_debug
|
|
|
|
#define _do_init \
|
|
GST_DEBUG_CATEGORY_INIT (gst_qa_pad_monitor_debug, "qa_pad_monitor", 0, "QA PadMonitor");
|
|
#define gst_qa_pad_monitor_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstQaPadMonitor, gst_qa_pad_monitor,
|
|
GST_TYPE_QA_MONITOR, _do_init);
|
|
|
|
#define PAD_IS_IN_PUSH_MODE(p) ((p)->mode == GST_ACTIVATE_PUSH)
|
|
#define PENDING_FIELDS "pending-fields"
|
|
|
|
/*
|
|
* Locking the parent should always be done before locking the
|
|
* pad-monitor to prevent deadlocks in case another monitor from
|
|
* another pad on the same element starts an operation that also
|
|
* requires locking itself and some other monitors from internally
|
|
* linked pads.
|
|
*
|
|
* An example:
|
|
* An element has a sink and a src pad. Some test starts running at sinkpad
|
|
* and it locks the parent, and then it locks itself. In case it needs to get
|
|
* some information from the srcpad, it is able to lock the srcpad and get it
|
|
* because the srcpad should never lock itself before locking the parent (which
|
|
* it won't be able as sinkpad already locked it).
|
|
*
|
|
* As a side one, it is possible that srcpad locks itself without locking the
|
|
* parent in case it wants to do a check that won't need to use other internally
|
|
* linked pads (sinkpad). But in this case it might lock and unlock freely without
|
|
* causing deadlocks.
|
|
*/
|
|
#define GST_QA_PAD_MONITOR_PARENT_LOCK(m) \
|
|
G_STMT_START { \
|
|
if (G_LIKELY (GST_QA_MONITOR_GET_PARENT (m))) { \
|
|
GST_QA_MONITOR_LOCK (GST_QA_MONITOR_GET_PARENT (m)); \
|
|
} else { \
|
|
GST_WARNING_OBJECT (m, "No parent found, can't lock"); \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
#define GST_QA_PAD_MONITOR_PARENT_UNLOCK(m) \
|
|
G_STMT_START { \
|
|
if (G_LIKELY (GST_QA_MONITOR_GET_PARENT (m))) { \
|
|
GST_QA_MONITOR_UNLOCK (GST_QA_MONITOR_GET_PARENT (m)); \
|
|
} else { \
|
|
GST_WARNING_OBJECT (m, "No parent found, can't unlock"); \
|
|
} \
|
|
} G_STMT_END
|
|
|
|
typedef struct
|
|
{
|
|
GstClockTime timestamp;
|
|
GstEvent *event;
|
|
} SerializedEventData;
|
|
|
|
static void
|
|
_serialized_event_data_free (SerializedEventData * serialized_event)
|
|
{
|
|
gst_event_unref (serialized_event->event);
|
|
g_slice_free (SerializedEventData, serialized_event);
|
|
}
|
|
|
|
static gboolean gst_qa_pad_monitor_do_setup (GstQaMonitor * monitor);
|
|
|
|
/* This was copied from gstpad.c and might need
|
|
* updating whenever it changes in core */
|
|
static GstCaps *
|
|
_gst_pad_get_caps_default (GstPad * pad)
|
|
{
|
|
GstCaps *result = NULL;
|
|
GstPadTemplate *templ;
|
|
|
|
if ((templ = GST_PAD_PAD_TEMPLATE (pad))) {
|
|
result = GST_PAD_TEMPLATE_CAPS (templ);
|
|
GST_DEBUG_OBJECT (pad, "using pad template %p with caps %p %"
|
|
GST_PTR_FORMAT, templ, result, result);
|
|
|
|
result = gst_caps_ref (result);
|
|
goto done;
|
|
}
|
|
if ((result = GST_PAD_CAPS (pad))) {
|
|
GST_DEBUG_OBJECT (pad, "using pad caps %p %" GST_PTR_FORMAT, result,
|
|
result);
|
|
|
|
result = gst_caps_ref (result);
|
|
goto done;
|
|
}
|
|
|
|
/* this almost never happens */
|
|
GST_DEBUG_OBJECT (pad, "pad has no caps");
|
|
result = gst_caps_new_empty ();
|
|
|
|
done:
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
_structure_is_raw_video (GstStructure * structure)
|
|
{
|
|
return gst_structure_has_name (structure, "video/x-raw-yuv")
|
|
|| gst_structure_has_name (structure, "video/x-raw-rgb")
|
|
|| gst_structure_has_name (structure, "video/x-raw-gray");
|
|
}
|
|
|
|
static gboolean
|
|
_structure_is_raw_audio (GstStructure * structure)
|
|
{
|
|
return gst_structure_has_name (structure, "audio/x-raw-int")
|
|
|| gst_structure_has_name (structure, "audio/x-raw-float");
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
_check_field_type (GstQaPadMonitor * monitor, GstStructure * structure,
|
|
const gchar * field, ...)
|
|
{
|
|
va_list var_args;
|
|
GType type;
|
|
gchar *joined_types = NULL;
|
|
const gchar *rejected_types[5];
|
|
gint rejected_types_index = 0;
|
|
|
|
if (!gst_structure_has_field (structure, field)) {
|
|
GST_QA_REPORT_WARNING (GST_QA_REPORTER (monitor), FALSE, CAPS_NEGOTIATION,
|
|
MISSING_FIELD, "%s is missing from structure: %" GST_PTR_FORMAT, field,
|
|
structure);
|
|
return;
|
|
}
|
|
|
|
memset (rejected_types, 0, sizeof (rejected_types));
|
|
va_start (var_args, field);
|
|
while ((type = va_arg (var_args, GType)) != 0) {
|
|
if (gst_structure_has_field_typed (structure, field, type)) {
|
|
va_end (var_args);
|
|
return;
|
|
}
|
|
rejected_types[rejected_types_index++] = g_type_name (type);
|
|
}
|
|
va_end (var_args);
|
|
|
|
joined_types = g_strjoinv (" / ", (gchar **) rejected_types);
|
|
GST_QA_REPORT_CRITICAL (GST_QA_REPORTER (monitor), FALSE, CAPS_NEGOTIATION,
|
|
BAD_FIELD_TYPE, "%s has wrong type %s in structure '%" GST_PTR_FORMAT
|
|
"'. Expected: %s", field,
|
|
g_type_name (gst_structure_get_field_type (structure, field)),
|
|
structure, joined_types);
|
|
g_free (joined_types);
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_check_raw_video_caps_complete (GstQaPadMonitor * monitor,
|
|
GstStructure * structure)
|
|
{
|
|
_check_field_type (monitor, structure, "width", G_TYPE_INT,
|
|
GST_TYPE_INT_RANGE, 0);
|
|
_check_field_type (monitor, structure, "height", G_TYPE_INT,
|
|
GST_TYPE_INT_RANGE, 0);
|
|
_check_field_type (monitor, structure, "framerate", GST_TYPE_FRACTION,
|
|
GST_TYPE_FRACTION_RANGE, 0);
|
|
_check_field_type (monitor, structure, "pixel-aspect-ratio",
|
|
GST_TYPE_FRACTION, GST_TYPE_FRACTION_RANGE, 0);
|
|
|
|
if (gst_structure_has_name (structure, "video/x-raw-yuv")) {
|
|
_check_field_type (monitor, structure, "format", GST_TYPE_FOURCC,
|
|
GST_TYPE_LIST);
|
|
|
|
} else if (gst_structure_has_name (structure, "video/x-raw-rgb")) {
|
|
_check_field_type (monitor, structure, "bpp", G_TYPE_INT, GST_TYPE_LIST, 0);
|
|
_check_field_type (monitor, structure, "depth", G_TYPE_INT, GST_TYPE_LIST,
|
|
0);
|
|
_check_field_type (monitor, structure, "endianness", G_TYPE_INT,
|
|
GST_TYPE_LIST, 0);
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_check_raw_audio_caps_complete (GstQaPadMonitor * monitor,
|
|
GstStructure * structure)
|
|
{
|
|
_check_field_type (monitor, structure, "rate", G_TYPE_INT, GST_TYPE_LIST,
|
|
GST_TYPE_INT_RANGE, 0);
|
|
_check_field_type (monitor, structure, "channels", G_TYPE_INT,
|
|
GST_TYPE_LIST, GST_TYPE_INT_RANGE, 0);
|
|
_check_field_type (monitor, structure, "endianness", G_TYPE_INT,
|
|
GST_TYPE_LIST, 0);
|
|
_check_field_type (monitor, structure, "channel-layout", G_TYPE_STRING,
|
|
GST_TYPE_LIST, 0);
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_check_caps_complete (GstQaPadMonitor * monitor,
|
|
GstCaps * caps)
|
|
{
|
|
GstStructure *structure;
|
|
gint i;
|
|
|
|
for (i = 0; i < gst_caps_get_size (caps); i++) {
|
|
structure = gst_caps_get_structure (caps, i);
|
|
|
|
if (_structure_is_raw_video (structure)) {
|
|
gst_qa_pad_monitor_check_raw_video_caps_complete (monitor, structure);
|
|
|
|
} else if (_structure_is_raw_audio (structure)) {
|
|
gst_qa_pad_monitor_check_raw_audio_caps_complete (monitor, structure);
|
|
}
|
|
}
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_qa_pad_monitor_get_othercaps (GstQaPadMonitor * monitor)
|
|
{
|
|
GstCaps *caps = gst_caps_new_empty ();
|
|
GstIterator *iter;
|
|
gboolean done;
|
|
GstPad *otherpad;
|
|
GstCaps *peercaps;
|
|
|
|
iter = gst_pad_iterate_internal_links (GST_QA_PAD_MONITOR_GET_PAD (monitor));
|
|
done = FALSE;
|
|
while (!done) {
|
|
switch (gst_iterator_next (iter, (gpointer *) & otherpad)) {
|
|
case GST_ITERATOR_OK:
|
|
|
|
/* TODO What would be the correct caps operation to merge the caps in
|
|
* case one sink is internally linked to multiple srcs? */
|
|
peercaps = gst_pad_peer_get_caps_reffed (otherpad);
|
|
if (peercaps)
|
|
gst_caps_merge (caps, peercaps);
|
|
gst_object_unref (otherpad);
|
|
|
|
break;
|
|
case GST_ITERATOR_RESYNC:
|
|
gst_iterator_resync (iter);
|
|
gst_caps_replace (&caps, gst_caps_new_empty ());
|
|
break;
|
|
case GST_ITERATOR_ERROR:
|
|
GST_WARNING_OBJECT (monitor, "Internal links pad iteration error");
|
|
done = TRUE;
|
|
break;
|
|
case GST_ITERATOR_DONE:
|
|
done = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
gst_iterator_free (iter);
|
|
|
|
GST_DEBUG_OBJECT (monitor, "Otherpad caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
return caps;
|
|
}
|
|
|
|
static gboolean
|
|
_structure_is_video (GstStructure * structure)
|
|
{
|
|
const gchar *name = gst_structure_get_name (structure);
|
|
|
|
return g_strstr_len (name, 6, "video/")
|
|
&& strcmp (name, "video/quicktime") != 0;
|
|
}
|
|
|
|
static gboolean
|
|
_structure_is_audio (GstStructure * structure)
|
|
{
|
|
const gchar *name = gst_structure_get_name (structure);
|
|
|
|
return g_strstr_len (name, 6, "audio/") != NULL;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_pad_should_proxy_othercaps (GstQaPadMonitor * monitor)
|
|
{
|
|
GstQaMonitor *parent = GST_QA_MONITOR_GET_PARENT (monitor);
|
|
/* We only know how to handle othercaps checks for codecs so far */
|
|
return GST_QA_ELEMENT_MONITOR_ELEMENT_IS_DECODER (parent) ||
|
|
GST_QA_ELEMENT_MONITOR_ELEMENT_IS_ENCODER (parent);
|
|
}
|
|
|
|
|
|
/* Check if the field @f from @s2 (if present) is represented in @s1
|
|
* Represented here means either equal or @s1's value is in a list/range
|
|
* from @s2
|
|
*/
|
|
static gboolean
|
|
_structures_field_is_contained (GstStructure * s1, GstStructure * s2,
|
|
const gchar * f)
|
|
{
|
|
const GValue *v1;
|
|
const GValue *v2;
|
|
|
|
v2 = gst_structure_get_value (s2, f);
|
|
if (!v2)
|
|
return TRUE; /* nothing to compare to */
|
|
|
|
v1 = gst_structure_get_value (s1, f);
|
|
if (!v1)
|
|
return FALSE;
|
|
|
|
if (gst_value_compare (v1, v2) == GST_VALUE_EQUAL)
|
|
return TRUE;
|
|
|
|
if (GST_VALUE_HOLDS_LIST (v2)) {
|
|
gint i;
|
|
for (i = 0; i < gst_value_list_get_size (v2); i++) {
|
|
const GValue *v2_subvalue = gst_value_list_get_value (v2, i);
|
|
if (gst_value_compare (v1, v2_subvalue) == GST_VALUE_EQUAL)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (GST_VALUE_HOLDS_ARRAY (v2)) {
|
|
gint i;
|
|
for (i = 0; i < gst_value_array_get_size (v2); i++) {
|
|
const GValue *v2_subvalue = gst_value_array_get_value (v2, i);
|
|
if (gst_value_compare (v1, v2_subvalue) == GST_VALUE_EQUAL)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (GST_VALUE_HOLDS_INT_RANGE (v2)) {
|
|
gint min, max;
|
|
|
|
min = gst_value_get_int_range_min (v2);
|
|
max = gst_value_get_int_range_max (v2);
|
|
|
|
if (G_VALUE_HOLDS_INT (v1)) {
|
|
gint v = g_value_get_int (v1);
|
|
|
|
return v >= min && v <= max;
|
|
} else {
|
|
/* TODO compare int ranges with int ranges
|
|
* or with lists if useful */
|
|
}
|
|
}
|
|
|
|
if (GST_VALUE_HOLDS_FRACTION_RANGE (v2)) {
|
|
const GValue *min, *max;
|
|
|
|
min = gst_value_get_fraction_range_min (v2);
|
|
max = gst_value_get_fraction_range_max (v2);
|
|
|
|
if (GST_VALUE_HOLDS_FRACTION (v1)) {
|
|
gint v_min = gst_value_compare (v1, min);
|
|
gint v_max = gst_value_compare (v1, max);
|
|
|
|
return (v_min == GST_VALUE_EQUAL || v_min == GST_VALUE_GREATER_THAN) &&
|
|
(v_max == GST_VALUE_EQUAL || v_max == GST_VALUE_LESS_THAN);
|
|
} else {
|
|
/* TODO compare fraction ranges with fraction ranges
|
|
* or with lists if useful */
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_check_caps_fields_proxied (GstQaPadMonitor * monitor,
|
|
GstCaps * caps)
|
|
{
|
|
GstStructure *structure;
|
|
GstStructure *otherstructure;
|
|
GstCaps *othercaps;
|
|
gint i, j;
|
|
|
|
if (!gst_qa_pad_monitor_pad_should_proxy_othercaps (monitor))
|
|
return;
|
|
|
|
othercaps = gst_qa_pad_monitor_get_othercaps (monitor);
|
|
|
|
for (i = 0; i < gst_caps_get_size (othercaps); i++) {
|
|
gboolean found = FALSE;
|
|
gboolean type_match = FALSE;
|
|
|
|
otherstructure = gst_caps_get_structure (othercaps, i);
|
|
|
|
/* look for a proxied version of 'otherstructure' */
|
|
if (_structure_is_video (otherstructure)) {
|
|
for (j = 0; j < gst_caps_get_size (caps); j++) {
|
|
structure = gst_caps_get_structure (caps, j);
|
|
if (_structure_is_video (structure)) {
|
|
type_match = TRUE;
|
|
if (_structures_field_is_contained (structure, otherstructure,
|
|
"width")
|
|
&& _structures_field_is_contained (structure, otherstructure,
|
|
"height")
|
|
&& _structures_field_is_contained (structure, otherstructure,
|
|
"framerate")
|
|
&& _structures_field_is_contained (structure, otherstructure,
|
|
"pixel-aspect-ratio")) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (_structure_is_audio (otherstructure)) {
|
|
for (j = 0; j < gst_caps_get_size (caps); j++) {
|
|
structure = gst_caps_get_structure (caps, j);
|
|
if (_structure_is_audio (structure)) {
|
|
type_match = TRUE;
|
|
if (_structures_field_is_contained (structure, otherstructure, "rate")
|
|
&& _structures_field_is_contained (structure, otherstructure,
|
|
"channels")) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type_match && !found) {
|
|
GST_QA_REPORT_WARNING (monitor, FALSE, CAPS_NEGOTIATION,
|
|
GET_CAPS,
|
|
"Peer pad structure '%" GST_PTR_FORMAT "' has no similar version "
|
|
"on pad's caps '%" GST_PTR_FORMAT "'", otherstructure, caps);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_check_late_serialized_events (GstQaPadMonitor * monitor,
|
|
GstClockTime ts)
|
|
{
|
|
gint i;
|
|
if (!GST_CLOCK_TIME_IS_VALID (ts))
|
|
return;
|
|
|
|
for (i = 0; i < monitor->serialized_events->len; i++) {
|
|
SerializedEventData *data =
|
|
g_ptr_array_index (monitor->serialized_events, i);
|
|
if (data->timestamp < ts) {
|
|
GST_QA_REPORT_WARNING (monitor, FALSE, EVENT, EXPECTED,
|
|
"Serialized event %" GST_PTR_FORMAT " wasn't pushed before expected "
|
|
"timestamp %" GST_TIME_FORMAT " on pad %s:%s", data->event,
|
|
GST_TIME_ARGS (data->timestamp),
|
|
GST_DEBUG_PAD_NAME (GST_QA_PAD_MONITOR_GET_PAD (monitor)));
|
|
} else {
|
|
/* events should be ordered by ts */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i)
|
|
g_ptr_array_remove_range (monitor->serialized_events, 0, i);
|
|
}
|
|
|
|
void
|
|
_parent_set_cb (GstObject * object, GstObject * parent, GstQaMonitor * monitor)
|
|
{
|
|
gst_qa_reporter_set_name (GST_QA_REPORTER (monitor), g_strdup_printf ("%s:%s",
|
|
GST_DEBUG_PAD_NAME (object)));
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_dispose (GObject * object)
|
|
{
|
|
GstQaPadMonitor *monitor = GST_QA_PAD_MONITOR_CAST (object);
|
|
GstPad *pad = GST_QA_PAD_MONITOR_GET_PAD (monitor);
|
|
|
|
if (pad) {
|
|
if (monitor->buffer_probe_id)
|
|
gst_pad_remove_data_probe (pad, monitor->buffer_probe_id);
|
|
if (monitor->event_probe_id)
|
|
gst_pad_remove_data_probe (pad, monitor->event_probe_id);
|
|
|
|
g_signal_handlers_disconnect_by_func (pad, (GCallback) _parent_set_cb,
|
|
monitor);
|
|
}
|
|
|
|
if (monitor->expected_segment)
|
|
gst_event_unref (monitor->expected_segment);
|
|
|
|
gst_structure_free (monitor->pending_setcaps_fields);
|
|
g_ptr_array_unref (monitor->serialized_events);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_class_init (GstQaPadMonitorClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstQaMonitorClass *monitor_klass;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
monitor_klass = GST_QA_MONITOR_CLASS (klass);
|
|
|
|
gobject_class->dispose = gst_qa_pad_monitor_dispose;
|
|
|
|
monitor_klass->setup = gst_qa_pad_monitor_do_setup;
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_init (GstQaPadMonitor * pad_monitor)
|
|
{
|
|
pad_monitor->pending_setcaps_fields =
|
|
gst_structure_empty_new (PENDING_FIELDS);
|
|
pad_monitor->serialized_events =
|
|
g_ptr_array_new_with_free_func ((GDestroyNotify)
|
|
_serialized_event_data_free);
|
|
gst_segment_init (&pad_monitor->segment, GST_FORMAT_BYTES);
|
|
pad_monitor->first_buffer = TRUE;
|
|
|
|
pad_monitor->timestamp_range_start = GST_CLOCK_TIME_NONE;
|
|
pad_monitor->timestamp_range_end = GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
/**
|
|
* gst_qa_pad_monitor_new:
|
|
* @pad: (transfer-none): a #GstPad to run QA on
|
|
*/
|
|
GstQaPadMonitor *
|
|
gst_qa_pad_monitor_new (GstPad * pad, GstQaRunner * runner,
|
|
GstQaElementMonitor * parent)
|
|
{
|
|
GstQaPadMonitor *monitor = g_object_new (GST_TYPE_QA_PAD_MONITOR,
|
|
"object", pad, "qa-runner", runner, "qa-parent",
|
|
parent, NULL);
|
|
|
|
if (GST_QA_PAD_MONITOR_GET_PAD (monitor) == NULL) {
|
|
g_object_unref (monitor);
|
|
return NULL;
|
|
}
|
|
return monitor;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_timestamp_is_in_received_range (GstQaPadMonitor * monitor,
|
|
GstClockTime ts)
|
|
{
|
|
GST_DEBUG_OBJECT (monitor, "Checking if timestamp %" GST_TIME_FORMAT
|
|
" is in range: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT " for pad "
|
|
"%s:%s", GST_TIME_ARGS (ts),
|
|
GST_TIME_ARGS (monitor->timestamp_range_start),
|
|
GST_TIME_ARGS (monitor->timestamp_range_end),
|
|
GST_DEBUG_PAD_NAME (GST_QA_PAD_MONITOR_GET_PAD (monitor)));
|
|
return !GST_CLOCK_TIME_IS_VALID (monitor->timestamp_range_start) ||
|
|
!GST_CLOCK_TIME_IS_VALID (monitor->timestamp_range_end) ||
|
|
(monitor->timestamp_range_start <= ts
|
|
&& ts <= monitor->timestamp_range_end);
|
|
}
|
|
|
|
/* Iterates over internal links (sinkpads) to check that this buffer has
|
|
* a timestamp that is in the range of the lastly received buffers */
|
|
static void
|
|
gst_qa_pad_monitor_check_buffer_timestamp_in_received_range (GstQaPadMonitor *
|
|
monitor, GstBuffer * buffer)
|
|
{
|
|
GstClockTime ts;
|
|
GstClockTime ts_end;
|
|
GstIterator *iter;
|
|
gboolean has_one = FALSE;
|
|
gboolean found = FALSE;
|
|
gboolean done;
|
|
GstPad *otherpad;
|
|
GstQaPadMonitor *othermonitor;
|
|
|
|
if (!GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer))
|
|
|| !GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer))) {
|
|
GST_DEBUG_OBJECT (monitor,
|
|
"Can't check buffer timestamps range as "
|
|
"buffer has no valid timestamp/duration");
|
|
return;
|
|
}
|
|
ts = GST_BUFFER_TIMESTAMP (buffer);
|
|
ts_end = ts + GST_BUFFER_DURATION (buffer);
|
|
|
|
iter = gst_pad_iterate_internal_links (GST_QA_PAD_MONITOR_GET_PAD (monitor));
|
|
done = FALSE;
|
|
while (!done) {
|
|
switch (gst_iterator_next (iter, (gpointer *) & otherpad)) {
|
|
case GST_ITERATOR_OK:
|
|
GST_DEBUG_OBJECT (monitor, "Checking pad %s:%s input timestamps",
|
|
GST_DEBUG_PAD_NAME (otherpad));
|
|
othermonitor = g_object_get_data ((GObject *) otherpad, "qa-monitor");
|
|
GST_QA_MONITOR_LOCK (othermonitor);
|
|
if (gst_qa_pad_monitor_timestamp_is_in_received_range (othermonitor, ts)
|
|
&& gst_qa_pad_monitor_timestamp_is_in_received_range (othermonitor,
|
|
ts_end)) {
|
|
done = TRUE;
|
|
found = TRUE;
|
|
}
|
|
GST_QA_MONITOR_UNLOCK (othermonitor);
|
|
gst_object_unref (otherpad);
|
|
has_one = TRUE;
|
|
break;
|
|
case GST_ITERATOR_RESYNC:
|
|
gst_iterator_resync (iter);
|
|
has_one = FALSE;
|
|
found = FALSE;
|
|
break;
|
|
case GST_ITERATOR_ERROR:
|
|
GST_WARNING_OBJECT (monitor, "Internal links pad iteration error");
|
|
done = TRUE;
|
|
break;
|
|
case GST_ITERATOR_DONE:
|
|
done = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
gst_iterator_free (iter);
|
|
|
|
if (!has_one) {
|
|
GST_DEBUG_OBJECT (monitor, "Skipping timestamp in range check as no "
|
|
"internal linked pad was found");
|
|
return;
|
|
}
|
|
if (!found) {
|
|
GST_QA_REPORT_WARNING (monitor, FALSE, BUFFER, TIMESTAMP,
|
|
"Timestamp %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT
|
|
" is out of range of received input", GST_TIME_ARGS (ts),
|
|
GST_TIME_ARGS (ts_end));
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_check_first_buffer (GstQaPadMonitor * pad_monitor,
|
|
GstBuffer * buffer)
|
|
{
|
|
if (G_UNLIKELY (pad_monitor->first_buffer)) {
|
|
pad_monitor->first_buffer = FALSE;
|
|
|
|
if (!pad_monitor->has_segment
|
|
&& PAD_IS_IN_PUSH_MODE (GST_QA_PAD_MONITOR_GET_PAD (pad_monitor))) {
|
|
GST_QA_REPORT_WARNING (GST_QA_REPORTER (pad_monitor), FALSE, EVENT,
|
|
EXPECTED, "Received buffer before Segment event");
|
|
}
|
|
if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer))) {
|
|
gint64 running_time = gst_segment_to_running_time (&pad_monitor->segment,
|
|
pad_monitor->segment.format, GST_BUFFER_TIMESTAMP (buffer));
|
|
if (running_time != 0) {
|
|
GST_QA_REPORT_WARNING (pad_monitor, FALSE, BUFFER, TIMESTAMP,
|
|
"First buffer running time is not 0, it is: %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (running_time));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_update_buffer_data (GstQaPadMonitor * pad_monitor,
|
|
GstBuffer * buffer)
|
|
{
|
|
pad_monitor->current_timestamp = GST_BUFFER_TIMESTAMP (buffer);
|
|
pad_monitor->current_duration = GST_BUFFER_DURATION (buffer);
|
|
if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer))) {
|
|
if (GST_CLOCK_TIME_IS_VALID (pad_monitor->timestamp_range_start)) {
|
|
pad_monitor->timestamp_range_start =
|
|
MIN (pad_monitor->timestamp_range_start,
|
|
GST_BUFFER_TIMESTAMP (buffer));
|
|
} else {
|
|
pad_monitor->timestamp_range_start = GST_BUFFER_TIMESTAMP (buffer);
|
|
}
|
|
|
|
if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer))) {
|
|
GstClockTime endts =
|
|
GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
|
|
if (GST_CLOCK_TIME_IS_VALID (pad_monitor->timestamp_range_end)) {
|
|
pad_monitor->timestamp_range_end =
|
|
MAX (pad_monitor->timestamp_range_end, endts);
|
|
} else {
|
|
pad_monitor->timestamp_range_end = endts;
|
|
}
|
|
}
|
|
}
|
|
GST_DEBUG_OBJECT (pad_monitor, "Current stored range: %" GST_TIME_FORMAT
|
|
" - %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (pad_monitor->timestamp_range_start),
|
|
GST_TIME_ARGS (pad_monitor->timestamp_range_end));
|
|
}
|
|
|
|
static GstFlowReturn
|
|
_combine_flows (GstFlowReturn ret1, GstFlowReturn ret2)
|
|
{
|
|
if (ret1 == ret2)
|
|
return ret1;
|
|
if (ret1 <= GST_FLOW_NOT_NEGOTIATED)
|
|
return ret1;
|
|
if (ret2 <= GST_FLOW_NOT_NEGOTIATED)
|
|
return ret2;
|
|
if (ret1 == GST_FLOW_OK || ret2 == GST_FLOW_OK)
|
|
return GST_FLOW_OK;
|
|
return ret2;
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_check_aggregated_return (GstQaPadMonitor * monitor,
|
|
GstFlowReturn ret)
|
|
{
|
|
GstIterator *iter;
|
|
gboolean done;
|
|
GstPad *otherpad;
|
|
GstPad *peerpad;
|
|
GstQaPadMonitor *othermonitor;
|
|
GstFlowReturn aggregated = GST_FLOW_NOT_LINKED;
|
|
gboolean found_a_pad = FALSE;
|
|
|
|
iter = gst_pad_iterate_internal_links (GST_QA_PAD_MONITOR_GET_PAD (monitor));
|
|
done = FALSE;
|
|
while (!done) {
|
|
switch (gst_iterator_next (iter, (gpointer *) & otherpad)) {
|
|
case GST_ITERATOR_OK:
|
|
peerpad = gst_pad_get_peer (otherpad);
|
|
if (peerpad) {
|
|
othermonitor = g_object_get_data ((GObject *) peerpad, "qa-monitor");
|
|
if (othermonitor) {
|
|
found_a_pad = TRUE;
|
|
GST_QA_MONITOR_LOCK (othermonitor);
|
|
aggregated =
|
|
_combine_flows (aggregated, othermonitor->last_flow_return);
|
|
GST_QA_MONITOR_UNLOCK (othermonitor);
|
|
}
|
|
|
|
gst_object_unref (peerpad);
|
|
}
|
|
gst_object_unref (otherpad);
|
|
break;
|
|
case GST_ITERATOR_RESYNC:
|
|
gst_iterator_resync (iter);
|
|
break;
|
|
case GST_ITERATOR_ERROR:
|
|
GST_WARNING_OBJECT (monitor, "Internal links pad iteration error");
|
|
done = TRUE;
|
|
break;
|
|
case GST_ITERATOR_DONE:
|
|
done = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
gst_iterator_free (iter);
|
|
if (!found_a_pad) {
|
|
/* no peer pad found, nothing to do */
|
|
return;
|
|
}
|
|
if (monitor->is_eos && ret == GST_FLOW_UNEXPECTED) {
|
|
/* this is acceptable */
|
|
return;
|
|
}
|
|
if (aggregated != ret) {
|
|
/* TODO review this error code */
|
|
GST_QA_REPORT_CRITICAL (monitor, TRUE, BUFFER, UNEXPECTED,
|
|
"Wrong combined flow return %s(%d). Expected: %s(%d)",
|
|
gst_flow_get_name (ret), ret,
|
|
gst_flow_get_name (aggregated), aggregated);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_otherpad_add_pending_serialized_event (GstQaPadMonitor *
|
|
monitor, GstEvent * event, GstClockTime last_ts)
|
|
{
|
|
GstIterator *iter;
|
|
gboolean done;
|
|
GstPad *otherpad;
|
|
GstQaPadMonitor *othermonitor;
|
|
|
|
if (!GST_EVENT_IS_SERIALIZED (event))
|
|
return;
|
|
|
|
iter = gst_pad_iterate_internal_links (GST_QA_PAD_MONITOR_GET_PAD (monitor));
|
|
done = FALSE;
|
|
while (!done) {
|
|
switch (gst_iterator_next (iter, (gpointer *) & otherpad)) {
|
|
case GST_ITERATOR_OK:
|
|
othermonitor = g_object_get_data ((GObject *) otherpad, "qa-monitor");
|
|
if (othermonitor) {
|
|
SerializedEventData *data = g_slice_new0 (SerializedEventData);
|
|
data->timestamp = last_ts;
|
|
data->event = gst_event_ref (event);
|
|
GST_QA_MONITOR_LOCK (othermonitor);
|
|
g_ptr_array_add (othermonitor->serialized_events, data);
|
|
GST_QA_MONITOR_UNLOCK (othermonitor);
|
|
}
|
|
break;
|
|
case GST_ITERATOR_RESYNC:
|
|
gst_iterator_resync (iter);
|
|
break;
|
|
case GST_ITERATOR_ERROR:
|
|
GST_WARNING_OBJECT (monitor, "Internal links pad iteration error");
|
|
done = TRUE;
|
|
break;
|
|
case GST_ITERATOR_DONE:
|
|
done = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
gst_iterator_free (iter);
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_otherpad_add_pending_field (GstQaPadMonitor * monitor,
|
|
GstStructure * structure, const gchar * field)
|
|
{
|
|
GstIterator *iter;
|
|
gboolean done;
|
|
GstPad *otherpad;
|
|
GstQaPadMonitor *othermonitor;
|
|
const GValue *v;
|
|
|
|
v = gst_structure_get_value (structure, field);
|
|
if (v == NULL) {
|
|
GST_DEBUG_OBJECT (monitor, "Not adding pending field %s as it isn't "
|
|
"present on structure %" GST_PTR_FORMAT, field, structure);
|
|
return;
|
|
}
|
|
|
|
iter = gst_pad_iterate_internal_links (GST_QA_PAD_MONITOR_GET_PAD (monitor));
|
|
done = FALSE;
|
|
while (!done) {
|
|
switch (gst_iterator_next (iter, (gpointer *) & otherpad)) {
|
|
case GST_ITERATOR_OK:
|
|
othermonitor = g_object_get_data ((GObject *) otherpad, "qa-monitor");
|
|
if (othermonitor) {
|
|
GST_QA_MONITOR_LOCK (othermonitor);
|
|
g_assert (othermonitor->pending_setcaps_fields != NULL);
|
|
gst_structure_set_value (othermonitor->pending_setcaps_fields,
|
|
field, v);
|
|
GST_QA_MONITOR_UNLOCK (othermonitor);
|
|
}
|
|
break;
|
|
case GST_ITERATOR_RESYNC:
|
|
gst_iterator_resync (iter);
|
|
break;
|
|
case GST_ITERATOR_ERROR:
|
|
GST_WARNING_OBJECT (monitor, "Internal links pad iteration error");
|
|
done = TRUE;
|
|
break;
|
|
case GST_ITERATOR_DONE:
|
|
done = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
gst_iterator_free (iter);
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_otherpad_clear_pending_fields (GstQaPadMonitor * monitor)
|
|
{
|
|
GstIterator *iter;
|
|
gboolean done;
|
|
GstPad *otherpad;
|
|
GstQaPadMonitor *othermonitor;
|
|
|
|
iter = gst_pad_iterate_internal_links (GST_QA_PAD_MONITOR_GET_PAD (monitor));
|
|
done = FALSE;
|
|
while (!done) {
|
|
switch (gst_iterator_next (iter, (gpointer *) & otherpad)) {
|
|
case GST_ITERATOR_OK:
|
|
othermonitor = g_object_get_data ((GObject *) otherpad, "qa-monitor");
|
|
if (othermonitor) {
|
|
GST_QA_MONITOR_LOCK (othermonitor);
|
|
g_assert (othermonitor->pending_setcaps_fields != NULL);
|
|
gst_structure_free (othermonitor->pending_setcaps_fields);
|
|
othermonitor->pending_setcaps_fields =
|
|
gst_structure_empty_new (PENDING_FIELDS);
|
|
GST_QA_MONITOR_UNLOCK (othermonitor);
|
|
}
|
|
break;
|
|
case GST_ITERATOR_RESYNC:
|
|
gst_iterator_resync (iter);
|
|
break;
|
|
case GST_ITERATOR_ERROR:
|
|
GST_WARNING_OBJECT (monitor, "Internal links pad iteration error");
|
|
done = TRUE;
|
|
break;
|
|
case GST_ITERATOR_DONE:
|
|
done = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
gst_iterator_free (iter);
|
|
}
|
|
|
|
static void
|
|
gst_qa_pad_monitor_add_expected_newsegment (GstQaPadMonitor * monitor,
|
|
GstEvent * event)
|
|
{
|
|
GstIterator *iter;
|
|
gboolean done;
|
|
GstPad *otherpad;
|
|
GstQaPadMonitor *othermonitor;
|
|
|
|
iter = gst_pad_iterate_internal_links (GST_QA_PAD_MONITOR_GET_PAD (monitor));
|
|
done = FALSE;
|
|
while (!done) {
|
|
switch (gst_iterator_next (iter, (gpointer *) & otherpad)) {
|
|
case GST_ITERATOR_OK:
|
|
othermonitor = g_object_get_data ((GObject *) otherpad, "qa-monitor");
|
|
GST_QA_MONITOR_LOCK (othermonitor);
|
|
if (othermonitor->expected_segment) {
|
|
GST_QA_REPORT_WARNING (othermonitor, FALSE, EVENT, EXPECTED,
|
|
"expected newsegment event never pushed");
|
|
gst_event_unref (othermonitor->expected_segment);
|
|
}
|
|
othermonitor->expected_segment = gst_event_ref (event);
|
|
GST_QA_MONITOR_UNLOCK (othermonitor);
|
|
gst_object_unref (otherpad);
|
|
break;
|
|
case GST_ITERATOR_RESYNC:
|
|
gst_iterator_resync (iter);
|
|
break;
|
|
case GST_ITERATOR_ERROR:
|
|
GST_WARNING_OBJECT (monitor, "Internal links pad iteration error");
|
|
done = TRUE;
|
|
break;
|
|
case GST_ITERATOR_DONE:
|
|
done = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
gst_iterator_free (iter);
|
|
}
|
|
|
|
/* common checks for both sink and src event functions */
|
|
static void
|
|
gst_qa_pad_monitor_common_event_check (GstQaPadMonitor * pad_monitor,
|
|
GstEvent * event)
|
|
{
|
|
guint32 seqnum = gst_event_get_seqnum (event);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_FLUSH_START:
|
|
{
|
|
if (pad_monitor->pending_flush_start_seqnum) {
|
|
if (seqnum == pad_monitor->pending_flush_start_seqnum) {
|
|
pad_monitor->pending_flush_start_seqnum = 0;
|
|
} else {
|
|
GST_QA_REPORT_ISSUE (GST_QA_REPORTER (pad_monitor), TRUE, EVENT,
|
|
SEQNUM,
|
|
"The expected flush-start seqnum should be the same as the "
|
|
"one from the event that caused it (probably a seek). Got: %u."
|
|
" Expected: %u", seqnum, pad_monitor->pending_flush_start_seqnum);
|
|
}
|
|
}
|
|
|
|
if (pad_monitor->pending_flush_stop) {
|
|
GST_QA_REPORT_ISSUE (GST_QA_REPORTER (pad_monitor), TRUE, EVENT,
|
|
UNEXPECTED,
|
|
"Received flush-start from %" GST_PTR_FORMAT
|
|
" when flush-stop was expected", GST_EVENT_SRC (event));
|
|
}
|
|
pad_monitor->pending_flush_stop = TRUE;
|
|
}
|
|
break;
|
|
case GST_EVENT_FLUSH_STOP:
|
|
{
|
|
if (pad_monitor->pending_flush_stop_seqnum) {
|
|
if (seqnum == pad_monitor->pending_flush_stop_seqnum) {
|
|
pad_monitor->pending_flush_stop_seqnum = 0;
|
|
} else {
|
|
GST_QA_REPORT_ISSUE (GST_QA_REPORTER (pad_monitor), TRUE, EVENT,
|
|
SEQNUM,
|
|
"The expected flush-stop seqnum should be the same as the "
|
|
"one from the event that caused it (probably a seek). Got: %u."
|
|
" Expected: %u", seqnum, pad_monitor->pending_flush_stop_seqnum);
|
|
}
|
|
}
|
|
|
|
if (!pad_monitor->pending_flush_stop) {
|
|
GST_QA_REPORT_ISSUE (GST_QA_REPORTER (pad_monitor), TRUE, EVENT,
|
|
UNEXPECTED, "Unexpected flush-stop %p from %" GST_PTR_FORMAT, event,
|
|
GST_EVENT_SRC (event));
|
|
}
|
|
pad_monitor->pending_flush_stop = FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_sink_event_check (GstQaPadMonitor * pad_monitor,
|
|
GstEvent * event, GstPadEventFunction handler)
|
|
{
|
|
gboolean ret = TRUE;
|
|
gboolean update;
|
|
gdouble rate, applied_rate;
|
|
GstFormat format;
|
|
gint64 start, stop, position;
|
|
guint32 seqnum = gst_event_get_seqnum (event);
|
|
GstPad *pad = GST_QA_PAD_MONITOR_GET_PAD (pad_monitor);
|
|
|
|
gst_qa_pad_monitor_common_event_check (pad_monitor, event);
|
|
|
|
/* pre checks */
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_NEWSEGMENT:
|
|
/* parse newsegment data to be used if event is handled */
|
|
gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
|
|
&format, &start, &stop, &position);
|
|
|
|
if (pad_monitor->pending_newsegment_seqnum) {
|
|
if (pad_monitor->pending_newsegment_seqnum == seqnum) {
|
|
pad_monitor->pending_newsegment_seqnum = 0;
|
|
} else {
|
|
/* TODO is this an error? could be a segment from the start
|
|
* received just before the seek segment */
|
|
}
|
|
}
|
|
|
|
if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) {
|
|
gst_qa_pad_monitor_add_expected_newsegment (pad_monitor, event);
|
|
} else {
|
|
/* check if this segment is the expected one */
|
|
if (pad_monitor->expected_segment) {
|
|
gint64 exp_start, exp_stop, exp_position;
|
|
gdouble exp_rate, exp_applied_rate;
|
|
gboolean exp_update;
|
|
GstFormat exp_format;
|
|
|
|
if (pad_monitor->expected_segment != event) {
|
|
gst_event_parse_new_segment_full (event, &exp_update, &exp_rate,
|
|
&exp_applied_rate, &exp_format, &exp_start, &exp_stop,
|
|
&exp_position);
|
|
if (format == exp_format) {
|
|
if (update != exp_update
|
|
|| (exp_rate * exp_applied_rate != rate * applied_rate)
|
|
|| exp_start != start || exp_stop != stop
|
|
|| exp_position != position) {
|
|
GST_QA_REPORT_WARNING (pad_monitor, TRUE, EVENT,
|
|
EXPECTED,
|
|
"Expected segment didn't match received segment event");
|
|
}
|
|
}
|
|
}
|
|
gst_event_replace (&pad_monitor->expected_segment, NULL);
|
|
}
|
|
}
|
|
break;
|
|
case GST_EVENT_EOS:
|
|
pad_monitor->is_eos = TRUE;
|
|
/*
|
|
* TODO add end of stream checks for
|
|
* - events not pushed
|
|
* - buffer data not pushed
|
|
* - pending events not received
|
|
*/
|
|
break;
|
|
|
|
/* both flushes are handled by the common event function */
|
|
case GST_EVENT_FLUSH_START:
|
|
case GST_EVENT_FLUSH_STOP:
|
|
case GST_EVENT_TAG:
|
|
case GST_EVENT_SINK_MESSAGE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (handler) {
|
|
GST_QA_MONITOR_UNLOCK (pad_monitor);
|
|
GST_QA_PAD_MONITOR_PARENT_UNLOCK (pad_monitor);
|
|
gst_event_ref (event);
|
|
ret = pad_monitor->event_func (pad, event);
|
|
GST_QA_PAD_MONITOR_PARENT_LOCK (pad_monitor);
|
|
GST_QA_MONITOR_LOCK (pad_monitor);
|
|
}
|
|
|
|
/* post checks */
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_NEWSEGMENT:
|
|
if (ret) {
|
|
if (!pad_monitor->has_segment && pad_monitor->segment.format != format) {
|
|
gst_segment_init (&pad_monitor->segment, format);
|
|
}
|
|
gst_segment_set_newsegment_full (&pad_monitor->segment, update, rate,
|
|
applied_rate, format, start, stop, position);
|
|
pad_monitor->has_segment = TRUE;
|
|
}
|
|
break;
|
|
case GST_EVENT_FLUSH_START:
|
|
case GST_EVENT_FLUSH_STOP:
|
|
case GST_EVENT_EOS:
|
|
case GST_EVENT_TAG:
|
|
case GST_EVENT_SINK_MESSAGE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (handler)
|
|
gst_event_unref (event);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_src_event_check (GstQaPadMonitor * pad_monitor,
|
|
GstEvent * event, GstPadEventFunction handler)
|
|
{
|
|
gboolean ret = TRUE;
|
|
gdouble rate;
|
|
GstFormat format;
|
|
gint64 start, stop;
|
|
GstSeekFlags seek_flags;
|
|
GstSeekType start_type, stop_type;
|
|
guint32 seqnum = gst_event_get_seqnum (event);
|
|
GstPad *pad = GST_QA_PAD_MONITOR_GET_PAD (pad_monitor);
|
|
|
|
gst_qa_pad_monitor_common_event_check (pad_monitor, event);
|
|
|
|
/* pre checks */
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEEK:
|
|
{
|
|
gst_event_parse_seek (event, &rate, &format, &seek_flags, &start_type,
|
|
&start, &stop_type, &stop);
|
|
/* upstream seek - store the seek event seqnum to check
|
|
* flushes and newsegments share the same */
|
|
|
|
/* TODO we might need to use a list as multiple seeks can be sent
|
|
* before the flushes arrive here */
|
|
if (seek_flags & GST_SEEK_FLAG_FLUSH) {
|
|
pad_monitor->pending_flush_start_seqnum = seqnum;
|
|
pad_monitor->pending_flush_stop_seqnum = seqnum;
|
|
}
|
|
pad_monitor->pending_newsegment_seqnum = seqnum;
|
|
}
|
|
break;
|
|
/* both flushes are handled by the common event handling function */
|
|
case GST_EVENT_FLUSH_START:
|
|
case GST_EVENT_FLUSH_STOP:
|
|
case GST_EVENT_NAVIGATION:
|
|
case GST_EVENT_LATENCY:
|
|
case GST_EVENT_STEP:
|
|
case GST_EVENT_QOS:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (handler) {
|
|
GST_QA_MONITOR_UNLOCK (pad_monitor);
|
|
gst_event_ref (event);
|
|
ret = pad_monitor->event_func (pad, event);
|
|
GST_QA_MONITOR_LOCK (pad_monitor);
|
|
}
|
|
|
|
/* post checks */
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_FLUSH_START:
|
|
case GST_EVENT_FLUSH_STOP:
|
|
case GST_EVENT_QOS:
|
|
case GST_EVENT_SEEK:
|
|
case GST_EVENT_NAVIGATION:
|
|
case GST_EVENT_LATENCY:
|
|
case GST_EVENT_STEP:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (handler)
|
|
gst_event_unref (event);
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_qa_pad_monitor_chain_func (GstPad * pad, GstBuffer * buffer)
|
|
{
|
|
GstQaPadMonitor *pad_monitor =
|
|
g_object_get_data ((GObject *) pad, "qa-monitor");
|
|
GstFlowReturn ret;
|
|
|
|
GST_QA_MONITOR_LOCK (pad_monitor);
|
|
|
|
gst_qa_pad_monitor_check_first_buffer (pad_monitor, buffer);
|
|
gst_qa_pad_monitor_update_buffer_data (pad_monitor, buffer);
|
|
|
|
GST_QA_MONITOR_UNLOCK (pad_monitor);
|
|
|
|
ret = pad_monitor->chain_func (pad, buffer);
|
|
|
|
GST_QA_PAD_MONITOR_PARENT_LOCK (pad_monitor);
|
|
GST_QA_MONITOR_LOCK (pad_monitor);
|
|
|
|
pad_monitor->last_flow_return = ret;
|
|
gst_qa_pad_monitor_check_aggregated_return (pad_monitor, ret);
|
|
|
|
GST_QA_MONITOR_UNLOCK (pad_monitor);
|
|
GST_QA_PAD_MONITOR_PARENT_UNLOCK (pad_monitor);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_sink_event_func (GstPad * pad, GstEvent * event)
|
|
{
|
|
GstQaPadMonitor *pad_monitor =
|
|
g_object_get_data ((GObject *) pad, "qa-monitor");
|
|
gboolean ret;
|
|
|
|
GST_QA_PAD_MONITOR_PARENT_LOCK (pad_monitor);
|
|
GST_QA_MONITOR_LOCK (pad_monitor);
|
|
|
|
if (GST_EVENT_IS_SERIALIZED (event)) {
|
|
GstClockTime last_ts;
|
|
if (GST_CLOCK_TIME_IS_VALID (pad_monitor->current_timestamp)) {
|
|
last_ts = pad_monitor->current_timestamp;
|
|
if (GST_CLOCK_TIME_IS_VALID (pad_monitor->current_duration)) {
|
|
last_ts += pad_monitor->current_duration;
|
|
}
|
|
} else {
|
|
last_ts = 0;
|
|
}
|
|
gst_qa_pad_monitor_otherpad_add_pending_serialized_event (pad_monitor,
|
|
event, last_ts);
|
|
}
|
|
|
|
ret = gst_qa_pad_monitor_sink_event_check (pad_monitor, event,
|
|
pad_monitor->event_func);
|
|
|
|
GST_QA_MONITOR_UNLOCK (pad_monitor);
|
|
GST_QA_PAD_MONITOR_PARENT_UNLOCK (pad_monitor);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_src_event_func (GstPad * pad, GstEvent * event)
|
|
{
|
|
GstQaPadMonitor *pad_monitor =
|
|
g_object_get_data ((GObject *) pad, "qa-monitor");
|
|
gboolean ret;
|
|
|
|
GST_QA_MONITOR_LOCK (pad_monitor);
|
|
ret = gst_qa_pad_monitor_src_event_check (pad_monitor, event,
|
|
pad_monitor->event_func);
|
|
GST_QA_MONITOR_UNLOCK (pad_monitor);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_query_func (GstPad * pad, GstQuery * query)
|
|
{
|
|
GstQaPadMonitor *pad_monitor =
|
|
g_object_get_data ((GObject *) pad, "qa-monitor");
|
|
gboolean ret;
|
|
ret = pad_monitor->query_func (pad, query);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_buffer_alloc_func (GstPad * pad, guint64 offset, guint size,
|
|
GstCaps * caps, GstBuffer ** buffer)
|
|
{
|
|
GstQaPadMonitor *pad_monitor =
|
|
g_object_get_data ((GObject *) pad, "qa-monitor");
|
|
gboolean ret;
|
|
ret = pad_monitor->bufferalloc_func (pad, offset, size, caps, buffer);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_get_range_func (GstPad * pad, guint64 offset, guint size,
|
|
GstBuffer ** buffer)
|
|
{
|
|
GstQaPadMonitor *pad_monitor =
|
|
g_object_get_data ((GObject *) pad, "qa-monitor");
|
|
gboolean ret;
|
|
ret = pad_monitor->getrange_func (pad, offset, size, buffer);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_buffer_probe (GstPad * pad, GstBuffer * buffer,
|
|
gpointer udata)
|
|
{
|
|
GstQaPadMonitor *monitor = udata;
|
|
|
|
GST_QA_PAD_MONITOR_PARENT_LOCK (monitor);
|
|
GST_QA_MONITOR_LOCK (monitor);
|
|
|
|
gst_qa_pad_monitor_check_first_buffer (monitor, buffer);
|
|
gst_qa_pad_monitor_update_buffer_data (monitor, buffer);
|
|
|
|
gst_qa_pad_monitor_check_buffer_timestamp_in_received_range (monitor, buffer);
|
|
|
|
gst_qa_pad_monitor_check_late_serialized_events (monitor,
|
|
GST_BUFFER_TIMESTAMP (buffer));
|
|
|
|
if (G_LIKELY (GST_QA_MONITOR_GET_PARENT (monitor))) {
|
|
/* a GstQaPadMonitor parent must be a GstQaElementMonitor */
|
|
if (GST_QA_ELEMENT_MONITOR_ELEMENT_IS_DECODER (monitor)) {
|
|
/* should not push out of segment data */
|
|
if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer)) &&
|
|
GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer)) &&
|
|
!gst_segment_clip (&monitor->segment, monitor->segment.format,
|
|
GST_BUFFER_TIMESTAMP (buffer), GST_BUFFER_TIMESTAMP (buffer) +
|
|
GST_BUFFER_DURATION (buffer), NULL, NULL)) {
|
|
/* TODO is this a timestamp issue? */
|
|
GST_QA_REPORT_ISSUE (monitor, FALSE, BUFFER, TIMESTAMP,
|
|
"buffer is out of segment and shouldn't be pushed. Timestamp: %"
|
|
GST_TIME_FORMAT " - duration: %" GST_TIME_FORMAT
|
|
". Range: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
|
|
GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)),
|
|
GST_TIME_ARGS (monitor->segment.start),
|
|
GST_TIME_ARGS (monitor->segment.stop));
|
|
}
|
|
}
|
|
}
|
|
GST_QA_MONITOR_UNLOCK (monitor);
|
|
GST_QA_PAD_MONITOR_PARENT_UNLOCK (monitor);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_event_probe (GstPad * pad, GstEvent * event, gpointer udata)
|
|
{
|
|
GstQaPadMonitor *monitor = GST_QA_PAD_MONITOR_CAST (udata);
|
|
gboolean ret;
|
|
|
|
GST_QA_PAD_MONITOR_PARENT_LOCK (monitor);
|
|
GST_QA_MONITOR_LOCK (monitor);
|
|
|
|
if (GST_EVENT_IS_SERIALIZED (event)) {
|
|
gint i;
|
|
|
|
if (monitor->serialized_events->len > 0) {
|
|
SerializedEventData *next_event =
|
|
g_ptr_array_index (monitor->serialized_events, 0);
|
|
|
|
if (event == next_event->event
|
|
|| GST_EVENT_TYPE (event) == GST_EVENT_TYPE (next_event->event)) {
|
|
g_ptr_array_remove_index (monitor->serialized_events, 0);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < monitor->serialized_events->len; i++) {
|
|
SerializedEventData *stored_event =
|
|
g_ptr_array_index (monitor->serialized_events, i);
|
|
|
|
if (event == stored_event->event
|
|
|| GST_EVENT_TYPE (event) == GST_EVENT_TYPE (stored_event->event)) {
|
|
GST_QA_REPORT_WARNING (monitor, FALSE, EVENT, UNEXPECTED,
|
|
"Serialized event %" GST_PTR_FORMAT " was pushed out of original "
|
|
"serialization order in pad %s:%s", event,
|
|
GST_DEBUG_PAD_NAME (GST_QA_PAD_MONITOR_GET_PAD (monitor)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This so far is just like an event that is flowing downstream,
|
|
* so we do the same checks as a sinkpad event handler */
|
|
ret = gst_qa_pad_monitor_sink_event_check (monitor, event, NULL);
|
|
GST_QA_MONITOR_UNLOCK (monitor);
|
|
GST_QA_PAD_MONITOR_PARENT_UNLOCK (monitor);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_qa_pad_monitor_getcaps_func (GstPad * pad)
|
|
{
|
|
GstQaPadMonitor *pad_monitor =
|
|
g_object_get_data ((GObject *) pad, "qa-monitor");
|
|
GstCaps *ret = NULL;
|
|
|
|
if (pad_monitor->getcaps_func) {
|
|
ret = pad_monitor->getcaps_func (pad);
|
|
} else {
|
|
ret = _gst_pad_get_caps_default (pad);
|
|
}
|
|
|
|
if (ret) {
|
|
/* We shouldn't need to lock the parent as this doesn't modify
|
|
* other monitors, just does some peer_pad_caps */
|
|
GST_QA_MONITOR_LOCK (pad_monitor);
|
|
gst_qa_pad_monitor_check_caps_complete (pad_monitor, ret);
|
|
|
|
if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) {
|
|
gst_qa_pad_monitor_check_caps_fields_proxied (pad_monitor, ret);
|
|
}
|
|
GST_QA_MONITOR_UNLOCK (pad_monitor);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_setcaps_func (GstPad * pad, GstCaps * caps)
|
|
{
|
|
GstQaPadMonitor *pad_monitor =
|
|
g_object_get_data ((GObject *) pad, "qa-monitor");
|
|
gboolean ret = TRUE;
|
|
GstStructure *structure;
|
|
|
|
GST_QA_PAD_MONITOR_PARENT_LOCK (pad_monitor);
|
|
GST_QA_MONITOR_LOCK (pad_monitor);
|
|
|
|
if (caps) {
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
if (gst_structure_n_fields (pad_monitor->pending_setcaps_fields)) {
|
|
gint i;
|
|
for (i = 0;
|
|
i < gst_structure_n_fields (pad_monitor->pending_setcaps_fields);
|
|
i++) {
|
|
const gchar *name =
|
|
gst_structure_nth_field_name (pad_monitor->pending_setcaps_fields,
|
|
i);
|
|
const GValue *v = gst_structure_get_value (structure, name);
|
|
const GValue *otherv =
|
|
gst_structure_get_value (pad_monitor->pending_setcaps_fields, name);
|
|
|
|
if (v == NULL) {
|
|
GST_QA_REPORT_WARNING (pad_monitor, FALSE, CAPS_NEGOTIATION,
|
|
MISSING_FIELD,
|
|
"Field %s is missing from setcaps caps '%" GST_PTR_FORMAT "'",
|
|
name, caps);
|
|
} else if (gst_value_compare (v, otherv) != GST_VALUE_EQUAL) {
|
|
GST_QA_REPORT_WARNING (pad_monitor, FALSE, CAPS_NEGOTIATION,
|
|
MISSING_FIELD,
|
|
"Field %s from setcaps caps '%" GST_PTR_FORMAT "' is different "
|
|
"from expected value in caps '%" GST_PTR_FORMAT "'", name, caps,
|
|
pad_monitor->pending_setcaps_fields);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gst_qa_pad_monitor_pad_should_proxy_othercaps (pad_monitor)) {
|
|
if (_structure_is_video (structure)) {
|
|
gst_qa_pad_monitor_otherpad_add_pending_field (pad_monitor, structure,
|
|
"width");
|
|
gst_qa_pad_monitor_otherpad_add_pending_field (pad_monitor, structure,
|
|
"height");
|
|
gst_qa_pad_monitor_otherpad_add_pending_field (pad_monitor, structure,
|
|
"framerate");
|
|
gst_qa_pad_monitor_otherpad_add_pending_field (pad_monitor, structure,
|
|
"pixel-aspect-ratio");
|
|
} else if (_structure_is_audio (structure)) {
|
|
gst_qa_pad_monitor_otherpad_add_pending_field (pad_monitor, structure,
|
|
"rate");
|
|
gst_qa_pad_monitor_otherpad_add_pending_field (pad_monitor, structure,
|
|
"channels");
|
|
}
|
|
}
|
|
}
|
|
|
|
gst_structure_free (pad_monitor->pending_setcaps_fields);
|
|
pad_monitor->pending_setcaps_fields =
|
|
gst_structure_empty_new (PENDING_FIELDS);
|
|
|
|
GST_QA_MONITOR_UNLOCK (pad_monitor);
|
|
GST_QA_PAD_MONITOR_PARENT_UNLOCK (pad_monitor);
|
|
|
|
if (pad_monitor->setcaps_func) {
|
|
ret = pad_monitor->setcaps_func (pad, caps);
|
|
}
|
|
|
|
GST_QA_PAD_MONITOR_PARENT_LOCK (pad_monitor);
|
|
if (!ret)
|
|
gst_qa_pad_monitor_otherpad_clear_pending_fields (pad_monitor);
|
|
GST_QA_PAD_MONITOR_PARENT_UNLOCK (pad_monitor);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qa_pad_monitor_do_setup (GstQaMonitor * monitor)
|
|
{
|
|
GstQaPadMonitor *pad_monitor = GST_QA_PAD_MONITOR_CAST (monitor);
|
|
GstPad *pad;
|
|
if (!GST_IS_PAD (GST_QA_MONITOR_GET_OBJECT (monitor))) {
|
|
GST_WARNING_OBJECT (monitor, "Trying to create pad monitor with other "
|
|
"type of object");
|
|
return FALSE;
|
|
}
|
|
|
|
pad = GST_QA_PAD_MONITOR_GET_PAD (pad_monitor);
|
|
|
|
if (g_object_get_data ((GObject *) pad, "qa-monitor")) {
|
|
GST_WARNING_OBJECT (pad_monitor, "Pad already has a qa-monitor associated");
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_set_data ((GObject *) pad, "qa-monitor", pad_monitor);
|
|
|
|
pad_monitor->event_func = GST_PAD_EVENTFUNC (pad);
|
|
pad_monitor->query_func = GST_PAD_QUERYFUNC (pad);
|
|
pad_monitor->setcaps_func = GST_PAD_SETCAPSFUNC (pad);
|
|
pad_monitor->getcaps_func = GST_PAD_GETCAPSFUNC (pad);
|
|
if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) {
|
|
pad_monitor->bufferalloc_func = GST_PAD_BUFFERALLOCFUNC (pad);
|
|
if (pad_monitor->bufferalloc_func)
|
|
gst_pad_set_bufferalloc_function (pad, gst_qa_pad_buffer_alloc_func);
|
|
|
|
pad_monitor->chain_func = GST_PAD_CHAINFUNC (pad);
|
|
if (pad_monitor->chain_func)
|
|
gst_pad_set_chain_function (pad, gst_qa_pad_monitor_chain_func);
|
|
|
|
gst_pad_set_event_function (pad, gst_qa_pad_monitor_sink_event_func);
|
|
} else {
|
|
pad_monitor->getrange_func = GST_PAD_GETRANGEFUNC (pad);
|
|
if (pad_monitor->getrange_func)
|
|
gst_pad_set_getrange_function (pad, gst_qa_pad_get_range_func);
|
|
|
|
gst_pad_set_event_function (pad, gst_qa_pad_monitor_src_event_func);
|
|
|
|
/* add buffer/event probes */
|
|
pad_monitor->buffer_probe_id = gst_pad_add_buffer_probe (pad,
|
|
(GCallback) gst_qa_pad_monitor_buffer_probe, pad_monitor);
|
|
pad_monitor->event_probe_id = gst_pad_add_event_probe (pad,
|
|
(GCallback) gst_qa_pad_monitor_event_probe, pad_monitor);
|
|
}
|
|
gst_pad_set_query_function (pad, gst_qa_pad_monitor_query_func);
|
|
gst_pad_set_getcaps_function (pad, gst_qa_pad_monitor_getcaps_func);
|
|
gst_pad_set_setcaps_function (pad, gst_qa_pad_monitor_setcaps_func);
|
|
|
|
gst_qa_reporter_set_name (GST_QA_REPORTER (monitor), g_strdup_printf ("%s:%s",
|
|
GST_DEBUG_PAD_NAME (pad)));
|
|
|
|
g_signal_connect (pad, "parent-set", (GCallback) _parent_set_cb, monitor);
|
|
|
|
return TRUE;
|
|
}
|