| /* |
| * Copyright (C) 2019-2022 Igalia S.L. All rights reserved. |
| * Copyright (C) 2022 Metrological Group B.V. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| #include "GStreamerStatsCollector.h" |
| |
| #if ENABLE(WEB_RTC) && USE(GSTREAMER_WEBRTC) |
| |
| #include "GStreamerCommon.h" |
| #include "JSDOMMapLike.h" |
| #include "JSRTCStatsReport.h" |
| |
| #define GST_USE_UNSTABLE_API |
| #include <gst/webrtc/webrtc.h> |
| #undef GST_USE_UNSTABLE_API |
| |
| #include <wtf/MainThread.h> |
| #include <wtf/glib/WTFGType.h> |
| |
| GST_DEBUG_CATEGORY_EXTERN(webkit_webrtc_endpoint_debug); |
| #define GST_CAT_DEFAULT webkit_webrtc_endpoint_debug |
| |
| namespace WebCore { |
| |
| static inline void fillRTCStats(RTCStatsReport::Stats& stats, const GstStructure* structure) |
| { |
| double timestamp; |
| if (gst_structure_get_double(structure, "timestamp", ×tamp)) |
| stats.timestamp = timestamp; |
| stats.id = String::fromLatin1(gst_structure_get_string(structure, "id")); |
| } |
| |
| static inline void fillRTCRTPStreamStats(RTCStatsReport::RtpStreamStats& stats, const GstStructure* structure) |
| { |
| fillRTCStats(stats, structure); |
| |
| stats.transportId = String::fromLatin1(gst_structure_get_string(structure, "transport-id")); |
| stats.codecId = String::fromLatin1(gst_structure_get_string(structure, "codec-id")); |
| |
| unsigned value; |
| if (gst_structure_get_uint(structure, "ssrc", &value)) |
| stats.ssrc = value; |
| |
| // FIXME: |
| // stats.transportId |
| // stats.codecId |
| // stats.mediaType |
| // stats.kind |
| } |
| |
| static inline void fillRTCCodecStats(RTCStatsReport::CodecStats& stats, const GstStructure* structure) |
| { |
| fillRTCStats(stats, structure); |
| |
| unsigned value; |
| if (gst_structure_get_uint(structure, "payload-type", &value)) |
| stats.payloadType = value; |
| if (gst_structure_get_uint(structure, "clock-rate", &value)) |
| stats.clockRate = value; |
| |
| // FIXME: |
| // stats.mimeType = |
| // stats.channels = |
| // stats.sdpFmtpLine = |
| // stats.implementation = |
| } |
| |
| static inline void fillInboundRTPStreamStats(RTCStatsReport::InboundRtpStreamStats& stats, const GstStructure* structure) |
| { |
| fillRTCRTPStreamStats(stats, structure); |
| |
| uint64_t value; |
| if (gst_structure_get_uint64(structure, "packets-received", &value)) |
| stats.packetsReceived = value; |
| if (gst_structure_get_uint64(structure, "bytes-received", &value)) |
| stats.bytesReceived = value; |
| |
| #if GST_CHECK_VERSION(1, 21, 0) |
| int64_t packetsLost; |
| if (gst_structure_get_int64(structure, "packets-lost", &packetsLost)) |
| stats.packetsLost = packetsLost; |
| #else |
| unsigned packetsLost; |
| if (gst_structure_get_uint(structure, "packets-lost", &packetsLost)) |
| stats.packetsLost = packetsLost; |
| #endif |
| |
| double jitter; |
| if (gst_structure_get_double(structure, "jitter", &jitter)) |
| stats.jitter = jitter; |
| |
| if (gst_structure_get_uint64(structure, "packets-repaired", &value)) |
| stats.packetsRepaired = value; |
| |
| if (gst_structure_get_uint64(structure, "packets-discarded", &value)) |
| stats.packetsDiscarded = value; |
| |
| if (gst_structure_get_uint64(structure, "packets-duplicated", &value)) |
| stats.packetsDuplicated = value; |
| |
| unsigned firCount; |
| if (gst_structure_get_uint(structure, "fir-count", &firCount)) |
| stats.firCount = firCount; |
| |
| unsigned pliCount; |
| if (gst_structure_get_uint(structure, "pli-count", &pliCount)) |
| stats.pliCount = pliCount; |
| |
| unsigned nackCount; |
| if (gst_structure_get_uint(structure, "nack-count", &nackCount)) |
| stats.nackCount = nackCount; |
| |
| uint64_t bytesReceived; |
| if (gst_structure_get_uint64(structure, "bytes-received", &bytesReceived)) |
| stats.bytesReceived = bytesReceived; |
| |
| // FIXME: |
| // stats.fractionLost = |
| // stats.burstPacketsLost = |
| // stats.burstPacketsDiscarded = |
| // stats.burstLossCount = |
| // stats.burstDiscardCount = |
| // stats.burstLossRate = |
| // stats.burstDiscardRate = |
| // stats.gapLossRate = |
| // stats.gapDiscardRate = |
| // stats.framesDecoded = |
| } |
| |
| static inline void fillOutboundRTPStreamStats(RTCStatsReport::OutboundRtpStreamStats& stats, const GstStructure* structure) |
| { |
| fillRTCRTPStreamStats(stats, structure); |
| |
| uint64_t value; |
| if (gst_structure_get_uint64(structure, "packets-sent", &value)) |
| stats.packetsSent = value; |
| if (gst_structure_get_uint64(structure, "bytes-sent", &value)) |
| stats.bytesSent = value; |
| |
| unsigned firCount; |
| if (gst_structure_get_uint(structure, "fir-count", &firCount)) |
| stats.firCount = firCount; |
| |
| unsigned pliCount; |
| if (gst_structure_get_uint(structure, "pli-count", &pliCount)) |
| stats.pliCount = pliCount; |
| |
| unsigned nackCount; |
| if (gst_structure_get_uint(structure, "nack-count", &nackCount)) |
| stats.nackCount = nackCount; |
| |
| if (const char* remoteId = gst_structure_get_string(structure, "remote-id")) |
| stats.remoteId = String::fromLatin1(remoteId); |
| |
| // FIXME |
| // stats.targetBitrate = |
| // stats.framesEncoded = |
| } |
| |
| static inline void fillRTCPeerConnectionStats(RTCStatsReport::PeerConnectionStats& stats, const GstStructure* structure) |
| { |
| fillRTCStats(stats, structure); |
| |
| int value; |
| if (gst_structure_get_int(structure, "data-channels-opened", &value)) |
| stats.dataChannelsOpened = value; |
| if (gst_structure_get_int(structure, "data-channels-closed", &value)) |
| stats.dataChannelsClosed = value; |
| } |
| |
| static inline void fillRTCTransportStats(RTCStatsReport::TransportStats& stats, const GstStructure* structure) |
| { |
| fillRTCStats(stats, structure); |
| |
| // FIXME |
| // stats.bytesSent = |
| // stats.bytesReceived = |
| // stats.rtcpTransportStatsId = |
| // stats.selectedCandidatePairId = |
| // stats.localCertificateId = |
| // stats.remoteCertificateId = |
| // stats.dtlsState = |
| // stats.tlsVersion = |
| // stats.dtlsCipher = |
| // stats.srtpCipher = |
| } |
| |
| static inline std::optional<RTCIceCandidateType> iceCandidateType(const String& type) |
| { |
| if (type == "host"_s) |
| return RTCIceCandidateType::Host; |
| if (type == "srflx"_s) |
| return RTCIceCandidateType::Srflx; |
| if (type == "prflx"_s) |
| return RTCIceCandidateType::Prflx; |
| if (type == "relay"_s) |
| return RTCIceCandidateType::Relay; |
| |
| return { }; |
| } |
| |
| static inline void fillRTCCandidateStats(RTCStatsReport::IceCandidateStats& stats, GstWebRTCStatsType statsType, const GstStructure* structure) |
| { |
| stats.type = statsType == GST_WEBRTC_STATS_REMOTE_CANDIDATE ? RTCStatsReport::Type::RemoteCandidate : RTCStatsReport::Type::LocalCandidate; |
| |
| fillRTCStats(stats, structure); |
| |
| stats.transportId = String::fromLatin1(gst_structure_get_string(structure, "transport-id")); |
| stats.address = String::fromLatin1(gst_structure_get_string(structure, "address")); |
| stats.protocol = String::fromLatin1(gst_structure_get_string(structure, "protocol")); |
| stats.url = String::fromLatin1(gst_structure_get_string(structure, "url")); |
| |
| unsigned port; |
| if (gst_structure_get_uint(structure, "port", &port)) |
| stats.port = port; |
| |
| auto candidateType = String::fromLatin1(gst_structure_get_string(structure, "candidate-type")); |
| stats.candidateType = iceCandidateType(candidateType); |
| |
| uint64_t priority; |
| if (gst_structure_get_uint64(structure, "priority", &priority)) |
| stats.priority = priority; |
| } |
| |
| static inline void fillRTCCandidatePairStats(RTCStatsReport::IceCandidatePairStats& stats, const GstStructure* structure) |
| { |
| fillRTCStats(stats, structure); |
| |
| stats.localCandidateId = String::fromLatin1(gst_structure_get_string(structure, "local-candidate-id")); |
| stats.remoteCandidateId = String::fromLatin1(gst_structure_get_string(structure, "remote-candidate-id")); |
| |
| // FIXME |
| // stats.transportId = |
| // stats.state = |
| // stats.priority = |
| // stats.nominated = |
| // stats.writable = |
| // stats.readable = |
| // stats.bytesSent = |
| // stats.bytesReceived = |
| // stats.totalRoundTripTime = |
| // stats.currentRoundTripTime = |
| // stats.availableOutgoingBitrate = |
| // stats.availableIncomingBitrate = |
| // stats.requestsReceived = |
| // stats.requestsSent = |
| // stats.responsesReceived = |
| // stats.responsesSent = |
| // stats.retransmissionsReceived = |
| // stats.retransmissionsSent = |
| // stats.consentRequestsReceived = |
| // stats.consentRequestsSent = |
| // stats.consentResponsesReceived = |
| // stats.consentResponsesSent = |
| } |
| |
| static gboolean fillReportCallback(GQuark, const GValue* value, gpointer userData) |
| { |
| if (!GST_VALUE_HOLDS_STRUCTURE(value)) |
| return TRUE; |
| |
| const GstStructure* structure = gst_value_get_structure(value); |
| GstWebRTCStatsType statsType; |
| if (!gst_structure_get(structure, "type", GST_TYPE_WEBRTC_STATS_TYPE, &statsType, nullptr)) |
| return TRUE; |
| |
| DOMMapAdapter& report = *reinterpret_cast<DOMMapAdapter*>(userData); |
| |
| switch (statsType) { |
| case GST_WEBRTC_STATS_CODEC: { |
| RTCStatsReport::CodecStats stats; |
| fillRTCCodecStats(stats, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::CodecStats>>(stats.id, WTFMove(stats)); |
| break; |
| } |
| case GST_WEBRTC_STATS_INBOUND_RTP: { |
| RTCStatsReport::InboundRtpStreamStats stats; |
| fillInboundRTPStreamStats(stats, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::InboundRtpStreamStats>>(stats.id, WTFMove(stats)); |
| break; |
| } |
| case GST_WEBRTC_STATS_OUTBOUND_RTP: { |
| RTCStatsReport::OutboundRtpStreamStats stats; |
| fillOutboundRTPStreamStats(stats, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::OutboundRtpStreamStats>>(stats.id, WTFMove(stats)); |
| break; |
| } |
| case GST_WEBRTC_STATS_REMOTE_INBOUND_RTP: { |
| RTCStatsReport::InboundRtpStreamStats stats; |
| fillInboundRTPStreamStats(stats, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::InboundRtpStreamStats>>(stats.id, WTFMove(stats)); |
| break; |
| } |
| case GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP: { |
| RTCStatsReport::OutboundRtpStreamStats stats; |
| fillOutboundRTPStreamStats(stats, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::OutboundRtpStreamStats>>(stats.id, WTFMove(stats)); |
| break; |
| } |
| case GST_WEBRTC_STATS_CSRC: |
| GST_FIXME("Deprecated stats: csrc"); |
| break; |
| case GST_WEBRTC_STATS_PEER_CONNECTION: { |
| RTCStatsReport::PeerConnectionStats stats; |
| fillRTCPeerConnectionStats(stats, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::PeerConnectionStats>>(stats.id, WTFMove(stats)); |
| break; |
| } |
| case GST_WEBRTC_STATS_TRANSPORT: { |
| RTCStatsReport::TransportStats stats; |
| fillRTCTransportStats(stats, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::TransportStats>>(stats.id, WTFMove(stats)); |
| break; |
| } |
| case GST_WEBRTC_STATS_STREAM: |
| GST_FIXME("Deprecated stats: stream"); |
| break; |
| case GST_WEBRTC_STATS_DATA_CHANNEL: |
| GST_FIXME("Missing data-channel stats support"); |
| break; |
| case GST_WEBRTC_STATS_LOCAL_CANDIDATE: |
| case GST_WEBRTC_STATS_REMOTE_CANDIDATE: |
| if (webkitGstCheckVersion(1, 21, 0)) { |
| RTCStatsReport::IceCandidateStats stats; |
| fillRTCCandidateStats(stats, statsType, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::IceCandidateStats>>(stats.id, WTFMove(stats)); |
| } |
| break; |
| case GST_WEBRTC_STATS_CANDIDATE_PAIR: |
| if (webkitGstCheckVersion(1, 21, 0)) { |
| RTCStatsReport::IceCandidatePairStats stats; |
| fillRTCCandidatePairStats(stats, structure); |
| report.set<IDLDOMString, IDLDictionary<RTCStatsReport::IceCandidatePairStats>>(stats.id, WTFMove(stats)); |
| } |
| break; |
| case GST_WEBRTC_STATS_CERTIFICATE: |
| GST_FIXME("Missing certificate stats support"); |
| break; |
| } |
| |
| return TRUE; |
| } |
| |
| struct CallbackHolder { |
| GStreamerStatsCollector::CollectorCallback callback; |
| }; |
| |
| WEBKIT_DEFINE_ASYNC_DATA_STRUCT(CallbackHolder) |
| |
| void GStreamerStatsCollector::getStats(CollectorCallback&& callback, GstPad* pad) |
| { |
| if (!m_webrtcBin) { |
| callback(nullptr); |
| return; |
| } |
| |
| auto* holder = createCallbackHolder(); |
| holder->callback = WTFMove(callback); |
| g_signal_emit_by_name(m_webrtcBin.get(), "get-stats", pad, gst_promise_new_with_change_func([](GstPromise* rawPromise, gpointer userData) { |
| auto promise = adoptGRef(rawPromise); |
| auto* holder = static_cast<CallbackHolder*>(userData); |
| if (gst_promise_wait(promise.get()) != GST_PROMISE_RESULT_REPLIED) { |
| holder->callback(nullptr); |
| return; |
| } |
| |
| const auto* stats = gst_promise_get_reply(promise.get()); |
| if (!stats) { |
| holder->callback(nullptr); |
| return; |
| } |
| |
| if (gst_structure_has_field(stats, "error")) { |
| GUniqueOutPtr<GError> error; |
| gst_structure_get(stats, "error", G_TYPE_ERROR, &error.outPtr(), nullptr); |
| GST_WARNING("Unable to get stats, error: %s", error->message); |
| holder->callback(nullptr); |
| return; |
| } |
| |
| callOnMainThreadAndWait([promise = WTFMove(promise), holder] { |
| // Hold an additional ref to the promise because it is asynchronously used from the JS bindings. |
| holder->callback(RTCStatsReport::create([promise = GRefPtr<GstPromise>(promise.get())](auto& mapAdapter) { |
| const auto* stats = gst_promise_get_reply(promise.get()); |
| gst_structure_foreach(stats, fillReportCallback, &mapAdapter); |
| })); |
| }); |
| }, holder, reinterpret_cast<GDestroyNotify>(destroyCallbackHolder))); |
| } |
| |
| }; // namespace WebCore |
| |
| #endif // ENABLE(WEB_RTC) && USE(GSTREAMER_WEBRTC) |