xref: /aosp_15_r20/external/webrtc/modules/audio_coding/neteq/tools/neteq_delay_analyzer.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1  /*
2   *  Copyright (c) 2017 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 "modules/audio_coding/neteq/tools/neteq_delay_analyzer.h"
12  
13  #include <algorithm>
14  #include <fstream>
15  #include <ios>
16  #include <iterator>
17  #include <limits>
18  #include <utility>
19  
20  #include "absl/strings/string_view.h"
21  #include "modules/include/module_common_types_public.h"
22  #include "rtc_base/checks.h"
23  
24  namespace webrtc {
25  namespace test {
26  namespace {
27  constexpr char kArrivalDelayX[] = "arrival_delay_x";
28  constexpr char kArrivalDelayY[] = "arrival_delay_y";
29  constexpr char kTargetDelayX[] = "target_delay_x";
30  constexpr char kTargetDelayY[] = "target_delay_y";
31  constexpr char kPlayoutDelayX[] = "playout_delay_x";
32  constexpr char kPlayoutDelayY[] = "playout_delay_y";
33  
34  // Helper function for NetEqDelayAnalyzer::CreateGraphs. Returns the
35  // interpolated value of a function at the point x. Vector x_vec contains the
36  // sample points, and y_vec contains the function values at these points. The
37  // return value is a linear interpolation between y_vec values.
LinearInterpolate(double x,const std::vector<int64_t> & x_vec,const std::vector<int64_t> & y_vec)38  double LinearInterpolate(double x,
39                           const std::vector<int64_t>& x_vec,
40                           const std::vector<int64_t>& y_vec) {
41    // Find first element which is larger than x.
42    auto it = std::upper_bound(x_vec.begin(), x_vec.end(), x);
43    if (it == x_vec.end()) {
44      --it;
45    }
46    const size_t upper_ix = it - x_vec.begin();
47  
48    size_t lower_ix;
49    if (upper_ix == 0 || x_vec[upper_ix] <= x) {
50      lower_ix = upper_ix;
51    } else {
52      lower_ix = upper_ix - 1;
53    }
54    double y;
55    if (lower_ix == upper_ix) {
56      y = y_vec[lower_ix];
57    } else {
58      RTC_DCHECK_NE(x_vec[lower_ix], x_vec[upper_ix]);
59      y = (x - x_vec[lower_ix]) * (y_vec[upper_ix] - y_vec[lower_ix]) /
60              (x_vec[upper_ix] - x_vec[lower_ix]) +
61          y_vec[lower_ix];
62    }
63    return y;
64  }
65  
PrintDelays(const NetEqDelayAnalyzer::Delays & delays,int64_t ref_time_ms,absl::string_view var_name_x,absl::string_view var_name_y,std::ofstream & output,absl::string_view terminator="")66  void PrintDelays(const NetEqDelayAnalyzer::Delays& delays,
67                   int64_t ref_time_ms,
68                   absl::string_view var_name_x,
69                   absl::string_view var_name_y,
70                   std::ofstream& output,
71                   absl::string_view terminator = "") {
72    output << var_name_x << " = [ ";
73    for (const std::pair<int64_t, float>& delay : delays) {
74      output << (delay.first - ref_time_ms) / 1000.f << ", ";
75    }
76    output << "]" << terminator << std::endl;
77  
78    output << var_name_y << " = [ ";
79    for (const std::pair<int64_t, float>& delay : delays) {
80      output << delay.second << ", ";
81    }
82    output << "]" << terminator << std::endl;
83  }
84  
85  }  // namespace
86  
AfterInsertPacket(const test::NetEqInput::PacketData & packet,NetEq * neteq)87  void NetEqDelayAnalyzer::AfterInsertPacket(
88      const test::NetEqInput::PacketData& packet,
89      NetEq* neteq) {
90    data_.insert(
91        std::make_pair(packet.header.timestamp, TimingData(packet.time_ms)));
92    ssrcs_.insert(packet.header.ssrc);
93    payload_types_.insert(packet.header.payloadType);
94  }
95  
BeforeGetAudio(NetEq * neteq)96  void NetEqDelayAnalyzer::BeforeGetAudio(NetEq* neteq) {
97    last_sync_buffer_ms_ = neteq->SyncBufferSizeMs();
98  }
99  
AfterGetAudio(int64_t time_now_ms,const AudioFrame & audio_frame,bool,NetEq * neteq)100  void NetEqDelayAnalyzer::AfterGetAudio(int64_t time_now_ms,
101                                         const AudioFrame& audio_frame,
102                                         bool /*muted*/,
103                                         NetEq* neteq) {
104    get_audio_time_ms_.push_back(time_now_ms);
105    for (const RtpPacketInfo& info : audio_frame.packet_infos_) {
106      auto it = data_.find(info.rtp_timestamp());
107      if (it == data_.end()) {
108        // This is a packet that was split out from another packet. Skip it.
109        continue;
110      }
111      auto& it_timing = it->second;
112      RTC_CHECK(!it_timing.decode_get_audio_count)
113          << "Decode time already written";
114      it_timing.decode_get_audio_count = get_audio_count_;
115      RTC_CHECK(!it_timing.sync_delay_ms) << "Decode time already written";
116      it_timing.sync_delay_ms = last_sync_buffer_ms_;
117      it_timing.target_delay_ms = neteq->TargetDelayMs();
118      it_timing.current_delay_ms = neteq->FilteredCurrentDelayMs();
119    }
120    last_sample_rate_hz_ = audio_frame.sample_rate_hz_;
121    ++get_audio_count_;
122  }
123  
CreateGraphs(Delays * arrival_delay_ms,Delays * corrected_arrival_delay_ms,Delays * playout_delay_ms,Delays * target_delay_ms) const124  void NetEqDelayAnalyzer::CreateGraphs(Delays* arrival_delay_ms,
125                                        Delays* corrected_arrival_delay_ms,
126                                        Delays* playout_delay_ms,
127                                        Delays* target_delay_ms) const {
128    if (get_audio_time_ms_.empty()) {
129      return;
130    }
131    // Create nominal_get_audio_time_ms, a vector starting at
132    // get_audio_time_ms_[0] and increasing by 10 for each element.
133    std::vector<int64_t> nominal_get_audio_time_ms(get_audio_time_ms_.size());
134    nominal_get_audio_time_ms[0] = get_audio_time_ms_[0];
135    std::transform(
136        nominal_get_audio_time_ms.begin(), nominal_get_audio_time_ms.end() - 1,
137        nominal_get_audio_time_ms.begin() + 1, [](int64_t& x) { return x + 10; });
138    RTC_DCHECK(
139        std::is_sorted(get_audio_time_ms_.begin(), get_audio_time_ms_.end()));
140  
141    std::vector<double> rtp_timestamps_ms;
142    double offset = std::numeric_limits<double>::max();
143    TimestampUnwrapper unwrapper;
144    // This loop traverses data_ and populates rtp_timestamps_ms as well as
145    // calculates the base offset.
146    for (auto& d : data_) {
147      rtp_timestamps_ms.push_back(
148          static_cast<double>(unwrapper.Unwrap(d.first)) /
149          rtc::CheckedDivExact(last_sample_rate_hz_, 1000));
150      offset =
151          std::min(offset, d.second.arrival_time_ms - rtp_timestamps_ms.back());
152    }
153  
154    // This loop traverses the data again and populates the graph vectors. The
155    // reason to have two loops and traverse twice is that the offset cannot be
156    // known until the first traversal is done. Meanwhile, the final offset must
157    // be known already at the start of this second loop.
158    size_t i = 0;
159    for (const auto& data : data_) {
160      const double offset_send_time_ms = rtp_timestamps_ms[i++] + offset;
161      const auto& timing = data.second;
162      corrected_arrival_delay_ms->push_back(std::make_pair(
163          timing.arrival_time_ms,
164          LinearInterpolate(timing.arrival_time_ms, get_audio_time_ms_,
165                            nominal_get_audio_time_ms) -
166              offset_send_time_ms));
167      arrival_delay_ms->push_back(std::make_pair(
168          timing.arrival_time_ms, timing.arrival_time_ms - offset_send_time_ms));
169  
170      if (timing.decode_get_audio_count) {
171        // This packet was decoded.
172        RTC_DCHECK(timing.sync_delay_ms);
173        const int64_t get_audio_time =
174            *timing.decode_get_audio_count * 10 + get_audio_time_ms_[0];
175        const float playout_ms =
176            get_audio_time + *timing.sync_delay_ms - offset_send_time_ms;
177        playout_delay_ms->push_back(std::make_pair(get_audio_time, playout_ms));
178        RTC_DCHECK(timing.target_delay_ms);
179        RTC_DCHECK(timing.current_delay_ms);
180        const float target =
181            playout_ms - *timing.current_delay_ms + *timing.target_delay_ms;
182        target_delay_ms->push_back(std::make_pair(get_audio_time, target));
183      }
184    }
185  }
186  
CreateMatlabScript(absl::string_view script_name) const187  void NetEqDelayAnalyzer::CreateMatlabScript(
188      absl::string_view script_name) const {
189    Delays arrival_delay_ms;
190    Delays corrected_arrival_delay_ms;
191    Delays playout_delay_ms;
192    Delays target_delay_ms;
193    CreateGraphs(&arrival_delay_ms, &corrected_arrival_delay_ms,
194                 &playout_delay_ms, &target_delay_ms);
195  
196    // Maybe better to find the actually smallest timestamp, to surely avoid
197    // x-axis starting from negative.
198    const int64_t ref_time_ms = arrival_delay_ms.front().first;
199  
200    // Create an output file stream to Matlab script file.
201    std::ofstream output(std::string{script_name});
202  
203    PrintDelays(corrected_arrival_delay_ms, ref_time_ms, kArrivalDelayX,
204                kArrivalDelayY, output, ";");
205  
206    // PrintDelays(corrected_arrival_delay_x, kCorrectedArrivalDelayX,
207    // kCorrectedArrivalDelayY, output);
208  
209    PrintDelays(playout_delay_ms, ref_time_ms, kPlayoutDelayX, kPlayoutDelayY,
210                output, ";");
211  
212    PrintDelays(target_delay_ms, ref_time_ms, kTargetDelayX, kTargetDelayY,
213                output, ";");
214  
215    output << "h=plot(" << kArrivalDelayX << ", " << kArrivalDelayY << ", "
216           << kTargetDelayX << ", " << kTargetDelayY << ", 'g.', "
217           << kPlayoutDelayX << ", " << kPlayoutDelayY << ");" << std::endl;
218    output << "set(h(1),'color',0.75*[1 1 1]);" << std::endl;
219    output << "set(h(2),'markersize',6);" << std::endl;
220    output << "set(h(3),'linew',1.5);" << std::endl;
221    output << "ax1=axis;" << std::endl;
222    output << "axis tight" << std::endl;
223    output << "ax2=axis;" << std::endl;
224    output << "axis([ax2(1:3) ax1(4)])" << std::endl;
225    output << "xlabel('time [s]');" << std::endl;
226    output << "ylabel('relative delay [ms]');" << std::endl;
227    if (!ssrcs_.empty()) {
228      auto ssrc_it = ssrcs_.cbegin();
229      output << "title('SSRC: 0x" << std::hex << static_cast<int64_t>(*ssrc_it++);
230      while (ssrc_it != ssrcs_.end()) {
231        output << ", 0x" << std::hex << static_cast<int64_t>(*ssrc_it++);
232      }
233      output << std::dec;
234      auto pt_it = payload_types_.cbegin();
235      output << "; Payload Types: " << *pt_it++;
236      while (pt_it != payload_types_.end()) {
237        output << ", " << *pt_it++;
238      }
239      output << "');" << std::endl;
240    }
241  }
242  
CreatePythonScript(absl::string_view script_name) const243  void NetEqDelayAnalyzer::CreatePythonScript(
244      absl::string_view script_name) const {
245    Delays arrival_delay_ms;
246    Delays corrected_arrival_delay_ms;
247    Delays playout_delay_ms;
248    Delays target_delay_ms;
249    CreateGraphs(&arrival_delay_ms, &corrected_arrival_delay_ms,
250                 &playout_delay_ms, &target_delay_ms);
251  
252    // Maybe better to find the actually smallest timestamp, to surely avoid
253    // x-axis starting from negative.
254    const int64_t ref_time_ms = arrival_delay_ms.front().first;
255  
256    // Create an output file stream to the python script file.
257    std::ofstream output(std::string{script_name});
258  
259    // Necessary includes
260    output << "import numpy as np" << std::endl;
261    output << "import matplotlib.pyplot as plt" << std::endl;
262  
263    PrintDelays(corrected_arrival_delay_ms, ref_time_ms, kArrivalDelayX,
264                kArrivalDelayY, output);
265  
266    // PrintDelays(corrected_arrival_delay_x, kCorrectedArrivalDelayX,
267    // kCorrectedArrivalDelayY, output);
268  
269    PrintDelays(playout_delay_ms, ref_time_ms, kPlayoutDelayX, kPlayoutDelayY,
270                output);
271  
272    PrintDelays(target_delay_ms, ref_time_ms, kTargetDelayX, kTargetDelayY,
273                output);
274  
275    output << "if __name__ == '__main__':" << std::endl;
276    output << "  h=plt.plot(" << kArrivalDelayX << ", " << kArrivalDelayY << ", "
277           << kTargetDelayX << ", " << kTargetDelayY << ", 'g.', "
278           << kPlayoutDelayX << ", " << kPlayoutDelayY << ")" << std::endl;
279    output << "  plt.setp(h[0],'color',[.75, .75, .75])" << std::endl;
280    output << "  plt.setp(h[1],'markersize',6)" << std::endl;
281    output << "  plt.setp(h[2],'linewidth',1.5)" << std::endl;
282    output << "  plt.axis('tight')" << std::endl;
283    output << "  plt.xlabel('time [s]')" << std::endl;
284    output << "  plt.ylabel('relative delay [ms]')" << std::endl;
285    if (!ssrcs_.empty()) {
286      auto ssrc_it = ssrcs_.cbegin();
287      output << "  plt.title('SSRC: 0x" << std::hex
288             << static_cast<int64_t>(*ssrc_it++);
289      while (ssrc_it != ssrcs_.end()) {
290        output << ", 0x" << std::hex << static_cast<int64_t>(*ssrc_it++);
291      }
292      output << std::dec;
293      auto pt_it = payload_types_.cbegin();
294      output << "; Payload Types: " << *pt_it++;
295      while (pt_it != payload_types_.end()) {
296        output << ", " << *pt_it++;
297      }
298      output << "')" << std::endl;
299    }
300    output << "  plt.show()" << std::endl;
301  }
302  
303  }  // namespace test
304  }  // namespace webrtc
305