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