blob: af6e2ee12454e3187b86cbc9e00cee3ac51fe418 [file] [log] [blame]
Bjorn Tereliusc186e142020-06-05 08:47:191/*
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
Björn Terelius2ea77ca2023-10-30 10:33:2113#include <cstdint>
14#include <map>
Bjorn Tereliusc186e142020-06-05 08:47:1915#include <memory>
16#include <set>
Björn Terelius2ea77ca2023-10-30 10:33:2117#include <string>
Bjorn Tereliusc186e142020-06-05 08:47:1918#include <utility>
19#include <vector>
20
Björn Terelius2ea77ca2023-10-30 10:33:2121#include "absl/strings/string_view.h"
22#include "absl/types/optional.h"
23#include "api/audio_codecs/audio_codec_pair_id.h"
24#include "api/audio_codecs/audio_decoder.h"
25#include "api/audio_codecs/audio_decoder_factory.h"
26#include "api/audio_codecs/audio_format.h"
27#include "api/function_view.h"
28#include "api/make_ref_counted.h"
29#include "api/neteq/neteq.h"
30#include "api/scoped_refptr.h"
31#include "api/units/timestamp.h"
32#include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h"
33#include "logging/rtc_event_log/rtc_event_log_parser.h"
Bjorn Tereliusc186e142020-06-05 08:47:1934#include "modules/audio_coding/neteq/tools/audio_sink.h"
35#include "modules/audio_coding/neteq/tools/fake_decode_from_file.h"
36#include "modules/audio_coding/neteq/tools/neteq_delay_analyzer.h"
Jakob Ivarsson27d70f32023-04-04 19:54:1837#include "modules/audio_coding/neteq/tools/neteq_event_log_input.h"
Björn Terelius2ea77ca2023-10-30 10:33:2138#include "modules/audio_coding/neteq/tools/neteq_input.h"
Bjorn Tereliusc186e142020-06-05 08:47:1939#include "modules/audio_coding/neteq/tools/neteq_replacement_input.h"
Björn Terelius2ea77ca2023-10-30 10:33:2140#include "modules/audio_coding/neteq/tools/neteq_stats_getter.h"
Bjorn Tereliusc186e142020-06-05 08:47:1941#include "modules/audio_coding/neteq/tools/neteq_test.h"
42#include "modules/audio_coding/neteq/tools/resample_input_audio_file.h"
Björn Terelius2ea77ca2023-10-30 10:33:2143#include "rtc_base/checks.h"
44#include "rtc_tools/rtc_event_log_visualizer/analyzer_common.h"
45#include "rtc_tools/rtc_event_log_visualizer/plot_base.h"
Bjorn Tereliusc186e142020-06-05 08:47:1946
47namespace webrtc {
48
49void CreateAudioEncoderTargetBitrateGraph(const ParsedRtcEventLog& parsed_log,
50 const AnalyzerConfig& config,
51 Plot* plot) {
52 TimeSeries time_series("Audio encoder target bitrate", LineStyle::kLine,
53 PointStyle::kHighlight);
54 auto GetAnaBitrateBps = [](const LoggedAudioNetworkAdaptationEvent& ana_event)
55 -> absl::optional<float> {
56 if (ana_event.config.bitrate_bps)
57 return absl::optional<float>(
58 static_cast<float>(*ana_event.config.bitrate_bps));
59 return absl::nullopt;
60 };
61 auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
Björn Tereliuscb241582022-02-25 08:22:3862 return config.GetCallTimeSec(packet.log_time());
Bjorn Tereliusc186e142020-06-05 08:47:1963 };
64 ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
65 ToCallTime, GetAnaBitrateBps,
66 parsed_log.audio_network_adaptation_events(), &time_series);
67 plot->AppendTimeSeries(std::move(time_series));
68 plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
69 kLeftMargin, kRightMargin);
70 plot->SetSuggestedYAxis(0, 1, "Bitrate (bps)", kBottomMargin, kTopMargin);
71 plot->SetTitle("Reported audio encoder target bitrate");
72}
73
74void CreateAudioEncoderFrameLengthGraph(const ParsedRtcEventLog& parsed_log,
75 const AnalyzerConfig& config,
76 Plot* plot) {
77 TimeSeries time_series("Audio encoder frame length", LineStyle::kLine,
78 PointStyle::kHighlight);
79 auto GetAnaFrameLengthMs =
80 [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
81 if (ana_event.config.frame_length_ms)
82 return absl::optional<float>(
83 static_cast<float>(*ana_event.config.frame_length_ms));
84 return absl::optional<float>();
85 };
86 auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
Björn Tereliuscb241582022-02-25 08:22:3887 return config.GetCallTimeSec(packet.log_time());
Bjorn Tereliusc186e142020-06-05 08:47:1988 };
89 ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
90 ToCallTime, GetAnaFrameLengthMs,
91 parsed_log.audio_network_adaptation_events(), &time_series);
92 plot->AppendTimeSeries(std::move(time_series));
93 plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
94 kLeftMargin, kRightMargin);
95 plot->SetSuggestedYAxis(0, 1, "Frame length (ms)", kBottomMargin, kTopMargin);
96 plot->SetTitle("Reported audio encoder frame length");
97}
98
99void CreateAudioEncoderPacketLossGraph(const ParsedRtcEventLog& parsed_log,
100 const AnalyzerConfig& config,
101 Plot* plot) {
102 TimeSeries time_series("Audio encoder uplink packet loss fraction",
103 LineStyle::kLine, PointStyle::kHighlight);
104 auto GetAnaPacketLoss =
105 [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
106 if (ana_event.config.uplink_packet_loss_fraction)
107 return absl::optional<float>(static_cast<float>(
108 *ana_event.config.uplink_packet_loss_fraction));
109 return absl::optional<float>();
110 };
111 auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
Björn Tereliuscb241582022-02-25 08:22:38112 return config.GetCallTimeSec(packet.log_time());
Bjorn Tereliusc186e142020-06-05 08:47:19113 };
114 ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
115 ToCallTime, GetAnaPacketLoss,
116 parsed_log.audio_network_adaptation_events(), &time_series);
117 plot->AppendTimeSeries(std::move(time_series));
118 plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
119 kLeftMargin, kRightMargin);
120 plot->SetSuggestedYAxis(0, 10, "Percent lost packets", kBottomMargin,
121 kTopMargin);
122 plot->SetTitle("Reported audio encoder lost packets");
123}
124
125void CreateAudioEncoderEnableFecGraph(const ParsedRtcEventLog& parsed_log,
126 const AnalyzerConfig& config,
127 Plot* plot) {
128 TimeSeries time_series("Audio encoder FEC", LineStyle::kLine,
129 PointStyle::kHighlight);
130 auto GetAnaFecEnabled =
131 [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
132 if (ana_event.config.enable_fec)
133 return absl::optional<float>(
134 static_cast<float>(*ana_event.config.enable_fec));
135 return absl::optional<float>();
136 };
137 auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
Björn Tereliuscb241582022-02-25 08:22:38138 return config.GetCallTimeSec(packet.log_time());
Bjorn Tereliusc186e142020-06-05 08:47:19139 };
140 ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
141 ToCallTime, GetAnaFecEnabled,
142 parsed_log.audio_network_adaptation_events(), &time_series);
143 plot->AppendTimeSeries(std::move(time_series));
144 plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
145 kLeftMargin, kRightMargin);
146 plot->SetSuggestedYAxis(0, 1, "FEC (false/true)", kBottomMargin, kTopMargin);
147 plot->SetTitle("Reported audio encoder FEC");
148}
149
150void CreateAudioEncoderEnableDtxGraph(const ParsedRtcEventLog& parsed_log,
151 const AnalyzerConfig& config,
152 Plot* plot) {
153 TimeSeries time_series("Audio encoder DTX", LineStyle::kLine,
154 PointStyle::kHighlight);
155 auto GetAnaDtxEnabled =
156 [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
157 if (ana_event.config.enable_dtx)
158 return absl::optional<float>(
159 static_cast<float>(*ana_event.config.enable_dtx));
160 return absl::optional<float>();
161 };
162 auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
Björn Tereliuscb241582022-02-25 08:22:38163 return config.GetCallTimeSec(packet.log_time());
Bjorn Tereliusc186e142020-06-05 08:47:19164 };
165 ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
166 ToCallTime, GetAnaDtxEnabled,
167 parsed_log.audio_network_adaptation_events(), &time_series);
168 plot->AppendTimeSeries(std::move(time_series));
169 plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
170 kLeftMargin, kRightMargin);
171 plot->SetSuggestedYAxis(0, 1, "DTX (false/true)", kBottomMargin, kTopMargin);
172 plot->SetTitle("Reported audio encoder DTX");
173}
174
175void CreateAudioEncoderNumChannelsGraph(const ParsedRtcEventLog& parsed_log,
176 const AnalyzerConfig& config,
177 Plot* plot) {
178 TimeSeries time_series("Audio encoder number of channels", LineStyle::kLine,
179 PointStyle::kHighlight);
180 auto GetAnaNumChannels =
181 [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
182 if (ana_event.config.num_channels)
183 return absl::optional<float>(
184 static_cast<float>(*ana_event.config.num_channels));
185 return absl::optional<float>();
186 };
187 auto ToCallTime = [config](const LoggedAudioNetworkAdaptationEvent& packet) {
Björn Tereliuscb241582022-02-25 08:22:38188 return config.GetCallTimeSec(packet.log_time());
Bjorn Tereliusc186e142020-06-05 08:47:19189 };
190 ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
191 ToCallTime, GetAnaNumChannels,
192 parsed_log.audio_network_adaptation_events(), &time_series);
193 plot->AppendTimeSeries(std::move(time_series));
194 plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
195 kLeftMargin, kRightMargin);
196 plot->SetSuggestedYAxis(0, 1, "Number of channels (1 (mono)/2 (stereo))",
197 kBottomMargin, kTopMargin);
198 plot->SetTitle("Reported audio encoder number of channels");
199}
200
Bjorn Tereliusc186e142020-06-05 08:47:19201namespace {
202
203// Factory to create a "replacement decoder" that produces the decoded audio
204// by reading from a file rather than from the encoded payloads.
205class ReplacementAudioDecoderFactory : public AudioDecoderFactory {
206 public:
207 ReplacementAudioDecoderFactory(const absl::string_view replacement_file_name,
208 int file_sample_rate_hz)
209 : replacement_file_name_(replacement_file_name),
210 file_sample_rate_hz_(file_sample_rate_hz) {}
211
212 std::vector<AudioCodecSpec> GetSupportedDecoders() override {
Artem Titovd3251962021-11-15 15:57:07213 RTC_DCHECK_NOTREACHED();
Bjorn Tereliusc186e142020-06-05 08:47:19214 return {};
215 }
216
217 bool IsSupportedDecoder(const SdpAudioFormat& format) override {
218 return true;
219 }
220
221 std::unique_ptr<AudioDecoder> MakeAudioDecoder(
222 const SdpAudioFormat& format,
223 absl::optional<AudioCodecPairId> codec_pair_id) override {
224 auto replacement_file = std::make_unique<test::ResampleInputAudioFile>(
225 replacement_file_name_, file_sample_rate_hz_);
226 replacement_file->set_output_rate_hz(48000);
227 return std::make_unique<test::FakeDecodeFromFile>(
228 std::move(replacement_file), 48000, false);
229 }
230
231 private:
232 const std::string replacement_file_name_;
233 const int file_sample_rate_hz_;
234};
235
236// Creates a NetEq test object and all necessary input and output helpers. Runs
237// the test and returns the NetEqDelayAnalyzer object that was used to
238// instrument the test.
239std::unique_ptr<test::NetEqStatsGetter> CreateNetEqTestAndRun(
Jakob Ivarsson27d70f32023-04-04 19:54:18240 ParsedRtcEventLog parsed_log,
241 uint32_t ssrc,
Bjorn Tereliusc186e142020-06-05 08:47:19242 const std::string& replacement_file_name,
243 int file_sample_rate_hz) {
Jakob Ivarsson27d70f32023-04-04 19:54:18244 std::unique_ptr<test::NetEqInput> input =
245 test::CreateNetEqEventLogInput(parsed_log, ssrc);
Jakob Ivarssonc2d37892023-04-10 17:40:10246 if (!input) {
247 return nullptr;
248 }
Bjorn Tereliusc186e142020-06-05 08:47:19249
250 constexpr int kReplacementPt = 127;
251 std::set<uint8_t> cn_types;
252 std::set<uint8_t> forbidden_types;
253 input.reset(new test::NetEqReplacementInput(std::move(input), kReplacementPt,
254 cn_types, forbidden_types));
255
Bjorn Tereliusc186e142020-06-05 08:47:19256 std::unique_ptr<test::VoidAudioSink> output(new test::VoidAudioSink());
257
258 rtc::scoped_refptr<AudioDecoderFactory> decoder_factory =
Tommi87f70902021-04-27 12:43:08259 rtc::make_ref_counted<ReplacementAudioDecoderFactory>(
Bjorn Tereliusc186e142020-06-05 08:47:19260 replacement_file_name, file_sample_rate_hz);
261
262 test::NetEqTest::DecoderMap codecs = {
263 {kReplacementPt, SdpAudioFormat("l16", 48000, 1)}};
264
265 std::unique_ptr<test::NetEqDelayAnalyzer> delay_cb(
266 new test::NetEqDelayAnalyzer);
267 std::unique_ptr<test::NetEqStatsGetter> neteq_stats_getter(
268 new test::NetEqStatsGetter(std::move(delay_cb)));
269 test::DefaultNetEqTestErrorCallback error_cb;
270 test::NetEqTest::Callbacks callbacks;
271 callbacks.error_callback = &error_cb;
272 callbacks.post_insert_packet = neteq_stats_getter->delay_analyzer();
273 callbacks.get_audio_callback = neteq_stats_getter.get();
274
Jakob Ivarssonc0a4a092021-07-01 12:18:27275 NetEq::Config config;
Bjorn Tereliusc186e142020-06-05 08:47:19276 test::NetEqTest test(config, decoder_factory, codecs, /*text_log=*/nullptr,
277 /*factory=*/nullptr, std::move(input), std::move(output),
278 callbacks);
279 test.Run();
280 return neteq_stats_getter;
281}
282} // namespace
283
284NetEqStatsGetterMap SimulateNetEq(const ParsedRtcEventLog& parsed_log,
285 const AnalyzerConfig& config,
286 const std::string& replacement_file_name,
287 int file_sample_rate_hz) {
288 NetEqStatsGetterMap neteq_stats;
Jakob Ivarsson27d70f32023-04-04 19:54:18289 for (uint32_t ssrc : parsed_log.incoming_audio_ssrcs()) {
Jakob Ivarssonc2d37892023-04-10 17:40:10290 std::unique_ptr<test::NetEqStatsGetter> stats = CreateNetEqTestAndRun(
Jakob Ivarsson27d70f32023-04-04 19:54:18291 parsed_log, ssrc, replacement_file_name, file_sample_rate_hz);
Jakob Ivarssonc2d37892023-04-10 17:40:10292 if (stats) {
293 neteq_stats[ssrc] = std::move(stats);
294 }
Bjorn Tereliusc186e142020-06-05 08:47:19295 }
Bjorn Tereliusc186e142020-06-05 08:47:19296 return neteq_stats;
297}
298
299// Given a NetEqStatsGetter and the SSRC that the NetEqStatsGetter was created
300// for, this method generates a plot for the jitter buffer delay profile.
301void CreateAudioJitterBufferGraph(const ParsedRtcEventLog& parsed_log,
302 const AnalyzerConfig& config,
303 uint32_t ssrc,
304 const test::NetEqStatsGetter* stats_getter,
305 Plot* plot) {
306 test::NetEqDelayAnalyzer::Delays arrival_delay_ms;
307 test::NetEqDelayAnalyzer::Delays corrected_arrival_delay_ms;
308 test::NetEqDelayAnalyzer::Delays playout_delay_ms;
309 test::NetEqDelayAnalyzer::Delays target_delay_ms;
310
311 stats_getter->delay_analyzer()->CreateGraphs(
312 &arrival_delay_ms, &corrected_arrival_delay_ms, &playout_delay_ms,
313 &target_delay_ms);
314
315 TimeSeries time_series_packet_arrival("packet arrival delay",
316 LineStyle::kLine);
317 TimeSeries time_series_relative_packet_arrival(
318 "Relative packet arrival delay", LineStyle::kLine);
319 TimeSeries time_series_play_time("Playout delay", LineStyle::kLine);
320 TimeSeries time_series_target_time("Target delay", LineStyle::kLine,
321 PointStyle::kHighlight);
322
323 for (const auto& data : arrival_delay_ms) {
Björn Tereliuscb241582022-02-25 08:22:38324 const float x = config.GetCallTimeSec(Timestamp::Millis(data.first));
Bjorn Tereliusc186e142020-06-05 08:47:19325 const float y = data.second;
326 time_series_packet_arrival.points.emplace_back(TimeSeriesPoint(x, y));
327 }
328 for (const auto& data : corrected_arrival_delay_ms) {
Björn Tereliuscb241582022-02-25 08:22:38329 const float x = config.GetCallTimeSec(Timestamp::Millis(data.first));
Bjorn Tereliusc186e142020-06-05 08:47:19330 const float y = data.second;
331 time_series_relative_packet_arrival.points.emplace_back(
332 TimeSeriesPoint(x, y));
333 }
334 for (const auto& data : playout_delay_ms) {
Björn Tereliuscb241582022-02-25 08:22:38335 const float x = config.GetCallTimeSec(Timestamp::Millis(data.first));
Bjorn Tereliusc186e142020-06-05 08:47:19336 const float y = data.second;
337 time_series_play_time.points.emplace_back(TimeSeriesPoint(x, y));
338 }
339 for (const auto& data : target_delay_ms) {
Björn Tereliuscb241582022-02-25 08:22:38340 const float x = config.GetCallTimeSec(Timestamp::Millis(data.first));
Bjorn Tereliusc186e142020-06-05 08:47:19341 const float y = data.second;
342 time_series_target_time.points.emplace_back(TimeSeriesPoint(x, y));
343 }
344
345 plot->AppendTimeSeries(std::move(time_series_packet_arrival));
346 plot->AppendTimeSeries(std::move(time_series_relative_packet_arrival));
347 plot->AppendTimeSeries(std::move(time_series_play_time));
348 plot->AppendTimeSeries(std::move(time_series_target_time));
349
350 plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
351 kLeftMargin, kRightMargin);
352 plot->SetSuggestedYAxis(0, 1, "Relative delay (ms)", kBottomMargin,
353 kTopMargin);
354 plot->SetTitle("NetEq timing for " +
355 GetStreamName(parsed_log, kIncomingPacket, ssrc));
356}
357
358template <typename NetEqStatsType>
359void CreateNetEqStatsGraphInternal(
360 const ParsedRtcEventLog& parsed_log,
361 const AnalyzerConfig& config,
362 const NetEqStatsGetterMap& neteq_stats,
363 rtc::FunctionView<const std::vector<std::pair<int64_t, NetEqStatsType>>*(
364 const test::NetEqStatsGetter*)> data_extractor,
365 rtc::FunctionView<float(const NetEqStatsType&)> stats_extractor,
366 const std::string& plot_name,
367 Plot* plot) {
368 std::map<uint32_t, TimeSeries> time_series;
369
370 for (const auto& st : neteq_stats) {
371 const uint32_t ssrc = st.first;
372 const std::vector<std::pair<int64_t, NetEqStatsType>>* data_vector =
373 data_extractor(st.second.get());
374 for (const auto& data : *data_vector) {
Björn Tereliuscb241582022-02-25 08:22:38375 const float time = config.GetCallTimeSec(Timestamp::Millis(data.first));
Bjorn Tereliusc186e142020-06-05 08:47:19376 const float value = stats_extractor(data.second);
377 time_series[ssrc].points.emplace_back(TimeSeriesPoint(time, value));
378 }
379 }
380
381 for (auto& series : time_series) {
382 series.second.label =
383 GetStreamName(parsed_log, kIncomingPacket, series.first);
384 series.second.line_style = LineStyle::kLine;
385 plot->AppendTimeSeries(std::move(series.second));
386 }
387
388 plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
389 kLeftMargin, kRightMargin);
390 plot->SetSuggestedYAxis(0, 1, plot_name, kBottomMargin, kTopMargin);
391 plot->SetTitle(plot_name);
392}
393
394void CreateNetEqNetworkStatsGraph(
395 const ParsedRtcEventLog& parsed_log,
396 const AnalyzerConfig& config,
397 const NetEqStatsGetterMap& neteq_stats,
398 rtc::FunctionView<float(const NetEqNetworkStatistics&)> stats_extractor,
399 const std::string& plot_name,
400 Plot* plot) {
401 CreateNetEqStatsGraphInternal<NetEqNetworkStatistics>(
402 parsed_log, config, neteq_stats,
403 [](const test::NetEqStatsGetter* stats_getter) {
404 return stats_getter->stats();
405 },
406 stats_extractor, plot_name, plot);
407}
408
409void CreateNetEqLifetimeStatsGraph(
410 const ParsedRtcEventLog& parsed_log,
411 const AnalyzerConfig& config,
412 const NetEqStatsGetterMap& neteq_stats,
413 rtc::FunctionView<float(const NetEqLifetimeStatistics&)> stats_extractor,
414 const std::string& plot_name,
415 Plot* plot) {
416 CreateNetEqStatsGraphInternal<NetEqLifetimeStatistics>(
417 parsed_log, config, neteq_stats,
418 [](const test::NetEqStatsGetter* stats_getter) {
419 return stats_getter->lifetime_stats();
420 },
421 stats_extractor, plot_name, plot);
422}
423
424} // namespace webrtc