blob: c9cb5bd0b9f85deb278115e0ed2055d3dc23ed1b [file] [log] [blame]
/*
* 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", &timestamp))
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)