+/**
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gio/gio.h>
+
+#include "MediaTransporterConnectionStats.h"
+#include "MediaTransporterLog.h"
+
+using namespace tizen_media_transporter;
+
+// AbstractConnectionStats
+const GstStructure* AbstractConnectionStats::stats()
+{
+ const GstStructure* structure = nullptr;
+ g_object_get(const_cast<GstElement*>(_element), "stats", &structure, NULL);
+
+ gchar* str = gst_structure_to_string(structure);
+ SECURE_LOG_DEBUG(">>> stats : %s", str);
+ g_free(str);
+
+ return structure;
+}
+
+gboolean AbstractConnectionStats::_statsForeachCb(GQuark fieldId, const GValue* val, gpointer userData)
+{
+ std::string fieldName = g_quark_to_string(fieldId);
+
+ auto [ callback, prop ] = *(static_cast<std::pair<IInvokable*, ConnectionStatPropMap>*>(userData));
+
+ auto propInfo = translateGvalue(val, prop, fieldName);
+
+ if (propInfo.has_value()) {
+ if (!callback || !callback->invokeReturn(propInfo.value())) {
+ LOG_WARNING("stop calling stats callback");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+std::optional<mtprConnectionStatsPropInfo> AbstractConnectionStats::translateGvalue(const GValue* val,
+ const ConnectionStatPropMap& propList,
+ const std::string& fieldName)
+{
+ g_autofree gchar* addr = NULL;
+
+ if (propList.find(fieldName) == propList.end()) {
+ LOG_DEBUG("not supported [%s]", fieldName.c_str());
+ return std::nullopt;
+ }
+
+ mtprConnectionStatsProp prop = propList.at(fieldName);
+
+ switch (G_VALUE_TYPE(val)) {
+ case G_TYPE_BOOLEAN: {
+ gboolean result = g_value_get_boolean(val);
+ LOG_DEBUG("field[%s] GType[%s] value[%u]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), result);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_BOOL, .v_bool = static_cast<bool>(result)
+ };
+ }
+ case G_TYPE_INT: {
+ gint result = g_value_get_int(val);
+ LOG_DEBUG("field[%s] GType[%s] value[%d]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), result);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_INT, .v_int = result
+ };
+
+ }
+ case G_TYPE_UINT: {
+ guint result = g_value_get_uint(val);
+ LOG_DEBUG("field[%s] GType[%s] value[%u]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), result);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_UINT, .v_uint = result
+ };
+ }
+ case G_TYPE_INT64: {
+ gint64 result = g_value_get_int64(val);
+ LOG_DEBUG("field[%s] GType[%s] value[%" G_GINT64_FORMAT "]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), result);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_INT64, .v_int64 = result
+ };
+ }
+ case G_TYPE_UINT64: {
+ guint64 result = g_value_get_uint64(val);
+ LOG_DEBUG("field[%s] GType[%s] value[%" G_GUINT64_FORMAT "]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), result);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_UINT64, .v_uint64 = result
+ };
+ }
+ case G_TYPE_FLOAT: {
+ gfloat result = g_value_get_float(val);
+ LOG_DEBUG("field[%s] GType[%s] value[%f]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), result);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_FLOAT, .v_float = result
+ };
+ }
+ case G_TYPE_DOUBLE: {
+ gdouble result = g_value_get_double(val);
+ LOG_DEBUG("field[%s] GType[%s] value[%f]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), result);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_DOUBLE, .v_double = result
+ };
+ }
+ case G_TYPE_STRING: {
+ const gchar *result = g_value_get_string(val);
+ LOG_DEBUG("field[%s] GType[%s] value[%s]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), result);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_STRING, .v_string = result
+ };
+ }
+ default:
+ if (G_VALUE_TYPE(val) == G_TYPE_SOCKET_ADDRESS) { /* non-constexpr : g_socket_address_get_type() */
+ addr = g_inet_address_to_string
+ (g_inet_socket_address_get_address(G_INET_SOCKET_ADDRESS(g_value_get_object(val))));
+ LOG_DEBUG("field[%s] GType[%s] value[%s]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)), addr);
+
+ return mtprConnectionStatsPropInfo {
+ fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_STRING, .v_string = addr
+ };
+ }
+
+ LOG_ERROR("invalid type, field[%s] GType[%s]",
+ fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)));
+
+ return std::nullopt;
+ }
+}
+
+// ConnectionStatsFactory
+ForeachablePtr ConnectionStatsFactory::createReceiverSrtConnectionStats(const GstElement* element, InvokablePtr callback)
+{
+ return ForeachablePtr(new ReceiverSrtConnectionStats(element, std::move(callback)));
+}
+
+ForeachablePtr ConnectionStatsFactory::createSenderSrtConnectionStats(const GstElement* element, InvokablePtr callback)
+{
+ return ForeachablePtr(new SenderSrtConnectionStats(element, std::move(callback)));
+}
+
+// ReceiverSrtConnectionStats
+ReceiverSrtConnectionStats::ReceiverSrtConnectionStats(const GstElement* element, InvokablePtr callback)
+ : AbstractConnectionStats(element, std::move(callback), {
+ { "packets-received", MTPR_CONNECTION_STATS_PROP_PACKET_RECEIVED },
+ { "packets-received-lost", MTPR_CONNECTION_STATS_PROP_PACKET_RECEIVED_LOST },
+ { "packets-received-rate-mbps", MTPR_CONNECTION_STATS_PROP_RECEIVED_RATE_MBPS },
+ { "negotiated-latency-ms", MTPR_CONNECTION_STATS_PROP_NEGOTIATED_LATENCY_MS },
+ { "bandwidth-mbps", MTPR_CONNECTION_STATS_PROP_BANDWIDTH_MPBS },
+ { "rtt-ms", MTPR_CONNECTION_STATS_PROP_RTT_MS },
+ { "bytes-received-total", MTPR_CONNECTION_STATS_PROP_BYTES_RECEIVED_TOTAL }
+ })
+{
+}
+
+void ReceiverSrtConnectionStats::foreach()
+{
+ const GstStructure* structure = stats();
+ auto userdata = std::make_pair(_callback.get(), _prop);
+
+ gst_structure_foreach(structure, _statsForeachCb, &userdata);
+}
+
+// SenderSrtConnectionStats
+SenderSrtConnectionStats::SenderSrtConnectionStats(const GstElement* element, InvokablePtr callback)
+ : AbstractConnectionStats(element, std::move(callback), {
+ { "packets-sent", MTPR_CONNECTION_STATS_PROP_PACKET_SENT },
+ { "packets-sent-lost", MTPR_CONNECTION_STATS_PROP_PACKET_SENT_LOST },
+ { "send-rate-mbps", MTPR_CONNECTION_STATS_PROP_SEND_RATE_MPBS },
+ { "negotiated-latency-ms", MTPR_CONNECTION_STATS_PROP_NEGOTIATED_LATENCY_MS },
+ { "bandwidth-mbps", MTPR_CONNECTION_STATS_PROP_BANDWIDTH_MPBS },
+ { "rtt-ms", MTPR_CONNECTION_STATS_PROP_RTT_MS },
+ { "caller-address", MTPR_CONNECTION_STATS_PROP_CALLER_ADDRESS },
+ { "bytes-sent-total", MTPR_CONNECTION_STATS_PROP_BYTES_SENT_TOTAL }
+ })
+{
+}
+
+void SenderSrtConnectionStats::foreach()
+{
+ const GstStructure* structure = stats();
+ auto userdata = std::make_pair(_callback.get(), _prop);
+ gboolean ret = FALSE;
+
+ if (gst_structure_has_field(structure, "callers")) {
+ auto callers = static_cast<GValueArray *>(g_value_get_boxed(
+ gst_structure_get_value(structure, "callers")
+ ));
+
+ LOG_DEBUG("SENDER >>> num of callers : %d", callers->n_values);
+ for (int i = 0; i < (int)callers->n_values; i++) {
+ const GstStructure* callerStats = (GstStructure *)g_value_get_boxed(&callers->values[i]);
+
+ LOG_DEBUG("SENDER >>> caller idx : %d", i);
+ ret = gst_structure_foreach(callerStats, _statsForeachCb, &userdata);
+ if (!ret)
+ break;
+ }
+ }
+
+ if (ret && gst_structure_has_field(structure, "bytes-sent-total")) {
+ auto propInfo = translateGvalue(gst_structure_get_value(structure, "bytes-sent-total"),
+ _prop, "bytes-sent-total");
+ if (propInfo.has_value()) {
+ if (!_callback || !_callback->invokeReturn(propInfo.value())) {
+ LOG_WARNING("stop calling stats callback");
+ return;
+ }
+ }
+ }
+}