Revision control
++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qgst_debug_p.h"
++ #include "qgstreamermessage_p.h"
++
++ #include <gst/gstclock.h>
++
++ QT_BEGIN_NAMESPACE
++
++ // NOLINTBEGIN(performance-unnecessary-value-param)
++
++ QDebug operator<<(QDebug dbg, const QGString &str)
++ {
++ return dbg << str.get();
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstCaps &caps)
++ {
++ return dbg << caps.caps();
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstStructureView &structure)
++ {
++ return dbg << structure.structure;
++ }
++
++ QDebug operator<<(QDebug dbg, const QUniqueGstStructureHandle &structure)
++ {
++ return dbg << QGstStructureView{structure};
++ }
++
++ QDebug operator<<(QDebug dbg, const QGValue &value)
++ {
++ return dbg << value.value;
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstreamerMessage &msg)
++ {
++ return dbg << msg.message();
++ }
++
++ QDebug operator<<(QDebug dbg, const QUniqueGErrorHandle &handle)
++ {
++ return dbg << handle.get();
++ }
++
++ QDebug operator<<(QDebug dbg, const QUniqueGStringHandle &handle)
++ {
++ return dbg << handle.get();
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstStreamCollectionHandle &handle)
++ {
++ return dbg << handle.get();
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstStreamHandle &handle)
++ {
++ return dbg << handle.get();
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstTagListHandle &handle)
++ {
++ return dbg << handle.get();
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstElement &element)
++ {
++ return dbg << element.element();
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstPad &pad)
++ {
++ return dbg << pad.pad();
++ }
++
++ QDebug operator<<(QDebug dbg, const GstCaps *caps)
++ {
++ if (caps)
++ return dbg << QGString(gst_caps_to_string(caps));
++ else
++ return dbg << "null";
++ }
++
++ QDebug operator<<(QDebug dbg, const GstVideoInfo *info)
++ {
++ return dbg << QGstCaps{
++ gst_video_info_to_caps(info),
++ QGstCaps::NeedsRef,
++ };
++ }
++
++ QDebug operator<<(QDebug dbg, const GstStructure *structure)
++ {
++ if (structure)
++ return dbg << QGString(gst_structure_to_string(structure));
++ else
++ return dbg << "null";
++ }
++
++ QDebug operator<<(QDebug dbg, const GstObject *object)
++ {
++ if (!object)
++ return dbg << "null";
++
++ dbg << QGString{gst_object_get_name(const_cast<GstObject*>(object))};
++
++ {
++ QDebugStateSaver saver(dbg);
++ dbg.nospace();
++
++ dbg << "{";
++
++ guint numProperties;
++ GParamSpec **properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(object), &numProperties);
++
++ for (guint i = 0; i < numProperties; i++) {
++ GParamSpec *param = properties[i];
++
++ const gchar *name = g_param_spec_get_name(param);
++ constexpr bool trace_blurb = false;
++ if constexpr (trace_blurb) {
++ const gchar *blurb = g_param_spec_get_blurb(param);
++ dbg << name << " (" << blurb << "): ";
++ } else
++ dbg << name << ": ";
++
++ bool readable = bool(param->flags & G_PARAM_READABLE);
++ if (!readable) {
++ dbg << "(not readable)";
++ } else if (QLatin1StringView(name) == QLatin1StringView("parent")) {
++ if (object->parent)
++ dbg << QGString{ gst_object_get_name(object->parent) };
++ else
++ dbg << "(none)";
++ } else {
++ GValue value = {};
++ g_object_get_property(&const_cast<GstObject *>(object)->object, param->name,
++ &value);
++ dbg << &value;
++ }
++ if (i != numProperties - 1)
++ dbg << ", ";
++ }
++
++ dbg << "}";
++
++ g_free(properties);
++ }
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GstElement *element)
++ {
++ return dbg << GST_OBJECT_CAST(element); // LATER: output other members?
++ }
++
++ QDebug operator<<(QDebug dbg, const GstPad *pad)
++ {
++ return dbg << GST_OBJECT_CAST(pad); // LATER: output other members?
++ }
++
++ QDebug operator<<(QDebug dbg, const GstDevice *device)
++ {
++ if (!device)
++ return dbg << "null";
++
++ GstDevice *d = const_cast<GstDevice *>(device);
++ QDebugStateSaver saver(dbg);
++ dbg.nospace();
++
++ dbg << gst_device_get_display_name(d) << "(" << gst_device_get_device_class(d) << ") ";
++ dbg << "Caps: " << QGstCaps{ gst_device_get_caps(d), QGstCaps::NeedsRef, } << ", ";
++ dbg << "Properties: " << QUniqueGstStructureHandle{ gst_device_get_properties(d) }.get();
++ return dbg;
++ }
++
++ namespace {
++
++ struct Timepoint
++ {
++ explicit Timepoint(guint64 us) : ts{ us } { }
++ guint64 ts;
++ };
++
++ QDebug operator<<(QDebug dbg, Timepoint ts)
++ {
++ char buffer[128];
++ snprintf(buffer, sizeof(buffer), "%" GST_TIME_FORMAT, GST_TIME_ARGS(ts.ts));
++ dbg << buffer;
++ return dbg;
++ }
++
++ } // namespace
++
++ QDebug operator<<(QDebug dbg, const GstMessage *msg)
++ {
++ QDebugStateSaver saver(dbg);
++ dbg.nospace();
++
++ dbg << GST_MESSAGE_TYPE_NAME(msg) << ", Source: " << GST_MESSAGE_SRC_NAME(msg);
++ if (GST_MESSAGE_TIMESTAMP(msg) != 0xFFFFFFFFFFFFFFFF)
++ dbg << ", Timestamp: " << GST_MESSAGE_TIMESTAMP(msg);
++
++ switch (msg->type) {
++ case GST_MESSAGE_ERROR: {
++ QUniqueGErrorHandle err;
++ QGString debug;
++ gst_message_parse_error(const_cast<GstMessage *>(msg), &err, &debug);
++
++ dbg << ", Error: " << err << " (" << debug << ")";
++ break;
++ }
++
++ case GST_MESSAGE_WARNING: {
++ QUniqueGErrorHandle err;
++ QGString debug;
++ gst_message_parse_warning(const_cast<GstMessage *>(msg), &err, &debug);
++
++ dbg << ", Warning: " << err << " (" << debug << ")";
++ break;
++ }
++
++ case GST_MESSAGE_INFO: {
++ QUniqueGErrorHandle err;
++ QGString debug;
++ gst_message_parse_info(const_cast<GstMessage *>(msg), &err, &debug);
++
++ dbg << ", Info: " << err << " (" << debug << ")";
++ break;
++ }
++
++ case GST_MESSAGE_TAG: {
++ QGstTagListHandle tagList;
++ gst_message_parse_tag(const_cast<GstMessage *>(msg), &tagList);
++
++ dbg << ", Tags: " << tagList;
++ break;
++ }
++
++ case GST_MESSAGE_QOS: {
++ gboolean live;
++ guint64 running_time;
++ guint64 stream_time;
++ guint64 timestamp;
++ guint64 duration;
++
++ gst_message_parse_qos(const_cast<GstMessage *>(msg), &live, &running_time, &stream_time,
++ ×tamp, &duration);
++
++ dbg << ", Live: " << bool(live) << ", Running time: " << Timepoint{ running_time }
++ << ", Stream time: " << Timepoint{ stream_time }
++ << ", Timestamp: " << Timepoint{ timestamp } << ", Duration: " << Timepoint{ duration };
++ break;
++ }
++
++ case GST_MESSAGE_STATE_CHANGED: {
++ GstState oldState;
++ GstState newState;
++ GstState pending;
++
++ gst_message_parse_state_changed(const_cast<GstMessage *>(msg), &oldState, &newState,
++ &pending);
++
++ dbg << ", Transition: " << oldState << "->" << newState;
++
++ if (pending != GST_STATE_VOID_PENDING)
++ dbg << ", Pending State: " << pending;
++ break;
++ }
++
++ case GST_MESSAGE_STREAM_COLLECTION: {
++ QGstStreamCollectionHandle collection;
++ gst_message_parse_stream_collection(const_cast<GstMessage *>(msg), &collection);
++
++ dbg << ", " << collection;
++ break;
++ }
++
++ case GST_MESSAGE_STREAMS_SELECTED: {
++ QGstStreamCollectionHandle collection;
++ gst_message_parse_streams_selected(const_cast<GstMessage *>(msg), &collection);
++
++ dbg << ", " << collection;
++ break;
++ }
++
++ case GST_MESSAGE_STREAM_STATUS: {
++ GstStreamStatusType streamStatus;
++ gst_message_parse_stream_status(const_cast<GstMessage *>(msg), &streamStatus, nullptr);
++
++ dbg << ", Stream Status: " << streamStatus;
++ break;
++ }
++
++ case GST_MESSAGE_BUFFERING: {
++ int progress = 0;
++ gst_message_parse_buffering(const_cast<GstMessage *>(msg), &progress);
++
++ dbg << ", Buffering: " << progress << "%";
++ break;
++ }
++
++ case GST_MESSAGE_SEGMENT_START: {
++ gint64 pos;
++ GstFormat fmt{};
++ gst_message_parse_segment_start(const_cast<GstMessage *>(msg), &fmt, &pos);
++
++ switch (fmt) {
++ case GST_FORMAT_TIME: {
++ dbg << ", Position: " << std::chrono::nanoseconds{ pos };
++ break;
++ }
++ case GST_FORMAT_BYTES: {
++ dbg << ", Position: " << pos << "Bytes";
++ break;
++ }
++ default: {
++ dbg << ", Position: " << pos;
++ break;
++ }
++ }
++
++ break;
++ }
++
++ default:
++ break;
++ }
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GstTagList *tagList)
++ {
++ if (tagList)
++ dbg << QGString{ gst_tag_list_to_string(tagList) };
++ else
++ dbg << "NULL";
++
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GstQuery *query)
++ {
++ dbg << GST_QUERY_TYPE_NAME(query);
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GstEvent *event)
++ {
++ dbg << GST_EVENT_TYPE_NAME(event);
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GstPadTemplate *padTemplate)
++ {
++ QGstCaps caps = padTemplate
++ ? QGstCaps{ gst_pad_template_get_caps(const_cast<GstPadTemplate *>(padTemplate)), QGstCaps::HasRef, }
++ : QGstCaps{};
++
++ dbg << caps;
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GstStreamCollection *streamCollection)
++ {
++ QDebugStateSaver saver(dbg);
++ dbg.nospace();
++
++ GstStreamCollection *collection = const_cast<GstStreamCollection *>(streamCollection);
++ dbg << "Stream Collection: {";
++
++ qForeachStreamInCollection(collection, [&](GstStream *stream) {
++ dbg << stream << ", ";
++ });
++
++ dbg << "}";
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GstStream *cstream)
++ {
++ GstStream *stream = const_cast<GstStream *>(cstream);
++
++ QDebugStateSaver saver(dbg);
++ dbg.nospace();
++
++ dbg << gst_stream_get_stream_id(stream) << " (" << gst_stream_get_stream_type(stream) << ")";
++
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, GstState state)
++ {
++ return dbg << gst_element_state_get_name(state);
++ }
++
++ QDebug operator<<(QDebug dbg, GstStateChange transition)
++ {
++ return dbg << gst_state_change_get_name(transition);
++ }
++
++ QDebug operator<<(QDebug dbg, GstStateChangeReturn stateChangeReturn)
++ {
++ return dbg << gst_element_state_change_return_get_name(stateChangeReturn);
++ }
++
++ QDebug operator<<(QDebug dbg, GstMessageType type)
++ {
++ return dbg << gst_message_type_get_name(type);
++ }
++
++ #define ADD_ENUM_SWITCH(value) \
++ case value: \
++ return dbg << #value; \
++ static_assert(true, "enforce semicolon")
++
++ QDebug operator<<(QDebug dbg, GstPadDirection direction)
++ {
++ switch (direction) {
++ ADD_ENUM_SWITCH(GST_PAD_UNKNOWN);
++ ADD_ENUM_SWITCH(GST_PAD_SRC);
++ ADD_ENUM_SWITCH(GST_PAD_SINK);
++ default:
++ Q_UNREACHABLE_RETURN(dbg);
++ }
++ }
++
++ QDebug operator<<(QDebug dbg, GstStreamStatusType type)
++ {
++ switch (type) {
++ ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_CREATE);
++ ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_ENTER);
++ ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_LEAVE);
++ ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_DESTROY);
++ ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_START);
++ ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_PAUSE);
++ ADD_ENUM_SWITCH(GST_STREAM_STATUS_TYPE_STOP);
++ default:
++ Q_UNREACHABLE_RETURN(dbg);
++ }
++ return dbg;
++ }
++
++ #undef ADD_ENUM_SWITCH
++
++ QDebug operator<<(QDebug dbg, GstStreamType streamType)
++ {
++ dbg << gst_stream_type_get_name(streamType);
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GValue *value)
++ {
++ if (!value)
++ return dbg << "null";
++
++ switch (G_VALUE_TYPE(value)) {
++ case G_TYPE_STRING:
++ return dbg << g_value_get_string(value);
++ case G_TYPE_BOOLEAN:
++ return dbg << g_value_get_boolean(value);
++ case G_TYPE_ULONG:
++ return dbg << g_value_get_ulong(value);
++ case G_TYPE_LONG:
++ return dbg << g_value_get_long(value);
++ case G_TYPE_UINT:
++ return dbg << g_value_get_uint(value);
++ case G_TYPE_INT:
++ return dbg << g_value_get_int(value);
++ case G_TYPE_UINT64:
++ return dbg << g_value_get_uint64(value);
++ case G_TYPE_INT64:
++ return dbg << g_value_get_int64(value);
++ case G_TYPE_FLOAT:
++ return dbg << g_value_get_float(value);
++ case G_TYPE_DOUBLE:
++ return dbg << g_value_get_double(value);
++ default:
++ break;
++ }
++
++ if (GST_VALUE_HOLDS_BITMASK(value)) {
++ QDebugStateSaver saver(dbg);
++ return dbg << Qt::hex << gst_value_get_bitmask(value);
++ }
++
++ if (GST_VALUE_HOLDS_FRACTION(value))
++ return dbg << gst_value_get_fraction_numerator(value) << "/"
++ << gst_value_get_fraction_denominator(value);
++
++ if (GST_VALUE_HOLDS_CAPS(value))
++ return dbg << gst_value_get_caps(value);
++
++ if (GST_VALUE_HOLDS_STRUCTURE(value))
++ return dbg << gst_value_get_structure(value);
++
++ if (GST_VALUE_HOLDS_ARRAY(value)) {
++ const guint size = gst_value_array_get_size(value);
++ const guint last = size - 1;
++ dbg << "[";
++ for (guint index = 0; index != size; ++index) {
++ dbg << gst_value_array_get_value(value, index);
++ if (index != last)
++ dbg << ", ";
++ }
++ dbg << "}";
++ return dbg;
++ }
++
++ if (G_VALUE_TYPE(value) == GST_TYPE_PAD_DIRECTION) {
++ GstPadDirection direction = static_cast<GstPadDirection>(g_value_get_enum(value));
++ return dbg << direction;
++ }
++
++ if (G_VALUE_TYPE(value) == GST_TYPE_PAD_TEMPLATE) {
++ GstPadTemplate *padTemplate = static_cast<GstPadTemplate *>(g_value_get_object(value));
++ return dbg << padTemplate;
++ }
++
++ dbg << "(not implemented: " << G_VALUE_TYPE_NAME(value) << ")";
++
++ return dbg;
++ }
++
++ QDebug operator<<(QDebug dbg, const GError *error)
++ {
++ return dbg << error->message;
++ }
++
++ QCompactGstMessageAdaptor::QCompactGstMessageAdaptor(const QGstreamerMessage &m)
++ : QCompactGstMessageAdaptor{
++ m.message(),
++ }
++ {
++ }
++
++ QCompactGstMessageAdaptor::QCompactGstMessageAdaptor(GstMessage *m)
++ : msg{
++ m,
++ }
++ {
++ }
++
++ QDebug operator<<(QDebug dbg, const QCompactGstMessageAdaptor &m)
++ {
++ std::optional<QDebugStateSaver> saver(dbg);
++ dbg.nospace();
++
++ switch (GST_MESSAGE_TYPE(m.msg)) {
++ case GST_MESSAGE_ERROR: {
++ QUniqueGErrorHandle err;
++ QGString debug;
++ gst_message_parse_error(m.msg, &err, &debug);
++ dbg << err << " (" << debug << ")";
++ return dbg;
++ }
++
++ case GST_MESSAGE_WARNING: {
++ QUniqueGErrorHandle err;
++ QGString debug;
++ gst_message_parse_warning(m.msg, &err, &debug);
++ dbg << err << " (" << debug << ")";
++ return dbg;
++ }
++
++ case GST_MESSAGE_INFO: {
++ QUniqueGErrorHandle err;
++ QGString debug;
++ gst_message_parse_info(m.msg, &err, &debug);
++
++ dbg << err << " (" << debug << ")";
++ return dbg;
++ }
++
++ case GST_MESSAGE_STATE_CHANGED: {
++ GstState oldState;
++ GstState newState;
++ GstState pending;
++
++ gst_message_parse_state_changed(m.msg, &oldState, &newState, &pending);
++
++ dbg << oldState << " -> " << newState;
++ if (pending != GST_STATE_VOID_PENDING)
++ dbg << " (pending: " << pending << ")";
++ return dbg;
++ }
++
++ default: {
++ saver.reset();
++ return dbg << m.msg;
++ }
++ }
++ }
++
++ QDebug operator<<(QDebug dbg, GstPlayMessage type)
++ {
++ return dbg << gst_play_message_get_name(type);
++ }
++
++ QDebug operator<<(QDebug dbg, GstPlayState state)
++ {
++ return dbg << gst_play_state_get_name(state);
++ }
++
++ QGstPlayMessageAdaptor::QGstPlayMessageAdaptor(const QGstreamerMessage &m)
++ : QGstPlayMessageAdaptor{
++ m.message(),
++ }
++ {
++ }
++
++ QGstPlayMessageAdaptor::QGstPlayMessageAdaptor(GstMessage *m)
++ : msg{
++ m,
++ }
++ {
++ }
++
++ QDebug operator<<(QDebug dbg, const QGstPlayMessageAdaptor &m)
++ {
++ using namespace std::chrono;
++ Q_ASSERT(gst_play_is_play_message(m.msg));
++
++ std::optional<QDebugStateSaver> saver(dbg);
++
++ GstPlayMessage type;
++ gst_play_message_parse_type(m.msg, &type);
++
++ switch (type) {
++ case GST_PLAY_MESSAGE_URI_LOADED:
++ case GST_PLAY_MESSAGE_END_OF_STREAM:
++ case GST_PLAY_MESSAGE_MEDIA_INFO_UPDATED:
++ case GST_PLAY_MESSAGE_VOLUME_CHANGED:
++ case GST_PLAY_MESSAGE_MUTE_CHANGED:
++ case GST_PLAY_MESSAGE_SEEK_DONE:
++ return dbg << type;
++
++ case GST_PLAY_MESSAGE_POSITION_UPDATED: {
++ GstClockTime position;
++ gst_play_message_parse_position_updated(m.msg, &position);
++ return dbg << type << nanoseconds(position);
++ }
++ case GST_PLAY_MESSAGE_DURATION_CHANGED: {
++ GstClockTime duration;
++ gst_play_message_parse_duration_updated(m.msg, &duration);
++ return dbg << type << nanoseconds(duration);
++ }
++
++ case GST_PLAY_MESSAGE_STATE_CHANGED: {
++ GstPlayState state;
++ gst_play_message_parse_state_changed(m.msg, &state);
++ return dbg << type << state;
++ }
++ case GST_PLAY_MESSAGE_BUFFERING: {
++ guint percent;
++ gst_play_message_parse_buffering_percent(m.msg, &percent);
++ return dbg << type << percent;
++ }
++ case GST_PLAY_MESSAGE_ERROR: {
++ QUniqueGErrorHandle err;
++ QUniqueGstStructureHandle details;
++ gst_play_message_parse_error(m.msg, &err, &details);
++ return dbg << type << err << details.get();
++ }
++ case GST_PLAY_MESSAGE_WARNING: {
++ QUniqueGErrorHandle err;
++ QUniqueGstStructureHandle details;
++ gst_play_message_parse_warning(m.msg, &err, &details);
++ return dbg << type << err << details.get();
++ };
++
++ case GST_PLAY_MESSAGE_VIDEO_DIMENSIONS_CHANGED: {
++ guint width, height;
++ gst_play_message_parse_video_dimensions_changed(m.msg, &width, &height);
++ return dbg << type << QSize(width, height);
++ }
++ default:
++ Q_UNREACHABLE_RETURN(dbg);
++ }
++ }
++
++ QT_END_NAMESPACE