Client-Beobachtbarkeit: Android

Das Vonage Video SDK stellt detaillierte Metriken zur Stream-Qualität über eine High-Level-Statistik-API zur Verfügung, die für die meisten Anwendungsfälle empfohlen wird und die Audio-, Video-, Netzwerk- und Absenderstatistiken in einer einheitlichen, sitzungsspezifischen Form bereitstellt, die über Peer-Verbindungsübergänge hinweg stabil bleibt. Für fortgeschrittenes Debugging bietet das SDK auch Zugriff auf den rohen WebRTC-Statistikbericht, der unverarbeitete Peer-Verbindungsdaten wiedergibt.

Das SDK stellt auch Netzwerkzustandsmetriken zur Verfügung, die eine umfassende Bewertung des Verbindungszustands sowohl für Herausgeber als auch für Abonnenten ermöglichen. Diese Metriken umfassen eine Bewertung des Netzwerkzustands, den Grund für diese Bewertung und - für Abonnenten - eine Verschlechterungsquelle, die angibt, welche Seite der Verbindung für die beobachteten Probleme verantwortlich ist. Siehe Netzzustand und Ursache der Verschlechterung für Einzelheiten.

Das Vonage Video Android SDK sendet regelmäßig Audio-, Video- und Media-Link-Statistiken sowohl für Publisher als auch für Subscriber. Dazu gehören die Anzahl der Pakete, Bitraten, Frame-Rate-Daten, Pausen-/Freeze-Metriken, Codec-Informationen und Netzwerkmetriken auf Transportebene, wie z. B. Bandbreitenschätzung und Bewertung des Netzwerkzustands.

Die Statistiken werden über separate Hörer übermittelt:

Herausgeber:

  • PublisherKit.VideoStatsListener - Videostatistiken und Ereignisse zur Videoqualität
  • PublisherKit.AudioStatsListener - Audiostatistik
  • PublisherKit.MediaLinkStatsListener - Statistiken zur Medienverbindung (Transportebene)

Abonnent:

  • SubscriberKit.VideoStatsListener - Videostatistiken und Ereignisse zur Videoqualität
  • SubscriberKit.AudioStatsListener - Audiostatistik
  • SubscriberKit.MediaLinkStatsListener - Statistiken zur Medienverbindung (Transportebene)

Aktivieren von Statistiken für Verleger

Video-Statistik

publisher.setVideoStatsListener(new PublisherKit.VideoStatsListener() {
    @Override
    public void onVideoStats(PublisherKit publisher, PublisherKit.PublisherVideoStats[] statsArray) {
        if (statsArray != null && statsArray.length > 0) {
            // For routed sessions, first element is sufficient.
            // For relayed sessions, iterate all statsArray elements to get per-subscriber info.
            PublisherKit.PublisherVideoStats stats = statsArray[0];

            String connectionId = (stats.connectionId != null && !stats.connectionId.isEmpty())
                    ? stats.connectionId
                    : "<none>";
            String subscriberId = (stats.subscriberId != null && !stats.subscriberId.isEmpty())
                    ? stats.subscriberId
                    : "<none>";

            Log.d("VideoStats", "Publisher Video Stats for connectionId: " + connectionId
                    + ", subscriberId: " + subscriberId);

            Log.d("VideoStats", "Video bytes sent: " + stats.videoBytesSent);
            Log.d("VideoStats", "Video packets sent: " + stats.videoPacketsSent);
            Log.d("VideoStats", "Video packets lost: " + stats.videoPacketsLost);
            Log.d("VideoStats", "Stats timestamp: " + stats.timeStamp + " ms");

            if (stats.videoLayers != null) {
                for (PublisherKit.VideoLayerStats layer : stats.videoLayers) {
                    Log.d("VideoStats", "Layer: " + layer.width + "x" + layer.height);
                    Log.d("VideoStats", "  Encoded FPS: " + layer.encodedFrameRate);
                    Log.d("VideoStats", "  Bitrate: " + layer.bitrate + " bps");
                    Log.d("VideoStats", "  Total bitrate (incl. RTP overhead): " + layer.totalBitrate + " bps");
                    Log.d("VideoStats", "  Codec: " + (layer.codec != null ? layer.codec : "unknown"));
                    Log.d("VideoStats", "  Scalability mode: " + (layer.scalabilityMode != null ? layer.scalabilityMode : "none"));
                    Log.d("VideoStats", "  Quality limitation: " + layer.qualityLimitationReason);
                }
            }
        }
    }

    @Override
    public void onVideoQualityChanged(PublisherKit publisher, PublisherKit.PublisherVideoStats stats, String reason) {
        Log.d("Stats", "Publisher video quality event: " + reason);
    }
});

Audio-Statistik

publisher.setAudioStatsListener(new PublisherKit.AudioStatsListener() {
    @Override
    public void onAudioStats(PublisherKit publisher, PublisherKit.PublisherAudioStats[] statsArray) {
        if (statsArray != null && statsArray.length > 0) {
            // For routed sessions, first element is sufficient.
            // For relayed sessions, iterate all statsArray elements to get per-subscriber info.
            PublisherKit.PublisherAudioStats stats = statsArray[0];

            String connectionId = (stats.connectionId != null && !stats.connectionId.isEmpty())
                    ? stats.connectionId
                    : "<none>";
            String subscriberId = (stats.subscriberId != null && !stats.subscriberId.isEmpty())
                    ? stats.subscriberId
                    : "<none>";

            Log.d("AudioStats", "Publisher Audio Stats for connectionId: " + connectionId
                    + ", subscriberId: " + subscriberId);

            Log.d("AudioStats", "Audio bytes sent: " + stats.audioBytesSent);
            Log.d("AudioStats", "Audio packets sent: " + stats.audioPacketsSent);
            Log.d("AudioStats", "Audio packets lost: " + stats.audioPacketsLost);
            Log.d("AudioStats", "Stats timestamp: " + stats.timeStamp + " ms");
        }
    }
});
publisher.setMediaLinkStatsListener(new PublisherKit.MediaLinkStatsListener() {
    @Override
    public void onMediaLinkStats(PublisherKit publisher, PublisherKit.PublisherMediaLinkStats[] mediaLinkStats) {
        if (mediaLinkStats != null && mediaLinkStats.length > 0) {
            PublisherKit.PublisherMediaLinkStats stats = mediaLinkStats[0];
            if (stats.transport != null) {
                Log.d("MediaLinkStats", "Uplink bandwidth: " + stats.transport.getConnectionEstimatedBandwidth() + " bps");
                Log.d("MediaLinkStats", "Network condition: " + stats.transport.networkCondition);
                Log.d("MediaLinkStats", "Condition reason: " + stats.transport.networkConditionReason);
            }
        }
    }

    @Override
    public void onNetworkConditionChanged(PublisherKit publisher, PublisherKit.PublisherMediaLinkStats mediaLinkStats, String reason) {
        if (mediaLinkStats.transport != null) {
            Log.d("Stats", "Publisher network condition: " + mediaLinkStats.transport.networkCondition);
            Log.d("Stats", "Reason: " + mediaLinkStats.transport.networkConditionReason);
        }
    }
});

Siehe Netzzustand und Ursache der Verschlechterung für Einzelheiten zur Interpretation der Netzzustandsnoten und Gründe.

Für einen Verleger in einer gerouteten Sitzung (eine, die die Vonage Video-Media-Router), enthält das Array stats ein Objekt, das die Statistiken für den einzelnen Audio- oder Video-Medienstrom, der an den Vonage Video Media Router gesendet wird. In einer weitergeleiteten Sitzung enthält das stats-Array ein Objekt für jeden Teilnehmer des des veröffentlichten Streams.

Aktivieren von Statistiken für Abonnenten

Video-Statistik

subscriber.setVideoStatsListener(new SubscriberKit.VideoStatsListener() {
    @Override
    public void onVideoStats(SubscriberKit subscriber, SubscriberKit.SubscriberVideoStats stats) {
        Log.d("Stats", "Video bytes received: " + stats.videoBytesReceived);
    }

    @Override
    public void onVideoQualityChanged(SubscriberKit subscriber, SubscriberKit.SubscriberVideoStats stats, String reason) {
        Log.d("Stats", "Subscriber video quality event: " + reason);
    }
});

Audio-Statistik

subscriber.setAudioStatsListener(new SubscriberKit.AudioStatsListener() {
    @Override
    public void onAudioStats(SubscriberKit subscriber, SubscriberKit.SubscriberAudioStats stats) {
        Log.d("Stats", "Audio packets received: " + stats.audioPacketsReceived);
    }
});
subscriber.setMediaLinkStatsListener(new SubscriberKit.MediaLinkStatsListener() {
    @Override
    public void onMediaLinkStats(SubscriberKit subscriber, SubscriberKit.SubscriberMediaLinkStats mediaLinkStats) {
        if (mediaLinkStats.transport != null) {
            Log.d("MediaLinkStats", "Downlink bandwidth: " + mediaLinkStats.transport.getConnectionEstimatedBandwidth() + " bps");
        }
        if (mediaLinkStats.remotePublisherTransport != null) {
            Log.d("MediaLinkStats", "Remote publisher uplink bandwidth: " + mediaLinkStats.remotePublisherTransport.getConnectionEstimatedBandwidth() + " bps");
        }
        Log.d("MediaLinkStats", "Degradation source: " + mediaLinkStats.networkDegradationSource);
    }

    @Override
    public void onNetworkConditionChanged(SubscriberKit subscriber, SubscriberKit.SubscriberMediaLinkStats mediaLinkStats, String reason) {
        if (mediaLinkStats.transport != null) {
            Log.d("Stats", "Local network condition: " + mediaLinkStats.transport.networkCondition);
        }
        if (mediaLinkStats.remotePublisherTransport != null) {
            Log.d("Stats", "Remote publisher network condition: " + mediaLinkStats.remotePublisherTransport.networkCondition);
        }
        Log.d("Stats", "Degradation source: " + mediaLinkStats.networkDegradationSource);
    }
});

Siehe Netzzustand und Ursache der Verschlechterung für Einzelheiten zur Interpretation der Netzzustandsnoten und Gründe.

Statistik-Datenstrukturen

In diesem Abschnitt werden die Strukturen und Eigenschaften beschrieben, die von der Android-Audio- und Videostatistik-API bereitgestellt werden. Während alle Video-SDK-Plattformen denselben Satz an Statistiken bereitstellen, kann es geringfügige Unterschiede in der Strukturierung oder Benennung einzelner Felder auf jeder Plattform geben. Diese Unterschiede spiegeln eher plattformspezifische SDK-Designkonventionen als Unterschiede in den zugrunde liegenden Metriken wider.

Eine plattformunabhängige Erläuterung der verfügbaren Statistiken und ihrer Bedeutung finden Sie unter Übersicht über die Beobachtbarkeit der Kunden.

TransportStats

Stellt gemeinsame Metriken auf Transportebene dar.

  • connectionEstimatedBandwidth - Geschätzte verfügbare Verbindungsbandbreite (bps).
  • networkCondition - Aktuelle Netzzustandsbewertung (NETWORK_CONDITION_UNKNOWN, NETWORK_CONDITION_CRITICAL, NETWORK_CONDITION_WARNING, NETWORK_CONDITION_FAIR, NETWORK_CONDITION_GOOD, oder NETWORK_CONDITION_EXCELLENT).
  • networkConditionReason - Hauptgrund für die Beeinträchtigung des Netzzustandes (NETWORK_CONDITION_REASON_NONE, NETWORK_CONDITION_REASON_UNKNOWN, NETWORK_CONDITION_REASON_BANDWIDTH, oder NETWORK_CONDITION_REASON_PACKET_LOSS).

PublisherKit.PublisherVideoStats

Liefert Statistiken über die Videospur eines Herausgebers. Sie umfasst:

  • connectionId - In einer weitergeleiteten Sitzung die Verbindungs-ID des Clients, der den Stream abonniert. Undefiniert in einer gerouteten Sitzung.
  • subscriberId - In einer weitergeleiteten Sitzung die abonnierte ID des Clients, der den Stream abonniert. Undefiniert in einer gerouteten Sitzung.
  • videoPacketsLost - Geschätzte verlorene Videopakete.
  • videoPacketsSent - Gesendete Videopakete.
  • videoBytesSent - Video-Bytes gesendet.
  • timeStamp - Unix-Zeitstempel in Millisekunden, als die Statistiken erfasst wurden.
  • startTime - Der Zeitstempel in Millisekunden seit der Unix-Epoche, ab dem die kumulativen Summen zu kumulieren begannen.
  • videoLayers - Das Array der Video-Layer-Statistiken (siehe OTPublisherKitVideoLayerStats).

PublisherKit.PublisherAudioStats

Liefert Statistiken über den Audiotrack eines Verlags. Sie umfasst:

  • connectionId - In einer weitergeleiteten Sitzung die Verbindungs-ID des Clients, der den Stream abonniert. Undefiniert in einer gerouteten Sitzung.
  • subscriberId - In einer weitergeleiteten Sitzung die abonnierte ID des Clients, der den Stream abonniert. Undefiniert in einer gerouteten Sitzung.
  • audioPacketsLost - Geschätzte verlorene Pakete.
  • audioPacketsSent - Audio-Pakete gesendet.
  • audioBytesSent - Gesendete Audio-Bytes.
  • timeStamp - Unix-Zeitstempel in Millisekunden.
  • startTime - Der Zeitstempel in Millisekunden seit der Unix-Epoche, ab dem die kumulativen Summen zu kumulieren begannen.

PublisherKit.VideoLayerStats

Steht für eine Simulcast- oder SVC-Schicht.

  • width - Kodierte Bildbreite.
  • height - Kodierte Rahmenhöhe.
  • encodedFrameRate - Kodierte Bilder pro Sekunde.
  • bitrate - Bitrate der Schicht (bps).
  • totalBitrate - Bitrate der Schicht einschließlich RTP-Overhead (bps).
  • scalabilityMode - SVC/Skalierbarkeitsdeskriptor (z. B. "L3T3").
  • qualityLimitationReason - Grund für die Qualitätseinschränkung (Bandbreite, CPU, Codec, Auflösung oder Ebenenwechsel).
  • codec - Der von dieser Videoebene verwendete Codec.

SubscriberKit.SenderStats

Abschätzungsmetriken auf der Senderseite (gespiegelt auf Audio und Video).

  • connectionMaxAllocatedBitrate - Geschätzte maximale Bitrate für die Senderverbindung.
  • connectionEstimatedBandwidth - Aktuelle Bandbreitenabschätzung (bps).

SubscriberKit.SubscriberVideoStats

Liefert Statistiken über die Videospur eines Abonnenten. Sie umfasst:

  • videoPacketsLost - Geschätzte verlorene Videopakete.
  • videoPacketsReceived - Empfangene Videopakete.
  • videoBytesReceived - Empfangene Videobytes.
  • timeStamp - Unix-Zeitstempel in Millisekunden, als die Statistiken erfasst wurden.
  • senderStats - Metriken auf der Absenderseite (optional).
  • width - Dekodierte Bildbreite in Pixel.
  • height - Dekodierte Bildhöhe in Pixel.
  • decodedFrameRate - Dekodierte Bilder pro Sekunde.
  • bitrate - Video-Bitrate (bps).
  • totalBitrate - Bitrate einschließlich RTP-Overhead (bps).
  • pauseCount - Numbers of pauses (>5s seit dem letzten Frame). Einschließlich absichtlicher Deaktivierungen und Audio-Fallback-Fälle.
  • totalPausesDuration - Gesamte Pausendauer (ms).
  • freezeCount - Freeze Count (von WebRTC definiertes Freeze-Ereignis).
  • totalFreezesDuration - Gesamtdauer des Einfrierens (ms).
  • codec - Aktueller Decoder-Codec.

SubscriberKit.SubscriberAudioStats

Liefert Statistiken über den Audiotrack eines Abonnenten. Sie umfasst:

  • audioPacketsLost - Geschätzte verlorene Pakete.
  • audioPacketsReceived - Empfangene Pakete.
  • audioBytesReceived - Empfangene Bytes.
  • timeStamp - Unix-Zeitstempel in Millisekunden.
  • senderStats - Metriken auf der Absenderseite (optional).

PublisherKit.PublisherMediaLinkStats

Liefert Statistiken auf Transportebene für die Verbindung eines Verlags.

  • transport - Verkehrsstatistiken für diesen Verlag (siehe TransportStats)

SubscriberKit.SubscriberMediaLinkStats

Liefert Statistiken auf Transportebene für die Verbindungen eines Teilnehmers, einschließlich Einblick in die Netzwerkleistung des entfernten Herausgebers. So können Applications diagnostizieren, ob Verbindungsprobleme vom Downlink des Teilnehmers oder vom Uplink des Publishers ausgehen.

  • transport - Lokale Transport- und Netzstatistiken für diesen Teilnehmer. Kann eingeschränkt sein, wenn absenderseitige Statistiken und/oder Audio-Fallback deaktiviert sind.
  • remotePublisherTransport - Transport- und Netzwerkstatistiken des Fernverlegers. Kann eingeschränkt sein, wenn absenderseitige Statistiken und/oder Audio-Fallback deaktiviert sind.
  • networkDegradationSource - Zeigt die Quelle der Netzbeeinträchtigung an, falls vorhanden (NetworkDegradationSource.NONE, NetworkDegradationSource.LOCAL, NetworkDegradationSource.REMOTE, oder NetworkDegradationSource.BOTH_OR_UNCLEAR).

Statistik auf der Absenderseite

Siehe die Übersicht der Absenderstatistiken.

Aktivieren der absenderseitigen Statistik

Senderstatistiken werden von den Abonnenten empfangen. Um absenderseitige Statistiken zu erhalten, aktivieren Sie sie für den Herausgeber des Streams, indem Sie die Option senderStatisticsTrack Eigenschaft zu true bei der Erstellung des Verlags:

Publisher publisher = new Publisher.Builder(context)
        .senderStatsTrack(true) // Enable sender-side stats
        .build();

Wenn senderStatsTrack nicht aktiviert ist, wird kein Absenderstatistikkanal für diesen Verlag veröffentlicht. Der Standardwert ist false.

Empfangen von absenderseitigen Statistiken

Wenn der Herausgeber Statistiken auf der Absenderseite aktiviert hat, erhalten die Abonnenten diese automatisch über die Video- und Audiostatistik-Callbacks oben beschrieben. Die senderStats Eigenschaft auf beiden SubscriberKit.SubscriberVideoStats und SubscriberKit.SubscriberAudioStats liefert zwei Metriken:

  • connectionMaxAllocatedBitrate - Die maximale Bitrate, die für die Verbindung geschätzt werden kann
  • connectionEstimatedBandwidth - Die aktuell geschätzte Bandbreite für die Verbindung

Diese Metriken werden pro Audio-Video-Bündel berechnet, so dass in den Video- und Audiostatistiken dieselben Werte erscheinen.

subscriber.setVideoStatsListener((subscriber, stats) -> {
    if (stats.senderStats != null) {
        Log.d(TAG, "Connection max allocated bitrate: " + stats.senderStats.connectionMaxAllocatedBitrate);
        Log.d(TAG, "Connection current estimated bandwidth: " + stats.senderStats.connectionEstimatedBandwidth);
    }
});

Netzzustand und Ursache der Verschlechterung

Das SDK liefert sowohl für Publisher als auch für Abonnenten Echtzeit-Netzwerkzustandsmetriken, einschließlich einer Zustandsbewertung, des Grundes für diese Bewertung und einer Verschlechterungsquelle für Abonnenten. Eine vollständige Erläuterung des Netzwerkzustandsmodells, der Bewertungen, der Gründe und der Aktivierung finden Sie in der Übersicht über die Beobachtbarkeit der Kunden.

Netzzustandsdaten sind über zwei Kanäle verfügbar:

  • Regelmäßige Statistiken: Die Medienverbindungsstatistiken enthalten Transportmetriken mit networkCondition und networkConditionReason. Für Abonnenten umfasst die Medienlink-Statistik auch remotePublisherTransport und networkDegradationSource.
  • Ereignisse, bei denen sich der Netzzustand ändert: Dedizierte Rückrufe auf beiden Herausgeber und Teilnehmer werden ausgelöst, wenn eine signifikante Änderung der Netzbedingungen festgestellt wird.

Das folgende Beispiel zeigt, wie die Daten über den Zustand des Teilnehmernetzes verwendet werden können, um die Ursache der Verschlechterung zu ermitteln:

@Override
public void onNetworkConditionChanged(SubscriberKit subscriber, SubscriberKit.SubscriberMediaLinkStats mediaLinkStats, String reason) {

    int localCondition = mediaLinkStats.transport != null ? mediaLinkStats.transport.networkCondition : TransportStats.NETWORK_CONDITION_UNKNOWN;
    int remoteCondition = mediaLinkStats.remotePublisherTransport != null ? mediaLinkStats.remotePublisherTransport.networkCondition : TransportStats.NETWORK_CONDITION_UNKNOWN;
    int source = mediaLinkStats.networkDegradationSource;

    if (source == NetworkDegradationSource.LOCAL) {
        Log.d("Stats", "Local network is degraded (condition: " + localCondition + ")");
    } else if (source == NetworkDegradationSource.REMOTE) {
        Log.d("Stats", "Remote publisher network is degraded (condition: " + remoteCondition + ")");
    } else if (source == NetworkDegradationSource.BOTH_OR_UNCLEAR) {
        Log.d("Stats", "Degradation source unclear — local: " + localCondition + ", remote: " + remoteCondition);
    }
}

RTC-Statistikbericht

Um Low-Level-Peer-Verbindungsstatistiken für einen Publisher zu erhalten, verwenden Sie die PublisherKit.getRtcStatsReport() Methode. Sie liefert RTC-Statistikberichte für den Medienstrom. Dies ist ein asynchroner Vorgang. Rufen Sie die PublisherKit.setRtcStatsReportListener(PublisherKit.PublisherRtcStatsReportListener listener) Methode, und implementieren Sie dann die PublisherKit.PublisherRtcStatsReportListener.onRtcStatsReport(PublisherKit publisher, PublisherKit.PublisherRtcStats[] stats) Methode vor dem Aufruf PublisherKit.getRtcStatsReport().

Wenn die Statistiken verfügbar sind, wird die Implementierung der PublisherKit.PublisherRtcStatsReportListener.onRtcStatsReport(PublisherKit publisher, PublisherKit.PublisherRtcStats[] stats) Methode aufgerufen wird.

Eine Reihe von PublisherRtcStats Objekte an diese Methode übergeben wird. Die PublisherRtcStats Objekt enthält eine jsonArrayOfReports Eigenschaft. Dies ist ein JSON-Array mit RTC-Statistikberichten, die dem Format des in Webbrowsern implementierten RtcStatsReport-Objekts ähneln (siehe diese Mozilla-Dokumente).

Um Low-Level-Peer-Verbindungsstatistiken für einen Teilnehmer zu erhalten, verwenden Sie die SubscriberKit.getRtcStatsReport() Methode. Dies liefert einen RTC-Statistikbericht für den Medienstrom.

Dies ist ein asynchroner Vorgang. Rufen Sie die SubscriberKit.setRtcStatsReportListener(SubscriberKit.SubscriberRtcStatsReportListener listener) Methode, und implementieren Sie dann die SubscriberKit.SubscriberRtcStatsReportListener.onRtcStatsReport(SubscriberKit subscriber, java.lang.String jsonArrayOfReports) Methode vor dem Aufruf SubscriberKit.getRtcStatsReport().

Wenn die Statistiken verfügbar sind, wird die Implementierung der SubscriberKit.SubscriberRtcStatsReportListener.onRtcStatsReport(SubscriberKit subscriber, java.lang.String jsonArrayOfReports) Methode aufgerufen wird. Die jsonArrayOfReports ist ein JSON-Array von RTC-Statistikberichten, die dem Format des in Webbrowsern implementierten RtcStatsReport-Objekts ähneln (siehe diese Mozilla-Dokumente).

Siehe auch diese W3C-Dokumentation.