[1.0.22] Add API to get connection stats 96/291196/7 accepted/tizen/unified/20230413.123713
authorEunhye Choi <eunhae1.choi@samsung.com>
Tue, 11 Apr 2023 12:01:13 +0000 (21:01 +0900)
committerEunhye Choi <eunhae1.choi@samsung.com>
Wed, 12 Apr 2023 03:06:49 +0000 (12:06 +0900)
- support connection type : SRT sender/receiver
- add unit test for each connection type

Change-Id: I06ded8ff81630158864f1c07116714ac81f183c9

20 files changed:
include/MediaTransporter.h
include/MediaTransporterBase.h
include/MediaTransporterCallback.h
include/MediaTransporterReceiverSrt.h
include/MediaTransporterSenderSrt.h
include/mtpr.h
packaging/capi-media-transporter.spec
src/MediaTransporter.cpp
src/MediaTransporterBase.cpp
src/MediaTransporterCallback.cpp
src/MediaTransporterReceiverSrt.cpp
src/MediaTransporterSenderSrt.cpp
test/mtpr_test.c
unittest/ut_rist_receiver.cpp
unittest/ut_rist_sender.cpp
unittest/ut_rtsp_receiver.cpp
unittest/ut_rtsp_sender.cpp
unittest/ut_rtsp_sender_to_server.cpp
unittest/ut_srt_receiver.cpp
unittest/ut_srt_sender.cpp

index c3d77e5..6bd9ce9 100644 (file)
@@ -18,13 +18,16 @@ using mtprSourceType = mtpr_source_type_e;
 using mtprMediaType = mtpr_media_type_e;
 using mtprSourceAudioFormat = mtpr_source_audio_format_e;
 using mtprState = mtpr_state_e;
+using mtprError = mtpr_error_e;
 using mtprPacketSourceBufferState = mtpr_media_packet_source_buffer_state_e;
+using mtprConnectionStatsProp = mtpr_connection_stats_prop_e;
+using mtprConnectionStatsPropInfo = mtpr_connection_stats_prop_info_s;
 using mtprTrackAddedCallback = mtpr_track_added_cb;
 using mtprNoMoreTrackCallback = mtpr_no_more_track_cb;
 using mtprPacketCallback = mtpr_encoded_frame_cb;
 using mtprErrorCallback = mtpr_error_cb;
 using mtprPacketSourceBufferStateCallback = mtpr_media_packet_source_buffer_state_changed_cb;
-using mtprError = mtpr_error_e;
+using mtprConnectionStatsCallback = mtpr_connection_stats_cb;
 
 using mtprDisplayType = mtpr_display_type_e;
 using mtprDisplayMode = mtpr_display_mode_e;
index 8c78bbb..77a2669 100644 (file)
@@ -69,12 +69,15 @@ public:
        virtual void setConnection(std::string name, std::string value);
        virtual std::string getConnection(std::string name);
 
+       virtual void foreachConnectionStats(void* handle, mtprConnectionStatsCallback callback, void* userData);
+
        void setResourceManager(std::shared_ptr<MediaTransporterResource> resourceManager);
        void changed() override; // resource interrupted
 
 protected:
        mtprGstreamer _gst {};
        std::unique_ptr<IInvokable> _errorCallback;
+       std::unique_ptr<IInvokable> _connectionStatsCallback;
        std::shared_ptr<MediaTransporterResource> _resourceManager;
 
 private:
index b315eb9..9064377 100644 (file)
@@ -20,6 +20,8 @@
 #include <variant>
 #include <memory>
 #include <media_packet.h>
+#include <map>
+#include <glib-object.h>
 
 #include "MediaTransporter.h"
 
@@ -29,7 +31,8 @@ extern "C" {
 
 namespace tizen_media_transporter {
 
-using VariantData = std::variant<mtprError, media_packet_h, unsigned int, mtprMediaType, mtprPacketSourceBufferState>;
+using VariantData = std::variant<mtprError, media_packet_h, unsigned int, mtprMediaType,
+       mtprPacketSourceBufferState, std::string, const GValue*, std::map<std::string, mtprConnectionStatsProp>>;
 
 class IInvokable
 {
@@ -39,6 +42,7 @@ public:
        virtual void invoke() = 0;
        virtual void invoke(VariantData data) = 0;
        virtual void invoke(VariantData data1, VariantData data2) = 0;
+       virtual bool invoke(VariantData data1, VariantData data2, VariantData data3) = 0;
 };
 
 class AbstractCallback : public IInvokable
@@ -51,6 +55,7 @@ public:
        void invoke() override {}
        void invoke(VariantData data) override {}
        void invoke(VariantData data1, VariantData data2) override {}
+       bool invoke(VariantData data1, VariantData data2, VariantData data3) override { return TRUE; }
 
 protected:
        void* _handle;
@@ -118,6 +123,18 @@ private:
        mtprPacketSourceBufferStateCallback _callback;
 };
 
+class ConnectionStatsCallback : public AbstractCallback
+{
+public:
+       ConnectionStatsCallback(void* handle, mtprConnectionStatsCallback cb, void* userdata);
+       virtual ~ConnectionStatsCallback() = default;
+
+       bool invoke(VariantData data1, VariantData data2, VariantData data3) override;
+
+private:
+       mtprConnectionStatsCallback _callback;
+};
+
 } // namespace
 
 #ifdef __cplusplus
index 6e66402..f83a1c5 100644 (file)
@@ -46,13 +46,16 @@ public:
        std::string getSenderAddress() override { return _senderAddress; }
 
        mtprConnectionType type() override { return MTPR_CONNECTION_TYPE_SRT_RECEIVER; }
+       void foreachConnectionStats(void* handle, mtprConnectionStatsCallback callback, void* userData) override;
 
 private:
        static void _demuxPadAddedCallback(GstElement* demux, GstPad* new_pad, gpointer userData);
        static void _demuxNoMorePadsCallback(GstElement* demux, gpointer userData);
+       static gboolean _statsForeachCb(GQuark fieldId, const GValue* val, gpointer userData);
 
        param::srt::connectionParam _connectionParam;
        std::string _senderAddress;
+       GstElement* _srtSrc { nullptr };
 };
 
 } // namespace
index 88b79b8..e314a7e 100644 (file)
@@ -47,11 +47,14 @@ public:
 
        mtprConnectionType type() override { return MTPR_CONNECTION_TYPE_SRT_SENDER; }
 
+       void foreachConnectionStats(void* handle, mtprConnectionStatsCallback callback, void* userData) override;
+
 private:
        void startStatsMonitoring();
        void stopStatsMonitoring();
        static gpointer _statsMonitorThread(gpointer userData);
        static gpointer _updateStats(gpointer userData);
+       static gboolean _statsForeachCb(GQuark fieldId, const GValue* val, gpointer userData);
 
        struct stats {
                gint64 packetsSent { 0 };
index f3235cc..fb85ff8 100644 (file)
@@ -145,6 +145,64 @@ typedef enum {
 } mtpr_media_packet_source_buffer_state_e;
 
 /**
+ * @brief Enumeration for Media Transporter connection statistics property.
+ * @since_tizen 7.5
+ */
+typedef enum {
+       /* sender */
+       MTPR_CONNECTION_STATS_PROP_PACKET_SENT,
+       MTPR_CONNECTION_STATS_PROP_PACKET_SENT_LOST,
+       MTPR_CONNECTION_STATS_PROP_SEND_RATE_MPBS,
+       MTPR_CONNECTION_STATS_PROP_CALLER_ADDRESS,
+       MTPR_CONNECTION_STATS_PROP_BYTES_SENT_TOTAL,
+       /* receiver */
+       MTPR_CONNECTION_STATS_PROP_PACKET_RECEIVED,
+       MTPR_CONNECTION_STATS_PROP_PACKET_RECEIVED_LOST,
+       MTPR_CONNECTION_STATS_PROP_RECEIVED_RATE_MBPS,
+       MTPR_CONNECTION_STATS_PROP_BYTES_RECEIVED_TOTAL,
+       /* common */
+       MTPR_CONNECTION_STATS_PROP_NEGOTIATED_LATENCY_MS,
+       MTPR_CONNECTION_STATS_PROP_BANDWIDTH_MPBS,
+       MTPR_CONNECTION_STATS_PROP_RTT_MS,
+} mtpr_connection_stats_prop_e;
+
+/**
+ * @brief Enumeration for connection statistics property data type.
+ * @since_tizen 7.5
+ */
+typedef enum {
+       MTPR_CONNECTION_STATS_PROP_TYPE_BOOL,    /**< Boolean type */
+       MTPR_CONNECTION_STATS_PROP_TYPE_INT,     /**< Signed integer type */
+       MTPR_CONNECTION_STATS_PROP_TYPE_UINT,    /**< Unsigned integer type */
+       MTPR_CONNECTION_STATS_PROP_TYPE_INT64,   /**< 64-bit signed integer type */
+       MTPR_CONNECTION_STATS_PROP_TYPE_UINT64,  /**< 64-bit unsigned integer type */
+       MTPR_CONNECTION_STATS_PROP_TYPE_FLOAT,   /**< Float type */
+       MTPR_CONNECTION_STATS_PROP_TYPE_DOUBLE,  /**< Double type */
+       MTPR_CONNECTION_STATS_PROP_TYPE_STRING,  /**< String type */
+} mtpr_connection_stats_prop_type_e;
+
+/**
+ * @brief The structure type for connection statistics property information.
+ * @since_tizen 7.5
+ */
+typedef struct {
+       const char *name;                          /**< The property name */
+       mtpr_connection_stats_prop_e prop;         /**< The property enum */
+       mtpr_connection_stats_prop_type_e type;    /**< The property data type */
+       /** The property value as per the data type above */
+       union {
+               bool v_bool;                /**< For boolean */
+               int v_int;                  /**< For signed integer */
+               unsigned int v_uint;        /**< For unsigned integer */
+               int64_t v_int64;            /**< For 64-bit signed integer */
+               uint64_t v_uint64;          /**< For 64-bit unsigned integer */
+               float v_float;              /**< For float */
+               double v_double;            /**< For double */
+               const char *v_string;       /**< For string */
+       };
+} mtpr_connection_stats_prop_info_s;
+
+/**
  * @brief Definition for mode parameter of SRT.
  * @details The connection mode of SRT.\n
             0: not connected.\n
@@ -313,6 +371,37 @@ typedef void (*mtpr_encoded_frame_cb)(mtpr_h mtpr, mtpr_media_type_e type, unsig
 typedef void (*mtpr_media_packet_source_buffer_state_changed_cb)(unsigned int source_id, mtpr_media_packet_source_buffer_state_e state, void *user_data);
 
 /**
+ * @brief Called iteratively to report the connection statistics properties.
+ * @since_tizen 7.5
+ * @remarks The @a prop_info can be used only in the callback. To use outside, make a copy.
+ * @param[in] mtpr       Media Transporter handle
+ * @param[in] prop_info  The connection statistics property information
+ * @param[in] user_data  The user data passed from the callback registration function
+ * @return @c true to continue with the next iteration of the loop,
+ *         otherwise @c false to break out of the loop
+ * @see mtpr_foreach_connection_stats()
+ * @par Example
+ * @code
+       bool __connection_stats_cb(mtpr_h mtpr, const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+       {
+               switch (prop_info->type) {
+               case MTPR_CONNECTION_STATS_PROP_TYPE_BOOL:
+                       printf("type[0x%x] prop[%s, 0x%08x, value:%d]\n", type, prop_info->name, prop_info->prop, prop_info->v_bool);
+                       break;
+               case MTPR_CONNECTION_STATS_PROP_TYPE_INT:
+                       printf("type[0x%x] prop[%s, 0x%08x, value:%d]\n", type, prop_info->name, prop_info->prop, prop_info->v_int);
+                       break;
+               default:
+                       printf("no need more stats info");
+                       return false;
+               }
+               return true;
+       }
+ * @endcode
+ */
+typedef bool (*mtpr_connection_stats_cb)(mtpr_h mtpr, const mtpr_connection_stats_prop_info_s *prop_info, void *user_data);
+
+/**
  * @brief Creates an instance of Media Transporter.
  * @since_tizen 7.0
  * @privlevel public
@@ -1071,6 +1160,26 @@ int mtpr_set_video_packet_cb(mtpr_h mtpr, mtpr_encoded_frame_cb callback, void *
 int mtpr_unset_video_packet_cb(mtpr_h mtpr);
 
 /**
+ * @brief Retrieves the connection statistics properties.
+ * @since_tizen 7.5
+ * @remarks The registered callback will be invoked in an internal thread.
+ * @remarks If current connection type does not support this api,
+ *          #MTPR_ERROR_INVALID_OPERATION will be return.
+ * @param[in] mtpr        Media Transporter handle
+ * @param[in] callback    Callback function pointer
+ * @param[in] user_data   The user data to be passed to the callback function
+ * @return @c 0 on success,
+ *         otherwise a negative error value
+ * @retval #MTPR_ERROR_NONE    Successful
+ * @retval #MTPR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #MTPR_ERROR_INVALID_OPERATION Invalid operation
+ * @retval #MTPR_ERROR_INVALID_STATE Invalid state
+ * @pre @a mtpr state must not be set to #MTPR_STATE_IDLE.
+ * @post mtpr_connection_stats_cb() will be invoked.
+ */
+int mtpr_foreach_connection_stats(mtpr_h mtpr, mtpr_connection_stats_cb callback, void *user_data);
+
+/**
  * @}
  */
 
index ef17de3..3a84756 100644 (file)
@@ -1,6 +1,6 @@
 Name:       capi-media-transporter
 Summary:    A Media Transporter library in Tizen Native API
-Version:    1.0.21
+Version:    1.0.22
 Release:    0
 Group:      Multimedia/API
 License:    Apache-2.0
index 830e4c0..fb39ada 100644 (file)
@@ -1052,3 +1052,24 @@ int mtpr_set_sound_stream_info(mtpr_h mtpr, sound_stream_info_h stream_info)
 
        return MTPR_ERROR_NONE;
 }
+
+int mtpr_foreach_connection_stats(mtpr_h mtpr, mtpr_connection_stats_cb callback, void *user_data)
+{
+       RET_ERR_IF_INVALID_INSTANCE(mtpr);
+       RET_ERR_IF_NULL_ARG(callback);
+
+       try {
+               auto handle = static_cast<media_transporter_s*>(mtpr);
+               assert(handle->base);
+
+               if (handle->base->state() == MTPR_STATE_IDLE)
+                       throw MediaTransporterException(MTPR_ERROR_INVALID_STATE, "state must not be IDLE");
+
+               handle->base->foreachConnectionStats(mtpr, callback, user_data);
+       } catch (const MediaTransporterException& e) {
+               LOG_ERROR("Failed to foreach connection statistics!!! : %s", e.what());
+               return e.error();
+       }
+
+       return MTPR_ERROR_NONE;
+}
\ No newline at end of file
index f483cb4..ca01adc 100644 (file)
@@ -233,6 +233,11 @@ void MediaTransporterBase::unsetErrorCallback()
        _errorCallback = nullptr;
 }
 
+void MediaTransporterBase::foreachConnectionStats(void* handle, mtprConnectionStatsCallback callback, void* userData)
+{
+       throw MediaTransporterException(MTPR_ERROR_INVALID_OPERATION, "not supported");
+}
+
 void MediaTransporterBase::setResourceManager(std::shared_ptr<MediaTransporterResource> resourceManager)
 {
        _resourceManager = resourceManager;
index d7c754d..f8508f6 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <gio/gio.h>
 
 #include "MediaTransporterCallback.h"
 #include "MediaTransporterLog.h"
@@ -104,4 +105,137 @@ void PacketSourceBufferStateCallback::invoke(VariantData data1, VariantData data
                        _callback, _handle, sourceId, state, _userdata);
 
        _callback(sourceId, state, _userdata);
+}
+
+ConnectionStatsCallback::ConnectionStatsCallback(void* handle, mtprConnectionStatsCallback cb,  void* userdata)
+                                                       : AbstractCallback(handle, userdata), _callback(cb)
+{
+       LOG_DEBUG(">>> callback[%p], handle[%p], user_data[%p] registered",
+                       cb, handle, userdata);
+}
+
+bool ConnectionStatsCallback::invoke(VariantData data1, VariantData data2, VariantData data3)
+{
+       auto fieldName = std::get<std::string>(data1);
+       auto val = std::get<const GValue*>(data2);
+       auto propList = std::get<std::map<std::string, mtprConnectionStatsProp>>(data3);
+       g_autofree gchar* addr = NULL;
+
+       if (propList.find(fieldName) == propList.end()) {
+               LOG_DEBUG("not supported [%s]", fieldName.c_str());
+               return true;
+       }
+
+       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);
+
+               mtprConnectionStatsPropInfo propInfo {
+                       fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_BOOL, .v_bool = static_cast<bool>(result)
+               };
+
+               return _callback(_handle, &propInfo, _userdata);
+       }
+       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);
+
+               mtprConnectionStatsPropInfo propInfo {
+                       fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_INT, .v_int = result
+               };
+
+               return _callback(_handle, &propInfo, _userdata);
+       }
+       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);
+
+               mtprConnectionStatsPropInfo propInfo {
+                       fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_UINT, .v_uint = result
+               };
+
+               return _callback(_handle, &propInfo, _userdata);
+       }
+       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);
+
+               mtprConnectionStatsPropInfo propInfo {
+                       fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_INT64, .v_int64 = result
+               };
+
+               return _callback(_handle, &propInfo, _userdata);
+       }
+       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);
+
+               mtprConnectionStatsPropInfo propInfo {
+                       fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_UINT64, .v_uint64 = result
+               };
+
+               return _callback(_handle, &propInfo, _userdata);
+       }
+       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);
+
+               mtprConnectionStatsPropInfo propInfo {
+                       fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_FLOAT, .v_float = result
+               };
+
+               return _callback(_handle, &propInfo, _userdata);
+       }
+       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);
+
+               mtprConnectionStatsPropInfo propInfo {
+                       fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_DOUBLE, .v_double = result
+               };
+
+               return _callback(_handle, &propInfo, _userdata);
+       }
+       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);
+
+               mtprConnectionStatsPropInfo propInfo {
+                       fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_STRING, .v_string = result
+               };
+
+               return _callback(_handle, &propInfo, _userdata);
+       }
+       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);
+
+                       mtprConnectionStatsPropInfo propInfo {
+                               fieldName.c_str(), prop, MTPR_CONNECTION_STATS_PROP_TYPE_STRING, .v_string = addr
+                       };
+
+                       return _callback(_handle, &propInfo, _userdata);
+               }
+
+               LOG_ERROR("invalid type, field[%s] GType[%s]",
+                       fieldName.c_str(), g_type_name(G_VALUE_TYPE(val)));
+
+               break;
+       }
+
+       return true;
 }
\ No newline at end of file
index 2d60276..dcbd835 100644 (file)
 using namespace tizen_media_transporter;
 using namespace tizen_media_transporter::param::srt;
 
+static const std::map<std::string, mtprConnectionStatsProp> __receiverProp = {
+       { "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 }};
+
 MediaTransporterReceiverSrt::MediaTransporterReceiverSrt()
 {
        LOG_DEBUG("ctor: %p", this);
@@ -118,6 +127,7 @@ void MediaTransporterReceiverSrt::buildPipeline()
                gst::_connectAndAppendSignal(&_gst.signals, G_OBJECT(tsdemux), "pad-added", G_CALLBACK(_demuxPadAddedCallback), this);
                gst::_connectAndAppendSignal(&_gst.signals, G_OBJECT(tsdemux), "no-more-pads", G_CALLBACK(_demuxNoMorePadsCallback), this);
                LOG_INFO("linked mux and sink");
+               _srtSrc = src;
        } catch (const MediaTransporterException& e) {
                LOG_ERROR("%s", e.what());
 
@@ -141,6 +151,7 @@ void MediaTransporterReceiverSrt::stopPipeline()
 {
        gst::_setPipelineState(_gst.pipeline, GST_STATE_NULL,
                                                        MediaTransporterIni::get().general().timeout);
+       _srtSrc = NULL;
 }
 
 void MediaTransporterReceiverSrt::setConnection(std::string name, std::string val)
@@ -157,3 +168,34 @@ void MediaTransporterReceiverSrt::setSenderAddress(std::string address)
 {
        _senderAddress = address;
 }
+
+gboolean MediaTransporterReceiverSrt::_statsForeachCb(GQuark fieldId, const GValue* val, gpointer userData)
+{
+       MediaTransporterReceiverSrt* srt = static_cast<MediaTransporterReceiverSrt*>(userData);
+       std::string fieldName = g_quark_to_string(fieldId);
+
+       if (!srt->_connectionStatsCallback ||
+               !srt->_connectionStatsCallback->invoke(fieldName, val, __receiverProp)) {
+               LOG_WARNING("stop calling stats callback");
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+void MediaTransporterReceiverSrt::foreachConnectionStats(void* handle, mtprConnectionStatsCallback callback, void* userData)
+{
+       GstStructure* structure = NULL;
+
+       _connectionStatsCallback = std::unique_ptr<IInvokable>(new ConnectionStatsCallback(handle, callback, userData));
+
+       g_object_get(_srtSrc, "stats", &structure, NULL);
+
+       gchar* str = gst_structure_to_string(structure);
+       SECURE_LOG_DEBUG("RECEIVER >>> stats : %s", str);
+       g_free(str);
+
+       gst_structure_foreach(structure, _statsForeachCb, (gpointer)this);
+
+       _connectionStatsCallback = nullptr;
+}
\ No newline at end of file
index 9bef241..11cdd68 100644 (file)
@@ -28,6 +28,16 @@ using namespace tizen_media_transporter::param::srt;
 
 constexpr unsigned int STATS_UPDATE_INTERVAL = 1000;
 
+static const std::map<std::string, mtprConnectionStatsProp> __senderProp = {
+       { "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 }};
+
 static void __callerAddedCb(GstElement* element, gint socket, GSocketAddress* address, gpointer userData)
 {
        g_autofree gchar* ip_addr = g_inet_address_to_string(g_inet_socket_address_get_address((GInetSocketAddress*)address));
@@ -121,7 +131,6 @@ gpointer MediaTransporterSenderSrt::_updateStats(gpointer userData)
        return NULL;
 }
 
-
 MediaTransporterSenderSrt::MediaTransporterSenderSrt()
 {
        LOG_DEBUG("ctor: %p", this);
@@ -230,3 +239,48 @@ void MediaTransporterSenderSrt::setSenderAddress(std::string address)
 {
        _senderAddress = address;
 }
+
+gboolean MediaTransporterSenderSrt::_statsForeachCb(GQuark fieldId, const GValue* val, gpointer userData)
+{
+       MediaTransporterSenderSrt* srt = static_cast<MediaTransporterSenderSrt*>(userData);
+       std::string fieldName = g_quark_to_string(fieldId);
+
+       if (!srt->_connectionStatsCallback ||
+               !srt->_connectionStatsCallback->invoke(fieldName, val, __senderProp)) {
+               LOG_WARNING("stop calling stats callback");
+               srt->_connectionStatsCallback = NULL;
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+void MediaTransporterSenderSrt::foreachConnectionStats(void* handle, mtprConnectionStatsCallback callback, void* userData)
+{
+       const GstStructure* structure = NULL;
+
+       _connectionStatsCallback = std::unique_ptr<IInvokable>(new ConnectionStatsCallback(handle, callback, userData));
+
+       g_object_get(_srtSink, "stats", &structure, NULL);
+       gchar* str = gst_structure_to_string(structure);
+       SECURE_LOG_DEBUG("SENDER >>> stats : %s", str);
+       g_free(str);
+
+       if (gst_structure_has_field(structure, "callers")) {
+               GValueArray* callers = (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);
+                       gst_structure_foreach(callerStats, _statsForeachCb, (gpointer)this);
+               }
+       }
+
+       if (_connectionStatsCallback && gst_structure_has_field(structure, "bytes-sent-total"))
+               _connectionStatsCallback->invoke("bytes-sent-total",
+                               gst_structure_get_value(structure, "bytes-sent-total"), __senderProp);
+
+       _connectionStatsCallback = nullptr;
+}
index f4bc0de..92db6b7 100644 (file)
@@ -279,6 +279,54 @@ static void __encoded_frame_cb(mtpr_h mtpr, mtpr_media_type_e type, unsigned int
        media_packet_destroy(packet);
 }
 
+bool __connection_stats_cb(mtpr_h mtpr, const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+{
+       if (!prop_info) {
+               g_print("                         => [STATS_CB][ERROR] mtpr[%p] invalid prop_info\n", mtpr);
+               return false;
+       }
+
+       switch(prop_info->type) {
+       case MTPR_CONNECTION_STATS_PROP_TYPE_BOOL:
+               g_print("                         => [STATS_CB] mtpr[%p], prop[%d], type[%d], %s:%u\n",
+                       mtpr, prop_info->prop, prop_info->type, prop_info->name, prop_info->v_bool);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_INT:
+               g_print("                         => [STATS_CB] mtpr[%p], prop[%d], type[%d], %s:%d\n",
+                       mtpr, prop_info->prop, prop_info->type, prop_info->name, prop_info->v_int);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_UINT:
+               g_print("                         => [STATS_CB] mtpr[%p], prop[%d], type[%d], %s:%u\n",
+                       mtpr, prop_info->prop, prop_info->type, prop_info->name, prop_info->v_uint);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_INT64:
+               g_print("                         => [STATS_CB] mtpr[%p], prop[%d], type[%d], %s:%" G_GINT64_FORMAT "\n",
+                       mtpr, prop_info->prop, prop_info->type, prop_info->name, prop_info->v_int64);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_UINT64:
+               g_print("                         => [STATS_CB] mtpr[%p], prop[%d], type[%d], %s:%" G_GUINT64_FORMAT "\n",
+                       mtpr, prop_info->prop, prop_info->type, prop_info->name, prop_info->v_uint64);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_FLOAT:
+               g_print("                         => [STATS_CB] mtpr[%p], prop[%d], type[%d], %s:%f\n",
+                       mtpr, prop_info->prop, prop_info->type, prop_info->name, prop_info->v_float);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_DOUBLE:
+               g_print("                         => [STATS_CB] mtpr[%p], prop[%d], type[%d], %s:%f\n",
+                       mtpr, prop_info->prop, prop_info->type, prop_info->name, prop_info->v_double);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_STRING:
+               g_print("                         => [STATS_CB] mtpr[%p], prop[%d], type[%d], %s:%s\n",
+                       mtpr, prop_info->prop, prop_info->type, prop_info->name, prop_info->v_string);
+               break;
+       default:
+               g_print("                         => [STATS_CB][ERROR] mtpr[%p] invalid type %d\n", mtpr, prop_info->type);
+               break;
+       }
+
+       return true;
+}
+
 static void _mtpr_test_set_error_cb()
 {
        int ret = MTPR_ERROR_NONE;
@@ -493,6 +541,18 @@ static void _mtpr_test_get_receiver_address()
                free(address);
 }
 
+static void _mtpr_test_foreach_connection_stats()
+{
+       int ret = MTPR_ERROR_NONE;
+
+       ret = mtpr_foreach_connection_stats(g_mtpr, __connection_stats_cb, g_mtpr);
+       if (ret != MTPR_ERROR_NONE) {
+               g_print("             => failed to mtpr_foreach_connection_stats(), ret[0x%x]\n", ret);
+       } else {
+               g_print("             => foreach stats done\n");
+       }
+}
+
 static void __set_aec_reference_device(sound_stream_info_h stream_info)
 {
        int ret;
@@ -1045,6 +1105,8 @@ void _interpret_main_menu(char *cmd)
                        _mtpr_test_set_track_cb();
                } else if (strncmp(cmd, "rs", 2) == 0) {
                        g_menu_state = CURRENT_STATUS_SET_SOUND_STREAM_INFO;
+               } else if (strncmp(cmd, "fs", 2) == 0) {
+                       _mtpr_test_foreach_connection_stats();
                } else {
                        g_print("unknown menu \n");
                }
@@ -1153,6 +1215,9 @@ void display_sub_basic()
        g_print("ev. set video frame cb \t");
        g_print("et. set track added & no more track cb \n\n");
 
+       g_print("-- << Connection Stats Info >> ----------------------------------------------------------------\n");
+       g_print("fs. foreach stats\n");
+
        g_print("=========================================================================================\n");
 }
 
index ca8b5ea..4035720 100644 (file)
@@ -21,7 +21,14 @@ static GMainLoop* mainloop;
 
 using namespace std::chrono_literals;
 
-static void* _run_rist_sender(void* ptr) {
+static bool connectionStatsCb(mtpr_h mtpr,
+       const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+{
+       return true;
+}
+
+static void* _run_rist_sender(void* ptr)
+{
        mtpr_h mtpr;
        unsigned int src_id = 0;
        mtpr_create(MTPR_CONNECTION_TYPE_RIST_SENDER, &mtpr);
@@ -44,7 +51,8 @@ static void* _run_rist_sender(void* ptr) {
 }
 
 static void dataPacketCb(mtpr_h mtpr, mtpr_media_type_e type,
-                                               unsigned int id, media_packet_h packet, void *user_data) {
+                                               unsigned int id, media_packet_h packet, void *user_data)
+{
        LOGD("callback is invoked. type: %d, id: %d", type, id);
 
        static std::once_flag flag;
@@ -56,7 +64,8 @@ static void dataPacketCb(mtpr_h mtpr, mtpr_media_type_e type,
        });
 }
 
-static bool waitDataPacket(std::future<bool>& f) {
+static bool waitDataPacket(std::future<bool>& f)
+{
        LOGI("start waiting for packet arrival...");
 
        if (f.wait_for(1s) != std::future_status::ready) {
@@ -429,3 +438,17 @@ TEST_F(MediaTransporterTestRistReceiverWithSender, start_p)
 
        ASSERT_TRUE(waitDataPacket(f));
 }
+
+TEST_F(MediaTransporterTestRistReceiverWithSender, foreach_connection_stats_n)
+{
+       int ret = MTPR_ERROR_NONE;
+
+       ret = mtpr_set_receiver_address(_mtpr, _receiverPath.c_str());
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_start(_mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_OPERATION);
+}
\ No newline at end of file
index 795749d..449ada2 100644 (file)
 
 #include "ut_base.hpp"
 
+static bool connectionStatsCb(mtpr_h mtpr,
+       const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+{
+       return true;
+}
+
 class MediaTransporterTestRistSender : public MediaTransporterTestBase {
 public:
        MediaTransporterTestRistSender() = default;
@@ -564,3 +570,22 @@ TEST_F(MediaTransporterTestRistSender, get_connection_param_n2)
        ret = mtpr_get_connection_param(_mtpr, MTPR_CONNECTION_PARAM_RIST_BONDING_ADDRESS, &value);
        ASSERT_EQ(ret, MTPR_ERROR_NO_DATA);
 }
+
+TEST_F(MediaTransporterTestRistSender, foreach_connection_stats_n)
+{
+       int ret = MTPR_ERROR_NONE;
+       unsigned int source_id = 0;
+
+       ret = mtpr_set_receiver_address(_mtpr, _receiverPath.c_str());
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_add_media_source(_mtpr, MTPR_SOURCE_TYPE_VIDEOTEST, &source_id);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+       ASSERT_GE(source_id, 0);
+
+       ret = mtpr_start(_mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_OPERATION);
+}
index 3f40f00..10b5a22 100644 (file)
@@ -21,7 +21,14 @@ static GMainLoop* mainloop;
 
 using namespace std::chrono_literals;
 
-static void* _run_rtsp_sender(void* ptr) {
+static bool connectionStatsCb(mtpr_h mtpr,
+       const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+{
+       return true;
+}
+
+static void* _run_rtsp_sender(void* ptr)
+{
        mtpr_h mtpr;
        unsigned int src_id = 0;
        mtpr_create(MTPR_CONNECTION_TYPE_RTSP_SENDER, &mtpr);
@@ -44,7 +51,8 @@ static void* _run_rtsp_sender(void* ptr) {
 }
 
 static void dataPacketCb(mtpr_h mtpr, mtpr_media_type_e type,
-                                               unsigned int id, media_packet_h packet, void *user_data) {
+                                               unsigned int id, media_packet_h packet, void *user_data)
+{
        LOGD("callback is invoked. type: %d, id: %d", type, id);
 
     static std::once_flag flag;
@@ -56,7 +64,8 @@ static void dataPacketCb(mtpr_h mtpr, mtpr_media_type_e type,
     });
 }
 
-static bool waitDataPacket(std::future<bool>& f) {
+static bool waitDataPacket(std::future<bool>& f)
+{
        LOGI("start waiting for packet arrival...");
 
        if (f.wait_for(10s) != std::future_status::ready) {
@@ -313,6 +322,19 @@ TEST_F(MediaTransporterTestRtspReceiver, get_state_n)
        ASSERT_EQ(ret, MTPR_ERROR_INVALID_PARAMETER);
 }
 
+TEST_F(MediaTransporterTestRtspReceiver, foreach_connection_stats_n)
+{
+       int ret = MTPR_ERROR_NONE;
+
+       ret = mtpr_set_sender_address(_mtpr, _senderPath.c_str());
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_start(_mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_OPERATION);
+}
 
 // MediaTransporterTestRtspReceiverWithSender
 
index 994311c..3687293 100644 (file)
 
 #include "ut_base.hpp"
 
+static bool connectionStatsCb(mtpr_h mtpr,
+       const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+{
+       return true;
+}
+
 class MediaTransporterTestRtspSender : public MediaTransporterTestBase {
 public:
        MediaTransporterTestRtspSender() = default;
@@ -345,3 +351,22 @@ TEST_F(MediaTransporterTestRtspSender, get_connection_param_n)
        ret = mtpr_get_connection_param(_mtpr, MTPR_CONNECTION_PARAM_RIST_MAX_RTCP_BANDWIDTH, &value);
        ASSERT_EQ(ret, MTPR_ERROR_INVALID_OPERATION);
 }
+
+TEST_F(MediaTransporterTestRtspSender, foreach_connection_stats_n)
+{
+       int ret = MTPR_ERROR_NONE;
+       unsigned int source_id = 0;
+
+       ret = mtpr_set_sender_address(_mtpr, _senderPath.c_str());
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_add_media_source(_mtpr, MTPR_SOURCE_TYPE_VIDEOTEST, &source_id);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+       ASSERT_GE(source_id, 0);
+
+       ret = mtpr_start(_mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_OPERATION);
+}
index f16d651..c5b5fe4 100644 (file)
 
 static GMainLoop* _mainloop;
 
+static bool connectionStatsCb(mtpr_h mtpr,
+       const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+{
+       return true;
+}
+
 static void _clientConnectedCb(GstRTSPServer* server, GstRTSPClient* client)
 {
        LOGD("client connected %p", client);
@@ -467,3 +473,22 @@ TEST_F(MediaTransporterTestRtspSenderToServerRun, get_state_p2)
        ASSERT_EQ(ret, MTPR_ERROR_NONE);
        ASSERT_EQ(state, MTPR_STATE_IDLE);
 }
+
+TEST_F(MediaTransporterTestRtspSenderToServerRun, foreach_connection_stats_n)
+{
+       int ret = MTPR_ERROR_NONE;
+       unsigned int source_id = 0;
+
+       ret = mtpr_set_receiver_address(_mtpr, _receiverPath.c_str());
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_add_media_source(_mtpr, MTPR_SOURCE_TYPE_VIDEOTEST, &source_id);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+       ASSERT_GE(source_id, 0);
+
+       ret = mtpr_start(_mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_OPERATION);
+}
\ No newline at end of file
index b06f7a4..bdd80c8 100644 (file)
@@ -21,7 +21,8 @@ static GMainLoop* mainloop;
 
 using namespace std::chrono_literals;
 
-static void* _run_srt_sender(void* ptr) {
+static void* _run_srt_sender(void* ptr)
+{
        mtpr_h mtpr;
        unsigned int src_id = 0;
        mtpr_create(MTPR_CONNECTION_TYPE_SRT_SENDER, &mtpr);
@@ -44,7 +45,8 @@ static void* _run_srt_sender(void* ptr) {
 }
 
 static void dataPacketCb(mtpr_h mtpr, mtpr_media_type_e type,
-                                               unsigned int id, media_packet_h packet, void *user_data) {
+                                               unsigned int id, media_packet_h packet, void *user_data)
+{
        LOGD("callback is invoked. type: %d, id: %d", type, id);
 
        static std::once_flag flag;
@@ -56,7 +58,8 @@ static void dataPacketCb(mtpr_h mtpr, mtpr_media_type_e type,
        });
 }
 
-static bool waitDataPacket(std::future<bool>& f) {
+static bool waitDataPacket(std::future<bool>& f)
+{
        LOGI("start waiting for packet arrival...");
 
        if (f.wait_for(1s) != std::future_status::ready) {
@@ -68,6 +71,26 @@ static bool waitDataPacket(std::future<bool>& f) {
        return f.get();
 }
 
+static bool connectionStatsCb(mtpr_h mtpr,
+       const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+{
+       assert(prop_info);
+
+       switch(prop_info->type) {
+       case MTPR_CONNECTION_STATS_PROP_TYPE_INT:
+               LOGI("> prop[%d], type[%d], %s:%d",
+                       prop_info->prop, prop_info->type, prop_info->name, prop_info->v_int);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_INT64:
+               LOGI("> prop[%d], type[%d], %s:%" G_GINT64_FORMAT,
+                       prop_info->prop, prop_info->type, prop_info->name, prop_info->v_int64);
+               break;
+       default:
+               return false;
+       }
+       return true;
+}
+
 class MediaTransporterTestSrtReceiverWithSender : public MediaTransporterTestBase {
 public:
        MediaTransporterTestSrtReceiverWithSender() = default;
@@ -562,6 +585,25 @@ TEST_F(MediaTransporterTestSrtReceiver, get_connection_param_n1)
        ASSERT_EQ(ret, MTPR_ERROR_INVALID_PARAMETER);
 }
 
+TEST_F(MediaTransporterTestSrtReceiver, foreach_connection_stats_n1)
+{
+       int ret = MTPR_ERROR_NONE;
+
+       ret = mtpr_foreach_connection_stats(NULL, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_PARAMETER);
+
+       ret = mtpr_foreach_connection_stats(_mtpr, NULL, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_PARAMETER);
+}
+
+TEST_F(MediaTransporterTestSrtReceiver, foreach_connection_stats_n2)
+{
+       int ret = MTPR_ERROR_NONE;
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_STATE);
+}
+
 TEST_F(MediaTransporterTestSrtReceiver, get_connection_param_n2)
 {
        int ret = MTPR_ERROR_NONE;
@@ -596,3 +638,19 @@ TEST_F(MediaTransporterTestSrtReceiverWithSender, start_p)
        ret = mtpr_unset_video_packet_cb(_mtpr);
        ASSERT_EQ(ret, MTPR_ERROR_NONE);
 }
+
+TEST_F(MediaTransporterTestSrtReceiverWithSender, foreach_connection_stats_p)
+{
+       int ret = MTPR_ERROR_NONE;
+
+       ret = mtpr_set_sender_address(_mtpr, _senderPath.c_str());
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_start(_mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       std::this_thread::sleep_for(std::chrono::seconds(1));
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+}
\ No newline at end of file
index 99eb302..ffb7dc4 100644 (file)
 
 #include "ut_base.hpp"
 
+static bool connectionStatsCb(mtpr_h mtpr,
+       const mtpr_connection_stats_prop_info_s *prop_info, void *user_data)
+{
+       assert(prop_info);
+
+       switch(prop_info->type) {
+       case MTPR_CONNECTION_STATS_PROP_TYPE_INT:
+               LOGI("> prop[%d], type[%d], %s:%d",
+                       prop_info->prop, prop_info->type, prop_info->name, prop_info->v_int);
+               break;
+       case MTPR_CONNECTION_STATS_PROP_TYPE_INT64:
+               LOGI("> prop[%d], type[%d], %s:%" G_GINT64_FORMAT,
+                       prop_info->prop, prop_info->type, prop_info->name, prop_info->v_int64);
+               break;
+       default:
+               return false;
+       }
+       return true;
+}
+
 class MediaTransporterTestSrtSender : public MediaTransporterTestBase {
 public:
        MediaTransporterTestSrtSender() = default;
@@ -536,3 +556,41 @@ TEST_F(MediaTransporterTestSrtSender, get_connection_param_n2)
        ret = mtpr_get_connection_param(_mtpr, MTPR_CONNECTION_PARAM_SRT_PASSPHRASE, &value);
        ASSERT_EQ(ret, MTPR_ERROR_NO_DATA);
 }
+
+TEST_F(MediaTransporterTestSrtSender, foreach_connection_stats_p)
+{
+       int ret = MTPR_ERROR_NONE;
+       unsigned int source_id = 0;
+
+       ret = mtpr_set_sender_address(_mtpr, _senderPath.c_str());
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_add_media_source(_mtpr, MTPR_SOURCE_TYPE_VIDEOTEST, &source_id);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+       ASSERT_GE(source_id, 0);
+
+       ret = mtpr_start(_mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_NONE);
+}
+
+TEST_F(MediaTransporterTestSrtSender, foreach_connection_stats_n1)
+{
+       int ret = MTPR_ERROR_NONE;
+
+       ret = mtpr_foreach_connection_stats(NULL, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_PARAMETER);
+
+       ret = mtpr_foreach_connection_stats(_mtpr, NULL, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_PARAMETER);
+}
+
+TEST_F(MediaTransporterTestSrtSender, foreach_connection_stats_n2)
+{
+       int ret = MTPR_ERROR_NONE;
+
+       ret = mtpr_foreach_connection_stats(_mtpr, connectionStatsCb, _mtpr);
+       ASSERT_EQ(ret, MTPR_ERROR_INVALID_STATE);
+}
\ No newline at end of file