Reland "ChannelStatistics used for RTP stats in VoipStatistics."
This is a reland of 444e04be6988fbdcc039d775481ac22481ff9ff4 Reason for reland: resolved the breaks from downstream project Original change's description: > ChannelStatistics used for RTP stats in VoipStatistics. > > - Added local and remote RTP statistics query API. > - Change includes simplifying remote SSRC change handling > via received RTP and RTCP packets. > > Bug: webrtc:11989 > Change-Id: Ia3ee62c1191baaedc67e033ea3c661d8c9301abc > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/199060 > Reviewed-by: Harald Alvestrand <hta@webrtc.org> > Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> > Reviewed-by: Sam Zackrisson <saza@webrtc.org> > Commit-Queue: Tim Na <natim@webrtc.org> > Cr-Commit-Position: refs/heads/master@{#32954} Bug: webrtc:11989 Change-Id: I88620a9f1c037b512821cac9d556905149666870 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/201481 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Reviewed-by: Sam Zackrisson <saza@webrtc.org> Commit-Queue: Tim Na <natim@webrtc.org> Cr-Commit-Position: refs/heads/master@{#32966}
This commit is contained in:
@ -26,6 +26,51 @@ struct IngressStatistics {
|
|||||||
double total_duration = 0.0;
|
double total_duration = 0.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Remote statistics obtained via remote RTCP SR/RR report received.
|
||||||
|
struct RemoteRtcpStatistics {
|
||||||
|
// Jitter as defined in RFC 3550 [6.4.1] expressed in seconds.
|
||||||
|
double jitter = 0.0;
|
||||||
|
|
||||||
|
// Cumulative packets lost as defined in RFC 3550 [6.4.1]
|
||||||
|
int64_t packets_lost = 0;
|
||||||
|
|
||||||
|
// Fraction lost as defined in RFC 3550 [6.4.1] expressed as a floating
|
||||||
|
// pointer number.
|
||||||
|
double fraction_lost = 0.0;
|
||||||
|
|
||||||
|
// https://w3c.github.io/webrtc-stats/#dom-rtcremoteinboundrtpstreamstats-roundtriptime
|
||||||
|
absl::optional<double> round_trip_time;
|
||||||
|
|
||||||
|
// Last time (not RTP timestamp) when RTCP report received in milliseconds.
|
||||||
|
int64_t last_report_received_timestamp_ms;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ChannelStatistics {
|
||||||
|
// https://w3c.github.io/webrtc-stats/#dom-rtcsentrtpstreamstats-packetssent
|
||||||
|
uint64_t packets_sent = 0;
|
||||||
|
|
||||||
|
// https://w3c.github.io/webrtc-stats/#dom-rtcsentrtpstreamstats-bytessent
|
||||||
|
uint64_t bytes_sent = 0;
|
||||||
|
|
||||||
|
// https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceived
|
||||||
|
uint64_t packets_received = 0;
|
||||||
|
|
||||||
|
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-bytesreceived
|
||||||
|
uint64_t bytes_received = 0;
|
||||||
|
|
||||||
|
// https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-jitter
|
||||||
|
double jitter = 0.0;
|
||||||
|
|
||||||
|
// https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetslost
|
||||||
|
int64_t packets_lost = 0;
|
||||||
|
|
||||||
|
// SSRC from remote media endpoint as indicated either by RTP header in RFC
|
||||||
|
// 3550 [5.1] or RTCP SSRC of sender in RFC 3550 [6.4.1].
|
||||||
|
absl::optional<uint32_t> remote_ssrc;
|
||||||
|
|
||||||
|
absl::optional<RemoteRtcpStatistics> remote_rtcp;
|
||||||
|
};
|
||||||
|
|
||||||
// VoipStatistics interface provides the interfaces for querying metrics around
|
// VoipStatistics interface provides the interfaces for querying metrics around
|
||||||
// the jitter buffer (NetEq) performance.
|
// the jitter buffer (NetEq) performance.
|
||||||
class VoipStatistics {
|
class VoipStatistics {
|
||||||
@ -37,6 +82,13 @@ class VoipStatistics {
|
|||||||
virtual VoipResult GetIngressStatistics(ChannelId channel_id,
|
virtual VoipResult GetIngressStatistics(ChannelId channel_id,
|
||||||
IngressStatistics& ingress_stats) = 0;
|
IngressStatistics& ingress_stats) = 0;
|
||||||
|
|
||||||
|
// Gets the channel statistics by |channel_stats| reference.
|
||||||
|
// Returns following VoipResult;
|
||||||
|
// kOk - successfully set provided ChannelStatistics reference.
|
||||||
|
// kInvalidArgument - |channel_id| is invalid.
|
||||||
|
virtual VoipResult GetChannelStatistics(ChannelId channel_id,
|
||||||
|
ChannelStatistics& channel_stats) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~VoipStatistics() = default;
|
virtual ~VoipStatistics() = default;
|
||||||
};
|
};
|
||||||
|
@ -67,6 +67,7 @@ rtc_library("audio_ingress") {
|
|||||||
"../../api:transport_api",
|
"../../api:transport_api",
|
||||||
"../../api/audio:audio_mixer_api",
|
"../../api/audio:audio_mixer_api",
|
||||||
"../../api/audio_codecs:audio_codecs_api",
|
"../../api/audio_codecs:audio_codecs_api",
|
||||||
|
"../../api/voip:voip_api",
|
||||||
"../../modules/audio_coding",
|
"../../modules/audio_coding",
|
||||||
"../../modules/rtp_rtcp",
|
"../../modules/rtp_rtcp",
|
||||||
"../../modules/rtp_rtcp:rtp_rtcp_format",
|
"../../modules/rtp_rtcp:rtp_rtcp_format",
|
||||||
|
@ -79,6 +79,12 @@ AudioChannel::~AudioChannel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
audio_mixer_->RemoveSource(ingress_.get());
|
audio_mixer_->RemoveSource(ingress_.get());
|
||||||
|
|
||||||
|
// AudioEgress could hold current global TaskQueueBase that we need to clear
|
||||||
|
// before ProcessThread::DeRegisterModule.
|
||||||
|
egress_.reset();
|
||||||
|
ingress_.reset();
|
||||||
|
|
||||||
process_thread_->DeRegisterModule(rtp_rtcp_.get());
|
process_thread_->DeRegisterModule(rtp_rtcp_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,4 +165,17 @@ IngressStatistics AudioChannel::GetIngressStatistics() {
|
|||||||
return ingress_stats;
|
return ingress_stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChannelStatistics AudioChannel::GetChannelStatistics() {
|
||||||
|
ChannelStatistics channel_stat = ingress_->GetChannelStatistics();
|
||||||
|
|
||||||
|
StreamDataCounters rtp_stats, rtx_stats;
|
||||||
|
rtp_rtcp_->GetSendStreamDataCounters(&rtp_stats, &rtx_stats);
|
||||||
|
channel_stat.bytes_sent =
|
||||||
|
rtp_stats.transmitted.payload_bytes + rtx_stats.transmitted.payload_bytes;
|
||||||
|
channel_stat.packets_sent =
|
||||||
|
rtp_stats.transmitted.packets + rtx_stats.transmitted.packets;
|
||||||
|
|
||||||
|
return channel_stat;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -84,6 +84,7 @@ class AudioChannel : public rtc::RefCountInterface {
|
|||||||
ingress_->SetReceiveCodecs(codecs);
|
ingress_->SetReceiveCodecs(codecs);
|
||||||
}
|
}
|
||||||
IngressStatistics GetIngressStatistics();
|
IngressStatistics GetIngressStatistics();
|
||||||
|
ChannelStatistics GetChannelStatistics();
|
||||||
|
|
||||||
// See comments on the methods used from AudioEgress and AudioIngress.
|
// See comments on the methods used from AudioEgress and AudioIngress.
|
||||||
// Conversion to double is following what is done in
|
// Conversion to double is following what is done in
|
||||||
@ -106,6 +107,12 @@ class AudioChannel : public rtc::RefCountInterface {
|
|||||||
return ingress_->GetOutputTotalDuration();
|
return ingress_->GetOutputTotalDuration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal API for testing purpose.
|
||||||
|
void SendRTCPReportForTesting(RTCPPacketType type) {
|
||||||
|
int32_t result = rtp_rtcp_->SendRTCP(type);
|
||||||
|
RTC_DCHECK(result == 0);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// ChannelId that this audio channel belongs for logging purpose.
|
// ChannelId that this audio channel belongs for logging purpose.
|
||||||
ChannelId id_;
|
ChannelId id_;
|
||||||
|
@ -17,6 +17,10 @@
|
|||||||
#include "api/audio_codecs/audio_format.h"
|
#include "api/audio_codecs/audio_format.h"
|
||||||
#include "audio/utility/audio_frame_operations.h"
|
#include "audio/utility/audio_frame_operations.h"
|
||||||
#include "modules/audio_coding/include/audio_coding_module.h"
|
#include "modules/audio_coding/include/audio_coding_module.h"
|
||||||
|
#include "modules/rtp_rtcp/source/byte_io.h"
|
||||||
|
#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h"
|
||||||
|
#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h"
|
||||||
|
#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h"
|
||||||
#include "rtc_base/logging.h"
|
#include "rtc_base/logging.h"
|
||||||
#include "rtc_base/numerics/safe_minmax.h"
|
#include "rtc_base/numerics/safe_minmax.h"
|
||||||
|
|
||||||
@ -153,6 +157,12 @@ void AudioIngress::ReceivedRTPPacket(rtc::ArrayView<const uint8_t> rtp_packet) {
|
|||||||
rtp_packet_received.set_payload_type_frequency(it->second);
|
rtp_packet_received.set_payload_type_frequency(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track current remote SSRC.
|
||||||
|
if (rtp_packet_received.Ssrc() != remote_ssrc_) {
|
||||||
|
rtp_rtcp_->SetRemoteSSRC(rtp_packet_received.Ssrc());
|
||||||
|
remote_ssrc_.store(rtp_packet_received.Ssrc());
|
||||||
|
}
|
||||||
|
|
||||||
rtp_receive_statistics_->OnRtpPacket(rtp_packet_received);
|
rtp_receive_statistics_->OnRtpPacket(rtp_packet_received);
|
||||||
|
|
||||||
RTPHeader header;
|
RTPHeader header;
|
||||||
@ -181,11 +191,28 @@ void AudioIngress::ReceivedRTPPacket(rtc::ArrayView<const uint8_t> rtp_packet) {
|
|||||||
|
|
||||||
void AudioIngress::ReceivedRTCPPacket(
|
void AudioIngress::ReceivedRTCPPacket(
|
||||||
rtc::ArrayView<const uint8_t> rtcp_packet) {
|
rtc::ArrayView<const uint8_t> rtcp_packet) {
|
||||||
// Deliver RTCP packet to RTP/RTCP module for parsing.
|
rtcp::CommonHeader rtcp_header;
|
||||||
|
if (rtcp_header.Parse(rtcp_packet.data(), rtcp_packet.size()) &&
|
||||||
|
(rtcp_header.type() == rtcp::SenderReport::kPacketType ||
|
||||||
|
rtcp_header.type() == rtcp::ReceiverReport::kPacketType)) {
|
||||||
|
RTC_DCHECK_GE(rtcp_packet.size(), 8);
|
||||||
|
|
||||||
|
uint32_t sender_ssrc =
|
||||||
|
ByteReader<uint32_t>::ReadBigEndian(rtcp_packet.data() + 4);
|
||||||
|
|
||||||
|
// If we don't have remote ssrc at this point, it's likely that remote
|
||||||
|
// endpoint is receive-only or it could have restarted the media.
|
||||||
|
if (sender_ssrc != remote_ssrc_) {
|
||||||
|
rtp_rtcp_->SetRemoteSSRC(sender_ssrc);
|
||||||
|
remote_ssrc_.store(sender_ssrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deliver RTCP packet to RTP/RTCP module for parsing and processing.
|
||||||
rtp_rtcp_->IncomingRtcpPacket(rtcp_packet.data(), rtcp_packet.size());
|
rtp_rtcp_->IncomingRtcpPacket(rtcp_packet.data(), rtcp_packet.size());
|
||||||
|
|
||||||
absl::optional<int64_t> rtt = GetRoundTripTime();
|
int64_t rtt = 0;
|
||||||
if (!rtt.has_value()) {
|
if (rtp_rtcp_->RTT(remote_ssrc_, &rtt, nullptr, nullptr, nullptr) != 0) {
|
||||||
// Waiting for valid RTT.
|
// Waiting for valid RTT.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -199,38 +226,69 @@ void AudioIngress::ReceivedRTCPPacket(
|
|||||||
|
|
||||||
{
|
{
|
||||||
MutexLock lock(&lock_);
|
MutexLock lock(&lock_);
|
||||||
ntp_estimator_.UpdateRtcpTimestamp(*rtt, ntp_secs, ntp_frac, rtp_timestamp);
|
ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::optional<int64_t> AudioIngress::GetRoundTripTime() {
|
ChannelStatistics AudioIngress::GetChannelStatistics() {
|
||||||
|
ChannelStatistics channel_stats;
|
||||||
|
|
||||||
|
// Get clockrate for current decoder ahead of jitter calculation.
|
||||||
|
uint32_t clockrate_hz = 0;
|
||||||
|
absl::optional<std::pair<int, SdpAudioFormat>> decoder =
|
||||||
|
acm_receiver_.LastDecoder();
|
||||||
|
if (decoder) {
|
||||||
|
clockrate_hz = decoder->second.clockrate_hz;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamStatistician* statistician =
|
||||||
|
rtp_receive_statistics_->GetStatistician(remote_ssrc_);
|
||||||
|
if (statistician) {
|
||||||
|
RtpReceiveStats stats = statistician->GetStats();
|
||||||
|
channel_stats.packets_lost = stats.packets_lost;
|
||||||
|
channel_stats.packets_received = stats.packet_counter.packets;
|
||||||
|
channel_stats.bytes_received = stats.packet_counter.payload_bytes;
|
||||||
|
channel_stats.remote_ssrc = remote_ssrc_;
|
||||||
|
if (clockrate_hz > 0) {
|
||||||
|
channel_stats.jitter = static_cast<double>(stats.jitter) / clockrate_hz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get RTCP report using remote SSRC.
|
||||||
const std::vector<ReportBlockData>& report_data =
|
const std::vector<ReportBlockData>& report_data =
|
||||||
rtp_rtcp_->GetLatestReportBlockData();
|
rtp_rtcp_->GetLatestReportBlockData();
|
||||||
|
for (const ReportBlockData& block_data : report_data) {
|
||||||
|
const RTCPReportBlock& rtcp_report = block_data.report_block();
|
||||||
|
if (rtp_rtcp_->SSRC() != rtcp_report.source_ssrc ||
|
||||||
|
remote_ssrc_ != rtcp_report.sender_ssrc) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RemoteRtcpStatistics remote_stat;
|
||||||
|
remote_stat.packets_lost = rtcp_report.packets_lost;
|
||||||
|
remote_stat.fraction_lost =
|
||||||
|
static_cast<double>(rtcp_report.fraction_lost) / (1 << 8);
|
||||||
|
if (clockrate_hz > 0) {
|
||||||
|
remote_stat.jitter =
|
||||||
|
static_cast<double>(rtcp_report.jitter) / clockrate_hz;
|
||||||
|
}
|
||||||
|
if (block_data.has_rtt()) {
|
||||||
|
remote_stat.round_trip_time =
|
||||||
|
static_cast<double>(block_data.last_rtt_ms()) /
|
||||||
|
rtc::kNumMillisecsPerSec;
|
||||||
|
}
|
||||||
|
remote_stat.last_report_received_timestamp_ms =
|
||||||
|
block_data.report_block_timestamp_utc_us() /
|
||||||
|
rtc::kNumMicrosecsPerMillisec;
|
||||||
|
channel_stats.remote_rtcp = remote_stat;
|
||||||
|
|
||||||
// If we do not have report block which means remote RTCP hasn't be received
|
// Receive only channel won't send any RTP packets.
|
||||||
// yet, return -1 as to indicate uninitialized value.
|
if (!channel_stats.remote_ssrc.has_value()) {
|
||||||
if (report_data.empty()) {
|
channel_stats.remote_ssrc = remote_ssrc_;
|
||||||
return absl::nullopt;
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't know in advance the remote SSRC used by the other end's receiver
|
return channel_stats;
|
||||||
// reports, so use the SSRC of the first report block as remote SSRC for now.
|
|
||||||
// TODO(natim@webrtc.org): handle the case where remote end is changing ssrc
|
|
||||||
// and update accordingly here.
|
|
||||||
const ReportBlockData& block_data = report_data[0];
|
|
||||||
|
|
||||||
const uint32_t sender_ssrc = block_data.report_block().sender_ssrc;
|
|
||||||
|
|
||||||
if (sender_ssrc != remote_ssrc_.load()) {
|
|
||||||
remote_ssrc_.store(sender_ssrc);
|
|
||||||
rtp_rtcp_->SetRemoteSSRC(sender_ssrc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!block_data.has_rtt()) {
|
|
||||||
return absl::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return block_data.last_rtt_ms();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "api/audio/audio_mixer.h"
|
#include "api/audio/audio_mixer.h"
|
||||||
#include "api/rtp_headers.h"
|
#include "api/rtp_headers.h"
|
||||||
#include "api/scoped_refptr.h"
|
#include "api/scoped_refptr.h"
|
||||||
|
#include "api/voip/voip_statistics.h"
|
||||||
#include "audio/audio_level.h"
|
#include "audio/audio_level.h"
|
||||||
#include "modules/audio_coding/acm2/acm_receiver.h"
|
#include "modules/audio_coding/acm2/acm_receiver.h"
|
||||||
#include "modules/audio_coding/include/audio_coding_module.h"
|
#include "modules/audio_coding/include/audio_coding_module.h"
|
||||||
@ -86,6 +87,8 @@ class AudioIngress : public AudioMixer::Source {
|
|||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChannelStatistics GetChannelStatistics();
|
||||||
|
|
||||||
// Implementation of AudioMixer::Source interface.
|
// Implementation of AudioMixer::Source interface.
|
||||||
AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo(
|
AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo(
|
||||||
int sampling_rate,
|
int sampling_rate,
|
||||||
@ -102,10 +105,6 @@ class AudioIngress : public AudioMixer::Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Returns network round trip time (RTT) measued by RTCP exchange with
|
|
||||||
// remote media endpoint. Returns absl::nullopt when it's not initialized.
|
|
||||||
absl::optional<int64_t> GetRoundTripTime();
|
|
||||||
|
|
||||||
// Indicates AudioIngress status as caller invokes Start/StopPlaying.
|
// Indicates AudioIngress status as caller invokes Start/StopPlaying.
|
||||||
// If not playing, incoming RTP data processing is skipped, thus
|
// If not playing, incoming RTP data processing is skipped, thus
|
||||||
// producing no data to output device.
|
// producing no data to output device.
|
||||||
|
@ -9,6 +9,16 @@
|
|||||||
import("../../../webrtc.gni")
|
import("../../../webrtc.gni")
|
||||||
|
|
||||||
if (rtc_include_tests) {
|
if (rtc_include_tests) {
|
||||||
|
rtc_source_set("mock_task_queue") {
|
||||||
|
testonly = true
|
||||||
|
visibility = [ "*" ]
|
||||||
|
sources = [ "mock_task_queue.h" ]
|
||||||
|
deps = [
|
||||||
|
"../../../api/task_queue:task_queue",
|
||||||
|
"../../../test:test_support",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
rtc_library("voip_core_unittests") {
|
rtc_library("voip_core_unittests") {
|
||||||
testonly = true
|
testonly = true
|
||||||
sources = [ "voip_core_unittest.cc" ]
|
sources = [ "voip_core_unittest.cc" ]
|
||||||
@ -30,18 +40,18 @@ if (rtc_include_tests) {
|
|||||||
testonly = true
|
testonly = true
|
||||||
sources = [ "audio_channel_unittest.cc" ]
|
sources = [ "audio_channel_unittest.cc" ]
|
||||||
deps = [
|
deps = [
|
||||||
|
":mock_task_queue",
|
||||||
"..:audio_channel",
|
"..:audio_channel",
|
||||||
"../../../api:transport_api",
|
"../../../api:transport_api",
|
||||||
"../../../api/audio_codecs:builtin_audio_decoder_factory",
|
"../../../api/audio_codecs:builtin_audio_decoder_factory",
|
||||||
"../../../api/audio_codecs:builtin_audio_encoder_factory",
|
"../../../api/audio_codecs:builtin_audio_encoder_factory",
|
||||||
"../../../api/task_queue:default_task_queue_factory",
|
"../../../api/task_queue:task_queue",
|
||||||
"../../../modules/audio_mixer:audio_mixer_impl",
|
"../../../modules/audio_mixer:audio_mixer_impl",
|
||||||
"../../../modules/audio_mixer:audio_mixer_test_utils",
|
"../../../modules/audio_mixer:audio_mixer_test_utils",
|
||||||
"../../../modules/rtp_rtcp:rtp_rtcp",
|
"../../../modules/rtp_rtcp:rtp_rtcp",
|
||||||
"../../../modules/rtp_rtcp:rtp_rtcp_format",
|
"../../../modules/rtp_rtcp:rtp_rtcp_format",
|
||||||
"../../../modules/utility",
|
"../../../modules/utility",
|
||||||
"../../../rtc_base:logging",
|
"../../../rtc_base:logging",
|
||||||
"../../../rtc_base:rtc_event",
|
|
||||||
"../../../test:mock_transport",
|
"../../../test:mock_transport",
|
||||||
"../../../test:test_support",
|
"../../../test:test_support",
|
||||||
]
|
]
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
|
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
|
||||||
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
|
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
|
||||||
#include "api/call/transport.h"
|
#include "api/call/transport.h"
|
||||||
#include "api/task_queue/default_task_queue_factory.h"
|
#include "api/task_queue/task_queue_factory.h"
|
||||||
|
#include "audio/voip/test/mock_task_queue.h"
|
||||||
#include "modules/audio_mixer/audio_mixer_impl.h"
|
#include "modules/audio_mixer/audio_mixer_impl.h"
|
||||||
#include "modules/audio_mixer/sine_wave_generator.h"
|
#include "modules/audio_mixer/sine_wave_generator.h"
|
||||||
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
|
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
|
||||||
#include "modules/utility/include/process_thread.h"
|
#include "modules/utility/include/process_thread.h"
|
||||||
#include "rtc_base/event.h"
|
|
||||||
#include "rtc_base/logging.h"
|
#include "rtc_base/logging.h"
|
||||||
#include "test/gmock.h"
|
#include "test/gmock.h"
|
||||||
#include "test/gtest.h"
|
#include "test/gtest.h"
|
||||||
@ -41,11 +41,16 @@ class AudioChannelTest : public ::testing::Test {
|
|||||||
|
|
||||||
AudioChannelTest()
|
AudioChannelTest()
|
||||||
: fake_clock_(kStartTime), wave_generator_(1000.0, kAudioLevel) {
|
: fake_clock_(kStartTime), wave_generator_(1000.0, kAudioLevel) {
|
||||||
|
task_queue_factory_ = std::make_unique<MockTaskQueueFactory>(&task_queue_);
|
||||||
process_thread_ = ProcessThread::Create("ModuleProcessThread");
|
process_thread_ = ProcessThread::Create("ModuleProcessThread");
|
||||||
audio_mixer_ = AudioMixerImpl::Create();
|
audio_mixer_ = AudioMixerImpl::Create();
|
||||||
task_queue_factory_ = CreateDefaultTaskQueueFactory();
|
|
||||||
encoder_factory_ = CreateBuiltinAudioEncoderFactory();
|
encoder_factory_ = CreateBuiltinAudioEncoderFactory();
|
||||||
decoder_factory_ = CreateBuiltinAudioDecoderFactory();
|
decoder_factory_ = CreateBuiltinAudioDecoderFactory();
|
||||||
|
|
||||||
|
// By default, run the queued task immediately.
|
||||||
|
ON_CALL(task_queue_, PostTask)
|
||||||
|
.WillByDefault(
|
||||||
|
Invoke([&](std::unique_ptr<QueuedTask> task) { task->Run(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
@ -80,6 +85,7 @@ class AudioChannelTest : public ::testing::Test {
|
|||||||
SimulatedClock fake_clock_;
|
SimulatedClock fake_clock_;
|
||||||
SineWaveGenerator wave_generator_;
|
SineWaveGenerator wave_generator_;
|
||||||
NiceMock<MockTransport> transport_;
|
NiceMock<MockTransport> transport_;
|
||||||
|
NiceMock<MockTaskQueue> task_queue_;
|
||||||
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
||||||
rtc::scoped_refptr<AudioMixer> audio_mixer_;
|
rtc::scoped_refptr<AudioMixer> audio_mixer_;
|
||||||
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_;
|
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory_;
|
||||||
@ -92,11 +98,9 @@ class AudioChannelTest : public ::testing::Test {
|
|||||||
// Resulted RTP packet is looped back into AudioChannel and gets decoded into
|
// Resulted RTP packet is looped back into AudioChannel and gets decoded into
|
||||||
// audio frame to see if it has some signal to indicate its validity.
|
// audio frame to see if it has some signal to indicate its validity.
|
||||||
TEST_F(AudioChannelTest, PlayRtpByLocalLoop) {
|
TEST_F(AudioChannelTest, PlayRtpByLocalLoop) {
|
||||||
rtc::Event event;
|
|
||||||
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
||||||
audio_channel_->ReceivedRTPPacket(
|
audio_channel_->ReceivedRTPPacket(
|
||||||
rtc::ArrayView<const uint8_t>(packet, length));
|
rtc::ArrayView<const uint8_t>(packet, length));
|
||||||
event.Set();
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
EXPECT_CALL(transport_, SendRtp).WillOnce(Invoke(loop_rtp));
|
EXPECT_CALL(transport_, SendRtp).WillOnce(Invoke(loop_rtp));
|
||||||
@ -105,8 +109,6 @@ TEST_F(AudioChannelTest, PlayRtpByLocalLoop) {
|
|||||||
audio_sender->SendAudioData(GetAudioFrame(0));
|
audio_sender->SendAudioData(GetAudioFrame(0));
|
||||||
audio_sender->SendAudioData(GetAudioFrame(1));
|
audio_sender->SendAudioData(GetAudioFrame(1));
|
||||||
|
|
||||||
event.Wait(/*ms=*/1000);
|
|
||||||
|
|
||||||
AudioFrame empty_frame, audio_frame;
|
AudioFrame empty_frame, audio_frame;
|
||||||
empty_frame.Mute();
|
empty_frame.Mute();
|
||||||
empty_frame.mutable_data(); // This will zero out the data.
|
empty_frame.mutable_data(); // This will zero out the data.
|
||||||
@ -122,10 +124,8 @@ TEST_F(AudioChannelTest, PlayRtpByLocalLoop) {
|
|||||||
// Validate assigned local SSRC is resulted in RTP packet.
|
// Validate assigned local SSRC is resulted in RTP packet.
|
||||||
TEST_F(AudioChannelTest, VerifyLocalSsrcAsAssigned) {
|
TEST_F(AudioChannelTest, VerifyLocalSsrcAsAssigned) {
|
||||||
RtpPacketReceived rtp;
|
RtpPacketReceived rtp;
|
||||||
rtc::Event event;
|
|
||||||
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
||||||
rtp.Parse(packet, length);
|
rtp.Parse(packet, length);
|
||||||
event.Set();
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
EXPECT_CALL(transport_, SendRtp).WillOnce(Invoke(loop_rtp));
|
EXPECT_CALL(transport_, SendRtp).WillOnce(Invoke(loop_rtp));
|
||||||
@ -134,18 +134,14 @@ TEST_F(AudioChannelTest, VerifyLocalSsrcAsAssigned) {
|
|||||||
audio_sender->SendAudioData(GetAudioFrame(0));
|
audio_sender->SendAudioData(GetAudioFrame(0));
|
||||||
audio_sender->SendAudioData(GetAudioFrame(1));
|
audio_sender->SendAudioData(GetAudioFrame(1));
|
||||||
|
|
||||||
event.Wait(/*ms=*/1000);
|
|
||||||
|
|
||||||
EXPECT_EQ(rtp.Ssrc(), kLocalSsrc);
|
EXPECT_EQ(rtp.Ssrc(), kLocalSsrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check metrics after processing an RTP packet.
|
// Check metrics after processing an RTP packet.
|
||||||
TEST_F(AudioChannelTest, TestIngressStatistics) {
|
TEST_F(AudioChannelTest, TestIngressStatistics) {
|
||||||
auto event = std::make_unique<rtc::Event>();
|
|
||||||
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
||||||
audio_channel_->ReceivedRTPPacket(
|
audio_channel_->ReceivedRTPPacket(
|
||||||
rtc::ArrayView<const uint8_t>(packet, length));
|
rtc::ArrayView<const uint8_t>(packet, length));
|
||||||
event->Set();
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(loop_rtp));
|
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(loop_rtp));
|
||||||
@ -153,7 +149,6 @@ TEST_F(AudioChannelTest, TestIngressStatistics) {
|
|||||||
auto audio_sender = audio_channel_->GetAudioSender();
|
auto audio_sender = audio_channel_->GetAudioSender();
|
||||||
audio_sender->SendAudioData(GetAudioFrame(0));
|
audio_sender->SendAudioData(GetAudioFrame(0));
|
||||||
audio_sender->SendAudioData(GetAudioFrame(1));
|
audio_sender->SendAudioData(GetAudioFrame(1));
|
||||||
event->Wait(/*give_up_after_ms=*/1000);
|
|
||||||
|
|
||||||
AudioFrame audio_frame;
|
AudioFrame audio_frame;
|
||||||
audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame);
|
audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame);
|
||||||
@ -182,10 +177,8 @@ TEST_F(AudioChannelTest, TestIngressStatistics) {
|
|||||||
audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame);
|
audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame);
|
||||||
|
|
||||||
// Send another RTP packet to intentionally break PLC.
|
// Send another RTP packet to intentionally break PLC.
|
||||||
event = std::make_unique<rtc::Event>();
|
|
||||||
audio_sender->SendAudioData(GetAudioFrame(2));
|
audio_sender->SendAudioData(GetAudioFrame(2));
|
||||||
audio_sender->SendAudioData(GetAudioFrame(3));
|
audio_sender->SendAudioData(GetAudioFrame(3));
|
||||||
event->Wait(/*give_up_after_ms=*/1000);
|
|
||||||
|
|
||||||
ingress_stats = audio_channel_->GetIngressStatistics();
|
ingress_stats = audio_channel_->GetIngressStatistics();
|
||||||
EXPECT_TRUE(ingress_stats);
|
EXPECT_TRUE(ingress_stats);
|
||||||
@ -222,5 +215,59 @@ TEST_F(AudioChannelTest, TestIngressStatistics) {
|
|||||||
EXPECT_DOUBLE_EQ(ingress_stats->total_duration, 0.06);
|
EXPECT_DOUBLE_EQ(ingress_stats->total_duration, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check ChannelStatistics metric after processing RTP and RTCP packets.
|
||||||
|
TEST_F(AudioChannelTest, TestChannelStatistics) {
|
||||||
|
auto loop_rtp = [&](const uint8_t* packet, size_t length, Unused) {
|
||||||
|
audio_channel_->ReceivedRTPPacket(
|
||||||
|
rtc::ArrayView<const uint8_t>(packet, length));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
auto loop_rtcp = [&](const uint8_t* packet, size_t length) {
|
||||||
|
audio_channel_->ReceivedRTCPPacket(
|
||||||
|
rtc::ArrayView<const uint8_t>(packet, length));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(loop_rtp));
|
||||||
|
EXPECT_CALL(transport_, SendRtcp).WillRepeatedly(Invoke(loop_rtcp));
|
||||||
|
|
||||||
|
// Simulate microphone giving audio frame (10 ms). This will trigger tranport
|
||||||
|
// to send RTP as handled in loop_rtp above.
|
||||||
|
auto audio_sender = audio_channel_->GetAudioSender();
|
||||||
|
audio_sender->SendAudioData(GetAudioFrame(0));
|
||||||
|
audio_sender->SendAudioData(GetAudioFrame(1));
|
||||||
|
|
||||||
|
// Simulate speaker requesting audio frame (10 ms). This will trigger VoIP
|
||||||
|
// engine to fetch audio samples from RTP packets stored in jitter buffer.
|
||||||
|
AudioFrame audio_frame;
|
||||||
|
audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame);
|
||||||
|
audio_mixer_->Mix(/*number_of_channels=*/1, &audio_frame);
|
||||||
|
|
||||||
|
// Force sending RTCP SR report in order to have remote_rtcp field available
|
||||||
|
// in channel statistics. This will trigger tranport to send RTCP as handled
|
||||||
|
// in loop_rtcp above.
|
||||||
|
audio_channel_->SendRTCPReportForTesting(kRtcpSr);
|
||||||
|
|
||||||
|
absl::optional<ChannelStatistics> channel_stats =
|
||||||
|
audio_channel_->GetChannelStatistics();
|
||||||
|
EXPECT_TRUE(channel_stats);
|
||||||
|
|
||||||
|
EXPECT_EQ(channel_stats->packets_sent, 1ULL);
|
||||||
|
EXPECT_EQ(channel_stats->bytes_sent, 160ULL);
|
||||||
|
|
||||||
|
EXPECT_EQ(channel_stats->packets_received, 1ULL);
|
||||||
|
EXPECT_EQ(channel_stats->bytes_received, 160ULL);
|
||||||
|
EXPECT_EQ(channel_stats->jitter, 0);
|
||||||
|
EXPECT_EQ(channel_stats->packets_lost, 0);
|
||||||
|
EXPECT_EQ(channel_stats->remote_ssrc.value(), kLocalSsrc);
|
||||||
|
|
||||||
|
EXPECT_TRUE(channel_stats->remote_rtcp.has_value());
|
||||||
|
|
||||||
|
EXPECT_EQ(channel_stats->remote_rtcp->jitter, 0);
|
||||||
|
EXPECT_EQ(channel_stats->remote_rtcp->packets_lost, 0);
|
||||||
|
EXPECT_EQ(channel_stats->remote_rtcp->fraction_lost, 0);
|
||||||
|
EXPECT_GT(channel_stats->remote_rtcp->last_report_received_timestamp_ms, 0);
|
||||||
|
EXPECT_FALSE(channel_stats->remote_rtcp->round_trip_time.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
60
audio/voip/test/mock_task_queue.h
Normal file
60
audio/voip/test/mock_task_queue.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef AUDIO_VOIP_TEST_MOCK_TASK_QUEUE_H_
|
||||||
|
#define AUDIO_VOIP_TEST_MOCK_TASK_QUEUE_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "api/task_queue/task_queue_factory.h"
|
||||||
|
#include "test/gmock.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
// MockTaskQueue enables immediate task run from global TaskQueueBase.
|
||||||
|
// It's necessary for some tests depending on TaskQueueBase internally.
|
||||||
|
class MockTaskQueue : public TaskQueueBase {
|
||||||
|
public:
|
||||||
|
MockTaskQueue() : current_(this) {}
|
||||||
|
|
||||||
|
// Delete is deliberately defined as no-op as MockTaskQueue is expected to
|
||||||
|
// hold onto current global TaskQueueBase throughout the testing.
|
||||||
|
void Delete() override {}
|
||||||
|
|
||||||
|
MOCK_METHOD(void, PostTask, (std::unique_ptr<QueuedTask>), (override));
|
||||||
|
MOCK_METHOD(void,
|
||||||
|
PostDelayedTask,
|
||||||
|
(std::unique_ptr<QueuedTask>, uint32_t),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
private:
|
||||||
|
CurrentTaskQueueSetter current_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockTaskQueueFactory : public TaskQueueFactory {
|
||||||
|
public:
|
||||||
|
explicit MockTaskQueueFactory(MockTaskQueue* task_queue)
|
||||||
|
: task_queue_(task_queue) {}
|
||||||
|
|
||||||
|
std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue(
|
||||||
|
absl::string_view name,
|
||||||
|
Priority priority) const override {
|
||||||
|
// Default MockTaskQueue::Delete is no-op, therefore it's safe to pass the
|
||||||
|
// raw pointer.
|
||||||
|
return std::unique_ptr<TaskQueueBase, TaskQueueDeleter>(task_queue_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MockTaskQueue* task_queue_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // AUDIO_VOIP_TEST_MOCK_TASK_QUEUE_H_
|
@ -458,6 +458,19 @@ VoipResult VoipCore::GetIngressStatistics(ChannelId channel_id,
|
|||||||
return VoipResult::kOk;
|
return VoipResult::kOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VoipResult VoipCore::GetChannelStatistics(ChannelId channel_id,
|
||||||
|
ChannelStatistics& channel_stats) {
|
||||||
|
rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id);
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
return VoipResult::kInvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel_stats = channel->GetChannelStatistics();
|
||||||
|
|
||||||
|
return VoipResult::kOk;
|
||||||
|
}
|
||||||
|
|
||||||
VoipResult VoipCore::SetInputMuted(ChannelId channel_id, bool enable) {
|
VoipResult VoipCore::SetInputMuted(ChannelId channel_id, bool enable) {
|
||||||
rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id);
|
rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id);
|
||||||
|
|
||||||
|
@ -109,6 +109,8 @@ class VoipCore : public VoipEngine,
|
|||||||
// Implements VoipStatistics interfaces.
|
// Implements VoipStatistics interfaces.
|
||||||
VoipResult GetIngressStatistics(ChannelId channel_id,
|
VoipResult GetIngressStatistics(ChannelId channel_id,
|
||||||
IngressStatistics& ingress_stats) override;
|
IngressStatistics& ingress_stats) override;
|
||||||
|
VoipResult GetChannelStatistics(ChannelId channe_id,
|
||||||
|
ChannelStatistics& channel_stats) override;
|
||||||
|
|
||||||
// Implements VoipVolumeControl interfaces.
|
// Implements VoipVolumeControl interfaces.
|
||||||
VoipResult SetInputMuted(ChannelId channel_id, bool enable) override;
|
VoipResult SetInputMuted(ChannelId channel_id, bool enable) override;
|
||||||
|
Reference in New Issue
Block a user