• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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,const std::string & 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                  const std::string& 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   // Check what timestamps were decoded in the last GetAudio call.
106   std::vector<uint32_t> dec_ts = neteq->LastDecodedTimestamps();
107   // Find those timestamps in data_, insert their decoding time and sync
108   // delay.
109   for (uint32_t ts : dec_ts) {
110     auto it = data_.find(ts);
111     if (it == data_.end()) {
112       // This is a packet that was split out from another packet. Skip it.
113       continue;
114     }
115     auto& it_timing = it->second;
116     RTC_CHECK(!it_timing.decode_get_audio_count)
117         << "Decode time already written";
118     it_timing.decode_get_audio_count = get_audio_count_;
119     RTC_CHECK(!it_timing.sync_delay_ms) << "Decode time already written";
120     it_timing.sync_delay_ms = last_sync_buffer_ms_;
121     it_timing.target_delay_ms = neteq->TargetDelayMs();
122     it_timing.current_delay_ms = neteq->FilteredCurrentDelayMs();
123   }
124   last_sample_rate_hz_ = audio_frame.sample_rate_hz_;
125   ++get_audio_count_;
126 }
127 
CreateGraphs(Delays * arrival_delay_ms,Delays * corrected_arrival_delay_ms,Delays * playout_delay_ms,Delays * target_delay_ms) const128 void NetEqDelayAnalyzer::CreateGraphs(Delays* arrival_delay_ms,
129                                       Delays* corrected_arrival_delay_ms,
130                                       Delays* playout_delay_ms,
131                                       Delays* target_delay_ms) const {
132   if (get_audio_time_ms_.empty()) {
133     return;
134   }
135   // Create nominal_get_audio_time_ms, a vector starting at
136   // get_audio_time_ms_[0] and increasing by 10 for each element.
137   std::vector<int64_t> nominal_get_audio_time_ms(get_audio_time_ms_.size());
138   nominal_get_audio_time_ms[0] = get_audio_time_ms_[0];
139   std::transform(
140       nominal_get_audio_time_ms.begin(), nominal_get_audio_time_ms.end() - 1,
141       nominal_get_audio_time_ms.begin() + 1, [](int64_t& x) { return x + 10; });
142   RTC_DCHECK(
143       std::is_sorted(get_audio_time_ms_.begin(), get_audio_time_ms_.end()));
144 
145   std::vector<double> rtp_timestamps_ms;
146   double offset = std::numeric_limits<double>::max();
147   TimestampUnwrapper unwrapper;
148   // This loop traverses data_ and populates rtp_timestamps_ms as well as
149   // calculates the base offset.
150   for (auto& d : data_) {
151     rtp_timestamps_ms.push_back(
152         static_cast<double>(unwrapper.Unwrap(d.first)) /
153         rtc::CheckedDivExact(last_sample_rate_hz_, 1000));
154     offset =
155         std::min(offset, d.second.arrival_time_ms - rtp_timestamps_ms.back());
156   }
157 
158   // This loop traverses the data again and populates the graph vectors. The
159   // reason to have two loops and traverse twice is that the offset cannot be
160   // known until the first traversal is done. Meanwhile, the final offset must
161   // be known already at the start of this second loop.
162   size_t i = 0;
163   for (const auto& data : data_) {
164     const double offset_send_time_ms = rtp_timestamps_ms[i++] + offset;
165     const auto& timing = data.second;
166     corrected_arrival_delay_ms->push_back(std::make_pair(
167         timing.arrival_time_ms,
168         LinearInterpolate(timing.arrival_time_ms, get_audio_time_ms_,
169                           nominal_get_audio_time_ms) -
170             offset_send_time_ms));
171     arrival_delay_ms->push_back(std::make_pair(
172         timing.arrival_time_ms, timing.arrival_time_ms - offset_send_time_ms));
173 
174     if (timing.decode_get_audio_count) {
175       // This packet was decoded.
176       RTC_DCHECK(timing.sync_delay_ms);
177       const int64_t get_audio_time =
178           *timing.decode_get_audio_count * 10 + get_audio_time_ms_[0];
179       const float playout_ms =
180           get_audio_time + *timing.sync_delay_ms - offset_send_time_ms;
181       playout_delay_ms->push_back(std::make_pair(get_audio_time, playout_ms));
182       RTC_DCHECK(timing.target_delay_ms);
183       RTC_DCHECK(timing.current_delay_ms);
184       const float target =
185           playout_ms - *timing.current_delay_ms + *timing.target_delay_ms;
186       target_delay_ms->push_back(std::make_pair(get_audio_time, target));
187     }
188   }
189 }
190 
CreateMatlabScript(const std::string & script_name) const191 void NetEqDelayAnalyzer::CreateMatlabScript(
192     const std::string& script_name) const {
193   Delays arrival_delay_ms;
194   Delays corrected_arrival_delay_ms;
195   Delays playout_delay_ms;
196   Delays target_delay_ms;
197   CreateGraphs(&arrival_delay_ms, &corrected_arrival_delay_ms,
198                &playout_delay_ms, &target_delay_ms);
199 
200   // Maybe better to find the actually smallest timestamp, to surely avoid
201   // x-axis starting from negative.
202   const int64_t ref_time_ms = arrival_delay_ms.front().first;
203 
204   // Create an output file stream to Matlab script file.
205   std::ofstream output(script_name);
206 
207   PrintDelays(corrected_arrival_delay_ms, ref_time_ms, kArrivalDelayX,
208               kArrivalDelayY, output, ";");
209 
210   // PrintDelays(corrected_arrival_delay_x, kCorrectedArrivalDelayX,
211   // kCorrectedArrivalDelayY, output);
212 
213   PrintDelays(playout_delay_ms, ref_time_ms, kPlayoutDelayX, kPlayoutDelayY,
214               output, ";");
215 
216   PrintDelays(target_delay_ms, ref_time_ms, kTargetDelayX, kTargetDelayY,
217               output, ";");
218 
219   output << "h=plot(" << kArrivalDelayX << ", " << kArrivalDelayY << ", "
220          << kTargetDelayX << ", " << kTargetDelayY << ", 'g.', "
221          << kPlayoutDelayX << ", " << kPlayoutDelayY << ");" << std::endl;
222   output << "set(h(1),'color',0.75*[1 1 1]);" << std::endl;
223   output << "set(h(2),'markersize',6);" << std::endl;
224   output << "set(h(3),'linew',1.5);" << std::endl;
225   output << "ax1=axis;" << std::endl;
226   output << "axis tight" << std::endl;
227   output << "ax2=axis;" << std::endl;
228   output << "axis([ax2(1:3) ax1(4)])" << std::endl;
229   output << "xlabel('time [s]');" << std::endl;
230   output << "ylabel('relative delay [ms]');" << std::endl;
231   if (!ssrcs_.empty()) {
232     auto ssrc_it = ssrcs_.cbegin();
233     output << "title('SSRC: 0x" << std::hex << static_cast<int64_t>(*ssrc_it++);
234     while (ssrc_it != ssrcs_.end()) {
235       output << ", 0x" << std::hex << static_cast<int64_t>(*ssrc_it++);
236     }
237     output << std::dec;
238     auto pt_it = payload_types_.cbegin();
239     output << "; Payload Types: " << *pt_it++;
240     while (pt_it != payload_types_.end()) {
241       output << ", " << *pt_it++;
242     }
243     output << "');" << std::endl;
244   }
245 }
246 
CreatePythonScript(const std::string & script_name) const247 void NetEqDelayAnalyzer::CreatePythonScript(
248     const std::string& script_name) const {
249   Delays arrival_delay_ms;
250   Delays corrected_arrival_delay_ms;
251   Delays playout_delay_ms;
252   Delays target_delay_ms;
253   CreateGraphs(&arrival_delay_ms, &corrected_arrival_delay_ms,
254                &playout_delay_ms, &target_delay_ms);
255 
256   // Maybe better to find the actually smallest timestamp, to surely avoid
257   // x-axis starting from negative.
258   const int64_t ref_time_ms = arrival_delay_ms.front().first;
259 
260   // Create an output file stream to the python script file.
261   std::ofstream output(script_name);
262 
263   // Necessary includes
264   output << "import numpy as np" << std::endl;
265   output << "import matplotlib.pyplot as plt" << std::endl;
266 
267   PrintDelays(corrected_arrival_delay_ms, ref_time_ms, kArrivalDelayX,
268               kArrivalDelayY, output);
269 
270   // PrintDelays(corrected_arrival_delay_x, kCorrectedArrivalDelayX,
271   // kCorrectedArrivalDelayY, output);
272 
273   PrintDelays(playout_delay_ms, ref_time_ms, kPlayoutDelayX, kPlayoutDelayY,
274               output);
275 
276   PrintDelays(target_delay_ms, ref_time_ms, kTargetDelayX, kTargetDelayY,
277               output);
278 
279   output << "if __name__ == '__main__':" << std::endl;
280   output << "  h=plt.plot(" << kArrivalDelayX << ", " << kArrivalDelayY << ", "
281          << kTargetDelayX << ", " << kTargetDelayY << ", 'g.', "
282          << kPlayoutDelayX << ", " << kPlayoutDelayY << ")" << std::endl;
283   output << "  plt.setp(h[0],'color',[.75, .75, .75])" << std::endl;
284   output << "  plt.setp(h[1],'markersize',6)" << std::endl;
285   output << "  plt.setp(h[2],'linewidth',1.5)" << std::endl;
286   output << "  plt.axis('tight')" << std::endl;
287   output << "  plt.xlabel('time [s]')" << std::endl;
288   output << "  plt.ylabel('relative delay [ms]')" << std::endl;
289   if (!ssrcs_.empty()) {
290     auto ssrc_it = ssrcs_.cbegin();
291     output << "  plt.title('SSRC: 0x" << std::hex
292            << static_cast<int64_t>(*ssrc_it++);
293     while (ssrc_it != ssrcs_.end()) {
294       output << ", 0x" << std::hex << static_cast<int64_t>(*ssrc_it++);
295     }
296     output << std::dec;
297     auto pt_it = payload_types_.cbegin();
298     output << "; Payload Types: " << *pt_it++;
299     while (pt_it != payload_types_.end()) {
300       output << ", " << *pt_it++;
301     }
302     output << "')" << std::endl;
303   }
304   output << "  plt.show()" << std::endl;
305 }
306 
307 }  // namespace test
308 }  // namespace webrtc
309