| /* GStreamer |
| * Copyright (C) 2017 Matthew Waters <matthew@centricular.com> |
| * |
| * 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 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| /* for GValueArray... */ |
| #define GLIB_DISABLE_DEPRECATION_WARNINGS |
| |
| #include "gstwebrtcstats.h" |
| #include "gstwebrtcbin.h" |
| #include "transportstream.h" |
| #include "transportreceivebin.h" |
| #include "utils.h" |
| #include "webrtctransceiver.h" |
| |
| #define GST_CAT_DEFAULT gst_webrtc_stats_debug |
| GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); |
| |
| static void |
| _init_debug (void) |
| { |
| static gsize _init = 0; |
| |
| if (g_once_init_enter (&_init)) { |
| GST_DEBUG_CATEGORY_INIT (gst_webrtc_stats_debug, "webrtcice", 0, |
| "webrtcice"); |
| g_once_init_leave (&_init, 1); |
| } |
| } |
| |
| static double |
| monotonic_time_as_double_milliseconds (void) |
| { |
| return g_get_monotonic_time () / 1000.0; |
| } |
| |
| static void |
| _set_base_stats (GstStructure * s, GstWebRTCStatsType type, double ts, |
| const char *id) |
| { |
| gchar *name = _enum_value_to_string (GST_TYPE_WEBRTC_STATS_TYPE, |
| type); |
| |
| g_return_if_fail (name != NULL); |
| |
| gst_structure_set_name (s, name); |
| gst_structure_set (s, "type", GST_TYPE_WEBRTC_STATS_TYPE, type, "timestamp", |
| G_TYPE_DOUBLE, ts, "id", G_TYPE_STRING, id, NULL); |
| |
| g_free (name); |
| } |
| |
| static GstStructure * |
| _get_peer_connection_stats (GstWebRTCBin * webrtc) |
| { |
| GstStructure *s = gst_structure_new_empty ("unused"); |
| |
| /* FIXME: datachannel */ |
| gst_structure_set (s, "data-channels-opened", G_TYPE_UINT, 0, |
| "data-channels-closed", G_TYPE_UINT, 0, "data-channels-requested", |
| G_TYPE_UINT, 0, "data-channels-accepted", G_TYPE_UINT, 0, NULL); |
| |
| return s; |
| } |
| |
| #define CLOCK_RATE_VALUE_TO_SECONDS(v,r) ((double) v / (double) clock_rate) |
| |
| /* https://www.w3.org/TR/webrtc-stats/#inboundrtpstats-dict* |
| https://www.w3.org/TR/webrtc-stats/#outboundrtpstats-dict* */ |
| static void |
| _get_stats_from_rtp_source_stats (GstWebRTCBin * webrtc, |
| const GstStructure * source_stats, const gchar * codec_id, |
| const gchar * transport_id, GstStructure * s) |
| { |
| GstStructure *in, *out, *r_in, *r_out; |
| gchar *in_id, *out_id, *r_in_id, *r_out_id; |
| guint ssrc, fir, pli, nack, jitter; |
| int lost, clock_rate; |
| guint64 packets, bytes; |
| gboolean have_rb = FALSE, sent_rb = FALSE; |
| double ts; |
| |
| gst_structure_get_double (s, "timestamp", &ts); |
| gst_structure_get_uint (source_stats, "ssrc", &ssrc); |
| gst_structure_get (source_stats, "have-rb", G_TYPE_BOOLEAN, &have_rb, |
| "sent_rb", G_TYPE_BOOLEAN, &sent_rb, "clock-rate", G_TYPE_INT, |
| &clock_rate, NULL); |
| |
| in_id = g_strdup_printf ("rtp-inbound-stream-stats_%u", ssrc); |
| out_id = g_strdup_printf ("rtp-outbound-stream-stats_%u", ssrc); |
| r_in_id = g_strdup_printf ("rtp-remote-inbound-stream-stats_%u", ssrc); |
| r_out_id = g_strdup_printf ("rtp-remote-outbound-stream-stats_%u", ssrc); |
| |
| in = gst_structure_new_empty (in_id); |
| _set_base_stats (in, GST_WEBRTC_STATS_INBOUND_RTP, ts, in_id); |
| |
| /* RTCStreamStats */ |
| gst_structure_set (in, "ssrc", G_TYPE_UINT, ssrc, NULL); |
| gst_structure_set (in, "codec-id", G_TYPE_STRING, codec_id, NULL); |
| gst_structure_set (in, "transport-id", G_TYPE_STRING, transport_id, NULL); |
| if (gst_structure_get_uint (source_stats, "recv-fir-count", &fir)) |
| gst_structure_set (in, "fir-count", G_TYPE_UINT, fir, NULL); |
| if (gst_structure_get_uint (source_stats, "recv-pli-count", &pli)) |
| gst_structure_set (in, "pli-count", G_TYPE_UINT, pli, NULL); |
| if (gst_structure_get_uint (source_stats, "recv-nack-count", &nack)) |
| gst_structure_set (in, "nack-count", G_TYPE_UINT, nack, NULL); |
| /* XXX: mediaType, trackId, sliCount, qpSum */ |
| |
| /* RTCReceivedRTPStreamStats */ |
| if (gst_structure_get_uint64 (source_stats, "packets-received", &packets)) |
| gst_structure_set (in, "packets-received", G_TYPE_UINT64, packets, NULL); |
| if (gst_structure_get_uint64 (source_stats, "octets-received", &bytes)) |
| gst_structure_set (in, "bytes-received", G_TYPE_UINT64, bytes, NULL); |
| if (gst_structure_get_int (source_stats, "packets-lost", &lost)) |
| gst_structure_set (in, "packets-lost", G_TYPE_INT, lost, NULL); |
| if (gst_structure_get_uint (source_stats, "jitter", &jitter)) |
| gst_structure_set (in, "jitter", G_TYPE_DOUBLE, |
| CLOCK_RATE_VALUE_TO_SECONDS (jitter, clock_rate), NULL); |
| /* |
| RTCReceivedRTPStreamStats |
| double fractionLost; |
| unsigned long packetsDiscarded; |
| unsigned long packetsFailedDecryption; |
| unsigned long packetsRepaired; |
| unsigned long burstPacketsLost; |
| unsigned long burstPacketsDiscarded; |
| unsigned long burstLossCount; |
| unsigned long burstDiscardCount; |
| double burstLossRate; |
| double burstDiscardRate; |
| double gapLossRate; |
| double gapDiscardRate; |
| */ |
| |
| /* RTCInboundRTPStreamStats */ |
| gst_structure_set (in, "remote-id", G_TYPE_STRING, r_out_id, NULL); |
| /* XXX: framesDecoded, lastPacketReceivedTimestamp */ |
| |
| r_in = gst_structure_new_empty (r_in_id); |
| _set_base_stats (r_in, GST_WEBRTC_STATS_REMOTE_INBOUND_RTP, ts, r_in_id); |
| |
| /* RTCStreamStats */ |
| gst_structure_set (r_in, "ssrc", G_TYPE_UINT, ssrc, NULL); |
| gst_structure_set (r_in, "codec-id", G_TYPE_STRING, codec_id, NULL); |
| gst_structure_set (r_in, "transport-id", G_TYPE_STRING, transport_id, NULL); |
| /* XXX: mediaType, trackId, sliCount, qpSum */ |
| |
| /* RTCReceivedRTPStreamStats */ |
| if (sent_rb) { |
| if (gst_structure_get_uint (source_stats, "sent-rb-jitter", &jitter)) |
| gst_structure_set (r_in, "jitter", G_TYPE_DOUBLE, |
| CLOCK_RATE_VALUE_TO_SECONDS (jitter, clock_rate), NULL); |
| if (gst_structure_get_int (source_stats, "sent-rb-packetslost", &lost)) |
| gst_structure_set (r_in, "packets-lost", G_TYPE_INT, lost, NULL); |
| /* packetsReceived, bytesReceived */ |
| } else { |
| /* default values */ |
| gst_structure_set (r_in, "jitter", G_TYPE_DOUBLE, 0.0, "packets-lost", |
| G_TYPE_INT, 0, NULL); |
| } |
| /* XXX: RTCReceivedRTPStreamStats |
| double fractionLost; |
| unsigned long packetsDiscarded; |
| unsigned long packetsFailedDecryption; |
| unsigned long packetsRepaired; |
| unsigned long burstPacketsLost; |
| unsigned long burstPacketsDiscarded; |
| unsigned long burstLossCount; |
| unsigned long burstDiscardCount; |
| double burstLossRate; |
| double burstDiscardRate; |
| double gapLossRate; |
| double gapDiscardRate; |
| */ |
| |
| /* RTCRemoteInboundRTPStreamStats */ |
| gst_structure_set (r_in, "local-id", G_TYPE_STRING, out_id, NULL); |
| if (have_rb) { |
| guint32 rtt; |
| if (gst_structure_get_uint (source_stats, "rb-round-trip", &rtt)) { |
| /* 16.16 fixed point to double */ |
| double val = |
| (double) ((rtt & 0xffff0000) >> 16) + ((rtt & 0xffff) / 65536.0); |
| gst_structure_set (r_in, "round-trip-time", G_TYPE_DOUBLE, val, NULL); |
| } |
| } else { |
| /* default values */ |
| gst_structure_set (r_in, "round-trip-time", G_TYPE_DOUBLE, 0.0, NULL); |
| } |
| /* XXX: framesDecoded, lastPacketReceivedTimestamp */ |
| |
| out = gst_structure_new_empty (out_id); |
| _set_base_stats (out, GST_WEBRTC_STATS_OUTBOUND_RTP, ts, out_id); |
| |
| /* RTCStreamStats */ |
| gst_structure_set (out, "ssrc", G_TYPE_UINT, ssrc, NULL); |
| gst_structure_set (out, "codec-id", G_TYPE_STRING, codec_id, NULL); |
| gst_structure_set (out, "transport-id", G_TYPE_STRING, transport_id, NULL); |
| if (gst_structure_get_uint (source_stats, "sent-fir-count", &fir)) |
| gst_structure_set (out, "fir-count", G_TYPE_UINT, fir, NULL); |
| if (gst_structure_get_uint (source_stats, "sent-pli-count", &pli)) |
| gst_structure_set (out, "pli-count", G_TYPE_UINT, pli, NULL); |
| if (gst_structure_get_uint (source_stats, "sent-nack-count", &nack)) |
| gst_structure_set (out, "nack-count", G_TYPE_UINT, nack, NULL); |
| /* XXX: mediaType, trackId, sliCount, qpSum */ |
| |
| /* RTCSentRTPStreamStats */ |
| if (gst_structure_get_uint64 (source_stats, "octets-sent", &bytes)) |
| gst_structure_set (out, "bytes-sent", G_TYPE_UINT64, bytes, NULL); |
| if (gst_structure_get_uint64 (source_stats, "packets-sent", &packets)) |
| gst_structure_set (out, "packets-sent", G_TYPE_UINT64, packets, NULL); |
| /* XXX: |
| unsigned long packetsDiscardedOnSend; |
| unsigned long long bytesDiscardedOnSend; |
| */ |
| |
| /* RTCOutboundRTPStreamStats */ |
| gst_structure_set (out, "remote-id", G_TYPE_STRING, r_in_id, NULL); |
| /* XXX: |
| DOMHighResTimeStamp lastPacketSentTimestamp; |
| double targetBitrate; |
| unsigned long framesEncoded; |
| double totalEncodeTime; |
| double averageRTCPInterval; |
| */ |
| |
| r_out = gst_structure_new_empty (r_out_id); |
| _set_base_stats (r_out, GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP, ts, r_out_id); |
| /* RTCStreamStats */ |
| gst_structure_set (r_out, "ssrc", G_TYPE_UINT, ssrc, NULL); |
| gst_structure_set (r_out, "codec-id", G_TYPE_STRING, codec_id, NULL); |
| gst_structure_set (r_out, "transport-id", G_TYPE_STRING, transport_id, NULL); |
| /* XXX: mediaType, trackId, sliCount, qpSum */ |
| |
| /* RTCSentRTPStreamStats */ |
| /* if (gst_structure_get_uint64 (source_stats, "octets-sent", &bytes)) |
| gst_structure_set (r_out, "bytes-sent", G_TYPE_UINT64, bytes, NULL); |
| if (gst_structure_get_uint64 (source_stats, "packets-sent", &packets)) |
| gst_structure_set (r_out, "packets-sent", G_TYPE_UINT64, packets, NULL);*/ |
| /* XXX: |
| unsigned long packetsDiscardedOnSend; |
| unsigned long long bytesDiscardedOnSend; |
| */ |
| |
| gst_structure_set (r_out, "local-id", G_TYPE_STRING, in_id, NULL); |
| |
| gst_structure_set (s, in_id, GST_TYPE_STRUCTURE, in, NULL); |
| gst_structure_set (s, out_id, GST_TYPE_STRUCTURE, out, NULL); |
| gst_structure_set (s, r_in_id, GST_TYPE_STRUCTURE, r_in, NULL); |
| gst_structure_set (s, r_out_id, GST_TYPE_STRUCTURE, r_out, NULL); |
| |
| gst_structure_free (in); |
| gst_structure_free (out); |
| gst_structure_free (r_in); |
| gst_structure_free (r_out); |
| |
| g_free (in_id); |
| g_free (out_id); |
| g_free (r_in_id); |
| g_free (r_out_id); |
| } |
| |
| /* https://www.w3.org/TR/webrtc-stats/#candidatepair-dict* */ |
| static gchar * |
| _get_stats_from_ice_transport (GstWebRTCBin * webrtc, |
| GstWebRTCICETransport * transport, GstStructure * s) |
| { |
| GstStructure *stats; |
| gchar *id; |
| double ts; |
| |
| gst_structure_get_double (s, "timestamp", &ts); |
| |
| id = g_strdup_printf ("ice-candidate-pair_%s", GST_OBJECT_NAME (transport)); |
| stats = gst_structure_new_empty (id); |
| _set_base_stats (stats, GST_WEBRTC_STATS_TRANSPORT, ts, id); |
| |
| /* XXX: RTCIceCandidatePairStats |
| DOMString transportId; |
| DOMString localCandidateId; |
| DOMString remoteCandidateId; |
| RTCStatsIceCandidatePairState state; |
| unsigned long long priority; |
| boolean nominated; |
| unsigned long packetsSent; |
| unsigned long packetsReceived; |
| unsigned long long bytesSent; |
| unsigned long long bytesReceived; |
| DOMHighResTimeStamp lastPacketSentTimestamp; |
| DOMHighResTimeStamp lastPacketReceivedTimestamp; |
| DOMHighResTimeStamp firstRequestTimestamp; |
| DOMHighResTimeStamp lastRequestTimestamp; |
| DOMHighResTimeStamp lastResponseTimestamp; |
| double totalRoundTripTime; |
| double currentRoundTripTime; |
| double availableOutgoingBitrate; |
| double availableIncomingBitrate; |
| unsigned long circuitBreakerTriggerCount; |
| unsigned long long requestsReceived; |
| unsigned long long requestsSent; |
| unsigned long long responsesReceived; |
| unsigned long long responsesSent; |
| unsigned long long retransmissionsReceived; |
| unsigned long long retransmissionsSent; |
| unsigned long long consentRequestsSent; |
| DOMHighResTimeStamp consentExpiredTimestamp; |
| */ |
| |
| /* XXX: RTCIceCandidateStats |
| DOMString transportId; |
| boolean isRemote; |
| RTCNetworkType networkType; |
| DOMString ip; |
| long port; |
| DOMString protocol; |
| RTCIceCandidateType candidateType; |
| long priority; |
| DOMString url; |
| DOMString relayProtocol; |
| boolean deleted = false; |
| }; |
| */ |
| |
| gst_structure_set (s, id, GST_TYPE_STRUCTURE, stats, NULL); |
| gst_structure_free (stats); |
| |
| return id; |
| } |
| |
| /* https://www.w3.org/TR/webrtc-stats/#dom-rtctransportstats */ |
| static gchar * |
| _get_stats_from_dtls_transport (GstWebRTCBin * webrtc, |
| GstWebRTCDTLSTransport * transport, GstStructure * s) |
| { |
| GstStructure *stats; |
| gchar *id; |
| double ts; |
| |
| gst_structure_get_double (s, "timestamp", &ts); |
| |
| id = g_strdup_printf ("transport-stats_%s", GST_OBJECT_NAME (transport)); |
| stats = gst_structure_new_empty (id); |
| _set_base_stats (stats, GST_WEBRTC_STATS_TRANSPORT, ts, id); |
| |
| /* XXX: RTCTransportStats |
| unsigned long packetsSent; |
| unsigned long packetsReceived; |
| unsigned long long bytesSent; |
| unsigned long long bytesReceived; |
| DOMString rtcpTransportStatsId; |
| RTCIceRole iceRole; |
| RTCDtlsTransportState dtlsState; |
| DOMString selectedCandidatePairId; |
| DOMString localCertificateId; |
| DOMString remoteCertificateId; |
| */ |
| |
| /* XXX: RTCCertificateStats |
| DOMString fingerprint; |
| DOMString fingerprintAlgorithm; |
| DOMString base64Certificate; |
| DOMString issuerCertificateId; |
| */ |
| |
| /* XXX: RTCIceCandidateStats |
| DOMString transportId; |
| boolean isRemote; |
| DOMString ip; |
| long port; |
| DOMString protocol; |
| RTCIceCandidateType candidateType; |
| long priority; |
| DOMString url; |
| boolean deleted = false; |
| */ |
| |
| gst_structure_set (s, id, GST_TYPE_STRUCTURE, stats, NULL); |
| gst_structure_free (stats); |
| |
| _get_stats_from_ice_transport (webrtc, transport->transport, s); |
| |
| return id; |
| } |
| |
| static void |
| _get_stats_from_transport_channel (GstWebRTCBin * webrtc, |
| TransportStream * stream, const gchar * codec_id, GstStructure * s) |
| { |
| GstWebRTCDTLSTransport *transport; |
| GObject *rtp_session; |
| GstStructure *rtp_stats; |
| GValueArray *source_stats; |
| gchar *transport_id; |
| double ts; |
| int i; |
| |
| gst_structure_get_double (s, "timestamp", &ts); |
| |
| transport = stream->transport; |
| if (!transport) |
| transport = stream->transport; |
| if (!transport) |
| return; |
| |
| g_signal_emit_by_name (webrtc->rtpbin, "get-internal-session", |
| stream->session_id, &rtp_session); |
| g_object_get (rtp_session, "stats", &rtp_stats, NULL); |
| |
| gst_structure_get (rtp_stats, "source-stats", G_TYPE_VALUE_ARRAY, |
| &source_stats, NULL); |
| |
| GST_DEBUG_OBJECT (webrtc, "retrieving rtp stream stats from transport %" |
| GST_PTR_FORMAT " rtp session %" GST_PTR_FORMAT " with %u rtp sources, " |
| "transport %" GST_PTR_FORMAT, stream, rtp_session, source_stats->n_values, |
| transport); |
| |
| transport_id = _get_stats_from_dtls_transport (webrtc, transport, s); |
| |
| /* construct stats objects */ |
| for (i = 0; i < source_stats->n_values; i++) { |
| const GstStructure *stats; |
| const GValue *val = g_value_array_get_nth (source_stats, i); |
| gboolean internal; |
| |
| stats = gst_value_get_structure (val); |
| |
| /* skip internal sources */ |
| gst_structure_get (stats, "internal", G_TYPE_BOOLEAN, &internal, NULL); |
| if (internal) |
| continue; |
| |
| _get_stats_from_rtp_source_stats (webrtc, stats, codec_id, transport_id, s); |
| } |
| |
| g_object_unref (rtp_session); |
| gst_structure_free (rtp_stats); |
| g_value_array_free (source_stats); |
| g_free (transport_id); |
| } |
| |
| /* https://www.w3.org/TR/webrtc-stats/#codec-dict* */ |
| static gchar * |
| _get_codec_stats_from_pad (GstWebRTCBin * webrtc, GstPad * pad, |
| GstStructure * s) |
| { |
| GstStructure *stats; |
| GstCaps *caps; |
| gchar *id; |
| double ts; |
| |
| gst_structure_get_double (s, "timestamp", &ts); |
| |
| stats = gst_structure_new_empty ("unused"); |
| id = g_strdup_printf ("codec-stats-%s", GST_OBJECT_NAME (pad)); |
| _set_base_stats (stats, GST_WEBRTC_STATS_CODEC, ts, id); |
| |
| caps = gst_pad_get_current_caps (pad); |
| if (caps && gst_caps_is_fixed (caps)) { |
| GstStructure *caps_s = gst_caps_get_structure (caps, 0); |
| gint pt, clock_rate; |
| |
| if (gst_structure_get_int (caps_s, "payload", &pt)) |
| gst_structure_set (stats, "payload-type", G_TYPE_UINT, pt, NULL); |
| |
| if (gst_structure_get_int (caps_s, "clock-rate", &clock_rate)) |
| gst_structure_set (stats, "clock-rate", G_TYPE_UINT, clock_rate, NULL); |
| |
| /* FIXME: codecType, mimeType, channels, sdpFmtpLine, implementation, transportId */ |
| } |
| |
| if (caps) |
| gst_caps_unref (caps); |
| |
| gst_structure_set (s, id, GST_TYPE_STRUCTURE, stats, NULL); |
| gst_structure_free (stats); |
| |
| return id; |
| } |
| |
| static gboolean |
| _get_stats_from_pad (GstWebRTCBin * webrtc, GstPad * pad, GstStructure * s) |
| { |
| GstWebRTCBinPad *wpad = GST_WEBRTC_BIN_PAD (pad); |
| gchar *codec_id; |
| |
| codec_id = _get_codec_stats_from_pad (webrtc, pad, s); |
| if (wpad->trans) { |
| WebRTCTransceiver *trans; |
| trans = WEBRTC_TRANSCEIVER (wpad->trans); |
| if (trans->stream) |
| _get_stats_from_transport_channel (webrtc, trans->stream, codec_id, s); |
| } |
| |
| g_free (codec_id); |
| |
| return TRUE; |
| } |
| |
| void |
| gst_webrtc_bin_update_stats (GstWebRTCBin * webrtc) |
| { |
| GstStructure *s = gst_structure_new_empty ("application/x-webrtc-stats"); |
| double ts = monotonic_time_as_double_milliseconds (); |
| GstStructure *pc_stats; |
| |
| _init_debug (); |
| |
| gst_structure_set (s, "timestamp", G_TYPE_DOUBLE, ts, NULL); |
| |
| /* FIXME: better unique IDs */ |
| /* FIXME: rate limitting stat updates? */ |
| /* FIXME: all stats need to be kept forever */ |
| |
| GST_DEBUG_OBJECT (webrtc, "updating stats at time %f", ts); |
| |
| if ((pc_stats = _get_peer_connection_stats (webrtc))) { |
| const gchar *id = "peer-connection-stats"; |
| _set_base_stats (pc_stats, GST_WEBRTC_STATS_PEER_CONNECTION, ts, id); |
| gst_structure_set (s, id, GST_TYPE_STRUCTURE, pc_stats, NULL); |
| gst_structure_free (pc_stats); |
| } |
| |
| gst_element_foreach_pad (GST_ELEMENT (webrtc), |
| (GstElementForeachPadFunc) _get_stats_from_pad, s); |
| |
| gst_structure_remove_field (s, "timestamp"); |
| |
| if (webrtc->priv->stats) |
| gst_structure_free (webrtc->priv->stats); |
| webrtc->priv->stats = s; |
| } |