Skip to content

Commit

Permalink
Add GetMediaStats to StatsReport
Browse files Browse the repository at this point in the history
Add an API to get basic stats around rtpsender.

Add an API to get basic stats around rtpreceiver

Relates to #610
  • Loading branch information
obasajujoshua31 committed Sep 23, 2020
1 parent 7d79c76 commit bcd83e7
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 12 deletions.
14 changes: 13 additions & 1 deletion peerconnection.go
Expand Up @@ -1950,13 +1950,25 @@ func (pc *PeerConnection) GetStats() StatsReport {

statsCollector.Collect(stats.ID, stats)

pc.mu.Unlock()

transceivers := pc.GetTransceivers()
for _, transceiver := range transceivers {
if sender := transceiver.Sender(); sender != nil {
sender.collectStats(statsCollector)
}

if receiver := transceiver.Receiver(); receiver != nil {
receiver.collectStats(statsCollector)
}
}

certificates := pc.configuration.Certificates
for _, certificate := range certificates {
if err := certificate.collectStats(statsCollector); err != nil {
continue
}
}
pc.mu.Unlock()

pc.api.mediaEngine.collectStats(statsCollector)

Expand Down
48 changes: 47 additions & 1 deletion rtpreceiver.go
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"sync"
"time"

"github.com/pion/rtcp"
"github.com/pion/srtp"
Expand All @@ -30,7 +31,8 @@ type RTPReceiver struct {
mu sync.RWMutex

// A reference to the associated api object
api *API
api *API
statsID string
}

// NewRTPReceiver constructs a new RTPReceiver
Expand All @@ -46,6 +48,7 @@ func (api *API) NewRTPReceiver(kind RTPCodecType, transport *DTLSTransport) (*RT
closed: make(chan interface{}),
received: make(chan interface{}),
tracks: []trackStreams{},
statsID: fmt.Sprintf("rtp-receiver-%d", time.Now().UnixNano()),
}, nil
}

Expand Down Expand Up @@ -250,3 +253,46 @@ func (r *RTPReceiver) streamsForSSRC(ssrc uint32) (*srtp.ReadStreamSRTP, *srtp.R

return rtpReadStream, rtcpReadStream, nil
}

func (r *RTPReceiver) collectStats(report *statsReportCollector) {
report.Collecting()

switch r.kind {
case RTPCodecTypeVideo:
stats := VideoReceiverStats{
Timestamp: statsTimestampFrom(time.Now()),
Type: StatsTypeReceiver,
ID: r.statsID,
FrameWidth: 0, // Todo ...
FrameHeight: 0, // Todo ...
FramesPerSecond: 0, // Todo ...
EstimatedPlayoutTimestamp: 0, // Todo ...
JitterBufferDelay: 0, // Todo ...
JitterBufferEmittedCount: 0, // Todo ...
FramesReceived: 0, // Todo ...
KeyFramesReceived: 0, // Todo ...
FramesDecoded: 0, // Todo ...
FramesDropped: 0, // Todo ...
PartialFramesLost: 0, // Todo ...
FullFramesLost: 0, // Todo ...
}

report.Collect(stats.ID, stats)

case RTPCodecTypeAudio:
stats := AudioReceiverStats{
Timestamp: statsTimestampFrom(time.Now()),
Type: StatsTypeReceiver,
ID: r.statsID,
TotalSamplesDuration: 0, // Todo ...
EstimatedPlayoutTimestamp: 0, // Todo ...
JitterBufferDelay: 0, // Todo ...
JitterBufferEmittedCount: 0, // Todo ...
TotalSamplesReceived: 0, // Todo ...
ConcealedSamples: 0, // Todo ...
ConcealmentEvents: 0, // Todo ...
}

report.Collect(stats.ID, stats)
}
}
56 changes: 56 additions & 0 deletions rtpsender.go
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"sync"
"time"

"github.com/pion/rtcp"
"github.com/pion/rtp"
Expand All @@ -29,6 +30,7 @@ type RTPSender struct {

mu sync.RWMutex
sendCalled, stopCalled chan interface{}
statsID string
}

// NewRTPSender constructs a new RTPSender
Expand All @@ -52,6 +54,7 @@ func (api *API) NewRTPSender(track *Track, transport *DTLSTransport) (*RTPSender
api: api,
sendCalled: make(chan interface{}),
stopCalled: make(chan interface{}),
statsID: fmt.Sprintf("rtp-sender-%d", time.Now().UnixNano()),
}, nil
}

Expand Down Expand Up @@ -201,3 +204,56 @@ func (r *RTPSender) hasSent() bool {
return false
}
}

// collectStats collect rtpsender stats for this instance
func (r *RTPSender) collectStats(report *statsReportCollector) {

if r.Track() == nil {
return
}

report.Collecting()

switch r.Track().Kind() {
case RTPCodecTypeAudio:
totalSamplesDuration := float64(0)
totalSamplesSent := uint64(0)
samplesDuration, ok := r.track.totalSamplesDuration.Load().(float64)
if ok {
totalSamplesDuration = samplesDuration
}

samplesSent, ok := r.track.totalSamplesSent.Load().(uint64)
if ok {
totalSamplesSent = samplesSent
}

stats := AudioSenderStats{
Timestamp: statsTimestampFrom(time.Now()),
Type: StatsTypeSender,
ID: r.statsID,
TrackIdentifier: r.track.ID(),
Kind: r.track.Kind().String(),
AudioLevel: 0, // Todo ...
TotalAudioEnergy: 0, // Todo ...
VoiceActivityFlag: false, // Todo ...
TotalSamplesDuration: totalSamplesDuration,
TotalSamplesSent: totalSamplesSent,
}

report.Collect(stats.ID, stats)
case RTPCodecTypeVideo:
stats := VideoSenderStats{
Timestamp: statsTimestampFrom(time.Now()),
Type: StatsTypeSender,
ID: r.statsID,
FramesCaptured: 0, // Todo ...
FramesSent: 0, // Todo ...
HugeFramesSent: 0, // Todo ...
KeyFramesSent: 0, // Todo ...
}

report.Collect(stats.ID, stats)
}

}
99 changes: 99 additions & 0 deletions stats_go.go
Expand Up @@ -91,4 +91,103 @@ func (r StatsReport) GetCodecStats(c *RTPCodec) (CodecStats, bool) {
return CodecStats{}, false
}
return codecStats, true

}

// GetAudioSenderStats is a helper method to return the associated stats for a given AudioSender
func (r StatsReport) GetAudioSenderStats(s *RTPSender) (AudioSenderStats, bool) {
statsID := s.statsID
stats, ok := r[statsID]
if !ok {
return AudioSenderStats{}, false
}

audioSenderStats, ok := stats.(AudioSenderStats)
if !ok {
return AudioSenderStats{}, false
}
return audioSenderStats, true
}

// GetVideoSenderStats is a helper method to return the associated stats for a given AudioSender
func (r StatsReport) GetVideoSenderStats(s *RTPSender) (VideoSenderStats, bool) {
statsID := s.statsID
stats, ok := r[statsID]
if !ok {
return VideoSenderStats{}, false
}

videoSenderStats, ok := stats.(VideoSenderStats)
if !ok {
return VideoSenderStats{}, false
}
return videoSenderStats, true
}

// GetSenderStats is a helper method to return the associated stats for a given Sender
func (r StatsReport) GetSenderStats(s *RTPSender) (GetStatsType, bool) {
if s.Track().Kind() == RTPCodecTypeAudio {
return r.GetAudioSenderStats(s)
} else {
return r.GetVideoSenderStats(s)
}
}

// GetSenderStats is a helper method to return the associated stats for a given Receiver
func (r StatsReport) GetReceiverStats(s *RTPReceiver) (GetStatsType, bool) {
if s.Track().Kind() == RTPCodecTypeAudio {
return r.GetAudioReceiverStats(s)
} else {
return r.GetVideoReceiverStats(s)
}
}

func (a AudioReceiverStats) getType() StatsType {
return a.Type
}

func (v VideoReceiverStats) getType() StatsType {
return v.Type
}

func (a AudioSenderStats) getType() StatsType {
return a.Type
}

func (v VideoSenderStats) getType() StatsType {
return v.Type
}

type GetStatsType interface {
getType() StatsType
}

// GetAudioReceiverStats is a helper method to return the associated stats for a given AudioReceiver
func (r StatsReport) GetAudioReceiverStats(receiver *RTPReceiver) (AudioReceiverStats, bool) {
statsID := receiver.statsID
stats, ok := r[statsID]
if !ok {
return AudioReceiverStats{}, false
}

audioReceiverStats, ok := stats.(AudioReceiverStats)
if !ok {
return AudioReceiverStats{}, false
}
return audioReceiverStats, true
}

// GetVideoReceiverStats is a helper method to return the associated stats for a given VideoReceiver
func (r StatsReport) GetVideoReceiverStats(receiver *RTPReceiver) (VideoReceiverStats, bool) {
statsID := receiver.statsID
stats, ok := r[statsID]
if !ok {
return VideoReceiverStats{}, false
}

videoReceiverStats, ok := stats.(VideoReceiverStats)
if !ok {
return VideoReceiverStats{}, false
}
return videoReceiverStats, true
}
58 changes: 58 additions & 0 deletions stats_go_test.go
Expand Up @@ -123,6 +123,20 @@ func getCertificateStats(t *testing.T, report StatsReport, certificate *Certific
return certificateStats
}

func getSenderStats(t *testing.T, report StatsReport, sender *RTPSender) GetStatsType {
senderStats, ok := report.GetSenderStats(sender)
assert.True(t, ok)
assert.Equal(t, senderStats.getType(), StatsTypeSender)
return senderStats
}

func getReceiverStats(t *testing.T, report StatsReport, receiver *RTPReceiver) GetStatsType {
receiverStats, ok := report.GetReceiverStats(receiver)
assert.True(t, ok)
assert.Equal(t, receiverStats.getType(), StatsTypeReceiver)
return receiverStats
}

func findLocalCandidateStats(report StatsReport) []ICECandidateStats {
result := []ICECandidateStats{}
for _, s := range report {
Expand Down Expand Up @@ -208,6 +222,35 @@ func TestPeerConnection_GetStats(t *testing.T) {

_, err = offerPC.AddTrack(track1)
require.NoError(t, err)
trackVideo, err := offerPC.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
assert.NoError(t, err)

trackAudio, err := offerPC.NewTrack(DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion3")
assert.NoError(t, err)

trackAudioAnswer, err := answerPC.NewTrack(DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion4")
assert.NoError(t, err)

trackVideoAnswer, err := answerPC.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion5")
assert.NoError(t, err)

_, err = offerPC.AddTrack(trackVideo)
assert.NoError(t, err)

_, err = offerPC.AddTrack(trackAudio)
assert.NoError(t, err)

_, err = answerPC.AddTrack(trackVideoAnswer)
assert.NoError(t, err)

_, err = answerPC.AddTrack(trackAudioAnswer)
assert.NoError(t, err)

_, err = offerPC.AddTransceiverFromTrack(trackVideoAnswer)
assert.NoError(t, err)

_, err = offerPC.AddTransceiverFromTrack(trackAudioAnswer)
assert.NoError(t, err)

baseLineReportPCOffer := offerPC.GetStats()
baseLineReportPCAnswer := answerPC.GetStats()
Expand Down Expand Up @@ -329,6 +372,21 @@ func TestPeerConnection_GetStats(t *testing.T) {
assert.NotEmpty(t, certificateStats)
}

transceivers := offerPC.GetTransceivers()
for _, transceiver := range transceivers {
if sender := transceiver.Sender(); sender != nil {
if sender.Track() != nil {
assert.NotEmpty(t, getSenderStats(t, reportPCOffer, sender))
}
}

if receiver := transceiver.Receiver(); receiver != nil {
if receiver.Track() != nil {
assert.NotEmpty(t, getReceiverStats(t, reportPCOffer, receiver))
}
}
}

assert.NoError(t, offerPC.Close())
assert.NoError(t, answerPC.Close())
}
Expand Down

0 comments on commit bcd83e7

Please sign in to comment.