1 /*
2  *  Copyright (c) 2020 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 
11 #include "rtc_tools/rtc_event_log_visualizer/analyze_audio.h"
12 
13 #include <memory>
14 #include <set>
15 #include <utility>
16 #include <vector>
17 
18 #include "modules/audio_coding/neteq/tools/audio_sink.h"
19 #include "modules/audio_coding/neteq/tools/fake_decode_from_file.h"
20 #include "modules/audio_coding/neteq/tools/neteq_delay_analyzer.h"
21 #include "modules/audio_coding/neteq/tools/neteq_replacement_input.h"
22 #include "modules/audio_coding/neteq/tools/neteq_test.h"
23 #include "modules/audio_coding/neteq/tools/resample_input_audio_file.h"
24 #include "rtc_base/ref_counted_object.h"
25 
26 namespace webrtc {
27 
CreateAudioEncoderTargetBitrateGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,Plot * plot)28 void CreateAudioEncoderTargetBitrateGraph(const ParsedRtcEventLog& parsed_log,
29                                           const AnalyzerConfig& config,
30                                           Plot* plot) {
31   TimeSeries time_series("Audio encoder target bitrate", LineStyle::kLine,
32                          PointStyle::kHighlight);
33   auto GetAnaBitrateBps = [](const LoggedAudioNetworkAdaptationEvent& ana_event)
34       -> absl::optional<float> {
35     if (ana_event.config.bitrate_bps)
36       return absl::optional<float>(
37           static_cast<float>(*ana_event.config.bitrate_bps));
38     return absl::nullopt;
39   };
40   auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
41     return config.GetCallTimeSec(packet.log_time_us());
42   };
43   ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
44       ToCallTime, GetAnaBitrateBps,
45       parsed_log.audio_network_adaptation_events(), &time_series);
46   plot->AppendTimeSeries(std::move(time_series));
47   plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
48                  kLeftMargin, kRightMargin);
49   plot->SetSuggestedYAxis(0, 1, "Bitrate (bps)", kBottomMargin, kTopMargin);
50   plot->SetTitle("Reported audio encoder target bitrate");
51 }
52 
CreateAudioEncoderFrameLengthGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,Plot * plot)53 void CreateAudioEncoderFrameLengthGraph(const ParsedRtcEventLog& parsed_log,
54                                         const AnalyzerConfig& config,
55                                         Plot* plot) {
56   TimeSeries time_series("Audio encoder frame length", LineStyle::kLine,
57                          PointStyle::kHighlight);
58   auto GetAnaFrameLengthMs =
59       [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
60         if (ana_event.config.frame_length_ms)
61           return absl::optional<float>(
62               static_cast<float>(*ana_event.config.frame_length_ms));
63         return absl::optional<float>();
64       };
65   auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
66     return config.GetCallTimeSec(packet.log_time_us());
67   };
68   ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
69       ToCallTime, GetAnaFrameLengthMs,
70       parsed_log.audio_network_adaptation_events(), &time_series);
71   plot->AppendTimeSeries(std::move(time_series));
72   plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
73                  kLeftMargin, kRightMargin);
74   plot->SetSuggestedYAxis(0, 1, "Frame length (ms)", kBottomMargin, kTopMargin);
75   plot->SetTitle("Reported audio encoder frame length");
76 }
77 
CreateAudioEncoderPacketLossGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,Plot * plot)78 void CreateAudioEncoderPacketLossGraph(const ParsedRtcEventLog& parsed_log,
79                                        const AnalyzerConfig& config,
80                                        Plot* plot) {
81   TimeSeries time_series("Audio encoder uplink packet loss fraction",
82                          LineStyle::kLine, PointStyle::kHighlight);
83   auto GetAnaPacketLoss =
84       [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
85         if (ana_event.config.uplink_packet_loss_fraction)
86           return absl::optional<float>(static_cast<float>(
87               *ana_event.config.uplink_packet_loss_fraction));
88         return absl::optional<float>();
89       };
90   auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
91     return config.GetCallTimeSec(packet.log_time_us());
92   };
93   ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
94       ToCallTime, GetAnaPacketLoss,
95       parsed_log.audio_network_adaptation_events(), &time_series);
96   plot->AppendTimeSeries(std::move(time_series));
97   plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
98                  kLeftMargin, kRightMargin);
99   plot->SetSuggestedYAxis(0, 10, "Percent lost packets", kBottomMargin,
100                           kTopMargin);
101   plot->SetTitle("Reported audio encoder lost packets");
102 }
103 
CreateAudioEncoderEnableFecGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,Plot * plot)104 void CreateAudioEncoderEnableFecGraph(const ParsedRtcEventLog& parsed_log,
105                                       const AnalyzerConfig& config,
106                                       Plot* plot) {
107   TimeSeries time_series("Audio encoder FEC", LineStyle::kLine,
108                          PointStyle::kHighlight);
109   auto GetAnaFecEnabled =
110       [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
111         if (ana_event.config.enable_fec)
112           return absl::optional<float>(
113               static_cast<float>(*ana_event.config.enable_fec));
114         return absl::optional<float>();
115       };
116   auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
117     return config.GetCallTimeSec(packet.log_time_us());
118   };
119   ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
120       ToCallTime, GetAnaFecEnabled,
121       parsed_log.audio_network_adaptation_events(), &time_series);
122   plot->AppendTimeSeries(std::move(time_series));
123   plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
124                  kLeftMargin, kRightMargin);
125   plot->SetSuggestedYAxis(0, 1, "FEC (false/true)", kBottomMargin, kTopMargin);
126   plot->SetTitle("Reported audio encoder FEC");
127 }
128 
CreateAudioEncoderEnableDtxGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,Plot * plot)129 void CreateAudioEncoderEnableDtxGraph(const ParsedRtcEventLog& parsed_log,
130                                       const AnalyzerConfig& config,
131                                       Plot* plot) {
132   TimeSeries time_series("Audio encoder DTX", LineStyle::kLine,
133                          PointStyle::kHighlight);
134   auto GetAnaDtxEnabled =
135       [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
136         if (ana_event.config.enable_dtx)
137           return absl::optional<float>(
138               static_cast<float>(*ana_event.config.enable_dtx));
139         return absl::optional<float>();
140       };
141   auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
142     return config.GetCallTimeSec(packet.log_time_us());
143   };
144   ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
145       ToCallTime, GetAnaDtxEnabled,
146       parsed_log.audio_network_adaptation_events(), &time_series);
147   plot->AppendTimeSeries(std::move(time_series));
148   plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
149                  kLeftMargin, kRightMargin);
150   plot->SetSuggestedYAxis(0, 1, "DTX (false/true)", kBottomMargin, kTopMargin);
151   plot->SetTitle("Reported audio encoder DTX");
152 }
153 
CreateAudioEncoderNumChannelsGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,Plot * plot)154 void CreateAudioEncoderNumChannelsGraph(const ParsedRtcEventLog& parsed_log,
155                                         const AnalyzerConfig& config,
156                                         Plot* plot) {
157   TimeSeries time_series("Audio encoder number of channels", LineStyle::kLine,
158                          PointStyle::kHighlight);
159   auto GetAnaNumChannels =
160       [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
161         if (ana_event.config.num_channels)
162           return absl::optional<float>(
163               static_cast<float>(*ana_event.config.num_channels));
164         return absl::optional<float>();
165       };
166   auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
167     return config.GetCallTimeSec(packet.log_time_us());
168   };
169   ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
170       ToCallTime, GetAnaNumChannels,
171       parsed_log.audio_network_adaptation_events(), &time_series);
172   plot->AppendTimeSeries(std::move(time_series));
173   plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
174                  kLeftMargin, kRightMargin);
175   plot->SetSuggestedYAxis(0, 1, "Number of channels (1 (mono)/2 (stereo))",
176                           kBottomMargin, kTopMargin);
177   plot->SetTitle("Reported audio encoder number of channels");
178 }
179 
180 class NetEqStreamInput : public test::NetEqInput {
181  public:
182   // Does not take any ownership, and all pointers must refer to valid objects
183   // that outlive the one constructed.
NetEqStreamInput(const std::vector<LoggedRtpPacketIncoming> * packet_stream,const std::vector<LoggedAudioPlayoutEvent> * output_events,absl::optional<int64_t> end_time_ms)184   NetEqStreamInput(const std::vector<LoggedRtpPacketIncoming>* packet_stream,
185                    const std::vector<LoggedAudioPlayoutEvent>* output_events,
186                    absl::optional<int64_t> end_time_ms)
187       : packet_stream_(*packet_stream),
188         packet_stream_it_(packet_stream_.begin()),
189         output_events_it_(output_events->begin()),
190         output_events_end_(output_events->end()),
191         end_time_ms_(end_time_ms) {
192     RTC_DCHECK(packet_stream);
193     RTC_DCHECK(output_events);
194   }
195 
NextPacketTime() const196   absl::optional<int64_t> NextPacketTime() const override {
197     if (packet_stream_it_ == packet_stream_.end()) {
198       return absl::nullopt;
199     }
200     if (end_time_ms_ && packet_stream_it_->rtp.log_time_ms() > *end_time_ms_) {
201       return absl::nullopt;
202     }
203     return packet_stream_it_->rtp.log_time_ms();
204   }
205 
NextOutputEventTime() const206   absl::optional<int64_t> NextOutputEventTime() const override {
207     if (output_events_it_ == output_events_end_) {
208       return absl::nullopt;
209     }
210     if (end_time_ms_ && output_events_it_->log_time_ms() > *end_time_ms_) {
211       return absl::nullopt;
212     }
213     return output_events_it_->log_time_ms();
214   }
215 
PopPacket()216   std::unique_ptr<PacketData> PopPacket() override {
217     if (packet_stream_it_ == packet_stream_.end()) {
218       return std::unique_ptr<PacketData>();
219     }
220     std::unique_ptr<PacketData> packet_data(new PacketData());
221     packet_data->header = packet_stream_it_->rtp.header;
222     packet_data->time_ms = packet_stream_it_->rtp.log_time_ms();
223 
224     // This is a header-only "dummy" packet. Set the payload to all zeros, with
225     // length according to the virtual length.
226     packet_data->payload.SetSize(packet_stream_it_->rtp.total_length -
227                                  packet_stream_it_->rtp.header_length);
228     std::fill_n(packet_data->payload.data(), packet_data->payload.size(), 0);
229 
230     ++packet_stream_it_;
231     return packet_data;
232   }
233 
AdvanceOutputEvent()234   void AdvanceOutputEvent() override {
235     if (output_events_it_ != output_events_end_) {
236       ++output_events_it_;
237     }
238   }
239 
ended() const240   bool ended() const override { return !NextEventTime(); }
241 
NextHeader() const242   absl::optional<RTPHeader> NextHeader() const override {
243     if (packet_stream_it_ == packet_stream_.end()) {
244       return absl::nullopt;
245     }
246     return packet_stream_it_->rtp.header;
247   }
248 
249  private:
250   const std::vector<LoggedRtpPacketIncoming>& packet_stream_;
251   std::vector<LoggedRtpPacketIncoming>::const_iterator packet_stream_it_;
252   std::vector<LoggedAudioPlayoutEvent>::const_iterator output_events_it_;
253   const std::vector<LoggedAudioPlayoutEvent>::const_iterator output_events_end_;
254   const absl::optional<int64_t> end_time_ms_;
255 };
256 
257 namespace {
258 
259 // Factory to create a "replacement decoder" that produces the decoded audio
260 // by reading from a file rather than from the encoded payloads.
261 class ReplacementAudioDecoderFactory : public AudioDecoderFactory {
262  public:
ReplacementAudioDecoderFactory(const absl::string_view replacement_file_name,int file_sample_rate_hz)263   ReplacementAudioDecoderFactory(const absl::string_view replacement_file_name,
264                                  int file_sample_rate_hz)
265       : replacement_file_name_(replacement_file_name),
266         file_sample_rate_hz_(file_sample_rate_hz) {}
267 
GetSupportedDecoders()268   std::vector<AudioCodecSpec> GetSupportedDecoders() override {
269     RTC_NOTREACHED();
270     return {};
271   }
272 
IsSupportedDecoder(const SdpAudioFormat & format)273   bool IsSupportedDecoder(const SdpAudioFormat& format) override {
274     return true;
275   }
276 
MakeAudioDecoder(const SdpAudioFormat & format,absl::optional<AudioCodecPairId> codec_pair_id)277   std::unique_ptr<AudioDecoder> MakeAudioDecoder(
278       const SdpAudioFormat& format,
279       absl::optional<AudioCodecPairId> codec_pair_id) override {
280     auto replacement_file = std::make_unique<test::ResampleInputAudioFile>(
281         replacement_file_name_, file_sample_rate_hz_);
282     replacement_file->set_output_rate_hz(48000);
283     return std::make_unique<test::FakeDecodeFromFile>(
284         std::move(replacement_file), 48000, false);
285   }
286 
287  private:
288   const std::string replacement_file_name_;
289   const int file_sample_rate_hz_;
290 };
291 
292 // Creates a NetEq test object and all necessary input and output helpers. Runs
293 // the test and returns the NetEqDelayAnalyzer object that was used to
294 // instrument the test.
CreateNetEqTestAndRun(const std::vector<LoggedRtpPacketIncoming> * packet_stream,const std::vector<LoggedAudioPlayoutEvent> * output_events,absl::optional<int64_t> end_time_ms,const std::string & replacement_file_name,int file_sample_rate_hz)295 std::unique_ptr<test::NetEqStatsGetter> CreateNetEqTestAndRun(
296     const std::vector<LoggedRtpPacketIncoming>* packet_stream,
297     const std::vector<LoggedAudioPlayoutEvent>* output_events,
298     absl::optional<int64_t> end_time_ms,
299     const std::string& replacement_file_name,
300     int file_sample_rate_hz) {
301   std::unique_ptr<test::NetEqInput> input(
302       new NetEqStreamInput(packet_stream, output_events, end_time_ms));
303 
304   constexpr int kReplacementPt = 127;
305   std::set<uint8_t> cn_types;
306   std::set<uint8_t> forbidden_types;
307   input.reset(new test::NetEqReplacementInput(std::move(input), kReplacementPt,
308                                               cn_types, forbidden_types));
309 
310   NetEq::Config config;
311   config.max_packets_in_buffer = 200;
312   config.enable_fast_accelerate = true;
313 
314   std::unique_ptr<test::VoidAudioSink> output(new test::VoidAudioSink());
315 
316   rtc::scoped_refptr<AudioDecoderFactory> decoder_factory =
317       new rtc::RefCountedObject<ReplacementAudioDecoderFactory>(
318           replacement_file_name, file_sample_rate_hz);
319 
320   test::NetEqTest::DecoderMap codecs = {
321       {kReplacementPt, SdpAudioFormat("l16", 48000, 1)}};
322 
323   std::unique_ptr<test::NetEqDelayAnalyzer> delay_cb(
324       new test::NetEqDelayAnalyzer);
325   std::unique_ptr<test::NetEqStatsGetter> neteq_stats_getter(
326       new test::NetEqStatsGetter(std::move(delay_cb)));
327   test::DefaultNetEqTestErrorCallback error_cb;
328   test::NetEqTest::Callbacks callbacks;
329   callbacks.error_callback = &error_cb;
330   callbacks.post_insert_packet = neteq_stats_getter->delay_analyzer();
331   callbacks.get_audio_callback = neteq_stats_getter.get();
332 
333   test::NetEqTest test(config, decoder_factory, codecs, /*text_log=*/nullptr,
334                        /*factory=*/nullptr, std::move(input), std::move(output),
335                        callbacks);
336   test.Run();
337   return neteq_stats_getter;
338 }
339 }  // namespace
340 
SimulateNetEq(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,const std::string & replacement_file_name,int file_sample_rate_hz)341 NetEqStatsGetterMap SimulateNetEq(const ParsedRtcEventLog& parsed_log,
342                                   const AnalyzerConfig& config,
343                                   const std::string& replacement_file_name,
344                                   int file_sample_rate_hz) {
345   NetEqStatsGetterMap neteq_stats;
346 
347   for (const auto& stream : parsed_log.incoming_rtp_packets_by_ssrc()) {
348     const uint32_t ssrc = stream.ssrc;
349     if (!IsAudioSsrc(parsed_log, kIncomingPacket, ssrc))
350       continue;
351     const std::vector<LoggedRtpPacketIncoming>* audio_packets =
352         &stream.incoming_packets;
353     if (audio_packets == nullptr) {
354       // No incoming audio stream found.
355       continue;
356     }
357 
358     RTC_DCHECK(neteq_stats.find(ssrc) == neteq_stats.end());
359 
360     std::map<uint32_t, std::vector<LoggedAudioPlayoutEvent>>::const_iterator
361         output_events_it = parsed_log.audio_playout_events().find(ssrc);
362     if (output_events_it == parsed_log.audio_playout_events().end()) {
363       // Could not find output events with SSRC matching the input audio stream.
364       // Using the first available stream of output events.
365       output_events_it = parsed_log.audio_playout_events().cbegin();
366     }
367 
368     int64_t end_time_ms = parsed_log.first_log_segment().stop_time_ms();
369 
370     neteq_stats[ssrc] = CreateNetEqTestAndRun(
371         audio_packets, &output_events_it->second, end_time_ms,
372         replacement_file_name, file_sample_rate_hz);
373   }
374 
375   return neteq_stats;
376 }
377 
378 // Given a NetEqStatsGetter and the SSRC that the NetEqStatsGetter was created
379 // for, this method generates a plot for the jitter buffer delay profile.
CreateAudioJitterBufferGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,uint32_t ssrc,const test::NetEqStatsGetter * stats_getter,Plot * plot)380 void CreateAudioJitterBufferGraph(const ParsedRtcEventLog& parsed_log,
381                                   const AnalyzerConfig& config,
382                                   uint32_t ssrc,
383                                   const test::NetEqStatsGetter* stats_getter,
384                                   Plot* plot) {
385   test::NetEqDelayAnalyzer::Delays arrival_delay_ms;
386   test::NetEqDelayAnalyzer::Delays corrected_arrival_delay_ms;
387   test::NetEqDelayAnalyzer::Delays playout_delay_ms;
388   test::NetEqDelayAnalyzer::Delays target_delay_ms;
389 
390   stats_getter->delay_analyzer()->CreateGraphs(
391       &arrival_delay_ms, &corrected_arrival_delay_ms, &playout_delay_ms,
392       &target_delay_ms);
393 
394   TimeSeries time_series_packet_arrival("packet arrival delay",
395                                         LineStyle::kLine);
396   TimeSeries time_series_relative_packet_arrival(
397       "Relative packet arrival delay", LineStyle::kLine);
398   TimeSeries time_series_play_time("Playout delay", LineStyle::kLine);
399   TimeSeries time_series_target_time("Target delay", LineStyle::kLine,
400                                      PointStyle::kHighlight);
401 
402   for (const auto& data : arrival_delay_ms) {
403     const float x = config.GetCallTimeSec(data.first * 1000);  // ms to us.
404     const float y = data.second;
405     time_series_packet_arrival.points.emplace_back(TimeSeriesPoint(x, y));
406   }
407   for (const auto& data : corrected_arrival_delay_ms) {
408     const float x = config.GetCallTimeSec(data.first * 1000);  // ms to us.
409     const float y = data.second;
410     time_series_relative_packet_arrival.points.emplace_back(
411         TimeSeriesPoint(x, y));
412   }
413   for (const auto& data : playout_delay_ms) {
414     const float x = config.GetCallTimeSec(data.first * 1000);  // ms to us.
415     const float y = data.second;
416     time_series_play_time.points.emplace_back(TimeSeriesPoint(x, y));
417   }
418   for (const auto& data : target_delay_ms) {
419     const float x = config.GetCallTimeSec(data.first * 1000);  // ms to us.
420     const float y = data.second;
421     time_series_target_time.points.emplace_back(TimeSeriesPoint(x, y));
422   }
423 
424   plot->AppendTimeSeries(std::move(time_series_packet_arrival));
425   plot->AppendTimeSeries(std::move(time_series_relative_packet_arrival));
426   plot->AppendTimeSeries(std::move(time_series_play_time));
427   plot->AppendTimeSeries(std::move(time_series_target_time));
428 
429   plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
430                  kLeftMargin, kRightMargin);
431   plot->SetSuggestedYAxis(0, 1, "Relative delay (ms)", kBottomMargin,
432                           kTopMargin);
433   plot->SetTitle("NetEq timing for " +
434                  GetStreamName(parsed_log, kIncomingPacket, ssrc));
435 }
436 
437 template <typename NetEqStatsType>
CreateNetEqStatsGraphInternal(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,const NetEqStatsGetterMap & neteq_stats,rtc::FunctionView<const std::vector<std::pair<int64_t,NetEqStatsType>> * (const test::NetEqStatsGetter *)> data_extractor,rtc::FunctionView<float (const NetEqStatsType &)> stats_extractor,const std::string & plot_name,Plot * plot)438 void CreateNetEqStatsGraphInternal(
439     const ParsedRtcEventLog& parsed_log,
440     const AnalyzerConfig& config,
441     const NetEqStatsGetterMap& neteq_stats,
442     rtc::FunctionView<const std::vector<std::pair<int64_t, NetEqStatsType>>*(
443         const test::NetEqStatsGetter*)> data_extractor,
444     rtc::FunctionView<float(const NetEqStatsType&)> stats_extractor,
445     const std::string& plot_name,
446     Plot* plot) {
447   std::map<uint32_t, TimeSeries> time_series;
448 
449   for (const auto& st : neteq_stats) {
450     const uint32_t ssrc = st.first;
451     const std::vector<std::pair<int64_t, NetEqStatsType>>* data_vector =
452         data_extractor(st.second.get());
453     for (const auto& data : *data_vector) {
454       const float time = config.GetCallTimeSec(data.first * 1000);  // ms to us.
455       const float value = stats_extractor(data.second);
456       time_series[ssrc].points.emplace_back(TimeSeriesPoint(time, value));
457     }
458   }
459 
460   for (auto& series : time_series) {
461     series.second.label =
462         GetStreamName(parsed_log, kIncomingPacket, series.first);
463     series.second.line_style = LineStyle::kLine;
464     plot->AppendTimeSeries(std::move(series.second));
465   }
466 
467   plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
468                  kLeftMargin, kRightMargin);
469   plot->SetSuggestedYAxis(0, 1, plot_name, kBottomMargin, kTopMargin);
470   plot->SetTitle(plot_name);
471 }
472 
CreateNetEqNetworkStatsGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,const NetEqStatsGetterMap & neteq_stats,rtc::FunctionView<float (const NetEqNetworkStatistics &)> stats_extractor,const std::string & plot_name,Plot * plot)473 void CreateNetEqNetworkStatsGraph(
474     const ParsedRtcEventLog& parsed_log,
475     const AnalyzerConfig& config,
476     const NetEqStatsGetterMap& neteq_stats,
477     rtc::FunctionView<float(const NetEqNetworkStatistics&)> stats_extractor,
478     const std::string& plot_name,
479     Plot* plot) {
480   CreateNetEqStatsGraphInternal<NetEqNetworkStatistics>(
481       parsed_log, config, neteq_stats,
482       [](const test::NetEqStatsGetter* stats_getter) {
483         return stats_getter->stats();
484       },
485       stats_extractor, plot_name, plot);
486 }
487 
CreateNetEqLifetimeStatsGraph(const ParsedRtcEventLog & parsed_log,const AnalyzerConfig & config,const NetEqStatsGetterMap & neteq_stats,rtc::FunctionView<float (const NetEqLifetimeStatistics &)> stats_extractor,const std::string & plot_name,Plot * plot)488 void CreateNetEqLifetimeStatsGraph(
489     const ParsedRtcEventLog& parsed_log,
490     const AnalyzerConfig& config,
491     const NetEqStatsGetterMap& neteq_stats,
492     rtc::FunctionView<float(const NetEqLifetimeStatistics&)> stats_extractor,
493     const std::string& plot_name,
494     Plot* plot) {
495   CreateNetEqStatsGraphInternal<NetEqLifetimeStatistics>(
496       parsed_log, config, neteq_stats,
497       [](const test::NetEqStatsGetter* stats_getter) {
498         return stats_getter->lifetime_stats();
499       },
500       stats_extractor, plot_name, plot);
501 }
502 
503 }  // namespace webrtc
504