diff --git a/webrtc/api/stats/rtcstats_objects.h b/webrtc/api/stats/rtcstats_objects.h index 071a7fb205..0cbea7cff5 100644 --- a/webrtc/api/stats/rtcstats_objects.h +++ b/webrtc/api/stats/rtcstats_objects.h @@ -273,9 +273,11 @@ class RTCMediaStreamTrackStats final : public RTCStats { // Audio-only members RTCStatsMember audio_level; RTCStatsMember total_audio_energy; - RTCStatsMember total_samples_duration; RTCStatsMember echo_return_loss; RTCStatsMember echo_return_loss_enhancement; + RTCStatsMember total_samples_received; + RTCStatsMember total_samples_duration; + RTCStatsMember concealed_samples; }; // https://w3c.github.io/webrtc-stats/#pcstats-dict* diff --git a/webrtc/api/statstypes.cc b/webrtc/api/statstypes.cc index 777e9f8e7f..fb9e1e5d75 100644 --- a/webrtc/api/statstypes.cc +++ b/webrtc/api/statstypes.cc @@ -371,6 +371,8 @@ const char* StatsReport::Value::display_name() const { return "audioInputLevel"; case kStatsValueNameBytesSent: return "bytesSent"; + case kStatsValueNameConcealedSamples: + return "concealedSamples"; case kStatsValueNamePacketsSent: return "packetsSent"; case kStatsValueNameBytesReceived: @@ -383,6 +385,8 @@ const char* StatsReport::Value::display_name() const { return "packetsLost"; case kStatsValueNameProtocol: return "protocol"; + case kStatsValueNameTotalSamplesReceived: + return "totalSamplesReceived"; case kStatsValueNameTransportId: return "transportId"; case kStatsValueNameSelectedCandidatePairId: diff --git a/webrtc/api/statstypes.h b/webrtc/api/statstypes.h index 57fe82da53..819bfe03af 100644 --- a/webrtc/api/statstypes.h +++ b/webrtc/api/statstypes.h @@ -104,6 +104,7 @@ class StatsReport { kStatsValueNameBytesReceived, kStatsValueNameBytesSent, kStatsValueNameCodecImplementationName, + kStatsValueNameConcealedSamples, kStatsValueNameDataChannelId, kStatsValueNameFramesDecoded, kStatsValueNameFramesEncoded, @@ -120,6 +121,7 @@ class StatsReport { kStatsValueNameState, kStatsValueNameTotalAudioEnergy, kStatsValueNameTotalSamplesDuration, + kStatsValueNameTotalSamplesReceived, kStatsValueNameTransportId, kStatsValueNameSentPingRequestsTotal, kStatsValueNameSentPingRequestsBeforeFirstResponse, diff --git a/webrtc/audio/audio_receive_stream.cc b/webrtc/audio/audio_receive_stream.cc index be5c18a57c..d2200f4794 100644 --- a/webrtc/audio/audio_receive_stream.cc +++ b/webrtc/audio/audio_receive_stream.cc @@ -194,6 +194,8 @@ webrtc::AudioReceiveStream::Stats AudioReceiveStream::GetStats() const { auto ns = channel_proxy_->GetNetworkStatistics(); stats.jitter_buffer_ms = ns.currentBufferSize; stats.jitter_buffer_preferred_ms = ns.preferredBufferSize; + stats.total_samples_received = ns.totalSamplesReceived; + stats.concealed_samples = ns.concealedSamples; stats.expand_rate = Q14ToFloat(ns.currentExpandRate); stats.speech_expand_rate = Q14ToFloat(ns.currentSpeechExpandRate); stats.secondary_decoded_rate = Q14ToFloat(ns.currentSecondaryDecodedRate); diff --git a/webrtc/audio/audio_receive_stream_unittest.cc b/webrtc/audio/audio_receive_stream_unittest.cc index 0b1d47a386..9723ee65fa 100644 --- a/webrtc/audio/audio_receive_stream_unittest.cc +++ b/webrtc/audio/audio_receive_stream_unittest.cc @@ -64,8 +64,9 @@ const CallStatistics kCallStats = { 345, 678, 901, 234, -12, 3456, 7890, 567, 890, 123}; const CodecInst kCodecInst = { 123, "codec_name_recv", 96000, -187, 0, -103}; -const NetworkStatistics kNetworkStats = { - 123, 456, false, 0, {}, 789, 12, 345, 678, 901, 0, -1, -1, -1, -1, -1, 0}; +const NetworkStatistics kNetworkStats = {123, 456, false, 789012, 3456, 0, {}, + 789, 12, 345, 678, 901, 0, -1, + -1, -1, -1, -1, 0}; const AudioDecodingCallStats kAudioDecodeStats = MakeAudioDecodeStatsForTest(); struct ConfigHelper { @@ -318,7 +319,9 @@ TEST(AudioReceiveStreamTest, GetStats) { stats.delay_estimate_ms); EXPECT_EQ(static_cast(kSpeechOutputLevel), stats.audio_level); EXPECT_EQ(kTotalOutputEnergy, stats.total_output_energy); + EXPECT_EQ(kNetworkStats.totalSamplesReceived, stats.total_samples_received); EXPECT_EQ(kTotalOutputDuration, stats.total_output_duration); + EXPECT_EQ(kNetworkStats.concealedSamples, stats.concealed_samples); EXPECT_EQ(Q14ToFloat(kNetworkStats.currentExpandRate), stats.expand_rate); EXPECT_EQ(Q14ToFloat(kNetworkStats.currentSpeechExpandRate), stats.speech_expand_rate); diff --git a/webrtc/call/audio_receive_stream.h b/webrtc/call/audio_receive_stream.h index d6924256c5..78f1bffded 100644 --- a/webrtc/call/audio_receive_stream.h +++ b/webrtc/call/audio_receive_stream.h @@ -52,7 +52,15 @@ class AudioReceiveStream { // See description of "totalAudioEnergy" in the WebRTC stats spec: // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalaudioenergy double total_output_energy = 0.0; + // See description of "totalSamplesReceived" in the WebRTC stats spec: + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesreceived + uint64_t total_samples_received = 0; + // See description of "totalSamplesDuration" in the WebRTC stats spec: + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesduration double total_output_duration = 0.0; + // See description of "concealedSamples" in the WebRTC stats spec: + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-concealedsamples + uint64_t concealed_samples = 0; float expand_rate = 0.0f; float speech_expand_rate = 0.0f; float secondary_decoded_rate = 0.0f; diff --git a/webrtc/common_types.h b/webrtc/common_types.h index 4034d54ce6..c4b5ce8e70 100644 --- a/webrtc/common_types.h +++ b/webrtc/common_types.h @@ -367,6 +367,13 @@ struct NetworkStatistics { uint16_t preferredBufferSize; // adding extra delay due to "peaky jitter" bool jitterPeaksFound; + // Total number of audio samples received, including synthesized samples. + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesreceived + uint64_t totalSamplesReceived; + // Total number of inbound audio samples that are based on synthesized data to + // conceal packet loss. + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-concealedsamples + uint64_t concealedSamples; // Loss rate (network + late); fraction between 0 and 1, scaled to Q14. uint16_t currentPacketLossRate; // Late loss rate; fraction between 0 and 1, scaled to Q14. diff --git a/webrtc/media/base/mediachannel.h b/webrtc/media/base/mediachannel.h index baa8c3a74a..76328ef440 100644 --- a/webrtc/media/base/mediachannel.h +++ b/webrtc/media/base/mediachannel.h @@ -652,7 +652,9 @@ struct VoiceReceiverInfo : public MediaReceiverInfo { delay_estimate_ms(0), audio_level(0), total_output_energy(0.0), + total_samples_received(0), total_output_duration(0.0), + concealed_samples(0), expand_rate(0), speech_expand_rate(0), secondary_decoded_rate(0), @@ -676,7 +678,15 @@ struct VoiceReceiverInfo : public MediaReceiverInfo { // See description of "totalAudioEnergy" in the WebRTC stats spec: // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalaudioenergy double total_output_energy; + // See description of "totalSamplesReceived" in the WebRTC stats spec: + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesreceived + uint64_t total_samples_received; + // See description of "totalSamplesDuration" in the WebRTC stats spec: + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesduration double total_output_duration; + // See description of "concealedSamples" in the WebRTC stats spec: + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-concealedsamples + uint64_t concealed_samples; // fraction of synthesized audio inserted through expansion. float expand_rate; // fraction of synthesized speech inserted through expansion. diff --git a/webrtc/media/engine/webrtcvoiceengine.cc b/webrtc/media/engine/webrtcvoiceengine.cc index b6a84da2df..fdbb5c78ed 100644 --- a/webrtc/media/engine/webrtcvoiceengine.cc +++ b/webrtc/media/engine/webrtcvoiceengine.cc @@ -2282,7 +2282,9 @@ bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info) { rinfo.delay_estimate_ms = stats.delay_estimate_ms; rinfo.audio_level = stats.audio_level; rinfo.total_output_energy = stats.total_output_energy; + rinfo.total_samples_received = stats.total_samples_received; rinfo.total_output_duration = stats.total_output_duration; + rinfo.concealed_samples = stats.concealed_samples; rinfo.expand_rate = stats.expand_rate; rinfo.speech_expand_rate = stats.speech_expand_rate; rinfo.secondary_decoded_rate = stats.secondary_decoded_rate; diff --git a/webrtc/media/engine/webrtcvoiceengine_unittest.cc b/webrtc/media/engine/webrtcvoiceengine_unittest.cc index d5743ca2ec..28d09e8af9 100644 --- a/webrtc/media/engine/webrtcvoiceengine_unittest.cc +++ b/webrtc/media/engine/webrtcvoiceengine_unittest.cc @@ -596,6 +596,8 @@ class WebRtcVoiceEngineTestFake : public testing::Test { stats.jitter_buffer_preferred_ms = 567; stats.delay_estimate_ms = 890; stats.audio_level = 1234; + stats.total_samples_received = 5678901; + stats.concealed_samples = 234; stats.expand_rate = 5.67f; stats.speech_expand_rate = 8.90f; stats.secondary_decoded_rate = 1.23f; @@ -632,6 +634,8 @@ class WebRtcVoiceEngineTestFake : public testing::Test { stats.jitter_buffer_preferred_ms); EXPECT_EQ(info.delay_estimate_ms, stats.delay_estimate_ms); EXPECT_EQ(info.audio_level, stats.audio_level); + EXPECT_EQ(info.total_samples_received, stats.total_samples_received); + EXPECT_EQ(info.concealed_samples, stats.concealed_samples); EXPECT_EQ(info.expand_rate, stats.expand_rate); EXPECT_EQ(info.speech_expand_rate, stats.speech_expand_rate); EXPECT_EQ(info.secondary_decoded_rate, stats.secondary_decoded_rate); diff --git a/webrtc/modules/audio_coding/acm2/acm_receiver.cc b/webrtc/modules/audio_coding/acm2/acm_receiver.cc index b512f842a3..8912eaa70c 100644 --- a/webrtc/modules/audio_coding/acm2/acm_receiver.cc +++ b/webrtc/modules/audio_coding/acm2/acm_receiver.cc @@ -332,6 +332,10 @@ void AcmReceiver::GetNetworkStatistics(NetworkStatistics* acm_stat) { acm_stat->medianWaitingTimeMs = neteq_stat.median_waiting_time_ms; acm_stat->minWaitingTimeMs = neteq_stat.min_waiting_time_ms; acm_stat->maxWaitingTimeMs = neteq_stat.max_waiting_time_ms; + + NetEqLifetimeStatistics neteq_lifetime_stat = neteq_->GetLifetimeStatistics(); + acm_stat->totalSamplesReceived = neteq_lifetime_stat.total_samples_received; + acm_stat->concealedSamples = neteq_lifetime_stat.concealed_samples; } int AcmReceiver::DecoderByPayloadType(uint8_t payload_type, diff --git a/webrtc/modules/audio_coding/neteq/include/neteq.h b/webrtc/modules/audio_coding/neteq/include/neteq.h index d05b76e75d..56c30e5f2d 100644 --- a/webrtc/modules/audio_coding/neteq/include/neteq.h +++ b/webrtc/modules/audio_coding/neteq/include/neteq.h @@ -58,6 +58,18 @@ struct NetEqNetworkStatistics { int max_waiting_time_ms; }; +// NetEq statistics that persist over the lifetime of the class. +// These metrics are never reset. +struct NetEqLifetimeStatistics { + // Total number of audio samples received, including synthesized samples. + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesreceived + uint64_t total_samples_received = 0; + // Total number of inbound audio samples that are based on synthesized data to + // conceal packet loss. + // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-concealedsamples + uint64_t concealed_samples = 0; +}; + enum NetEqPlayoutMode { kPlayoutOn, kPlayoutOff, @@ -220,6 +232,10 @@ class NetEq { // after the call. virtual int NetworkStatistics(NetEqNetworkStatistics* stats) = 0; + // Returns a copy of this class's lifetime statistics. These statistics are + // never reset. + virtual NetEqLifetimeStatistics GetLifetimeStatistics() const = 0; + // Writes the current RTCP statistics to |stats|. The statistics are reset // and a new report period is started with the call. virtual void GetRtcpStatistics(RtcpStatistics* stats) = 0; diff --git a/webrtc/modules/audio_coding/neteq/neteq_impl.cc b/webrtc/modules/audio_coding/neteq/neteq_impl.cc index 4b95253d71..7858e846f6 100644 --- a/webrtc/modules/audio_coding/neteq/neteq_impl.cc +++ b/webrtc/modules/audio_coding/neteq/neteq_impl.cc @@ -380,6 +380,11 @@ int NetEqImpl::NetworkStatistics(NetEqNetworkStatistics* stats) { return 0; } +NetEqLifetimeStatistics NetEqImpl::GetLifetimeStatistics() const { + rtc::CritScope lock(&crit_sect_); + return stats_.GetLifetimeStatistics(); +} + void NetEqImpl::GetRtcpStatistics(RtcpStatistics* stats) { rtc::CritScope lock(&crit_sect_); if (stats) { diff --git a/webrtc/modules/audio_coding/neteq/neteq_impl.h b/webrtc/modules/audio_coding/neteq/neteq_impl.h index 0eeff2e45f..f4b014adac 100644 --- a/webrtc/modules/audio_coding/neteq/neteq_impl.h +++ b/webrtc/modules/audio_coding/neteq/neteq_impl.h @@ -185,6 +185,8 @@ class NetEqImpl : public webrtc::NetEq { // and a new report period is started with the call. void GetRtcpStatistics(RtcpStatistics* stats) override; + NetEqLifetimeStatistics GetLifetimeStatistics() const override; + // Same as RtcpStatistics(), but does not reset anything. void GetRtcpStatisticsNoReset(RtcpStatistics* stats) override; diff --git a/webrtc/modules/audio_coding/neteq/statistics_calculator.cc b/webrtc/modules/audio_coding/neteq/statistics_calculator.cc index 3faed62d39..d7d1644aae 100644 --- a/webrtc/modules/audio_coding/neteq/statistics_calculator.cc +++ b/webrtc/modules/audio_coding/neteq/statistics_calculator.cc @@ -153,24 +153,29 @@ void StatisticsCalculator::ResetMcu() { void StatisticsCalculator::ExpandedVoiceSamples(size_t num_samples) { expanded_speech_samples_ += num_samples; + lifetime_stats_.concealed_samples += num_samples; } void StatisticsCalculator::ExpandedNoiseSamples(size_t num_samples) { expanded_noise_samples_ += num_samples; + lifetime_stats_.concealed_samples += num_samples; } void StatisticsCalculator::ExpandedVoiceSamplesCorrection(int num_samples) { expanded_speech_samples_ = AddIntToSizeTWithLowerCap(num_samples, expanded_speech_samples_); + lifetime_stats_.concealed_samples += num_samples; } void StatisticsCalculator::ExpandedNoiseSamplesCorrection(int num_samples) { expanded_noise_samples_ = AddIntToSizeTWithLowerCap(num_samples, expanded_noise_samples_); + lifetime_stats_.concealed_samples += num_samples; } void StatisticsCalculator::PreemptiveExpandedSamples(size_t num_samples) { preemptive_samples_ += num_samples; + lifetime_stats_.concealed_samples += num_samples; } void StatisticsCalculator::AcceleratedSamples(size_t num_samples) { @@ -205,6 +210,7 @@ void StatisticsCalculator::IncreaseCounter(size_t num_samples, int fs_hz) { timestamps_since_last_report_ = 0; discarded_packets_ = 0; } + lifetime_stats_.total_samples_received += num_samples; } void StatisticsCalculator::SecondaryDecodedSamples(int num_samples) { @@ -307,6 +313,10 @@ void StatisticsCalculator::GetNetworkStatistics( Reset(); } +NetEqLifetimeStatistics StatisticsCalculator::GetLifetimeStatistics() const { + return lifetime_stats_; +} + uint16_t StatisticsCalculator::CalculateQ14Ratio(size_t numerator, uint32_t denominator) { if (numerator == 0) { diff --git a/webrtc/modules/audio_coding/neteq/statistics_calculator.h b/webrtc/modules/audio_coding/neteq/statistics_calculator.h index 2877a162e9..f261a66dae 100644 --- a/webrtc/modules/audio_coding/neteq/statistics_calculator.h +++ b/webrtc/modules/audio_coding/neteq/statistics_calculator.h @@ -99,6 +99,10 @@ class StatisticsCalculator { const DecisionLogic& decision_logic, NetEqNetworkStatistics *stats); + // Returns a copy of this class's lifetime statistics. These statistics are + // never reset. + NetEqLifetimeStatistics GetLifetimeStatistics() const; + private: static const int kMaxReportPeriod = 60; // Seconds before auto-reset. static const size_t kLenWaitingTimes = 100; @@ -158,6 +162,8 @@ class StatisticsCalculator { // Calculates numerator / denominator, and returns the value in Q14. static uint16_t CalculateQ14Ratio(size_t numerator, uint32_t denominator); + // TODO(steveanton): Add unit tests for the lifetime stats. + NetEqLifetimeStatistics lifetime_stats_; size_t preemptive_samples_; size_t accelerate_samples_; size_t added_zero_samples_; diff --git a/webrtc/pc/rtcstats_integrationtest.cc b/webrtc/pc/rtcstats_integrationtest.cc index 56a4ef11ed..b886bcc993 100644 --- a/webrtc/pc/rtcstats_integrationtest.cc +++ b/webrtc/pc/rtcstats_integrationtest.cc @@ -525,6 +525,18 @@ class RTCStatsReportVerifier { verifier.MarkMemberTested( media_stream_track.echo_return_loss_enhancement, true); } + // totalSamplesReceived and concealedSamples are only present on inbound + // audio tracks. + if (*media_stream_track.kind == RTCMediaStreamTrackKind::kAudio && + *media_stream_track.remote_source) { + verifier.TestMemberIsNonNegative( + media_stream_track.total_samples_received); + verifier.TestMemberIsNonNegative( + media_stream_track.concealed_samples); + } else { + verifier.TestMemberIsUndefined(media_stream_track.total_samples_received); + verifier.TestMemberIsUndefined(media_stream_track.concealed_samples); + } return verifier.ExpectAllMembersSuccessfullyTested(); } diff --git a/webrtc/pc/rtcstatscollector.cc b/webrtc/pc/rtcstatscollector.cc index 4952012648..5c506e4778 100644 --- a/webrtc/pc/rtcstatscollector.cc +++ b/webrtc/pc/rtcstatscollector.cc @@ -424,8 +424,11 @@ ProduceMediaStreamTrackStatsFromVoiceReceiverInfo( } audio_track_stats->total_audio_energy = voice_receiver_info.total_output_energy; + audio_track_stats->total_samples_received = + voice_receiver_info.total_samples_received; audio_track_stats->total_samples_duration = voice_receiver_info.total_output_duration; + audio_track_stats->concealed_samples = voice_receiver_info.concealed_samples; return audio_track_stats; } diff --git a/webrtc/pc/rtcstatscollector_unittest.cc b/webrtc/pc/rtcstatscollector_unittest.cc index 4f3b35f55e..cc258edd3a 100644 --- a/webrtc/pc/rtcstatscollector_unittest.cc +++ b/webrtc/pc/rtcstatscollector_unittest.cc @@ -1554,7 +1554,9 @@ TEST_F(RTCStatsCollectorTest, voice_receiver_info.local_stats[0].ssrc = 3; voice_receiver_info.audio_level = 16383; voice_receiver_info.total_output_energy = 0.125; + voice_receiver_info.total_samples_received = 4567; voice_receiver_info.total_output_duration = 0.25; + voice_receiver_info.concealed_samples = 123; test_->CreateMockRtpSendersReceiversAndChannels( { std::make_pair(local_audio_track.get(), voice_sender_info_ssrc1), @@ -1628,7 +1630,9 @@ TEST_F(RTCStatsCollectorTest, expected_remote_audio_track.detached = false; expected_remote_audio_track.audio_level = 16383.0 / 32767.0; expected_remote_audio_track.total_audio_energy = 0.125; + expected_remote_audio_track.total_samples_received = 4567; expected_remote_audio_track.total_samples_duration = 0.25; + expected_remote_audio_track.concealed_samples = 123; ASSERT_TRUE(report->Get(expected_remote_audio_track.id())); EXPECT_EQ(expected_remote_audio_track, report->Get(expected_remote_audio_track.id())->cast_to< diff --git a/webrtc/stats/rtcstats_objects.cc b/webrtc/stats/rtcstats_objects.cc index 19e94b3c96..284dfe0b76 100644 --- a/webrtc/stats/rtcstats_objects.cc +++ b/webrtc/stats/rtcstats_objects.cc @@ -379,9 +379,11 @@ WEBRTC_RTCSTATS_IMPL(RTCMediaStreamTrackStats, RTCStats, "track", &full_frames_lost, &audio_level, &total_audio_energy, - &total_samples_duration, &echo_return_loss, - &echo_return_loss_enhancement); + &echo_return_loss_enhancement, + &total_samples_received, + &total_samples_duration, + &concealed_samples); // clang-format on RTCMediaStreamTrackStats::RTCMediaStreamTrackStats( @@ -410,9 +412,11 @@ RTCMediaStreamTrackStats::RTCMediaStreamTrackStats(std::string&& id, full_frames_lost("fullFramesLost"), audio_level("audioLevel"), total_audio_energy("totalAudioEnergy"), - total_samples_duration("totalSamplesDuration"), echo_return_loss("echoReturnLoss"), - echo_return_loss_enhancement("echoReturnLossEnhancement") { + echo_return_loss_enhancement("echoReturnLossEnhancement"), + total_samples_received("totalSamplesReceived"), + total_samples_duration("totalSamplesDuration"), + concealed_samples("concealedSamples") { RTC_DCHECK(kind == RTCMediaStreamTrackKind::kAudio || kind == RTCMediaStreamTrackKind::kVideo); } @@ -437,9 +441,11 @@ RTCMediaStreamTrackStats::RTCMediaStreamTrackStats( full_frames_lost(other.full_frames_lost), audio_level(other.audio_level), total_audio_energy(other.total_audio_energy), - total_samples_duration(other.total_samples_duration), echo_return_loss(other.echo_return_loss), - echo_return_loss_enhancement(other.echo_return_loss_enhancement) {} + echo_return_loss_enhancement(other.echo_return_loss_enhancement), + total_samples_received(other.total_samples_received), + total_samples_duration(other.total_samples_duration), + concealed_samples(other.concealed_samples) {} RTCMediaStreamTrackStats::~RTCMediaStreamTrackStats() { }