blob: 2bbb09cbc8abb8b6a830ff2e98c5bbdcd990f4b8 [file] [log] [blame]
terelius54ce6802016-07-13 06:44:41 -07001/*
2 * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "rtc_tools/event_log_visualizer/analyzer.h"
terelius54ce6802016-07-13 06:44:41 -070012
13#include <algorithm>
Oleh Prypin6581f212017-11-16 00:17:05 +010014#include <cmath>
terelius54ce6802016-07-13 06:44:41 -070015#include <limits>
16#include <map>
17#include <sstream>
18#include <string>
19#include <utility>
20
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020021#include "call/audio_receive_stream.h"
22#include "call/audio_send_stream.h"
23#include "call/call.h"
24#include "call/video_receive_stream.h"
25#include "call/video_send_stream.h"
Mirko Bonadei71207422017-09-15 13:58:09 +020026#include "common_types.h" // NOLINT(build/include)
Elad Alon99a81b62017-09-21 10:25:29 +020027#include "logging/rtc_event_log/rtc_stream_config.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020028#include "modules/audio_coding/neteq/tools/audio_sink.h"
29#include "modules/audio_coding/neteq/tools/fake_decode_from_file.h"
30#include "modules/audio_coding/neteq/tools/neteq_delay_analyzer.h"
31#include "modules/audio_coding/neteq/tools/neteq_replacement_input.h"
32#include "modules/audio_coding/neteq/tools/neteq_test.h"
33#include "modules/audio_coding/neteq/tools/resample_input_audio_file.h"
Bjorn Terelius6984ad22017-10-24 12:19:45 +020034#include "modules/congestion_controller/acknowledged_bitrate_estimator.h"
35#include "modules/congestion_controller/bitrate_estimator.h"
Bjorn Terelius28db2662017-10-04 14:22:43 +020036#include "modules/congestion_controller/include/receive_side_congestion_controller.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020037#include "modules/congestion_controller/include/send_side_congestion_controller.h"
38#include "modules/include/module_common_types.h"
Niels Möllerfd6c0912017-10-31 10:19:10 +010039#include "modules/pacing/packet_router.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020040#include "modules/rtp_rtcp/include/rtp_rtcp.h"
41#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
42#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h"
43#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h"
44#include "modules/rtp_rtcp/source/rtcp_packet/remb.h"
45#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h"
46#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
47#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
48#include "modules/rtp_rtcp/source/rtp_utility.h"
49#include "rtc_base/checks.h"
50#include "rtc_base/format_macros.h"
51#include "rtc_base/logging.h"
Bjorn Terelius0295a962017-10-25 17:42:41 +020052#include "rtc_base/numerics/sequence_number_util.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020053#include "rtc_base/ptr_util.h"
54#include "rtc_base/rate_statistics.h"
terelius54ce6802016-07-13 06:44:41 -070055
Bjorn Terelius6984ad22017-10-24 12:19:45 +020056#ifndef BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
57#define BWE_TEST_LOGGING_COMPILE_TIME_ENABLE 0
58#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
59
tereliusdc35dcd2016-08-01 12:03:27 -070060namespace webrtc {
61namespace plotting {
62
terelius54ce6802016-07-13 06:44:41 -070063namespace {
64
Qingsi Wang8eca1ff2018-02-02 11:49:44 -080065const int kNumMicrosecsPerSec = 1000000;
66
elad.alonec304f92017-03-08 05:03:53 -080067void SortPacketFeedbackVector(std::vector<PacketFeedback>* vec) {
68 auto pred = [](const PacketFeedback& packet_feedback) {
69 return packet_feedback.arrival_time_ms == PacketFeedback::kNotReceived;
70 };
71 vec->erase(std::remove_if(vec->begin(), vec->end(), pred), vec->end());
72 std::sort(vec->begin(), vec->end(), PacketFeedbackComparator());
73}
74
terelius54ce6802016-07-13 06:44:41 -070075std::string SsrcToString(uint32_t ssrc) {
76 std::stringstream ss;
77 ss << "SSRC " << ssrc;
78 return ss.str();
79}
80
81// Checks whether an SSRC is contained in the list of desired SSRCs.
82// Note that an empty SSRC list matches every SSRC.
83bool MatchingSsrc(uint32_t ssrc, const std::vector<uint32_t>& desired_ssrc) {
84 if (desired_ssrc.size() == 0)
85 return true;
86 return std::find(desired_ssrc.begin(), desired_ssrc.end(), ssrc) !=
87 desired_ssrc.end();
88}
89
90double AbsSendTimeToMicroseconds(int64_t abs_send_time) {
91 // The timestamp is a fixed point representation with 6 bits for seconds
92 // and 18 bits for fractions of a second. Thus, we divide by 2^18 to get the
Qingsi Wang8eca1ff2018-02-02 11:49:44 -080093 // time in seconds and then multiply by kNumMicrosecsPerSec to convert to
94 // microseconds.
terelius54ce6802016-07-13 06:44:41 -070095 static constexpr double kTimestampToMicroSec =
Qingsi Wang8eca1ff2018-02-02 11:49:44 -080096 static_cast<double>(kNumMicrosecsPerSec) / static_cast<double>(1ul << 18);
terelius54ce6802016-07-13 06:44:41 -070097 return abs_send_time * kTimestampToMicroSec;
98}
99
100// Computes the difference |later| - |earlier| where |later| and |earlier|
101// are counters that wrap at |modulus|. The difference is chosen to have the
102// least absolute value. For example if |modulus| is 8, then the difference will
103// be chosen in the range [-3, 4]. If |modulus| is 9, then the difference will
104// be in [-4, 4].
105int64_t WrappingDifference(uint32_t later, uint32_t earlier, int64_t modulus) {
106 RTC_DCHECK_LE(1, modulus);
107 RTC_DCHECK_LT(later, modulus);
108 RTC_DCHECK_LT(earlier, modulus);
109 int64_t difference =
110 static_cast<int64_t>(later) - static_cast<int64_t>(earlier);
111 int64_t max_difference = modulus / 2;
112 int64_t min_difference = max_difference - modulus + 1;
113 if (difference > max_difference) {
114 difference -= modulus;
115 }
116 if (difference < min_difference) {
117 difference += modulus;
118 }
terelius6addf492016-08-23 17:34:07 -0700119 if (difference > max_difference / 2 || difference < min_difference / 2) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100120 RTC_LOG(LS_WARNING) << "Difference between" << later << " and " << earlier
121 << " expected to be in the range ("
122 << min_difference / 2 << "," << max_difference / 2
123 << ") but is " << difference
124 << ". Correct unwrapping is uncertain.";
terelius6addf492016-08-23 17:34:07 -0700125 }
terelius54ce6802016-07-13 06:44:41 -0700126 return difference;
127}
128
ivocaac9d6f2016-09-22 07:01:47 -0700129// Return default values for header extensions, to use on streams without stored
130// mapping data. Currently this only applies to audio streams, since the mapping
131// is not stored in the event log.
132// TODO(ivoc): Remove this once this mapping is stored in the event log for
133// audio streams. Tracking bug: webrtc:6399
134webrtc::RtpHeaderExtensionMap GetDefaultHeaderExtensionMap() {
135 webrtc::RtpHeaderExtensionMap default_map;
danilchap4aecc582016-11-15 09:21:00 -0800136 default_map.Register<AudioLevel>(webrtc::RtpExtension::kAudioLevelDefaultId);
terelius007d5622017-08-08 05:40:26 -0700137 default_map.Register<TransmissionOffset>(
138 webrtc::RtpExtension::kTimestampOffsetDefaultId);
danilchap4aecc582016-11-15 09:21:00 -0800139 default_map.Register<AbsoluteSendTime>(
ivocaac9d6f2016-09-22 07:01:47 -0700140 webrtc::RtpExtension::kAbsSendTimeDefaultId);
terelius007d5622017-08-08 05:40:26 -0700141 default_map.Register<VideoOrientation>(
142 webrtc::RtpExtension::kVideoRotationDefaultId);
143 default_map.Register<VideoContentTypeExtension>(
144 webrtc::RtpExtension::kVideoContentTypeDefaultId);
145 default_map.Register<VideoTimingExtension>(
146 webrtc::RtpExtension::kVideoTimingDefaultId);
147 default_map.Register<TransportSequenceNumber>(
148 webrtc::RtpExtension::kTransportSequenceNumberDefaultId);
149 default_map.Register<PlayoutDelayLimits>(
150 webrtc::RtpExtension::kPlayoutDelayDefaultId);
ivocaac9d6f2016-09-22 07:01:47 -0700151 return default_map;
152}
153
tereliusdc35dcd2016-08-01 12:03:27 -0700154constexpr float kLeftMargin = 0.01f;
155constexpr float kRightMargin = 0.02f;
156constexpr float kBottomMargin = 0.02f;
157constexpr float kTopMargin = 0.05f;
terelius54ce6802016-07-13 06:44:41 -0700158
terelius53dc23c2017-03-13 05:24:05 -0700159rtc::Optional<double> NetworkDelayDiff_AbsSendTime(
160 const LoggedRtpPacket& old_packet,
161 const LoggedRtpPacket& new_packet) {
162 if (old_packet.header.extension.hasAbsoluteSendTime &&
163 new_packet.header.extension.hasAbsoluteSendTime) {
164 int64_t send_time_diff = WrappingDifference(
165 new_packet.header.extension.absoluteSendTime,
166 old_packet.header.extension.absoluteSendTime, 1ul << 24);
167 int64_t recv_time_diff = new_packet.timestamp - old_packet.timestamp;
168 double delay_change_us =
169 recv_time_diff - AbsSendTimeToMicroseconds(send_time_diff);
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100170 return delay_change_us / 1000;
terelius53dc23c2017-03-13 05:24:05 -0700171 } else {
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100172 return rtc::nullopt;
terelius6addf492016-08-23 17:34:07 -0700173 }
174}
175
terelius53dc23c2017-03-13 05:24:05 -0700176rtc::Optional<double> NetworkDelayDiff_CaptureTime(
177 const LoggedRtpPacket& old_packet,
178 const LoggedRtpPacket& new_packet) {
179 int64_t send_time_diff = WrappingDifference(
180 new_packet.header.timestamp, old_packet.header.timestamp, 1ull << 32);
181 int64_t recv_time_diff = new_packet.timestamp - old_packet.timestamp;
182
183 const double kVideoSampleRate = 90000;
184 // TODO(terelius): We treat all streams as video for now, even though
185 // audio might be sampled at e.g. 16kHz, because it is really difficult to
186 // figure out the true sampling rate of a stream. The effect is that the
187 // delay will be scaled incorrectly for non-video streams.
188
189 double delay_change =
190 static_cast<double>(recv_time_diff) / 1000 -
191 static_cast<double>(send_time_diff) / kVideoSampleRate * 1000;
192 if (delay_change < -10000 || 10000 < delay_change) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100193 RTC_LOG(LS_WARNING) << "Very large delay change. Timestamps correct?";
194 RTC_LOG(LS_WARNING) << "Old capture time " << old_packet.header.timestamp
195 << ", received time " << old_packet.timestamp;
196 RTC_LOG(LS_WARNING) << "New capture time " << new_packet.header.timestamp
197 << ", received time " << new_packet.timestamp;
198 RTC_LOG(LS_WARNING) << "Receive time difference " << recv_time_diff << " = "
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800199 << static_cast<double>(recv_time_diff) /
200 kNumMicrosecsPerSec
201 << "s";
Mirko Bonadei675513b2017-11-09 11:09:25 +0100202 RTC_LOG(LS_WARNING) << "Send time difference " << send_time_diff << " = "
203 << static_cast<double>(send_time_diff) /
204 kVideoSampleRate
205 << "s";
terelius53dc23c2017-03-13 05:24:05 -0700206 }
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100207 return delay_change;
terelius53dc23c2017-03-13 05:24:05 -0700208}
209
210// For each element in data, use |get_y()| to extract a y-coordinate and
211// store the result in a TimeSeries.
212template <typename DataType>
213void ProcessPoints(
214 rtc::FunctionView<rtc::Optional<float>(const DataType&)> get_y,
215 const std::vector<DataType>& data,
216 uint64_t begin_time,
217 TimeSeries* result) {
218 for (size_t i = 0; i < data.size(); i++) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800219 float x = static_cast<float>(data[i].timestamp - begin_time) /
220 kNumMicrosecsPerSec;
terelius53dc23c2017-03-13 05:24:05 -0700221 rtc::Optional<float> y = get_y(data[i]);
222 if (y)
223 result->points.emplace_back(x, *y);
224 }
225}
226
227// For each pair of adjacent elements in |data|, use |get_y| to extract a
terelius6addf492016-08-23 17:34:07 -0700228// y-coordinate and store the result in a TimeSeries. Note that the x-coordinate
229// will be the time of the second element in the pair.
terelius53dc23c2017-03-13 05:24:05 -0700230template <typename DataType, typename ResultType>
231void ProcessPairs(
232 rtc::FunctionView<rtc::Optional<ResultType>(const DataType&,
233 const DataType&)> get_y,
234 const std::vector<DataType>& data,
235 uint64_t begin_time,
236 TimeSeries* result) {
tereliusccbbf8d2016-08-10 07:34:28 -0700237 for (size_t i = 1; i < data.size(); i++) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800238 float x = static_cast<float>(data[i].timestamp - begin_time) /
239 kNumMicrosecsPerSec;
terelius53dc23c2017-03-13 05:24:05 -0700240 rtc::Optional<ResultType> y = get_y(data[i - 1], data[i]);
241 if (y)
242 result->points.emplace_back(x, static_cast<float>(*y));
243 }
244}
245
246// For each element in data, use |extract()| to extract a y-coordinate and
247// store the result in a TimeSeries.
248template <typename DataType, typename ResultType>
249void AccumulatePoints(
250 rtc::FunctionView<rtc::Optional<ResultType>(const DataType&)> extract,
251 const std::vector<DataType>& data,
252 uint64_t begin_time,
253 TimeSeries* result) {
254 ResultType sum = 0;
255 for (size_t i = 0; i < data.size(); i++) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800256 float x = static_cast<float>(data[i].timestamp - begin_time) /
257 kNumMicrosecsPerSec;
terelius53dc23c2017-03-13 05:24:05 -0700258 rtc::Optional<ResultType> y = extract(data[i]);
259 if (y) {
260 sum += *y;
261 result->points.emplace_back(x, static_cast<float>(sum));
262 }
263 }
264}
265
266// For each pair of adjacent elements in |data|, use |extract()| to extract a
267// y-coordinate and store the result in a TimeSeries. Note that the x-coordinate
268// will be the time of the second element in the pair.
269template <typename DataType, typename ResultType>
270void AccumulatePairs(
271 rtc::FunctionView<rtc::Optional<ResultType>(const DataType&,
272 const DataType&)> extract,
273 const std::vector<DataType>& data,
274 uint64_t begin_time,
275 TimeSeries* result) {
276 ResultType sum = 0;
277 for (size_t i = 1; i < data.size(); i++) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800278 float x = static_cast<float>(data[i].timestamp - begin_time) /
279 kNumMicrosecsPerSec;
terelius53dc23c2017-03-13 05:24:05 -0700280 rtc::Optional<ResultType> y = extract(data[i - 1], data[i]);
281 if (y)
282 sum += *y;
283 result->points.emplace_back(x, static_cast<float>(sum));
tereliusccbbf8d2016-08-10 07:34:28 -0700284 }
285}
286
terelius6addf492016-08-23 17:34:07 -0700287// Calculates a moving average of |data| and stores the result in a TimeSeries.
288// A data point is generated every |step| microseconds from |begin_time|
289// to |end_time|. The value of each data point is the average of the data
290// during the preceeding |window_duration_us| microseconds.
terelius53dc23c2017-03-13 05:24:05 -0700291template <typename DataType, typename ResultType>
292void MovingAverage(
293 rtc::FunctionView<rtc::Optional<ResultType>(const DataType&)> extract,
294 const std::vector<DataType>& data,
295 uint64_t begin_time,
296 uint64_t end_time,
297 uint64_t window_duration_us,
298 uint64_t step,
299 webrtc::plotting::TimeSeries* result) {
terelius6addf492016-08-23 17:34:07 -0700300 size_t window_index_begin = 0;
301 size_t window_index_end = 0;
terelius53dc23c2017-03-13 05:24:05 -0700302 ResultType sum_in_window = 0;
terelius6addf492016-08-23 17:34:07 -0700303
304 for (uint64_t t = begin_time; t < end_time + step; t += step) {
305 while (window_index_end < data.size() &&
306 data[window_index_end].timestamp < t) {
terelius53dc23c2017-03-13 05:24:05 -0700307 rtc::Optional<ResultType> value = extract(data[window_index_end]);
308 if (value)
309 sum_in_window += *value;
terelius6addf492016-08-23 17:34:07 -0700310 ++window_index_end;
311 }
312 while (window_index_begin < data.size() &&
313 data[window_index_begin].timestamp < t - window_duration_us) {
terelius53dc23c2017-03-13 05:24:05 -0700314 rtc::Optional<ResultType> value = extract(data[window_index_begin]);
315 if (value)
316 sum_in_window -= *value;
terelius6addf492016-08-23 17:34:07 -0700317 ++window_index_begin;
318 }
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800319 float window_duration_s =
320 static_cast<float>(window_duration_us) / kNumMicrosecsPerSec;
321 float x = static_cast<float>(t - begin_time) / kNumMicrosecsPerSec;
terelius53dc23c2017-03-13 05:24:05 -0700322 float y = sum_in_window / window_duration_s;
terelius6addf492016-08-23 17:34:07 -0700323 result->points.emplace_back(x, y);
324 }
325}
326
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800327const char kUnknownEnumValue[] = "unknown";
328
329const char kIceCandidateTypeLocal[] = "local";
330const char kIceCandidateTypeStun[] = "stun";
331const char kIceCandidateTypePrflx[] = "prflx";
332const char kIceCandidateTypeRelay[] = "relay";
333
334const char kProtocolUdp[] = "udp";
335const char kProtocolTcp[] = "tcp";
336const char kProtocolSsltcp[] = "ssltcp";
337const char kProtocolTls[] = "tls";
338
339const char kAddressFamilyIpv4[] = "ipv4";
340const char kAddressFamilyIpv6[] = "ipv6";
341
342const char kNetworkTypeEthernet[] = "ethernet";
343const char kNetworkTypeLoopback[] = "loopback";
344const char kNetworkTypeWifi[] = "wifi";
345const char kNetworkTypeVpn[] = "vpn";
346const char kNetworkTypeCellular[] = "cellular";
347
348std::string GetIceCandidateTypeAsString(webrtc::IceCandidateType type) {
349 switch (type) {
350 case webrtc::IceCandidateType::kLocal:
351 return kIceCandidateTypeLocal;
352 case webrtc::IceCandidateType::kStun:
353 return kIceCandidateTypeStun;
354 case webrtc::IceCandidateType::kPrflx:
355 return kIceCandidateTypePrflx;
356 case webrtc::IceCandidateType::kRelay:
357 return kIceCandidateTypeRelay;
358 default:
359 return kUnknownEnumValue;
360 }
361}
362
363std::string GetProtocolAsString(webrtc::IceCandidatePairProtocol protocol) {
364 switch (protocol) {
365 case webrtc::IceCandidatePairProtocol::kUdp:
366 return kProtocolUdp;
367 case webrtc::IceCandidatePairProtocol::kTcp:
368 return kProtocolTcp;
369 case webrtc::IceCandidatePairProtocol::kSsltcp:
370 return kProtocolSsltcp;
371 case webrtc::IceCandidatePairProtocol::kTls:
372 return kProtocolTls;
373 default:
374 return kUnknownEnumValue;
375 }
376}
377
378std::string GetAddressFamilyAsString(
379 webrtc::IceCandidatePairAddressFamily family) {
380 switch (family) {
381 case webrtc::IceCandidatePairAddressFamily::kIpv4:
382 return kAddressFamilyIpv4;
383 case webrtc::IceCandidatePairAddressFamily::kIpv6:
384 return kAddressFamilyIpv6;
385 default:
386 return kUnknownEnumValue;
387 }
388}
389
390std::string GetNetworkTypeAsString(webrtc::IceCandidateNetworkType type) {
391 switch (type) {
392 case webrtc::IceCandidateNetworkType::kEthernet:
393 return kNetworkTypeEthernet;
394 case webrtc::IceCandidateNetworkType::kLoopback:
395 return kNetworkTypeLoopback;
396 case webrtc::IceCandidateNetworkType::kWifi:
397 return kNetworkTypeWifi;
398 case webrtc::IceCandidateNetworkType::kVpn:
399 return kNetworkTypeVpn;
400 case webrtc::IceCandidateNetworkType::kCellular:
401 return kNetworkTypeCellular;
402 default:
403 return kUnknownEnumValue;
404 }
405}
406
407std::string GetCandidatePairLogDescriptionAsString(
408 const ParsedRtcEventLog::IceCandidatePairConfig& config) {
409 // Example: stun:wifi->relay(tcp):cellular@udp:ipv4
410 // represents a pair of a local server-reflexive candidate on a WiFi network
411 // and a remote relay candidate using TCP as the relay protocol on a cell
412 // network, when the candidate pair communicates over UDP using IPv4.
413 std::stringstream ss;
414 std::string local_candidate_type =
415 GetIceCandidateTypeAsString(config.local_candidate_type);
416 std::string remote_candidate_type =
417 GetIceCandidateTypeAsString(config.remote_candidate_type);
418 if (config.local_candidate_type == webrtc::IceCandidateType::kRelay) {
419 local_candidate_type +=
420 "(" + GetProtocolAsString(config.local_relay_protocol) + ")";
421 }
422 ss << local_candidate_type << ":"
423 << GetNetworkTypeAsString(config.local_network_type) << ":"
424 << GetAddressFamilyAsString(config.local_address_family) << "->"
425 << remote_candidate_type << ":"
426 << GetAddressFamilyAsString(config.remote_address_family) << "@"
427 << GetProtocolAsString(config.candidate_pair_protocol);
428 return ss.str();
429}
430
terelius54ce6802016-07-13 06:44:41 -0700431} // namespace
432
terelius54ce6802016-07-13 06:44:41 -0700433EventLogAnalyzer::EventLogAnalyzer(const ParsedRtcEventLog& log)
434 : parsed_log_(log), window_duration_(250000), step_(10000) {
435 uint64_t first_timestamp = std::numeric_limits<uint64_t>::max();
436 uint64_t last_timestamp = std::numeric_limits<uint64_t>::min();
terelius88e64e52016-07-19 01:51:06 -0700437
terelius88e64e52016-07-19 01:51:06 -0700438 PacketDirection direction;
terelius88e64e52016-07-19 01:51:06 -0700439 uint8_t header[IP_PACKET_SIZE];
440 size_t header_length;
441 size_t total_length;
442
perkjbbbad6d2017-05-19 06:30:28 -0700443 uint8_t last_incoming_rtcp_packet[IP_PACKET_SIZE];
444 uint8_t last_incoming_rtcp_packet_length = 0;
445
ivocaac9d6f2016-09-22 07:01:47 -0700446 // Make a default extension map for streams without configuration information.
447 // TODO(ivoc): Once configuration of audio streams is stored in the event log,
448 // this can be removed. Tracking bug: webrtc:6399
449 RtpHeaderExtensionMap default_extension_map = GetDefaultHeaderExtensionMap();
450
henrik.lundin3c938fc2017-06-14 06:09:58 -0700451 rtc::Optional<uint64_t> last_log_start;
452
terelius54ce6802016-07-13 06:44:41 -0700453 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) {
454 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i);
terelius88e64e52016-07-19 01:51:06 -0700455 if (event_type != ParsedRtcEventLog::VIDEO_RECEIVER_CONFIG_EVENT &&
456 event_type != ParsedRtcEventLog::VIDEO_SENDER_CONFIG_EVENT &&
457 event_type != ParsedRtcEventLog::AUDIO_RECEIVER_CONFIG_EVENT &&
terelius88c1d2b2016-08-01 05:20:33 -0700458 event_type != ParsedRtcEventLog::AUDIO_SENDER_CONFIG_EVENT &&
459 event_type != ParsedRtcEventLog::LOG_START &&
460 event_type != ParsedRtcEventLog::LOG_END) {
terelius88e64e52016-07-19 01:51:06 -0700461 uint64_t timestamp = parsed_log_.GetTimestamp(i);
462 first_timestamp = std::min(first_timestamp, timestamp);
463 last_timestamp = std::max(last_timestamp, timestamp);
464 }
465
466 switch (parsed_log_.GetEventType(i)) {
467 case ParsedRtcEventLog::VIDEO_RECEIVER_CONFIG_EVENT: {
terelius8fbc7652017-05-31 02:03:16 -0700468 rtclog::StreamConfig config = parsed_log_.GetVideoReceiveConfig(i);
perkj09e71da2017-05-22 03:26:49 -0700469 StreamId stream(config.remote_ssrc, kIncomingPacket);
terelius0740a202016-08-08 10:21:04 -0700470 video_ssrcs_.insert(stream);
perkj09e71da2017-05-22 03:26:49 -0700471 StreamId rtx_stream(config.rtx_ssrc, kIncomingPacket);
brandtr14742122017-01-27 04:53:07 -0800472 video_ssrcs_.insert(rtx_stream);
473 rtx_ssrcs_.insert(rtx_stream);
terelius88e64e52016-07-19 01:51:06 -0700474 break;
475 }
476 case ParsedRtcEventLog::VIDEO_SENDER_CONFIG_EVENT: {
terelius8fbc7652017-05-31 02:03:16 -0700477 std::vector<rtclog::StreamConfig> configs =
478 parsed_log_.GetVideoSendConfig(i);
terelius405f90c2017-06-01 03:50:31 -0700479 for (const auto& config : configs) {
480 StreamId stream(config.local_ssrc, kOutgoingPacket);
terelius8fbc7652017-05-31 02:03:16 -0700481 video_ssrcs_.insert(stream);
terelius405f90c2017-06-01 03:50:31 -0700482 StreamId rtx_stream(config.rtx_ssrc, kOutgoingPacket);
terelius8fbc7652017-05-31 02:03:16 -0700483 video_ssrcs_.insert(rtx_stream);
484 rtx_ssrcs_.insert(rtx_stream);
485 }
terelius88e64e52016-07-19 01:51:06 -0700486 break;
487 }
488 case ParsedRtcEventLog::AUDIO_RECEIVER_CONFIG_EVENT: {
terelius8fbc7652017-05-31 02:03:16 -0700489 rtclog::StreamConfig config = parsed_log_.GetAudioReceiveConfig(i);
perkjac8f52d2017-05-22 09:36:28 -0700490 StreamId stream(config.remote_ssrc, kIncomingPacket);
ivoce0928d82016-10-10 05:12:51 -0700491 audio_ssrcs_.insert(stream);
terelius88e64e52016-07-19 01:51:06 -0700492 break;
493 }
494 case ParsedRtcEventLog::AUDIO_SENDER_CONFIG_EVENT: {
terelius8fbc7652017-05-31 02:03:16 -0700495 rtclog::StreamConfig config = parsed_log_.GetAudioSendConfig(i);
perkjf4726992017-05-22 10:12:26 -0700496 StreamId stream(config.local_ssrc, kOutgoingPacket);
ivoce0928d82016-10-10 05:12:51 -0700497 audio_ssrcs_.insert(stream);
terelius88e64e52016-07-19 01:51:06 -0700498 break;
499 }
500 case ParsedRtcEventLog::RTP_EVENT: {
ilnika8e781a2017-06-12 01:02:46 -0700501 RtpHeaderExtensionMap* extension_map = parsed_log_.GetRtpHeader(
Elad Alon1d87b0e2017-10-03 15:01:03 +0200502 i, &direction, header, &header_length, &total_length, nullptr);
terelius88e64e52016-07-19 01:51:06 -0700503 RtpUtility::RtpHeaderParser rtp_parser(header, header_length);
504 RTPHeader parsed_header;
ilnika8e781a2017-06-12 01:02:46 -0700505 if (extension_map != nullptr) {
terelius88e64e52016-07-19 01:51:06 -0700506 rtp_parser.Parse(&parsed_header, extension_map);
ivocaac9d6f2016-09-22 07:01:47 -0700507 } else {
508 // Use the default extension map.
509 // TODO(ivoc): Once configuration of audio streams is stored in the
510 // event log, this can be removed.
511 // Tracking bug: webrtc:6399
512 rtp_parser.Parse(&parsed_header, &default_extension_map);
terelius88e64e52016-07-19 01:51:06 -0700513 }
514 uint64_t timestamp = parsed_log_.GetTimestamp(i);
ilnika8e781a2017-06-12 01:02:46 -0700515 StreamId stream(parsed_header.ssrc, direction);
terelius88e64e52016-07-19 01:51:06 -0700516 rtp_packets_[stream].push_back(
Stefan Holmer13181032016-07-29 14:48:54 +0200517 LoggedRtpPacket(timestamp, parsed_header, total_length));
terelius88e64e52016-07-19 01:51:06 -0700518 break;
519 }
520 case ParsedRtcEventLog::RTCP_EVENT: {
Stefan Holmer13181032016-07-29 14:48:54 +0200521 uint8_t packet[IP_PACKET_SIZE];
perkj77cd58e2017-05-30 03:52:10 -0700522 parsed_log_.GetRtcpPacket(i, &direction, packet, &total_length);
perkjbbbad6d2017-05-19 06:30:28 -0700523 // Currently incoming RTCP packets are logged twice, both for audio and
524 // video. Only act on one of them. Compare against the previous parsed
525 // incoming RTCP packet.
526 if (direction == webrtc::kIncomingPacket) {
527 RTC_CHECK_LE(total_length, IP_PACKET_SIZE);
528 if (total_length == last_incoming_rtcp_packet_length &&
529 memcmp(last_incoming_rtcp_packet, packet, total_length) == 0) {
530 continue;
531 } else {
532 memcpy(last_incoming_rtcp_packet, packet, total_length);
533 last_incoming_rtcp_packet_length = total_length;
534 }
535 }
536 rtcp::CommonHeader header;
537 const uint8_t* packet_end = packet + total_length;
538 for (const uint8_t* block = packet; block < packet_end;
539 block = header.NextPacket()) {
540 RTC_CHECK(header.Parse(block, packet_end - block));
541 if (header.type() == rtcp::TransportFeedback::kPacketType &&
542 header.fmt() == rtcp::TransportFeedback::kFeedbackMessageType) {
543 std::unique_ptr<rtcp::TransportFeedback> rtcp_packet(
terelius2c8e8a32017-06-02 01:29:48 -0700544 rtc::MakeUnique<rtcp::TransportFeedback>());
perkjbbbad6d2017-05-19 06:30:28 -0700545 if (rtcp_packet->Parse(header)) {
546 uint32_t ssrc = rtcp_packet->sender_ssrc();
547 StreamId stream(ssrc, direction);
548 uint64_t timestamp = parsed_log_.GetTimestamp(i);
549 rtcp_packets_[stream].push_back(LoggedRtcpPacket(
550 timestamp, kRtcpTransportFeedback, std::move(rtcp_packet)));
551 }
552 } else if (header.type() == rtcp::SenderReport::kPacketType) {
553 std::unique_ptr<rtcp::SenderReport> rtcp_packet(
terelius2c8e8a32017-06-02 01:29:48 -0700554 rtc::MakeUnique<rtcp::SenderReport>());
perkjbbbad6d2017-05-19 06:30:28 -0700555 if (rtcp_packet->Parse(header)) {
556 uint32_t ssrc = rtcp_packet->sender_ssrc();
557 StreamId stream(ssrc, direction);
558 uint64_t timestamp = parsed_log_.GetTimestamp(i);
559 rtcp_packets_[stream].push_back(
560 LoggedRtcpPacket(timestamp, kRtcpSr, std::move(rtcp_packet)));
561 }
562 } else if (header.type() == rtcp::ReceiverReport::kPacketType) {
563 std::unique_ptr<rtcp::ReceiverReport> rtcp_packet(
terelius2c8e8a32017-06-02 01:29:48 -0700564 rtc::MakeUnique<rtcp::ReceiverReport>());
perkjbbbad6d2017-05-19 06:30:28 -0700565 if (rtcp_packet->Parse(header)) {
566 uint32_t ssrc = rtcp_packet->sender_ssrc();
567 StreamId stream(ssrc, direction);
568 uint64_t timestamp = parsed_log_.GetTimestamp(i);
569 rtcp_packets_[stream].push_back(
570 LoggedRtcpPacket(timestamp, kRtcpRr, std::move(rtcp_packet)));
Stefan Holmer13181032016-07-29 14:48:54 +0200571 }
terelius2c8e8a32017-06-02 01:29:48 -0700572 } else if (header.type() == rtcp::Remb::kPacketType &&
573 header.fmt() == rtcp::Remb::kFeedbackMessageType) {
574 std::unique_ptr<rtcp::Remb> rtcp_packet(
575 rtc::MakeUnique<rtcp::Remb>());
576 if (rtcp_packet->Parse(header)) {
577 uint32_t ssrc = rtcp_packet->sender_ssrc();
578 StreamId stream(ssrc, direction);
579 uint64_t timestamp = parsed_log_.GetTimestamp(i);
580 rtcp_packets_[stream].push_back(LoggedRtcpPacket(
581 timestamp, kRtcpRemb, std::move(rtcp_packet)));
582 }
Stefan Holmer13181032016-07-29 14:48:54 +0200583 }
Stefan Holmer13181032016-07-29 14:48:54 +0200584 }
terelius88e64e52016-07-19 01:51:06 -0700585 break;
586 }
587 case ParsedRtcEventLog::LOG_START: {
henrik.lundin3c938fc2017-06-14 06:09:58 -0700588 if (last_log_start) {
589 // A LOG_END event was missing. Use last_timestamp.
590 RTC_DCHECK_GE(last_timestamp, *last_log_start);
591 log_segments_.push_back(
592 std::make_pair(*last_log_start, last_timestamp));
593 }
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100594 last_log_start = parsed_log_.GetTimestamp(i);
terelius88e64e52016-07-19 01:51:06 -0700595 break;
596 }
597 case ParsedRtcEventLog::LOG_END: {
henrik.lundin3c938fc2017-06-14 06:09:58 -0700598 RTC_DCHECK(last_log_start);
599 log_segments_.push_back(
600 std::make_pair(*last_log_start, parsed_log_.GetTimestamp(i)));
601 last_log_start.reset();
terelius88e64e52016-07-19 01:51:06 -0700602 break;
603 }
terelius424e6cf2017-02-20 05:14:41 -0800604 case ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT: {
henrik.lundin3c938fc2017-06-14 06:09:58 -0700605 uint32_t this_ssrc;
606 parsed_log_.GetAudioPlayout(i, &this_ssrc);
607 audio_playout_events_[this_ssrc].push_back(parsed_log_.GetTimestamp(i));
terelius424e6cf2017-02-20 05:14:41 -0800608 break;
609 }
610 case ParsedRtcEventLog::LOSS_BASED_BWE_UPDATE: {
611 LossBasedBweUpdate bwe_update;
terelius8058e582016-07-25 01:32:41 -0700612 bwe_update.timestamp = parsed_log_.GetTimestamp(i);
terelius424e6cf2017-02-20 05:14:41 -0800613 parsed_log_.GetLossBasedBweUpdate(i, &bwe_update.new_bitrate,
614 &bwe_update.fraction_loss,
615 &bwe_update.expected_packets);
terelius8058e582016-07-25 01:32:41 -0700616 bwe_loss_updates_.push_back(bwe_update);
terelius88e64e52016-07-19 01:51:06 -0700617 break;
618 }
terelius424e6cf2017-02-20 05:14:41 -0800619 case ParsedRtcEventLog::DELAY_BASED_BWE_UPDATE: {
philipel10fc0e62017-04-11 01:50:23 -0700620 bwe_delay_updates_.push_back(parsed_log_.GetDelayBasedBweUpdate(i));
terelius424e6cf2017-02-20 05:14:41 -0800621 break;
622 }
minyue4b7c9522017-01-24 04:54:59 -0800623 case ParsedRtcEventLog::AUDIO_NETWORK_ADAPTATION_EVENT: {
michaelt6e5b2192017-02-22 07:33:27 -0800624 AudioNetworkAdaptationEvent ana_event;
625 ana_event.timestamp = parsed_log_.GetTimestamp(i);
626 parsed_log_.GetAudioNetworkAdaptation(i, &ana_event.config);
627 audio_network_adaptation_events_.push_back(ana_event);
minyue4b7c9522017-01-24 04:54:59 -0800628 break;
629 }
philipel32d00102017-02-27 02:18:46 -0800630 case ParsedRtcEventLog::BWE_PROBE_CLUSTER_CREATED_EVENT: {
philipele127e7a2017-03-29 16:28:53 +0200631 bwe_probe_cluster_created_events_.push_back(
632 parsed_log_.GetBweProbeClusterCreated(i));
philipel32d00102017-02-27 02:18:46 -0800633 break;
634 }
635 case ParsedRtcEventLog::BWE_PROBE_RESULT_EVENT: {
philipele127e7a2017-03-29 16:28:53 +0200636 bwe_probe_result_events_.push_back(parsed_log_.GetBweProbeResult(i));
philipel32d00102017-02-27 02:18:46 -0800637 break;
638 }
Ilya Nikolaevskiya4259f62017-12-05 13:19:45 +0100639 case ParsedRtcEventLog::ALR_STATE_EVENT: {
640 alr_state_events_.push_back(parsed_log_.GetAlrState(i));
641 break;
642 }
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800643 case ParsedRtcEventLog::ICE_CANDIDATE_PAIR_CONFIG: {
644 ice_candidate_pair_configs_.push_back(
645 parsed_log_.GetIceCandidatePairConfig(i));
646 break;
647 }
648 case ParsedRtcEventLog::ICE_CANDIDATE_PAIR_EVENT: {
649 ice_candidate_pair_events_.push_back(
650 parsed_log_.GetIceCandidatePairEvent(i));
651 break;
652 }
terelius88e64e52016-07-19 01:51:06 -0700653 case ParsedRtcEventLog::UNKNOWN_EVENT: {
654 break;
655 }
656 }
terelius54ce6802016-07-13 06:44:41 -0700657 }
terelius88e64e52016-07-19 01:51:06 -0700658
terelius54ce6802016-07-13 06:44:41 -0700659 if (last_timestamp < first_timestamp) {
660 // No useful events in the log.
661 first_timestamp = last_timestamp = 0;
662 }
663 begin_time_ = first_timestamp;
664 end_time_ = last_timestamp;
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800665 call_duration_s_ = ToCallTime(end_time_);
henrik.lundin3c938fc2017-06-14 06:09:58 -0700666 if (last_log_start) {
667 // The log was missing the last LOG_END event. Fake it.
668 log_segments_.push_back(std::make_pair(*last_log_start, end_time_));
669 }
Bjorn Terelius2eb31882017-11-30 15:15:25 +0100670 RTC_LOG(LS_INFO) << "Found " << log_segments_.size()
671 << " (LOG_START, LOG_END) segments in log.";
terelius54ce6802016-07-13 06:44:41 -0700672}
673
Niels Möller245f17e2017-08-21 10:45:07 +0200674class BitrateObserver : public SendSideCongestionController::Observer,
Stefan Holmer13181032016-07-29 14:48:54 +0200675 public RemoteBitrateObserver {
676 public:
677 BitrateObserver() : last_bitrate_bps_(0), bitrate_updated_(false) {}
678
679 void OnNetworkChanged(uint32_t bitrate_bps,
680 uint8_t fraction_loss,
minyue78b4d562016-11-30 04:47:39 -0800681 int64_t rtt_ms,
682 int64_t probing_interval_ms) override {
Stefan Holmer13181032016-07-29 14:48:54 +0200683 last_bitrate_bps_ = bitrate_bps;
684 bitrate_updated_ = true;
685 }
686
687 void OnReceiveBitrateChanged(const std::vector<uint32_t>& ssrcs,
688 uint32_t bitrate) override {}
689
690 uint32_t last_bitrate_bps() const { return last_bitrate_bps_; }
691 bool GetAndResetBitrateUpdated() {
692 bool bitrate_updated = bitrate_updated_;
693 bitrate_updated_ = false;
694 return bitrate_updated;
695 }
696
697 private:
698 uint32_t last_bitrate_bps_;
699 bool bitrate_updated_;
700};
701
Stefan Holmer99f8e082016-09-09 13:37:50 +0200702bool EventLogAnalyzer::IsRtxSsrc(StreamId stream_id) const {
terelius0740a202016-08-08 10:21:04 -0700703 return rtx_ssrcs_.count(stream_id) == 1;
704}
705
Stefan Holmer99f8e082016-09-09 13:37:50 +0200706bool EventLogAnalyzer::IsVideoSsrc(StreamId stream_id) const {
terelius0740a202016-08-08 10:21:04 -0700707 return video_ssrcs_.count(stream_id) == 1;
708}
709
Stefan Holmer99f8e082016-09-09 13:37:50 +0200710bool EventLogAnalyzer::IsAudioSsrc(StreamId stream_id) const {
terelius0740a202016-08-08 10:21:04 -0700711 return audio_ssrcs_.count(stream_id) == 1;
712}
713
Stefan Holmer99f8e082016-09-09 13:37:50 +0200714std::string EventLogAnalyzer::GetStreamName(StreamId stream_id) const {
715 std::stringstream name;
716 if (IsAudioSsrc(stream_id)) {
717 name << "Audio ";
718 } else if (IsVideoSsrc(stream_id)) {
719 name << "Video ";
720 } else {
721 name << "Unknown ";
722 }
723 if (IsRtxSsrc(stream_id))
724 name << "RTX ";
ivocaac9d6f2016-09-22 07:01:47 -0700725 if (stream_id.GetDirection() == kIncomingPacket) {
726 name << "(In) ";
727 } else {
728 name << "(Out) ";
729 }
Stefan Holmer99f8e082016-09-09 13:37:50 +0200730 name << SsrcToString(stream_id.GetSsrc());
731 return name.str();
732}
733
Bjorn Terelius0295a962017-10-25 17:42:41 +0200734// This is much more reliable for outgoing streams than for incoming streams.
735rtc::Optional<uint32_t> EventLogAnalyzer::EstimateRtpClockFrequency(
736 const std::vector<LoggedRtpPacket>& packets) const {
737 RTC_CHECK(packets.size() >= 2);
738 uint64_t end_time_us = log_segments_.empty()
739 ? std::numeric_limits<uint64_t>::max()
740 : log_segments_.front().second;
741 SeqNumUnwrapper<uint32_t> unwrapper;
742 uint64_t first_rtp_timestamp = unwrapper.Unwrap(packets[0].header.timestamp);
743 uint64_t first_log_timestamp = packets[0].timestamp;
744 uint64_t last_rtp_timestamp = first_rtp_timestamp;
745 uint64_t last_log_timestamp = first_log_timestamp;
746 for (size_t i = 1; i < packets.size(); i++) {
747 if (packets[i].timestamp > end_time_us)
748 break;
749 last_rtp_timestamp = unwrapper.Unwrap(packets[i].header.timestamp);
750 last_log_timestamp = packets[i].timestamp;
751 }
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800752 if (last_log_timestamp - first_log_timestamp < kNumMicrosecsPerSec) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100753 RTC_LOG(LS_WARNING)
Bjorn Terelius0295a962017-10-25 17:42:41 +0200754 << "Failed to estimate RTP clock frequency: Stream too short. ("
755 << packets.size() << " packets, "
756 << last_log_timestamp - first_log_timestamp << " us)";
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100757 return rtc::nullopt;
Bjorn Terelius0295a962017-10-25 17:42:41 +0200758 }
759 double duration =
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800760 static_cast<double>(last_log_timestamp - first_log_timestamp) /
761 kNumMicrosecsPerSec;
Bjorn Terelius0295a962017-10-25 17:42:41 +0200762 double estimated_frequency =
763 (last_rtp_timestamp - first_rtp_timestamp) / duration;
764 for (uint32_t f : {8000, 16000, 32000, 48000, 90000}) {
765 if (std::fabs(estimated_frequency - f) < 0.05 * f) {
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100766 return f;
Bjorn Terelius0295a962017-10-25 17:42:41 +0200767 }
768 }
Mirko Bonadei675513b2017-11-09 11:09:25 +0100769 RTC_LOG(LS_WARNING) << "Failed to estimate RTP clock frequency: Estimate "
770 << estimated_frequency
771 << "not close to any stardard RTP frequency.";
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100772 return rtc::nullopt;
Bjorn Terelius0295a962017-10-25 17:42:41 +0200773}
774
Bjorn Terelius2eb31882017-11-30 15:15:25 +0100775float EventLogAnalyzer::ToCallTime(int64_t timestamp) const {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800776 return static_cast<float>(timestamp - begin_time_) / kNumMicrosecsPerSec;
Bjorn Terelius2eb31882017-11-30 15:15:25 +0100777}
778
terelius54ce6802016-07-13 06:44:41 -0700779void EventLogAnalyzer::CreatePacketGraph(PacketDirection desired_direction,
780 Plot* plot) {
terelius6addf492016-08-23 17:34:07 -0700781 for (auto& kv : rtp_packets_) {
782 StreamId stream_id = kv.first;
783 const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
784 // Filter on direction and SSRC.
785 if (stream_id.GetDirection() != desired_direction ||
786 !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
787 continue;
terelius54ce6802016-07-13 06:44:41 -0700788 }
terelius54ce6802016-07-13 06:44:41 -0700789
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +0100790 TimeSeries time_series(GetStreamName(stream_id), LineStyle::kBar);
terelius53dc23c2017-03-13 05:24:05 -0700791 ProcessPoints<LoggedRtpPacket>(
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100792 [](const LoggedRtpPacket& packet) {
terelius53dc23c2017-03-13 05:24:05 -0700793 return rtc::Optional<float>(packet.total_length);
794 },
795 packet_stream, begin_time_, &time_series);
philipel35ba9bd2017-04-19 05:58:51 -0700796 plot->AppendTimeSeries(std::move(time_series));
terelius54ce6802016-07-13 06:44:41 -0700797 }
798
tereliusdc35dcd2016-08-01 12:03:27 -0700799 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
800 plot->SetSuggestedYAxis(0, 1, "Packet size (bytes)", kBottomMargin,
801 kTopMargin);
terelius54ce6802016-07-13 06:44:41 -0700802 if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
tereliusdc35dcd2016-08-01 12:03:27 -0700803 plot->SetTitle("Incoming RTP packets");
terelius54ce6802016-07-13 06:44:41 -0700804 } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
tereliusdc35dcd2016-08-01 12:03:27 -0700805 plot->SetTitle("Outgoing RTP packets");
terelius54ce6802016-07-13 06:44:41 -0700806 }
807}
808
philipelccd74892016-09-05 02:46:25 -0700809template <typename T>
810void EventLogAnalyzer::CreateAccumulatedPacketsTimeSeries(
811 PacketDirection desired_direction,
812 Plot* plot,
813 const std::map<StreamId, std::vector<T>>& packets,
814 const std::string& label_prefix) {
815 for (auto& kv : packets) {
816 StreamId stream_id = kv.first;
817 const std::vector<T>& packet_stream = kv.second;
818 // Filter on direction and SSRC.
819 if (stream_id.GetDirection() != desired_direction ||
820 !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
821 continue;
822 }
823
terelius23c595a2017-03-15 01:59:12 -0700824 std::string label = label_prefix + " " + GetStreamName(stream_id);
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +0100825 TimeSeries time_series(label, LineStyle::kStep);
philipelccd74892016-09-05 02:46:25 -0700826 for (size_t i = 0; i < packet_stream.size(); i++) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800827 float x = ToCallTime(packet_stream[i].timestamp);
philipelccd74892016-09-05 02:46:25 -0700828 time_series.points.emplace_back(x, i + 1);
829 }
830
philipel35ba9bd2017-04-19 05:58:51 -0700831 plot->AppendTimeSeries(std::move(time_series));
philipelccd74892016-09-05 02:46:25 -0700832 }
833}
834
835void EventLogAnalyzer::CreateAccumulatedPacketsGraph(
836 PacketDirection desired_direction,
837 Plot* plot) {
838 CreateAccumulatedPacketsTimeSeries(desired_direction, plot, rtp_packets_,
839 "RTP");
840 CreateAccumulatedPacketsTimeSeries(desired_direction, plot, rtcp_packets_,
841 "RTCP");
842
843 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
844 plot->SetSuggestedYAxis(0, 1, "Received Packets", kBottomMargin, kTopMargin);
845 if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
846 plot->SetTitle("Accumulated Incoming RTP/RTCP packets");
847 } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
848 plot->SetTitle("Accumulated Outgoing RTP/RTCP packets");
849 }
850}
851
terelius54ce6802016-07-13 06:44:41 -0700852// For each SSRC, plot the time between the consecutive playouts.
853void EventLogAnalyzer::CreatePlayoutGraph(Plot* plot) {
854 std::map<uint32_t, TimeSeries> time_series;
855 std::map<uint32_t, uint64_t> last_playout;
856
857 uint32_t ssrc;
terelius54ce6802016-07-13 06:44:41 -0700858
859 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) {
860 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i);
861 if (event_type == ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT) {
862 parsed_log_.GetAudioPlayout(i, &ssrc);
863 uint64_t timestamp = parsed_log_.GetTimestamp(i);
864 if (MatchingSsrc(ssrc, desired_ssrc_)) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800865 float x = ToCallTime(timestamp);
terelius54ce6802016-07-13 06:44:41 -0700866 float y = static_cast<float>(timestamp - last_playout[ssrc]) / 1000;
867 if (time_series[ssrc].points.size() == 0) {
868 // There were no previusly logged playout for this SSRC.
869 // Generate a point, but place it on the x-axis.
870 y = 0;
871 }
terelius54ce6802016-07-13 06:44:41 -0700872 time_series[ssrc].points.push_back(TimeSeriesPoint(x, y));
873 last_playout[ssrc] = timestamp;
874 }
875 }
876 }
877
878 // Set labels and put in graph.
879 for (auto& kv : time_series) {
880 kv.second.label = SsrcToString(kv.first);
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +0100881 kv.second.line_style = LineStyle::kBar;
philipel35ba9bd2017-04-19 05:58:51 -0700882 plot->AppendTimeSeries(std::move(kv.second));
terelius54ce6802016-07-13 06:44:41 -0700883 }
884
tereliusdc35dcd2016-08-01 12:03:27 -0700885 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
886 plot->SetSuggestedYAxis(0, 1, "Time since last playout (ms)", kBottomMargin,
887 kTopMargin);
888 plot->SetTitle("Audio playout");
terelius54ce6802016-07-13 06:44:41 -0700889}
890
ivocaac9d6f2016-09-22 07:01:47 -0700891// For audio SSRCs, plot the audio level.
892void EventLogAnalyzer::CreateAudioLevelGraph(Plot* plot) {
893 std::map<StreamId, TimeSeries> time_series;
894
895 for (auto& kv : rtp_packets_) {
896 StreamId stream_id = kv.first;
897 const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
898 // TODO(ivoc): When audio send/receive configs are stored in the event
899 // log, a check should be added here to only process audio
900 // streams. Tracking bug: webrtc:6399
901 for (auto& packet : packet_stream) {
902 if (packet.header.extension.hasAudioLevel) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800903 float x = ToCallTime(packet.timestamp);
ivocaac9d6f2016-09-22 07:01:47 -0700904 // The audio level is stored in -dBov (so e.g. -10 dBov is stored as 10)
905 // Here we convert it to dBov.
906 float y = static_cast<float>(-packet.header.extension.audioLevel);
907 time_series[stream_id].points.emplace_back(TimeSeriesPoint(x, y));
908 }
909 }
910 }
911
912 for (auto& series : time_series) {
913 series.second.label = GetStreamName(series.first);
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +0100914 series.second.line_style = LineStyle::kLine;
philipel35ba9bd2017-04-19 05:58:51 -0700915 plot->AppendTimeSeries(std::move(series.second));
ivocaac9d6f2016-09-22 07:01:47 -0700916 }
917
918 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
ivocbf676632016-11-24 08:30:34 -0800919 plot->SetYAxis(-127, 0, "Audio level (dBov)", kBottomMargin,
ivocaac9d6f2016-09-22 07:01:47 -0700920 kTopMargin);
921 plot->SetTitle("Audio level");
922}
923
terelius54ce6802016-07-13 06:44:41 -0700924// For each SSRC, plot the time between the consecutive playouts.
925void EventLogAnalyzer::CreateSequenceNumberGraph(Plot* plot) {
terelius6addf492016-08-23 17:34:07 -0700926 for (auto& kv : rtp_packets_) {
927 StreamId stream_id = kv.first;
928 const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
929 // Filter on direction and SSRC.
930 if (stream_id.GetDirection() != kIncomingPacket ||
931 !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
932 continue;
terelius54ce6802016-07-13 06:44:41 -0700933 }
terelius54ce6802016-07-13 06:44:41 -0700934
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +0100935 TimeSeries time_series(GetStreamName(stream_id), LineStyle::kBar);
terelius53dc23c2017-03-13 05:24:05 -0700936 ProcessPairs<LoggedRtpPacket, float>(
937 [](const LoggedRtpPacket& old_packet,
938 const LoggedRtpPacket& new_packet) {
939 int64_t diff =
940 WrappingDifference(new_packet.header.sequenceNumber,
941 old_packet.header.sequenceNumber, 1ul << 16);
Oskar Sundbom3928dbc2017-11-16 10:53:09 +0100942 return diff;
terelius53dc23c2017-03-13 05:24:05 -0700943 },
944 packet_stream, begin_time_, &time_series);
philipel35ba9bd2017-04-19 05:58:51 -0700945 plot->AppendTimeSeries(std::move(time_series));
terelius54ce6802016-07-13 06:44:41 -0700946 }
947
tereliusdc35dcd2016-08-01 12:03:27 -0700948 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
949 plot->SetSuggestedYAxis(0, 1, "Difference since last packet", kBottomMargin,
950 kTopMargin);
951 plot->SetTitle("Sequence number");
terelius54ce6802016-07-13 06:44:41 -0700952}
953
Stefan Holmer99f8e082016-09-09 13:37:50 +0200954void EventLogAnalyzer::CreateIncomingPacketLossGraph(Plot* plot) {
955 for (auto& kv : rtp_packets_) {
956 StreamId stream_id = kv.first;
957 const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
958 // Filter on direction and SSRC.
959 if (stream_id.GetDirection() != kIncomingPacket ||
terelius4c9b4af2017-01-30 08:44:51 -0800960 !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
961 packet_stream.size() == 0) {
Stefan Holmer99f8e082016-09-09 13:37:50 +0200962 continue;
963 }
964
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +0100965 TimeSeries time_series(GetStreamName(stream_id), LineStyle::kLine,
966 PointStyle::kHighlight);
Stefan Holmer99f8e082016-09-09 13:37:50 +0200967 const uint64_t kWindowUs = 1000000;
terelius4c9b4af2017-01-30 08:44:51 -0800968 const uint64_t kStep = 1000000;
Bjorn Terelius2eb31882017-11-30 15:15:25 +0100969 SeqNumUnwrapper<uint16_t> unwrapper_;
970 SeqNumUnwrapper<uint16_t> prior_unwrapper_;
terelius4c9b4af2017-01-30 08:44:51 -0800971 size_t window_index_begin = 0;
972 size_t window_index_end = 0;
973 int64_t highest_seq_number =
974 unwrapper_.Unwrap(packet_stream[0].header.sequenceNumber) - 1;
975 int64_t highest_prior_seq_number =
976 prior_unwrapper_.Unwrap(packet_stream[0].header.sequenceNumber) - 1;
977
978 for (uint64_t t = begin_time_; t < end_time_ + kStep; t += kStep) {
979 while (window_index_end < packet_stream.size() &&
980 packet_stream[window_index_end].timestamp < t) {
981 int64_t sequence_number = unwrapper_.Unwrap(
982 packet_stream[window_index_end].header.sequenceNumber);
983 highest_seq_number = std::max(highest_seq_number, sequence_number);
984 ++window_index_end;
Stefan Holmer99f8e082016-09-09 13:37:50 +0200985 }
terelius4c9b4af2017-01-30 08:44:51 -0800986 while (window_index_begin < packet_stream.size() &&
987 packet_stream[window_index_begin].timestamp < t - kWindowUs) {
988 int64_t sequence_number = prior_unwrapper_.Unwrap(
989 packet_stream[window_index_begin].header.sequenceNumber);
990 highest_prior_seq_number =
991 std::max(highest_prior_seq_number, sequence_number);
992 ++window_index_begin;
993 }
Qingsi Wang8eca1ff2018-02-02 11:49:44 -0800994 float x = ToCallTime(t);
terelius4c9b4af2017-01-30 08:44:51 -0800995 int64_t expected_packets = highest_seq_number - highest_prior_seq_number;
996 if (expected_packets > 0) {
997 int64_t received_packets = window_index_end - window_index_begin;
998 int64_t lost_packets = expected_packets - received_packets;
999 float y = static_cast<float>(lost_packets) / expected_packets * 100;
1000 time_series.points.emplace_back(x, y);
1001 }
Stefan Holmer99f8e082016-09-09 13:37:50 +02001002 }
philipel35ba9bd2017-04-19 05:58:51 -07001003 plot->AppendTimeSeries(std::move(time_series));
Stefan Holmer99f8e082016-09-09 13:37:50 +02001004 }
1005
1006 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1007 plot->SetSuggestedYAxis(0, 1, "Estimated loss rate (%)", kBottomMargin,
1008 kTopMargin);
1009 plot->SetTitle("Estimated incoming loss rate");
1010}
1011
terelius2ee076d2017-08-15 02:04:02 -07001012void EventLogAnalyzer::CreateIncomingDelayDeltaGraph(Plot* plot) {
terelius88e64e52016-07-19 01:51:06 -07001013 for (auto& kv : rtp_packets_) {
1014 StreamId stream_id = kv.first;
tereliusccbbf8d2016-08-10 07:34:28 -07001015 const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
terelius88e64e52016-07-19 01:51:06 -07001016 // Filter on direction and SSRC.
1017 if (stream_id.GetDirection() != kIncomingPacket ||
Stefan Holmer99f8e082016-09-09 13:37:50 +02001018 !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
1019 IsAudioSsrc(stream_id) || !IsVideoSsrc(stream_id) ||
1020 IsRtxSsrc(stream_id)) {
terelius88e64e52016-07-19 01:51:06 -07001021 continue;
1022 }
terelius54ce6802016-07-13 06:44:41 -07001023
terelius23c595a2017-03-15 01:59:12 -07001024 TimeSeries capture_time_data(GetStreamName(stream_id) + " capture-time",
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001025 LineStyle::kBar);
terelius53dc23c2017-03-13 05:24:05 -07001026 ProcessPairs<LoggedRtpPacket, double>(NetworkDelayDiff_CaptureTime,
1027 packet_stream, begin_time_,
1028 &capture_time_data);
philipel35ba9bd2017-04-19 05:58:51 -07001029 plot->AppendTimeSeries(std::move(capture_time_data));
terelius88e64e52016-07-19 01:51:06 -07001030
terelius23c595a2017-03-15 01:59:12 -07001031 TimeSeries send_time_data(GetStreamName(stream_id) + " abs-send-time",
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001032 LineStyle::kBar);
terelius53dc23c2017-03-13 05:24:05 -07001033 ProcessPairs<LoggedRtpPacket, double>(NetworkDelayDiff_AbsSendTime,
1034 packet_stream, begin_time_,
1035 &send_time_data);
philipel35ba9bd2017-04-19 05:58:51 -07001036 plot->AppendTimeSeries(std::move(send_time_data));
terelius54ce6802016-07-13 06:44:41 -07001037 }
1038
tereliusdc35dcd2016-08-01 12:03:27 -07001039 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1040 plot->SetSuggestedYAxis(0, 1, "Latency change (ms)", kBottomMargin,
1041 kTopMargin);
terelius2ee076d2017-08-15 02:04:02 -07001042 plot->SetTitle("Network latency difference between consecutive packets");
terelius54ce6802016-07-13 06:44:41 -07001043}
1044
terelius2ee076d2017-08-15 02:04:02 -07001045void EventLogAnalyzer::CreateIncomingDelayGraph(Plot* plot) {
terelius88e64e52016-07-19 01:51:06 -07001046 for (auto& kv : rtp_packets_) {
1047 StreamId stream_id = kv.first;
tereliusccbbf8d2016-08-10 07:34:28 -07001048 const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
terelius88e64e52016-07-19 01:51:06 -07001049 // Filter on direction and SSRC.
1050 if (stream_id.GetDirection() != kIncomingPacket ||
Stefan Holmer99f8e082016-09-09 13:37:50 +02001051 !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
1052 IsAudioSsrc(stream_id) || !IsVideoSsrc(stream_id) ||
1053 IsRtxSsrc(stream_id)) {
terelius88e64e52016-07-19 01:51:06 -07001054 continue;
1055 }
terelius54ce6802016-07-13 06:44:41 -07001056
terelius23c595a2017-03-15 01:59:12 -07001057 TimeSeries capture_time_data(GetStreamName(stream_id) + " capture-time",
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001058 LineStyle::kLine);
terelius53dc23c2017-03-13 05:24:05 -07001059 AccumulatePairs<LoggedRtpPacket, double>(NetworkDelayDiff_CaptureTime,
1060 packet_stream, begin_time_,
1061 &capture_time_data);
philipel35ba9bd2017-04-19 05:58:51 -07001062 plot->AppendTimeSeries(std::move(capture_time_data));
terelius88e64e52016-07-19 01:51:06 -07001063
terelius23c595a2017-03-15 01:59:12 -07001064 TimeSeries send_time_data(GetStreamName(stream_id) + " abs-send-time",
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001065 LineStyle::kLine);
terelius53dc23c2017-03-13 05:24:05 -07001066 AccumulatePairs<LoggedRtpPacket, double>(NetworkDelayDiff_AbsSendTime,
1067 packet_stream, begin_time_,
1068 &send_time_data);
philipel35ba9bd2017-04-19 05:58:51 -07001069 plot->AppendTimeSeries(std::move(send_time_data));
terelius54ce6802016-07-13 06:44:41 -07001070 }
1071
tereliusdc35dcd2016-08-01 12:03:27 -07001072 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1073 plot->SetSuggestedYAxis(0, 1, "Latency change (ms)", kBottomMargin,
1074 kTopMargin);
terelius2ee076d2017-08-15 02:04:02 -07001075 plot->SetTitle("Network latency (relative to first packet)");
terelius54ce6802016-07-13 06:44:41 -07001076}
1077
tereliusf736d232016-08-04 10:00:11 -07001078// Plot the fraction of packets lost (as perceived by the loss-based BWE).
1079void EventLogAnalyzer::CreateFractionLossGraph(Plot* plot) {
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001080 TimeSeries time_series("Fraction lost", LineStyle::kLine,
1081 PointStyle::kHighlight);
tereliusf736d232016-08-04 10:00:11 -07001082 for (auto& bwe_update : bwe_loss_updates_) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001083 float x = ToCallTime(bwe_update.timestamp);
tereliusf736d232016-08-04 10:00:11 -07001084 float y = static_cast<float>(bwe_update.fraction_loss) / 255 * 100;
philipel35ba9bd2017-04-19 05:58:51 -07001085 time_series.points.emplace_back(x, y);
tereliusf736d232016-08-04 10:00:11 -07001086 }
tereliusf736d232016-08-04 10:00:11 -07001087
Bjorn Terelius19f5be32017-10-18 12:39:49 +02001088 plot->AppendTimeSeries(std::move(time_series));
tereliusf736d232016-08-04 10:00:11 -07001089 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1090 plot->SetSuggestedYAxis(0, 10, "Percent lost packets", kBottomMargin,
1091 kTopMargin);
1092 plot->SetTitle("Reported packet loss");
1093}
1094
terelius54ce6802016-07-13 06:44:41 -07001095// Plot the total bandwidth used by all RTP streams.
1096void EventLogAnalyzer::CreateTotalBitrateGraph(
1097 PacketDirection desired_direction,
philipel23c7f252017-07-14 06:30:03 -07001098 Plot* plot,
Ilya Nikolaevskiya4259f62017-12-05 13:19:45 +01001099 bool show_detector_state,
1100 bool show_alr_state) {
terelius54ce6802016-07-13 06:44:41 -07001101 struct TimestampSize {
1102 TimestampSize(uint64_t t, size_t s) : timestamp(t), size(s) {}
1103 uint64_t timestamp;
1104 size_t size;
1105 };
1106 std::vector<TimestampSize> packets;
1107
1108 PacketDirection direction;
1109 size_t total_length;
1110
1111 // Extract timestamps and sizes for the relevant packets.
1112 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) {
1113 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i);
1114 if (event_type == ParsedRtcEventLog::RTP_EVENT) {
Elad Alon1d87b0e2017-10-03 15:01:03 +02001115 parsed_log_.GetRtpHeader(i, &direction, nullptr, nullptr, &total_length,
1116 nullptr);
terelius54ce6802016-07-13 06:44:41 -07001117 if (direction == desired_direction) {
1118 uint64_t timestamp = parsed_log_.GetTimestamp(i);
1119 packets.push_back(TimestampSize(timestamp, total_length));
1120 }
1121 }
1122 }
1123
1124 size_t window_index_begin = 0;
1125 size_t window_index_end = 0;
1126 size_t bytes_in_window = 0;
terelius54ce6802016-07-13 06:44:41 -07001127
1128 // Calculate a moving average of the bitrate and store in a TimeSeries.
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001129 TimeSeries bitrate_series("Bitrate", LineStyle::kLine);
terelius54ce6802016-07-13 06:44:41 -07001130 for (uint64_t time = begin_time_; time < end_time_ + step_; time += step_) {
1131 while (window_index_end < packets.size() &&
1132 packets[window_index_end].timestamp < time) {
1133 bytes_in_window += packets[window_index_end].size;
terelius6addf492016-08-23 17:34:07 -07001134 ++window_index_end;
terelius54ce6802016-07-13 06:44:41 -07001135 }
1136 while (window_index_begin < packets.size() &&
1137 packets[window_index_begin].timestamp < time - window_duration_) {
1138 RTC_DCHECK_LE(packets[window_index_begin].size, bytes_in_window);
1139 bytes_in_window -= packets[window_index_begin].size;
terelius6addf492016-08-23 17:34:07 -07001140 ++window_index_begin;
terelius54ce6802016-07-13 06:44:41 -07001141 }
1142 float window_duration_in_seconds =
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001143 static_cast<float>(window_duration_) / kNumMicrosecsPerSec;
1144 float x = ToCallTime(time);
terelius54ce6802016-07-13 06:44:41 -07001145 float y = bytes_in_window * 8 / window_duration_in_seconds / 1000;
philipel35ba9bd2017-04-19 05:58:51 -07001146 bitrate_series.points.emplace_back(x, y);
terelius54ce6802016-07-13 06:44:41 -07001147 }
philipel35ba9bd2017-04-19 05:58:51 -07001148 plot->AppendTimeSeries(std::move(bitrate_series));
terelius54ce6802016-07-13 06:44:41 -07001149
terelius8058e582016-07-25 01:32:41 -07001150 // Overlay the send-side bandwidth estimate over the outgoing bitrate.
1151 if (desired_direction == kOutgoingPacket) {
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001152 TimeSeries loss_series("Loss-based estimate", LineStyle::kStep);
philipel10fc0e62017-04-11 01:50:23 -07001153 for (auto& loss_update : bwe_loss_updates_) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001154 float x = ToCallTime(loss_update.timestamp);
philipel10fc0e62017-04-11 01:50:23 -07001155 float y = static_cast<float>(loss_update.new_bitrate) / 1000;
philipel35ba9bd2017-04-19 05:58:51 -07001156 loss_series.points.emplace_back(x, y);
philipel10fc0e62017-04-11 01:50:23 -07001157 }
1158
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001159 TimeSeries delay_series("Delay-based estimate", LineStyle::kStep);
philipel23c7f252017-07-14 06:30:03 -07001160 IntervalSeries overusing_series("Overusing", "#ff8e82",
1161 IntervalSeries::kHorizontal);
1162 IntervalSeries underusing_series("Underusing", "#5092fc",
1163 IntervalSeries::kHorizontal);
1164 IntervalSeries normal_series("Normal", "#c4ffc4",
1165 IntervalSeries::kHorizontal);
1166 IntervalSeries* last_series = &normal_series;
1167 double last_detector_switch = 0.0;
1168
1169 BandwidthUsage last_detector_state = BandwidthUsage::kBwNormal;
1170
philipel10fc0e62017-04-11 01:50:23 -07001171 for (auto& delay_update : bwe_delay_updates_) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001172 float x = ToCallTime(delay_update.timestamp);
philipel10fc0e62017-04-11 01:50:23 -07001173 float y = static_cast<float>(delay_update.bitrate_bps) / 1000;
philipel23c7f252017-07-14 06:30:03 -07001174
1175 if (last_detector_state != delay_update.detector_state) {
1176 last_series->intervals.emplace_back(last_detector_switch, x);
1177 last_detector_state = delay_update.detector_state;
1178 last_detector_switch = x;
1179
1180 switch (delay_update.detector_state) {
1181 case BandwidthUsage::kBwNormal:
1182 last_series = &normal_series;
1183 break;
1184 case BandwidthUsage::kBwUnderusing:
1185 last_series = &underusing_series;
1186 break;
1187 case BandwidthUsage::kBwOverusing:
1188 last_series = &overusing_series;
1189 break;
Elad Alon1d87b0e2017-10-03 15:01:03 +02001190 case BandwidthUsage::kLast:
1191 RTC_NOTREACHED();
philipel23c7f252017-07-14 06:30:03 -07001192 }
1193 }
1194
philipel35ba9bd2017-04-19 05:58:51 -07001195 delay_series.points.emplace_back(x, y);
terelius8058e582016-07-25 01:32:41 -07001196 }
philipele127e7a2017-03-29 16:28:53 +02001197
philipel23c7f252017-07-14 06:30:03 -07001198 RTC_CHECK(last_series);
1199 last_series->intervals.emplace_back(last_detector_switch, end_time_);
1200
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001201 TimeSeries created_series("Probe cluster created.", LineStyle::kNone,
1202 PointStyle::kHighlight);
philipele127e7a2017-03-29 16:28:53 +02001203 for (auto& cluster : bwe_probe_cluster_created_events_) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001204 float x = ToCallTime(cluster.timestamp);
philipele127e7a2017-03-29 16:28:53 +02001205 float y = static_cast<float>(cluster.bitrate_bps) / 1000;
philipel35ba9bd2017-04-19 05:58:51 -07001206 created_series.points.emplace_back(x, y);
philipele127e7a2017-03-29 16:28:53 +02001207 }
1208
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001209 TimeSeries result_series("Probing results.", LineStyle::kNone,
1210 PointStyle::kHighlight);
philipele127e7a2017-03-29 16:28:53 +02001211 for (auto& result : bwe_probe_result_events_) {
1212 if (result.bitrate_bps) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001213 float x = ToCallTime(result.timestamp);
philipele127e7a2017-03-29 16:28:53 +02001214 float y = static_cast<float>(*result.bitrate_bps) / 1000;
philipel35ba9bd2017-04-19 05:58:51 -07001215 result_series.points.emplace_back(x, y);
philipele127e7a2017-03-29 16:28:53 +02001216 }
1217 }
philipel23c7f252017-07-14 06:30:03 -07001218
Ilya Nikolaevskiya4259f62017-12-05 13:19:45 +01001219 IntervalSeries alr_state("ALR", "#555555", IntervalSeries::kHorizontal);
1220 bool previously_in_alr = false;
1221 int64_t alr_start = 0;
1222 for (auto& alr : alr_state_events_) {
1223 float y = ToCallTime(alr.timestamp);
1224 if (!previously_in_alr && alr.in_alr) {
1225 alr_start = alr.timestamp;
1226 previously_in_alr = true;
1227 } else if (previously_in_alr && !alr.in_alr) {
1228 float x = ToCallTime(alr_start);
1229 alr_state.intervals.emplace_back(x, y);
1230 previously_in_alr = false;
1231 }
1232 }
1233
1234 if (previously_in_alr) {
1235 float x = ToCallTime(alr_start);
1236 float y = ToCallTime(end_time_);
1237 alr_state.intervals.emplace_back(x, y);
1238 }
1239
philipel23c7f252017-07-14 06:30:03 -07001240 if (show_detector_state) {
1241 plot->AppendIntervalSeries(std::move(overusing_series));
1242 plot->AppendIntervalSeries(std::move(underusing_series));
1243 plot->AppendIntervalSeries(std::move(normal_series));
1244 }
1245
Ilya Nikolaevskiya4259f62017-12-05 13:19:45 +01001246 if (show_alr_state) {
1247 plot->AppendIntervalSeries(std::move(alr_state));
1248 }
philipel35ba9bd2017-04-19 05:58:51 -07001249 plot->AppendTimeSeries(std::move(loss_series));
1250 plot->AppendTimeSeries(std::move(delay_series));
1251 plot->AppendTimeSeries(std::move(created_series));
1252 plot->AppendTimeSeries(std::move(result_series));
terelius8058e582016-07-25 01:32:41 -07001253 }
philipele127e7a2017-03-29 16:28:53 +02001254
terelius2c8e8a32017-06-02 01:29:48 -07001255 // Overlay the incoming REMB over the outgoing bitrate
1256 // and outgoing REMB over incoming bitrate.
1257 PacketDirection remb_direction =
1258 desired_direction == kOutgoingPacket ? kIncomingPacket : kOutgoingPacket;
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001259 TimeSeries remb_series("Remb", LineStyle::kStep);
terelius2c8e8a32017-06-02 01:29:48 -07001260 std::multimap<uint64_t, const LoggedRtcpPacket*> remb_packets;
1261 for (const auto& kv : rtcp_packets_) {
1262 if (kv.first.GetDirection() == remb_direction) {
1263 for (const LoggedRtcpPacket& rtcp_packet : kv.second) {
1264 if (rtcp_packet.type == kRtcpRemb) {
1265 remb_packets.insert(
1266 std::make_pair(rtcp_packet.timestamp, &rtcp_packet));
1267 }
1268 }
1269 }
1270 }
1271
1272 for (const auto& kv : remb_packets) {
1273 const LoggedRtcpPacket* const rtcp = kv.second;
1274 const rtcp::Remb* const remb = static_cast<rtcp::Remb*>(rtcp->packet.get());
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001275 float x = ToCallTime(rtcp->timestamp);
terelius2c8e8a32017-06-02 01:29:48 -07001276 float y = static_cast<float>(remb->bitrate_bps()) / 1000;
1277 remb_series.points.emplace_back(x, y);
1278 }
1279 plot->AppendTimeSeriesIfNotEmpty(std::move(remb_series));
1280
tereliusdc35dcd2016-08-01 12:03:27 -07001281 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1282 plot->SetSuggestedYAxis(0, 1, "Bitrate (kbps)", kBottomMargin, kTopMargin);
terelius54ce6802016-07-13 06:44:41 -07001283 if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
tereliusdc35dcd2016-08-01 12:03:27 -07001284 plot->SetTitle("Incoming RTP bitrate");
terelius54ce6802016-07-13 06:44:41 -07001285 } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
tereliusdc35dcd2016-08-01 12:03:27 -07001286 plot->SetTitle("Outgoing RTP bitrate");
terelius54ce6802016-07-13 06:44:41 -07001287 }
1288}
1289
1290// For each SSRC, plot the bandwidth used by that stream.
1291void EventLogAnalyzer::CreateStreamBitrateGraph(
1292 PacketDirection desired_direction,
1293 Plot* plot) {
terelius6addf492016-08-23 17:34:07 -07001294 for (auto& kv : rtp_packets_) {
1295 StreamId stream_id = kv.first;
1296 const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
1297 // Filter on direction and SSRC.
1298 if (stream_id.GetDirection() != desired_direction ||
1299 !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
1300 continue;
terelius54ce6802016-07-13 06:44:41 -07001301 }
1302
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001303 TimeSeries time_series(GetStreamName(stream_id), LineStyle::kLine);
terelius53dc23c2017-03-13 05:24:05 -07001304 MovingAverage<LoggedRtpPacket, double>(
1305 [](const LoggedRtpPacket& packet) {
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001306 return packet.total_length * 8.0 / 1000.0;
terelius53dc23c2017-03-13 05:24:05 -07001307 },
1308 packet_stream, begin_time_, end_time_, window_duration_, step_,
1309 &time_series);
philipel35ba9bd2017-04-19 05:58:51 -07001310 plot->AppendTimeSeries(std::move(time_series));
terelius54ce6802016-07-13 06:44:41 -07001311 }
1312
tereliusdc35dcd2016-08-01 12:03:27 -07001313 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1314 plot->SetSuggestedYAxis(0, 1, "Bitrate (kbps)", kBottomMargin, kTopMargin);
terelius54ce6802016-07-13 06:44:41 -07001315 if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
tereliusdc35dcd2016-08-01 12:03:27 -07001316 plot->SetTitle("Incoming bitrate per stream");
terelius54ce6802016-07-13 06:44:41 -07001317 } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
tereliusdc35dcd2016-08-01 12:03:27 -07001318 plot->SetTitle("Outgoing bitrate per stream");
terelius54ce6802016-07-13 06:44:41 -07001319 }
1320}
1321
Bjorn Terelius28db2662017-10-04 14:22:43 +02001322void EventLogAnalyzer::CreateSendSideBweSimulationGraph(Plot* plot) {
stefanff421622017-04-20 03:24:01 -07001323 std::multimap<uint64_t, const LoggedRtpPacket*> outgoing_rtp;
1324 std::multimap<uint64_t, const LoggedRtcpPacket*> incoming_rtcp;
Stefan Holmer13181032016-07-29 14:48:54 +02001325
1326 for (const auto& kv : rtp_packets_) {
1327 if (kv.first.GetDirection() == PacketDirection::kOutgoingPacket) {
1328 for (const LoggedRtpPacket& rtp_packet : kv.second)
1329 outgoing_rtp.insert(std::make_pair(rtp_packet.timestamp, &rtp_packet));
1330 }
1331 }
1332
1333 for (const auto& kv : rtcp_packets_) {
1334 if (kv.first.GetDirection() == PacketDirection::kIncomingPacket) {
1335 for (const LoggedRtcpPacket& rtcp_packet : kv.second)
1336 incoming_rtcp.insert(
1337 std::make_pair(rtcp_packet.timestamp, &rtcp_packet));
1338 }
1339 }
1340
1341 SimulatedClock clock(0);
1342 BitrateObserver observer;
1343 RtcEventLogNullImpl null_event_log;
nisse0245da02016-11-30 03:35:20 -08001344 PacketRouter packet_router;
Stefan Holmer5c8942a2017-08-22 16:16:44 +02001345 PacedSender pacer(&clock, &packet_router, &null_event_log);
1346 SendSideCongestionController cc(&clock, &observer, &null_event_log, &pacer);
Stefan Holmer13181032016-07-29 14:48:54 +02001347 // TODO(holmer): Log the call config and use that here instead.
1348 static const uint32_t kDefaultStartBitrateBps = 300000;
1349 cc.SetBweBitrates(0, kDefaultStartBitrateBps, -1);
1350
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001351 TimeSeries time_series("Delay-based estimate", LineStyle::kStep,
1352 PointStyle::kHighlight);
1353 TimeSeries acked_time_series("Acked bitrate", LineStyle::kLine,
1354 PointStyle::kHighlight);
1355 TimeSeries acked_estimate_time_series(
1356 "Acked bitrate estimate", LineStyle::kLine, PointStyle::kHighlight);
Stefan Holmer13181032016-07-29 14:48:54 +02001357
1358 auto rtp_iterator = outgoing_rtp.begin();
1359 auto rtcp_iterator = incoming_rtcp.begin();
1360
1361 auto NextRtpTime = [&]() {
1362 if (rtp_iterator != outgoing_rtp.end())
1363 return static_cast<int64_t>(rtp_iterator->first);
1364 return std::numeric_limits<int64_t>::max();
1365 };
1366
1367 auto NextRtcpTime = [&]() {
1368 if (rtcp_iterator != incoming_rtcp.end())
1369 return static_cast<int64_t>(rtcp_iterator->first);
1370 return std::numeric_limits<int64_t>::max();
1371 };
1372
1373 auto NextProcessTime = [&]() {
1374 if (rtcp_iterator != incoming_rtcp.end() ||
1375 rtp_iterator != outgoing_rtp.end()) {
1376 return clock.TimeInMicroseconds() +
1377 std::max<int64_t>(cc.TimeUntilNextProcess() * 1000, 0);
1378 }
1379 return std::numeric_limits<int64_t>::max();
1380 };
1381
Stefan Holmer492ee282016-10-27 17:19:20 +02001382 RateStatistics acked_bitrate(250, 8000);
Bjorn Terelius6984ad22017-10-24 12:19:45 +02001383#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
1384 // The event_log_visualizer should normally not be compiled with
1385 // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE since the normal plots won't work.
1386 // However, compiling with BWE_TEST_LOGGING, runnning with --plot_sendside_bwe
1387 // and piping the output to plot_dynamics.py can be used as a hack to get the
1388 // internal state of various BWE components. In this case, it is important
1389 // we don't instantiate the AcknowledgedBitrateEstimator both here and in
1390 // SendSideCongestionController since that would lead to duplicate outputs.
1391 AcknowledgedBitrateEstimator acknowledged_bitrate_estimator(
1392 rtc::MakeUnique<BitrateEstimator>());
1393#endif // !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
Stefan Holmer13181032016-07-29 14:48:54 +02001394 int64_t time_us = std::min(NextRtpTime(), NextRtcpTime());
Stefan Holmer492ee282016-10-27 17:19:20 +02001395 int64_t last_update_us = 0;
Stefan Holmer13181032016-07-29 14:48:54 +02001396 while (time_us != std::numeric_limits<int64_t>::max()) {
1397 clock.AdvanceTimeMicroseconds(time_us - clock.TimeInMicroseconds());
1398 if (clock.TimeInMicroseconds() >= NextRtcpTime()) {
stefanc3de0332016-08-02 07:22:17 -07001399 RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtcpTime());
Stefan Holmer13181032016-07-29 14:48:54 +02001400 const LoggedRtcpPacket& rtcp = *rtcp_iterator->second;
1401 if (rtcp.type == kRtcpTransportFeedback) {
elad.alon5bbf43f2017-03-09 06:40:08 -08001402 cc.OnTransportFeedback(
1403 *static_cast<rtcp::TransportFeedback*>(rtcp.packet.get()));
1404 std::vector<PacketFeedback> feedback = cc.GetTransportFeedbackVector();
elad.alonec304f92017-03-08 05:03:53 -08001405 SortPacketFeedbackVector(&feedback);
Stefan Holmer60e43462016-09-07 09:58:20 +02001406 rtc::Optional<uint32_t> bitrate_bps;
1407 if (!feedback.empty()) {
Bjorn Terelius6984ad22017-10-24 12:19:45 +02001408#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
1409 acknowledged_bitrate_estimator.IncomingPacketFeedbackVector(feedback);
1410#endif // !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
elad.alonf9490002017-03-06 05:32:21 -08001411 for (const PacketFeedback& packet : feedback)
Stefan Holmer60e43462016-09-07 09:58:20 +02001412 acked_bitrate.Update(packet.payload_size, packet.arrival_time_ms);
1413 bitrate_bps = acked_bitrate.Rate(feedback.back().arrival_time_ms);
1414 }
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001415 float x = ToCallTime(clock.TimeInMicroseconds());
Bjorn Terelius6984ad22017-10-24 12:19:45 +02001416 float y = bitrate_bps.value_or(0) / 1000;
Stefan Holmer60e43462016-09-07 09:58:20 +02001417 acked_time_series.points.emplace_back(x, y);
Bjorn Terelius6984ad22017-10-24 12:19:45 +02001418#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
1419 y = acknowledged_bitrate_estimator.bitrate_bps().value_or(0) / 1000;
1420 acked_estimate_time_series.points.emplace_back(x, y);
1421#endif // !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
Stefan Holmer13181032016-07-29 14:48:54 +02001422 }
1423 ++rtcp_iterator;
1424 }
1425 if (clock.TimeInMicroseconds() >= NextRtpTime()) {
stefanc3de0332016-08-02 07:22:17 -07001426 RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtpTime());
Stefan Holmer13181032016-07-29 14:48:54 +02001427 const LoggedRtpPacket& rtp = *rtp_iterator->second;
1428 if (rtp.header.extension.hasTransportSequenceNumber) {
1429 RTC_DCHECK(rtp.header.extension.hasTransportSequenceNumber);
elad.alond12a8e12017-03-23 11:04:48 -07001430 cc.AddPacket(rtp.header.ssrc,
1431 rtp.header.extension.transportSequenceNumber,
elad.alon5bbf43f2017-03-09 06:40:08 -08001432 rtp.total_length, PacedPacketInfo());
Stefan Holmer13181032016-07-29 14:48:54 +02001433 rtc::SentPacket sent_packet(
1434 rtp.header.extension.transportSequenceNumber, rtp.timestamp / 1000);
1435 cc.OnSentPacket(sent_packet);
1436 }
1437 ++rtp_iterator;
1438 }
stefanc3de0332016-08-02 07:22:17 -07001439 if (clock.TimeInMicroseconds() >= NextProcessTime()) {
1440 RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextProcessTime());
Stefan Holmer13181032016-07-29 14:48:54 +02001441 cc.Process();
stefanc3de0332016-08-02 07:22:17 -07001442 }
Stefan Holmer492ee282016-10-27 17:19:20 +02001443 if (observer.GetAndResetBitrateUpdated() ||
1444 time_us - last_update_us >= 1e6) {
Stefan Holmer13181032016-07-29 14:48:54 +02001445 uint32_t y = observer.last_bitrate_bps() / 1000;
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001446 float x = ToCallTime(clock.TimeInMicroseconds());
Stefan Holmer13181032016-07-29 14:48:54 +02001447 time_series.points.emplace_back(x, y);
Stefan Holmer492ee282016-10-27 17:19:20 +02001448 last_update_us = time_us;
Stefan Holmer13181032016-07-29 14:48:54 +02001449 }
1450 time_us = std::min({NextRtpTime(), NextRtcpTime(), NextProcessTime()});
1451 }
1452 // Add the data set to the plot.
philipel35ba9bd2017-04-19 05:58:51 -07001453 plot->AppendTimeSeries(std::move(time_series));
1454 plot->AppendTimeSeries(std::move(acked_time_series));
Bjorn Terelius6984ad22017-10-24 12:19:45 +02001455 plot->AppendTimeSeriesIfNotEmpty(std::move(acked_estimate_time_series));
Stefan Holmer13181032016-07-29 14:48:54 +02001456
tereliusdc35dcd2016-08-01 12:03:27 -07001457 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1458 plot->SetSuggestedYAxis(0, 10, "Bitrate (kbps)", kBottomMargin, kTopMargin);
Bjorn Terelius28db2662017-10-04 14:22:43 +02001459 plot->SetTitle("Simulated send-side BWE behavior");
1460}
1461
1462void EventLogAnalyzer::CreateReceiveSideBweSimulationGraph(Plot* plot) {
1463 class RembInterceptingPacketRouter : public PacketRouter {
1464 public:
1465 void OnReceiveBitrateChanged(const std::vector<uint32_t>& ssrcs,
1466 uint32_t bitrate_bps) override {
1467 last_bitrate_bps_ = bitrate_bps;
1468 bitrate_updated_ = true;
1469 PacketRouter::OnReceiveBitrateChanged(ssrcs, bitrate_bps);
1470 }
1471 uint32_t last_bitrate_bps() const { return last_bitrate_bps_; }
1472 bool GetAndResetBitrateUpdated() {
1473 bool bitrate_updated = bitrate_updated_;
1474 bitrate_updated_ = false;
1475 return bitrate_updated;
1476 }
1477
1478 private:
1479 uint32_t last_bitrate_bps_;
1480 bool bitrate_updated_;
1481 };
1482
1483 std::multimap<uint64_t, const LoggedRtpPacket*> incoming_rtp;
1484
1485 for (const auto& kv : rtp_packets_) {
1486 if (kv.first.GetDirection() == PacketDirection::kIncomingPacket &&
1487 IsVideoSsrc(kv.first)) {
1488 for (const LoggedRtpPacket& rtp_packet : kv.second)
1489 incoming_rtp.insert(std::make_pair(rtp_packet.timestamp, &rtp_packet));
1490 }
1491 }
1492
1493 SimulatedClock clock(0);
1494 RembInterceptingPacketRouter packet_router;
1495 // TODO(terelius): The PacketRrouter is the used as the RemoteBitrateObserver.
1496 // Is this intentional?
1497 ReceiveSideCongestionController rscc(&clock, &packet_router);
1498 // TODO(holmer): Log the call config and use that here instead.
1499 // static const uint32_t kDefaultStartBitrateBps = 300000;
1500 // rscc.SetBweBitrates(0, kDefaultStartBitrateBps, -1);
1501
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001502 TimeSeries time_series("Receive side estimate", LineStyle::kLine,
1503 PointStyle::kHighlight);
1504 TimeSeries acked_time_series("Received bitrate", LineStyle::kLine);
Bjorn Terelius28db2662017-10-04 14:22:43 +02001505
1506 RateStatistics acked_bitrate(250, 8000);
1507 int64_t last_update_us = 0;
1508 for (const auto& kv : incoming_rtp) {
1509 const LoggedRtpPacket& packet = *kv.second;
1510 int64_t arrival_time_ms = packet.timestamp / 1000;
1511 size_t payload = packet.total_length; /*Should subtract header?*/
1512 clock.AdvanceTimeMicroseconds(packet.timestamp -
1513 clock.TimeInMicroseconds());
1514 rscc.OnReceivedPacket(arrival_time_ms, payload, packet.header);
1515 acked_bitrate.Update(payload, arrival_time_ms);
1516 rtc::Optional<uint32_t> bitrate_bps = acked_bitrate.Rate(arrival_time_ms);
1517 if (bitrate_bps) {
1518 uint32_t y = *bitrate_bps / 1000;
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001519 float x = ToCallTime(clock.TimeInMicroseconds());
Bjorn Terelius28db2662017-10-04 14:22:43 +02001520 acked_time_series.points.emplace_back(x, y);
1521 }
1522 if (packet_router.GetAndResetBitrateUpdated() ||
1523 clock.TimeInMicroseconds() - last_update_us >= 1e6) {
1524 uint32_t y = packet_router.last_bitrate_bps() / 1000;
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001525 float x = ToCallTime(clock.TimeInMicroseconds());
Bjorn Terelius28db2662017-10-04 14:22:43 +02001526 time_series.points.emplace_back(x, y);
1527 last_update_us = clock.TimeInMicroseconds();
1528 }
1529 }
1530 // Add the data set to the plot.
1531 plot->AppendTimeSeries(std::move(time_series));
1532 plot->AppendTimeSeries(std::move(acked_time_series));
1533
1534 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1535 plot->SetSuggestedYAxis(0, 10, "Bitrate (kbps)", kBottomMargin, kTopMargin);
1536 plot->SetTitle("Simulated receive-side BWE behavior");
Stefan Holmer13181032016-07-29 14:48:54 +02001537}
1538
tereliuse34c19c2016-08-15 08:47:14 -07001539void EventLogAnalyzer::CreateNetworkDelayFeedbackGraph(Plot* plot) {
stefanff421622017-04-20 03:24:01 -07001540 std::multimap<uint64_t, const LoggedRtpPacket*> outgoing_rtp;
1541 std::multimap<uint64_t, const LoggedRtcpPacket*> incoming_rtcp;
stefanc3de0332016-08-02 07:22:17 -07001542
1543 for (const auto& kv : rtp_packets_) {
1544 if (kv.first.GetDirection() == PacketDirection::kOutgoingPacket) {
1545 for (const LoggedRtpPacket& rtp_packet : kv.second)
1546 outgoing_rtp.insert(std::make_pair(rtp_packet.timestamp, &rtp_packet));
1547 }
1548 }
1549
1550 for (const auto& kv : rtcp_packets_) {
1551 if (kv.first.GetDirection() == PacketDirection::kIncomingPacket) {
1552 for (const LoggedRtcpPacket& rtcp_packet : kv.second)
1553 incoming_rtcp.insert(
1554 std::make_pair(rtcp_packet.timestamp, &rtcp_packet));
1555 }
1556 }
1557
1558 SimulatedClock clock(0);
elad.alon5bbf43f2017-03-09 06:40:08 -08001559 TransportFeedbackAdapter feedback_adapter(&clock);
stefanc3de0332016-08-02 07:22:17 -07001560
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001561 TimeSeries late_feedback_series("Late feedback results.", LineStyle::kNone,
1562 PointStyle::kHighlight);
1563 TimeSeries time_series("Network Delay Change", LineStyle::kLine,
1564 PointStyle::kHighlight);
stefanc3de0332016-08-02 07:22:17 -07001565 int64_t estimated_base_delay_ms = std::numeric_limits<int64_t>::max();
1566
1567 auto rtp_iterator = outgoing_rtp.begin();
1568 auto rtcp_iterator = incoming_rtcp.begin();
1569
1570 auto NextRtpTime = [&]() {
1571 if (rtp_iterator != outgoing_rtp.end())
1572 return static_cast<int64_t>(rtp_iterator->first);
1573 return std::numeric_limits<int64_t>::max();
1574 };
1575
1576 auto NextRtcpTime = [&]() {
1577 if (rtcp_iterator != incoming_rtcp.end())
1578 return static_cast<int64_t>(rtcp_iterator->first);
1579 return std::numeric_limits<int64_t>::max();
1580 };
1581
1582 int64_t time_us = std::min(NextRtpTime(), NextRtcpTime());
stefana0a8ed72017-09-06 02:06:32 -07001583 int64_t prev_y = 0;
stefanc3de0332016-08-02 07:22:17 -07001584 while (time_us != std::numeric_limits<int64_t>::max()) {
1585 clock.AdvanceTimeMicroseconds(time_us - clock.TimeInMicroseconds());
1586 if (clock.TimeInMicroseconds() >= NextRtcpTime()) {
1587 RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtcpTime());
1588 const LoggedRtcpPacket& rtcp = *rtcp_iterator->second;
1589 if (rtcp.type == kRtcpTransportFeedback) {
Stefan Holmer60e43462016-09-07 09:58:20 +02001590 feedback_adapter.OnTransportFeedback(
1591 *static_cast<rtcp::TransportFeedback*>(rtcp.packet.get()));
elad.alonf9490002017-03-06 05:32:21 -08001592 std::vector<PacketFeedback> feedback =
1593 feedback_adapter.GetTransportFeedbackVector();
elad.alonec304f92017-03-08 05:03:53 -08001594 SortPacketFeedbackVector(&feedback);
elad.alonf9490002017-03-06 05:32:21 -08001595 for (const PacketFeedback& packet : feedback) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001596 float x = ToCallTime(clock.TimeInMicroseconds());
srtee0572e52017-11-30 09:59:33 +01001597 if (packet.send_time_ms == PacketFeedback::kNoSendTime) {
stefana0a8ed72017-09-06 02:06:32 -07001598 late_feedback_series.points.emplace_back(x, prev_y);
1599 continue;
1600 }
1601 int64_t y = packet.arrival_time_ms - packet.send_time_ms;
1602 prev_y = y;
stefanc3de0332016-08-02 07:22:17 -07001603 estimated_base_delay_ms = std::min(y, estimated_base_delay_ms);
1604 time_series.points.emplace_back(x, y);
1605 }
1606 }
1607 ++rtcp_iterator;
1608 }
1609 if (clock.TimeInMicroseconds() >= NextRtpTime()) {
1610 RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtpTime());
1611 const LoggedRtpPacket& rtp = *rtp_iterator->second;
1612 if (rtp.header.extension.hasTransportSequenceNumber) {
1613 RTC_DCHECK(rtp.header.extension.hasTransportSequenceNumber);
elad.alond12a8e12017-03-23 11:04:48 -07001614 feedback_adapter.AddPacket(rtp.header.ssrc,
1615 rtp.header.extension.transportSequenceNumber,
philipel8aadd502017-02-23 02:56:13 -08001616 rtp.total_length, PacedPacketInfo());
stefanc3de0332016-08-02 07:22:17 -07001617 feedback_adapter.OnSentPacket(
1618 rtp.header.extension.transportSequenceNumber, rtp.timestamp / 1000);
1619 }
1620 ++rtp_iterator;
1621 }
1622 time_us = std::min(NextRtpTime(), NextRtcpTime());
1623 }
1624 // We assume that the base network delay (w/o queues) is the min delay
1625 // observed during the call.
1626 for (TimeSeriesPoint& point : time_series.points)
1627 point.y -= estimated_base_delay_ms;
stefana0a8ed72017-09-06 02:06:32 -07001628 for (TimeSeriesPoint& point : late_feedback_series.points)
1629 point.y -= estimated_base_delay_ms;
stefanc3de0332016-08-02 07:22:17 -07001630 // Add the data set to the plot.
stefana0a8ed72017-09-06 02:06:32 -07001631 plot->AppendTimeSeriesIfNotEmpty(std::move(time_series));
1632 plot->AppendTimeSeriesIfNotEmpty(std::move(late_feedback_series));
stefanc3de0332016-08-02 07:22:17 -07001633
1634 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1635 plot->SetSuggestedYAxis(0, 10, "Delay (ms)", kBottomMargin, kTopMargin);
1636 plot->SetTitle("Network Delay Change.");
1637}
stefan08383272016-12-20 08:51:52 -08001638
1639std::vector<std::pair<int64_t, int64_t>> EventLogAnalyzer::GetFrameTimestamps()
1640 const {
1641 std::vector<std::pair<int64_t, int64_t>> timestamps;
1642 size_t largest_stream_size = 0;
1643 const std::vector<LoggedRtpPacket>* largest_video_stream = nullptr;
1644 // Find the incoming video stream with the most number of packets that is
1645 // not rtx.
1646 for (const auto& kv : rtp_packets_) {
1647 if (kv.first.GetDirection() == kIncomingPacket &&
1648 video_ssrcs_.find(kv.first) != video_ssrcs_.end() &&
1649 rtx_ssrcs_.find(kv.first) == rtx_ssrcs_.end() &&
1650 kv.second.size() > largest_stream_size) {
1651 largest_stream_size = kv.second.size();
1652 largest_video_stream = &kv.second;
1653 }
1654 }
1655 if (largest_video_stream == nullptr) {
1656 for (auto& packet : *largest_video_stream) {
1657 if (packet.header.markerBit) {
1658 int64_t capture_ms = packet.header.timestamp / 90.0;
1659 int64_t arrival_ms = packet.timestamp / 1000.0;
1660 timestamps.push_back(std::make_pair(capture_ms, arrival_ms));
1661 }
1662 }
1663 }
1664 return timestamps;
1665}
stefane372d3c2017-02-02 08:04:18 -08001666
Bjorn Terelius0295a962017-10-25 17:42:41 +02001667void EventLogAnalyzer::CreatePacerDelayGraph(Plot* plot) {
1668 for (const auto& kv : rtp_packets_) {
1669 const std::vector<LoggedRtpPacket>& packets = kv.second;
1670 StreamId stream_id = kv.first;
Bjorn Tereliusb87c27e2017-11-09 11:55:51 +01001671 if (stream_id.GetDirection() == kIncomingPacket)
1672 continue;
Bjorn Terelius0295a962017-10-25 17:42:41 +02001673
1674 if (packets.size() < 2) {
Mirko Bonadei675513b2017-11-09 11:09:25 +01001675 RTC_LOG(LS_WARNING)
1676 << "Can't estimate a the RTP clock frequency or the "
1677 "pacer delay with less than 2 packets in the stream";
Bjorn Terelius0295a962017-10-25 17:42:41 +02001678 continue;
1679 }
1680 rtc::Optional<uint32_t> estimated_frequency =
1681 EstimateRtpClockFrequency(packets);
1682 if (!estimated_frequency)
1683 continue;
1684 if (IsVideoSsrc(stream_id) && *estimated_frequency != 90000) {
Mirko Bonadei675513b2017-11-09 11:09:25 +01001685 RTC_LOG(LS_WARNING)
Bjorn Terelius0295a962017-10-25 17:42:41 +02001686 << "Video stream should use a 90 kHz clock but appears to use "
1687 << *estimated_frequency / 1000 << ". Discarding.";
1688 continue;
1689 }
1690
1691 TimeSeries pacer_delay_series(
1692 GetStreamName(stream_id) + "(" +
1693 std::to_string(*estimated_frequency / 1000) + " kHz)",
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001694 LineStyle::kLine, PointStyle::kHighlight);
Bjorn Terelius0295a962017-10-25 17:42:41 +02001695 SeqNumUnwrapper<uint32_t> timestamp_unwrapper;
1696 uint64_t first_capture_timestamp =
1697 timestamp_unwrapper.Unwrap(packets.front().header.timestamp);
1698 uint64_t first_send_timestamp = packets.front().timestamp;
1699 for (LoggedRtpPacket packet : packets) {
1700 double capture_time_ms = (static_cast<double>(timestamp_unwrapper.Unwrap(
1701 packet.header.timestamp)) -
1702 first_capture_timestamp) /
1703 *estimated_frequency * 1000;
1704 double send_time_ms =
1705 static_cast<double>(packet.timestamp - first_send_timestamp) / 1000;
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001706 float x = ToCallTime(packet.timestamp);
Bjorn Terelius0295a962017-10-25 17:42:41 +02001707 float y = send_time_ms - capture_time_ms;
1708 pacer_delay_series.points.emplace_back(x, y);
1709 }
1710 plot->AppendTimeSeries(std::move(pacer_delay_series));
1711 }
1712
1713 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1714 plot->SetSuggestedYAxis(0, 10, "Pacer delay (ms)", kBottomMargin, kTopMargin);
1715 plot->SetTitle(
1716 "Delay from capture to send time. (First packet normalized to 0.)");
1717}
1718
stefane372d3c2017-02-02 08:04:18 -08001719void EventLogAnalyzer::CreateTimestampGraph(Plot* plot) {
1720 for (const auto& kv : rtp_packets_) {
1721 const std::vector<LoggedRtpPacket>& rtp_packets = kv.second;
1722 StreamId stream_id = kv.first;
1723
1724 {
terelius23c595a2017-03-15 01:59:12 -07001725 TimeSeries timestamp_data(GetStreamName(stream_id) + " capture-time",
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001726 LineStyle::kLine, PointStyle::kHighlight);
stefane372d3c2017-02-02 08:04:18 -08001727 for (LoggedRtpPacket packet : rtp_packets) {
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001728 float x = ToCallTime(packet.timestamp);
stefane372d3c2017-02-02 08:04:18 -08001729 float y = packet.header.timestamp;
1730 timestamp_data.points.emplace_back(x, y);
1731 }
philipel35ba9bd2017-04-19 05:58:51 -07001732 plot->AppendTimeSeries(std::move(timestamp_data));
stefane372d3c2017-02-02 08:04:18 -08001733 }
1734
1735 {
1736 auto kv = rtcp_packets_.find(stream_id);
1737 if (kv != rtcp_packets_.end()) {
1738 const auto& packets = kv->second;
terelius23c595a2017-03-15 01:59:12 -07001739 TimeSeries timestamp_data(
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001740 GetStreamName(stream_id) + " rtcp capture-time", LineStyle::kLine,
1741 PointStyle::kHighlight);
stefane372d3c2017-02-02 08:04:18 -08001742 for (const LoggedRtcpPacket& rtcp : packets) {
1743 if (rtcp.type != kRtcpSr)
1744 continue;
1745 rtcp::SenderReport* sr;
1746 sr = static_cast<rtcp::SenderReport*>(rtcp.packet.get());
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08001747 float x = ToCallTime(rtcp.timestamp);
stefane372d3c2017-02-02 08:04:18 -08001748 float y = sr->rtp_timestamp();
1749 timestamp_data.points.emplace_back(x, y);
1750 }
philipel35ba9bd2017-04-19 05:58:51 -07001751 plot->AppendTimeSeries(std::move(timestamp_data));
stefane372d3c2017-02-02 08:04:18 -08001752 }
1753 }
1754 }
1755
1756 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1757 plot->SetSuggestedYAxis(0, 1, "Timestamp (90khz)", kBottomMargin, kTopMargin);
1758 plot->SetTitle("Timestamps");
1759}
michaelt6e5b2192017-02-22 07:33:27 -08001760
1761void EventLogAnalyzer::CreateAudioEncoderTargetBitrateGraph(Plot* plot) {
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001762 TimeSeries time_series("Audio encoder target bitrate", LineStyle::kLine,
1763 PointStyle::kHighlight);
terelius53dc23c2017-03-13 05:24:05 -07001764 ProcessPoints<AudioNetworkAdaptationEvent>(
1765 [](const AudioNetworkAdaptationEvent& ana_event) -> rtc::Optional<float> {
michaelt6e5b2192017-02-22 07:33:27 -08001766 if (ana_event.config.bitrate_bps)
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001767 return static_cast<float>(*ana_event.config.bitrate_bps);
1768 return rtc::nullopt;
terelius53dc23c2017-03-13 05:24:05 -07001769 },
philipel35ba9bd2017-04-19 05:58:51 -07001770 audio_network_adaptation_events_, begin_time_, &time_series);
1771 plot->AppendTimeSeries(std::move(time_series));
michaelt6e5b2192017-02-22 07:33:27 -08001772 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1773 plot->SetSuggestedYAxis(0, 1, "Bitrate (bps)", kBottomMargin, kTopMargin);
1774 plot->SetTitle("Reported audio encoder target bitrate");
1775}
1776
1777void EventLogAnalyzer::CreateAudioEncoderFrameLengthGraph(Plot* plot) {
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001778 TimeSeries time_series("Audio encoder frame length", LineStyle::kLine,
1779 PointStyle::kHighlight);
terelius53dc23c2017-03-13 05:24:05 -07001780 ProcessPoints<AudioNetworkAdaptationEvent>(
1781 [](const AudioNetworkAdaptationEvent& ana_event) {
michaelt6e5b2192017-02-22 07:33:27 -08001782 if (ana_event.config.frame_length_ms)
1783 return rtc::Optional<float>(
1784 static_cast<float>(*ana_event.config.frame_length_ms));
1785 return rtc::Optional<float>();
terelius53dc23c2017-03-13 05:24:05 -07001786 },
philipel35ba9bd2017-04-19 05:58:51 -07001787 audio_network_adaptation_events_, begin_time_, &time_series);
1788 plot->AppendTimeSeries(std::move(time_series));
michaelt6e5b2192017-02-22 07:33:27 -08001789 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1790 plot->SetSuggestedYAxis(0, 1, "Frame length (ms)", kBottomMargin, kTopMargin);
1791 plot->SetTitle("Reported audio encoder frame length");
1792}
1793
terelius2ee076d2017-08-15 02:04:02 -07001794void EventLogAnalyzer::CreateAudioEncoderPacketLossGraph(Plot* plot) {
philipel35ba9bd2017-04-19 05:58:51 -07001795 TimeSeries time_series("Audio encoder uplink packet loss fraction",
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001796 LineStyle::kLine, PointStyle::kHighlight);
terelius53dc23c2017-03-13 05:24:05 -07001797 ProcessPoints<AudioNetworkAdaptationEvent>(
1798 [](const AudioNetworkAdaptationEvent& ana_event) {
michaelt6e5b2192017-02-22 07:33:27 -08001799 if (ana_event.config.uplink_packet_loss_fraction)
1800 return rtc::Optional<float>(static_cast<float>(
1801 *ana_event.config.uplink_packet_loss_fraction));
1802 return rtc::Optional<float>();
terelius53dc23c2017-03-13 05:24:05 -07001803 },
philipel35ba9bd2017-04-19 05:58:51 -07001804 audio_network_adaptation_events_, begin_time_, &time_series);
1805 plot->AppendTimeSeries(std::move(time_series));
michaelt6e5b2192017-02-22 07:33:27 -08001806 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1807 plot->SetSuggestedYAxis(0, 10, "Percent lost packets", kBottomMargin,
1808 kTopMargin);
1809 plot->SetTitle("Reported audio encoder lost packets");
1810}
1811
1812void EventLogAnalyzer::CreateAudioEncoderEnableFecGraph(Plot* plot) {
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001813 TimeSeries time_series("Audio encoder FEC", LineStyle::kLine,
1814 PointStyle::kHighlight);
terelius53dc23c2017-03-13 05:24:05 -07001815 ProcessPoints<AudioNetworkAdaptationEvent>(
1816 [](const AudioNetworkAdaptationEvent& ana_event) {
michaelt6e5b2192017-02-22 07:33:27 -08001817 if (ana_event.config.enable_fec)
1818 return rtc::Optional<float>(
1819 static_cast<float>(*ana_event.config.enable_fec));
1820 return rtc::Optional<float>();
terelius53dc23c2017-03-13 05:24:05 -07001821 },
philipel35ba9bd2017-04-19 05:58:51 -07001822 audio_network_adaptation_events_, begin_time_, &time_series);
1823 plot->AppendTimeSeries(std::move(time_series));
michaelt6e5b2192017-02-22 07:33:27 -08001824 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1825 plot->SetSuggestedYAxis(0, 1, "FEC (false/true)", kBottomMargin, kTopMargin);
1826 plot->SetTitle("Reported audio encoder FEC");
1827}
1828
1829void EventLogAnalyzer::CreateAudioEncoderEnableDtxGraph(Plot* plot) {
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001830 TimeSeries time_series("Audio encoder DTX", LineStyle::kLine,
1831 PointStyle::kHighlight);
terelius53dc23c2017-03-13 05:24:05 -07001832 ProcessPoints<AudioNetworkAdaptationEvent>(
1833 [](const AudioNetworkAdaptationEvent& ana_event) {
michaelt6e5b2192017-02-22 07:33:27 -08001834 if (ana_event.config.enable_dtx)
1835 return rtc::Optional<float>(
1836 static_cast<float>(*ana_event.config.enable_dtx));
1837 return rtc::Optional<float>();
terelius53dc23c2017-03-13 05:24:05 -07001838 },
philipel35ba9bd2017-04-19 05:58:51 -07001839 audio_network_adaptation_events_, begin_time_, &time_series);
1840 plot->AppendTimeSeries(std::move(time_series));
michaelt6e5b2192017-02-22 07:33:27 -08001841 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1842 plot->SetSuggestedYAxis(0, 1, "DTX (false/true)", kBottomMargin, kTopMargin);
1843 plot->SetTitle("Reported audio encoder DTX");
1844}
1845
1846void EventLogAnalyzer::CreateAudioEncoderNumChannelsGraph(Plot* plot) {
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01001847 TimeSeries time_series("Audio encoder number of channels", LineStyle::kLine,
1848 PointStyle::kHighlight);
terelius53dc23c2017-03-13 05:24:05 -07001849 ProcessPoints<AudioNetworkAdaptationEvent>(
1850 [](const AudioNetworkAdaptationEvent& ana_event) {
michaelt6e5b2192017-02-22 07:33:27 -08001851 if (ana_event.config.num_channels)
1852 return rtc::Optional<float>(
1853 static_cast<float>(*ana_event.config.num_channels));
1854 return rtc::Optional<float>();
terelius53dc23c2017-03-13 05:24:05 -07001855 },
philipel35ba9bd2017-04-19 05:58:51 -07001856 audio_network_adaptation_events_, begin_time_, &time_series);
1857 plot->AppendTimeSeries(std::move(time_series));
michaelt6e5b2192017-02-22 07:33:27 -08001858 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
1859 plot->SetSuggestedYAxis(0, 1, "Number of channels (1 (mono)/2 (stereo))",
1860 kBottomMargin, kTopMargin);
1861 plot->SetTitle("Reported audio encoder number of channels");
1862}
henrik.lundin3c938fc2017-06-14 06:09:58 -07001863
1864class NetEqStreamInput : public test::NetEqInput {
1865 public:
1866 // Does not take any ownership, and all pointers must refer to valid objects
1867 // that outlive the one constructed.
1868 NetEqStreamInput(const std::vector<LoggedRtpPacket>* packet_stream,
1869 const std::vector<uint64_t>* output_events_us,
1870 rtc::Optional<uint64_t> end_time_us)
1871 : packet_stream_(*packet_stream),
1872 packet_stream_it_(packet_stream_.begin()),
1873 output_events_us_it_(output_events_us->begin()),
1874 output_events_us_end_(output_events_us->end()),
1875 end_time_us_(end_time_us) {
1876 RTC_DCHECK(packet_stream);
1877 RTC_DCHECK(output_events_us);
1878 }
1879
1880 rtc::Optional<int64_t> NextPacketTime() const override {
1881 if (packet_stream_it_ == packet_stream_.end()) {
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001882 return rtc::nullopt;
henrik.lundin3c938fc2017-06-14 06:09:58 -07001883 }
1884 if (end_time_us_ && packet_stream_it_->timestamp > *end_time_us_) {
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001885 return rtc::nullopt;
henrik.lundin3c938fc2017-06-14 06:09:58 -07001886 }
1887 // Convert from us to ms.
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001888 return packet_stream_it_->timestamp / 1000;
henrik.lundin3c938fc2017-06-14 06:09:58 -07001889 }
1890
1891 rtc::Optional<int64_t> NextOutputEventTime() const override {
1892 if (output_events_us_it_ == output_events_us_end_) {
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001893 return rtc::nullopt;
henrik.lundin3c938fc2017-06-14 06:09:58 -07001894 }
1895 if (end_time_us_ && *output_events_us_it_ > *end_time_us_) {
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001896 return rtc::nullopt;
henrik.lundin3c938fc2017-06-14 06:09:58 -07001897 }
1898 // Convert from us to ms.
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001899 return rtc::checked_cast<int64_t>(*output_events_us_it_ / 1000);
henrik.lundin3c938fc2017-06-14 06:09:58 -07001900 }
1901
1902 std::unique_ptr<PacketData> PopPacket() override {
1903 if (packet_stream_it_ == packet_stream_.end()) {
1904 return std::unique_ptr<PacketData>();
1905 }
1906 std::unique_ptr<PacketData> packet_data(new PacketData());
1907 packet_data->header = packet_stream_it_->header;
1908 // Convert from us to ms.
1909 packet_data->time_ms = packet_stream_it_->timestamp / 1000.0;
1910
1911 // This is a header-only "dummy" packet. Set the payload to all zeros, with
1912 // length according to the virtual length.
1913 packet_data->payload.SetSize(packet_stream_it_->total_length);
1914 std::fill_n(packet_data->payload.data(), packet_data->payload.size(), 0);
1915
1916 ++packet_stream_it_;
1917 return packet_data;
1918 }
1919
1920 void AdvanceOutputEvent() override {
1921 if (output_events_us_it_ != output_events_us_end_) {
1922 ++output_events_us_it_;
1923 }
1924 }
1925
1926 bool ended() const override { return !NextEventTime(); }
1927
1928 rtc::Optional<RTPHeader> NextHeader() const override {
1929 if (packet_stream_it_ == packet_stream_.end()) {
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001930 return rtc::nullopt;
henrik.lundin3c938fc2017-06-14 06:09:58 -07001931 }
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01001932 return packet_stream_it_->header;
henrik.lundin3c938fc2017-06-14 06:09:58 -07001933 }
1934
1935 private:
1936 const std::vector<LoggedRtpPacket>& packet_stream_;
1937 std::vector<LoggedRtpPacket>::const_iterator packet_stream_it_;
1938 std::vector<uint64_t>::const_iterator output_events_us_it_;
1939 const std::vector<uint64_t>::const_iterator output_events_us_end_;
1940 const rtc::Optional<uint64_t> end_time_us_;
1941};
1942
1943namespace {
1944// Creates a NetEq test object and all necessary input and output helpers. Runs
1945// the test and returns the NetEqDelayAnalyzer object that was used to
1946// instrument the test.
1947std::unique_ptr<test::NetEqDelayAnalyzer> CreateNetEqTestAndRun(
1948 const std::vector<LoggedRtpPacket>* packet_stream,
1949 const std::vector<uint64_t>* output_events_us,
1950 rtc::Optional<uint64_t> end_time_us,
1951 const std::string& replacement_file_name,
1952 int file_sample_rate_hz) {
1953 std::unique_ptr<test::NetEqInput> input(
1954 new NetEqStreamInput(packet_stream, output_events_us, end_time_us));
1955
1956 constexpr int kReplacementPt = 127;
1957 std::set<uint8_t> cn_types;
1958 std::set<uint8_t> forbidden_types;
1959 input.reset(new test::NetEqReplacementInput(std::move(input), kReplacementPt,
1960 cn_types, forbidden_types));
1961
1962 NetEq::Config config;
1963 config.max_packets_in_buffer = 200;
1964 config.enable_fast_accelerate = true;
1965
1966 std::unique_ptr<test::VoidAudioSink> output(new test::VoidAudioSink());
1967
1968 test::NetEqTest::DecoderMap codecs;
1969
1970 // Create a "replacement decoder" that produces the decoded audio by reading
1971 // from a file rather than from the encoded payloads.
1972 std::unique_ptr<test::ResampleInputAudioFile> replacement_file(
1973 new test::ResampleInputAudioFile(replacement_file_name,
1974 file_sample_rate_hz));
1975 replacement_file->set_output_rate_hz(48000);
1976 std::unique_ptr<AudioDecoder> replacement_decoder(
1977 new test::FakeDecodeFromFile(std::move(replacement_file), 48000, false));
1978 test::NetEqTest::ExtDecoderMap ext_codecs;
1979 ext_codecs[kReplacementPt] = {replacement_decoder.get(),
1980 NetEqDecoder::kDecoderArbitrary,
1981 "replacement codec"};
1982
1983 std::unique_ptr<test::NetEqDelayAnalyzer> delay_cb(
1984 new test::NetEqDelayAnalyzer);
1985 test::DefaultNetEqTestErrorCallback error_cb;
1986 test::NetEqTest::Callbacks callbacks;
1987 callbacks.error_callback = &error_cb;
1988 callbacks.post_insert_packet = delay_cb.get();
1989 callbacks.get_audio_callback = delay_cb.get();
1990
1991 test::NetEqTest test(config, codecs, ext_codecs, std::move(input),
1992 std::move(output), callbacks);
1993 test.Run();
1994 return delay_cb;
1995}
1996} // namespace
1997
1998// Plots the jitter buffer delay profile. This will plot only for the first
1999// incoming audio SSRC. If the stream contains more than one incoming audio
2000// SSRC, all but the first will be ignored.
2001void EventLogAnalyzer::CreateAudioJitterBufferGraph(
2002 const std::string& replacement_file_name,
2003 int file_sample_rate_hz,
2004 Plot* plot) {
2005 const auto& incoming_audio_kv = std::find_if(
2006 rtp_packets_.begin(), rtp_packets_.end(),
2007 [this](std::pair<StreamId, std::vector<LoggedRtpPacket>> kv) {
2008 return kv.first.GetDirection() == kIncomingPacket &&
2009 this->IsAudioSsrc(kv.first);
2010 });
2011 if (incoming_audio_kv == rtp_packets_.end()) {
2012 // No incoming audio stream found.
2013 return;
2014 }
2015
2016 const uint32_t ssrc = incoming_audio_kv->first.GetSsrc();
2017
2018 std::map<uint32_t, std::vector<uint64_t>>::const_iterator output_events_it =
2019 audio_playout_events_.find(ssrc);
2020 if (output_events_it == audio_playout_events_.end()) {
2021 // Could not find output events with SSRC matching the input audio stream.
2022 // Using the first available stream of output events.
2023 output_events_it = audio_playout_events_.cbegin();
2024 }
2025
2026 rtc::Optional<uint64_t> end_time_us =
2027 log_segments_.empty()
Oskar Sundbom3928dbc2017-11-16 10:53:09 +01002028 ? rtc::nullopt
henrik.lundin3c938fc2017-06-14 06:09:58 -07002029 : rtc::Optional<uint64_t>(log_segments_.front().second);
2030
2031 auto delay_cb = CreateNetEqTestAndRun(
2032 &incoming_audio_kv->second, &output_events_it->second, end_time_us,
2033 replacement_file_name, file_sample_rate_hz);
2034
2035 std::vector<float> send_times_s;
2036 std::vector<float> arrival_delay_ms;
2037 std::vector<float> corrected_arrival_delay_ms;
2038 std::vector<rtc::Optional<float>> playout_delay_ms;
2039 std::vector<rtc::Optional<float>> target_delay_ms;
2040 delay_cb->CreateGraphs(&send_times_s, &arrival_delay_ms,
2041 &corrected_arrival_delay_ms, &playout_delay_ms,
2042 &target_delay_ms);
2043 RTC_DCHECK_EQ(send_times_s.size(), arrival_delay_ms.size());
2044 RTC_DCHECK_EQ(send_times_s.size(), corrected_arrival_delay_ms.size());
2045 RTC_DCHECK_EQ(send_times_s.size(), playout_delay_ms.size());
2046 RTC_DCHECK_EQ(send_times_s.size(), target_delay_ms.size());
2047
2048 std::map<StreamId, TimeSeries> time_series_packet_arrival;
2049 std::map<StreamId, TimeSeries> time_series_relative_packet_arrival;
2050 std::map<StreamId, TimeSeries> time_series_play_time;
2051 std::map<StreamId, TimeSeries> time_series_target_time;
2052 float min_y_axis = 0.f;
2053 float max_y_axis = 0.f;
2054 const StreamId stream_id = incoming_audio_kv->first;
2055 for (size_t i = 0; i < send_times_s.size(); ++i) {
2056 time_series_packet_arrival[stream_id].points.emplace_back(
2057 TimeSeriesPoint(send_times_s[i], arrival_delay_ms[i]));
2058 time_series_relative_packet_arrival[stream_id].points.emplace_back(
2059 TimeSeriesPoint(send_times_s[i], corrected_arrival_delay_ms[i]));
2060 min_y_axis = std::min(min_y_axis, corrected_arrival_delay_ms[i]);
2061 max_y_axis = std::max(max_y_axis, corrected_arrival_delay_ms[i]);
2062 if (playout_delay_ms[i]) {
2063 time_series_play_time[stream_id].points.emplace_back(
2064 TimeSeriesPoint(send_times_s[i], *playout_delay_ms[i]));
2065 min_y_axis = std::min(min_y_axis, *playout_delay_ms[i]);
2066 max_y_axis = std::max(max_y_axis, *playout_delay_ms[i]);
2067 }
2068 if (target_delay_ms[i]) {
2069 time_series_target_time[stream_id].points.emplace_back(
2070 TimeSeriesPoint(send_times_s[i], *target_delay_ms[i]));
2071 min_y_axis = std::min(min_y_axis, *target_delay_ms[i]);
2072 max_y_axis = std::max(max_y_axis, *target_delay_ms[i]);
2073 }
2074 }
2075
2076 // This code is adapted for a single stream. The creation of the streams above
2077 // guarantee that no more than one steam is included. If multiple streams are
2078 // to be plotted, they should likely be given distinct labels below.
2079 RTC_DCHECK_EQ(time_series_relative_packet_arrival.size(), 1);
2080 for (auto& series : time_series_relative_packet_arrival) {
2081 series.second.label = "Relative packet arrival delay";
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01002082 series.second.line_style = LineStyle::kLine;
henrik.lundin3c938fc2017-06-14 06:09:58 -07002083 plot->AppendTimeSeries(std::move(series.second));
2084 }
2085 RTC_DCHECK_EQ(time_series_play_time.size(), 1);
2086 for (auto& series : time_series_play_time) {
2087 series.second.label = "Playout delay";
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01002088 series.second.line_style = LineStyle::kLine;
henrik.lundin3c938fc2017-06-14 06:09:58 -07002089 plot->AppendTimeSeries(std::move(series.second));
2090 }
2091 RTC_DCHECK_EQ(time_series_target_time.size(), 1);
2092 for (auto& series : time_series_target_time) {
2093 series.second.label = "Target delay";
Bjorn Tereliusb577d5e2017-11-10 16:21:34 +01002094 series.second.line_style = LineStyle::kLine;
2095 series.second.point_style = PointStyle::kHighlight;
henrik.lundin3c938fc2017-06-14 06:09:58 -07002096 plot->AppendTimeSeries(std::move(series.second));
2097 }
2098
2099 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
2100 plot->SetYAxis(min_y_axis, max_y_axis, "Relative delay (ms)", kBottomMargin,
2101 kTopMargin);
2102 plot->SetTitle("NetEq timing");
2103}
Bjorn Terelius2eb31882017-11-30 15:15:25 +01002104
Qingsi Wang8eca1ff2018-02-02 11:49:44 -08002105void EventLogAnalyzer::CreateIceCandidatePairConfigGraph(Plot* plot) {
2106 std::map<uint32_t, TimeSeries> configs_by_cp_id;
2107 for (const auto& config : ice_candidate_pair_configs_) {
2108 if (configs_by_cp_id.find(config.candidate_pair_id) ==
2109 configs_by_cp_id.end()) {
2110 const std::string candidate_pair_desc =
2111 GetCandidatePairLogDescriptionAsString(config);
2112 configs_by_cp_id[config.candidate_pair_id] = TimeSeries(
2113 candidate_pair_desc, LineStyle::kNone, PointStyle::kHighlight);
2114 candidate_pair_desc_by_id_[config.candidate_pair_id] =
2115 candidate_pair_desc;
2116 }
2117 float x = ToCallTime(config.timestamp);
2118 float y = static_cast<float>(config.type);
2119 configs_by_cp_id[config.candidate_pair_id].points.emplace_back(x, y);
2120 }
2121
2122 // TODO(qingsi): There can be a large number of candidate pairs generated by
2123 // certain calls and the frontend cannot render the chart in this case due to
2124 // the failure of generating a palette with the same number of colors.
2125 for (auto& kv : configs_by_cp_id) {
2126 plot->AppendTimeSeries(std::move(kv.second));
2127 }
2128
2129 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
2130 plot->SetSuggestedYAxis(0, 3, "Numeric Config Type", kBottomMargin,
2131 kTopMargin);
2132 plot->SetTitle("[IceEventLog] ICE candidate pair configs");
2133}
2134
2135std::string EventLogAnalyzer::GetCandidatePairLogDescriptionFromId(
2136 uint32_t candidate_pair_id) {
2137 if (candidate_pair_desc_by_id_.find(candidate_pair_id) !=
2138 candidate_pair_desc_by_id_.end()) {
2139 return candidate_pair_desc_by_id_[candidate_pair_id];
2140 }
2141 for (const auto& config : ice_candidate_pair_configs_) {
2142 // TODO(qingsi): Add the handling of the "Updated" config event after the
2143 // visualization of property change for candidate pairs is introduced.
2144 if (candidate_pair_desc_by_id_.find(config.candidate_pair_id) ==
2145 candidate_pair_desc_by_id_.end()) {
2146 const std::string candidate_pair_desc =
2147 GetCandidatePairLogDescriptionAsString(config);
2148 candidate_pair_desc_by_id_[config.candidate_pair_id] =
2149 candidate_pair_desc;
2150 }
2151 }
2152 return candidate_pair_desc_by_id_[candidate_pair_id];
2153}
2154
2155void EventLogAnalyzer::CreateIceConnectivityCheckGraph(Plot* plot) {
2156 std::map<uint32_t, TimeSeries> checks_by_cp_id;
2157 for (const auto& event : ice_candidate_pair_events_) {
2158 if (checks_by_cp_id.find(event.candidate_pair_id) ==
2159 checks_by_cp_id.end()) {
2160 checks_by_cp_id[event.candidate_pair_id] = TimeSeries(
2161 GetCandidatePairLogDescriptionFromId(event.candidate_pair_id),
2162 LineStyle::kNone, PointStyle::kHighlight);
2163 }
2164 float x = ToCallTime(event.timestamp);
2165 float y = static_cast<float>(event.type);
2166 checks_by_cp_id[event.candidate_pair_id].points.emplace_back(x, y);
2167 }
2168
2169 // TODO(qingsi): The same issue as in CreateIceCandidatePairConfigGraph.
2170 for (auto& kv : checks_by_cp_id) {
2171 plot->AppendTimeSeries(std::move(kv.second));
2172 }
2173
2174 plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
2175 plot->SetSuggestedYAxis(0, 4, "Numeric Connectivity State", kBottomMargin,
2176 kTopMargin);
2177 plot->SetTitle("[IceEventLog] ICE connectivity checks");
2178}
2179
Bjorn Terelius2eb31882017-11-30 15:15:25 +01002180void EventLogAnalyzer::Notification(
2181 std::unique_ptr<TriageNotification> notification) {
2182 notifications_.push_back(std::move(notification));
2183}
2184
2185void EventLogAnalyzer::PrintNotifications(FILE* file) {
2186 if (notifications_.size() == 0)
2187 return;
2188 fprintf(file, "========== TRIAGE NOTIFICATIONS ==========\n");
2189 for (const auto& notification : notifications_) {
2190 rtc::Optional<float> call_timestamp = notification->Time();
2191 if (call_timestamp.has_value()) {
2192 fprintf(file, "%3.3lf s : %s\n", call_timestamp.value(),
2193 notification->ToString().c_str());
2194 } else {
2195 fprintf(file, " : %s\n", notification->ToString().c_str());
2196 }
2197 }
2198 fprintf(file, "========== END TRIAGE NOTIFICATIONS ==========\n");
2199}
2200
2201// TODO(terelius): Notifications could possibly be generated by the same code
2202// that produces the graphs. There is some code duplication that could be
2203// avoided, but that might be solved anyway when we move functionality from the
2204// analyzer to the parser.
2205void EventLogAnalyzer::CreateTriageNotifications() {
2206 uint64_t end_time_us = log_segments_.empty()
2207 ? std::numeric_limits<uint64_t>::max()
2208 : log_segments_.front().second;
2209 // Check for gaps in sequence numbers and capture timestamps.
2210 for (auto& kv : rtp_packets_) {
2211 StreamId stream_id = kv.first;
2212 const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
2213
2214 SeqNumUnwrapper<uint16_t> seq_no_unwrapper;
2215 rtc::Optional<int64_t> last_seq_no;
2216 SeqNumUnwrapper<uint32_t> timestamp_unwrapper;
2217 rtc::Optional<int64_t> last_timestamp;
2218 for (const auto& packet : packet_stream) {
2219 if (packet.timestamp > end_time_us) {
2220 // Only process the first (LOG_START, LOG_END) segment.
2221 break;
2222 }
2223 int64_t seq_no = seq_no_unwrapper.Unwrap(packet.header.sequenceNumber);
2224 if (last_seq_no.has_value() &&
2225 std::abs(seq_no - last_seq_no.value()) > 1000) {
2226 // With roughly 100 packets per second (~800kbps), this would require 10
2227 // seconds without data to trigger incorrectly.
2228 if (stream_id.GetDirection() == kIncomingPacket) {
2229 Notification(rtc::MakeUnique<IncomingSeqNoJump>(
2230 ToCallTime(packet.timestamp), packet.header.ssrc));
2231 } else {
2232 Notification(rtc::MakeUnique<OutgoingSeqNoJump>(
2233 ToCallTime(packet.timestamp), packet.header.ssrc));
2234 }
2235 }
2236 last_seq_no.emplace(seq_no);
2237 int64_t timestamp = timestamp_unwrapper.Unwrap(packet.header.timestamp);
2238 if (last_timestamp.has_value() &&
2239 std::abs(timestamp - last_timestamp.value()) > 900000) {
2240 // With a 90 kHz clock, this would require 10 seconds without data to
2241 // trigger incorrectly.
2242 if (stream_id.GetDirection() == kIncomingPacket) {
2243 Notification(rtc::MakeUnique<IncomingCaptureTimeJump>(
2244 ToCallTime(packet.timestamp), packet.header.ssrc));
2245 } else {
2246 Notification(rtc::MakeUnique<OutgoingCaptureTimeJump>(
2247 ToCallTime(packet.timestamp), packet.header.ssrc));
2248 }
2249 }
2250 last_timestamp.emplace(timestamp);
2251 }
2252 }
2253
2254 // Check for gaps in RTP and RTCP streams
2255 for (const auto direction :
2256 {PacketDirection::kIncomingPacket, PacketDirection::kOutgoingPacket}) {
2257 // TODO(terelius): The parser could provide a list of all packets, ordered
2258 // by time, for each direction.
2259 std::multimap<uint64_t, const LoggedRtpPacket*> rtp_in_direction;
2260 for (const auto& kv : rtp_packets_) {
2261 if (kv.first.GetDirection() == direction) {
2262 for (const LoggedRtpPacket& rtp_packet : kv.second)
2263 rtp_in_direction.emplace(rtp_packet.timestamp, &rtp_packet);
2264 }
2265 }
2266 rtc::Optional<uint64_t> last_rtp_packet;
2267 for (const auto& kv : rtp_in_direction) {
2268 uint64_t timestamp = kv.first;
2269 if (timestamp > end_time_us) {
2270 // Only process the first (LOG_START, LOG_END) segment.
2271 break;
2272 }
2273 int64_t duration = timestamp - last_rtp_packet.value_or(0);
2274 if (last_rtp_packet.has_value() && duration > 500000) {
2275 // No incoming packet for more than 500 ms.
2276 if (direction == kIncomingPacket) {
2277 Notification(rtc::MakeUnique<IncomingRtpReceiveTimeGap>(
2278 ToCallTime(timestamp), duration / 1000));
2279 } else {
2280 Notification(rtc::MakeUnique<OutgoingRtpSendTimeGap>(
2281 ToCallTime(timestamp), duration / 1000));
2282 }
2283 }
2284 last_rtp_packet.emplace(timestamp);
2285 }
2286
2287 // TODO(terelius): The parser could provide a list of all packets, ordered
2288 // by time, for each direction.
2289 std::multimap<uint64_t, const LoggedRtcpPacket*> rtcp_in_direction;
2290 for (const auto& kv : rtcp_packets_) {
2291 if (kv.first.GetDirection() == direction) {
2292 for (const LoggedRtcpPacket& rtcp_packet : kv.second)
2293 rtcp_in_direction.emplace(rtcp_packet.timestamp, &rtcp_packet);
2294 }
2295 }
2296 rtc::Optional<uint64_t> last_incoming_rtcp_packet;
2297 for (const auto& kv : rtcp_in_direction) {
2298 uint64_t timestamp = kv.first;
2299 if (timestamp > end_time_us) {
2300 // Only process the first (LOG_START, LOG_END) segment.
2301 break;
2302 }
2303 int64_t duration = timestamp - last_incoming_rtcp_packet.value_or(0);
2304 if (last_incoming_rtcp_packet.has_value() && duration > 2000000) {
2305 // No incoming feedback for more than 2000 ms.
2306 if (direction == kIncomingPacket) {
2307 Notification(rtc::MakeUnique<IncomingRtcpReceiveTimeGap>(
2308 ToCallTime(timestamp), duration / 1000));
2309 } else {
2310 Notification(rtc::MakeUnique<OutgoingRtcpSendTimeGap>(
2311 ToCallTime(timestamp), duration / 1000));
2312 }
2313 }
2314 last_incoming_rtcp_packet.emplace(timestamp);
2315 }
2316 }
2317
2318 // Loss feedback
2319 int64_t total_lost_packets = 0;
2320 int64_t total_expected_packets = 0;
2321 for (auto& bwe_update : bwe_loss_updates_) {
2322 if (bwe_update.timestamp > end_time_us) {
2323 // Only process the first (LOG_START, LOG_END) segment.
2324 break;
2325 }
2326 int64_t lost_packets = static_cast<double>(bwe_update.fraction_loss) / 255 *
2327 bwe_update.expected_packets;
2328 total_lost_packets += lost_packets;
2329 total_expected_packets += bwe_update.expected_packets;
2330 }
2331 double avg_outgoing_loss =
2332 static_cast<double>(total_lost_packets) / total_expected_packets;
2333 if (avg_outgoing_loss > 0.05) {
2334 Notification(rtc::MakeUnique<OutgoingHighLoss>(avg_outgoing_loss));
2335 }
2336}
2337
terelius54ce6802016-07-13 06:44:41 -07002338} // namespace plotting
2339} // namespace webrtc