OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * Copyright (c) 2016 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 "webrtc/tools/event_log_visualizer/analyzer.h" | |
12 | |
13 #include <algorithm> | |
14 #include <limits> | |
15 #include <map> | |
16 #include <sstream> | |
17 #include <string> | |
18 #include <utility> | |
19 | |
20 #include "webrtc/audio_receive_stream.h" | |
21 #include "webrtc/audio_send_stream.h" | |
22 #include "webrtc/base/checks.h" | |
23 #include "webrtc/call.h" | |
24 #include "webrtc/common_types.h" | |
25 #include "webrtc/modules/rtp_rtcp/include/rtp_rtcp.h" | |
26 #include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h" | |
27 #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" | |
28 #include "webrtc/video_receive_stream.h" | |
29 #include "webrtc/video_send_stream.h" | |
30 | |
31 namespace { | |
32 | |
33 std::string HeaderToString(const webrtc::RTPHeader& parsed_header) { | |
stefan-webrtc
2016/06/29 11:13:05
Should we make this a method on RTPHeader instead?
terelius
2016/07/01 16:54:45
I have no opinion on this, but this function does
stefan-webrtc
2016/07/05 08:58:11
I think it's fine as is for now.
| |
34 std::stringstream ss; | |
35 ss << "Marker=" << parsed_header.markerBit | |
36 << ", PType=" << parsed_header.payloadType | |
37 << ", SeqNum=" << parsed_header.sequenceNumber | |
38 << ", CaptureTime=" << parsed_header.timestamp | |
39 << ", SSRC=" << parsed_header.ssrc; | |
40 return ss.str(); | |
41 } | |
42 | |
43 std::string SsrcToString(uint32_t ssrc) { | |
44 std::stringstream ss; | |
45 ss << "SSRC " << ssrc; | |
46 return ss.str(); | |
47 } | |
48 | |
49 // Checks whether an SSRC is contained in the list of desired SSRCs. | |
50 // Note that an empty SSRC list matches every SSRC. | |
51 bool MatchingSsrc(uint32_t ssrc, const std::vector<uint32_t>& desired_ssrc) { | |
52 if (desired_ssrc.size() == 0) | |
53 return true; | |
54 return std::find(desired_ssrc.begin(), desired_ssrc.end(), ssrc) != | |
55 desired_ssrc.end(); | |
56 } | |
57 | |
58 double AbsSendTimeToMicroseconds(int64_t abs_send_time) { | |
59 // The timestamp is a fixed point representation with 6 bits for seconds | |
60 // and 18 bits for fractions of a second. Thus, we divide by 2^18 to get the | |
61 // time in seconds and then multiply by 1000000 to convert to microseconds. | |
62 static constexpr double kTimestampToMicroSec = | |
63 1000000.0 / static_cast<double>(1 << 18); | |
64 return abs_send_time * kTimestampToMicroSec; | |
65 } | |
66 | |
67 // Computes the difference |later| - |earlier| where |later| and |earlier| | |
68 // are counters that wrap at |modulus|. The difference is chosen to have the | |
69 // least absolute value. For example if |modulus| is 8, then the difference will | |
70 // be chosen in the range [-3, 4]. If |modulus| is 9, then the difference will | |
71 // be in [-4, 4]. | |
72 int64_t WrappingDifference(uint32_t later, uint32_t earlier, int64_t modulus) { | |
73 RTC_DCHECK_LE(1, modulus); | |
74 RTC_DCHECK_LT(later, modulus); | |
75 RTC_DCHECK_LT(earlier, modulus); | |
76 int64_t difference = | |
77 static_cast<int64_t>(later) - static_cast<int64_t>(earlier); | |
78 int64_t max_difference = modulus / 2; | |
79 int64_t min_difference = max_difference - modulus + 1; | |
80 if (difference > max_difference) { | |
81 difference -= modulus; | |
82 } | |
83 if (difference < min_difference) { | |
84 difference += modulus; | |
85 } | |
86 return difference; | |
87 } | |
88 | |
89 // There could conceivably be e.g. an incoming and an outgoing stream with | |
90 // the same SSRC. To get a unique identifier for each stream we append some | |
91 // other values to the SSRC. | |
92 uint64_t GetStreamId(uint32_t ssrc, | |
stefan-webrtc
2016/06/29 11:13:04
Wouldn't it be nicer to make stream id a comparabl
terelius
2016/07/01 16:54:45
Done. However, the advantage of precomputing a 64-
| |
93 webrtc::PacketDirection direction, | |
94 webrtc::MediaType media_type) { | |
95 uint64_t stream = ssrc; | |
96 stream = (stream << 8) + | |
97 static_cast<uint64_t>(direction == webrtc::kIncomingPacket); | |
98 stream = (stream << 8) + static_cast<uint64_t>(media_type); | |
99 return stream; | |
100 } | |
101 | |
102 uint32_t GetSsrcFromStreamId(uint64_t stream) { | |
103 return stream >> 16; | |
104 } | |
105 | |
106 const double kXMargin = 1.02; | |
107 const double kYMargin = 1.1; | |
108 const double kDefaultXMin = -1; | |
109 const double kDefaultYMin = -1; | |
110 | |
111 } // namespace | |
112 | |
113 namespace webrtc { | |
114 namespace plotting { | |
115 | |
116 EventLogAnalyzer::EventLogAnalyzer(const ParsedRtcEventLog& log, | |
117 bool extra_info) | |
118 : parsed_log_(log), extra_point_info_(extra_info) { | |
119 uint64_t first_timestamp = std::numeric_limits<uint64_t>::max(); | |
120 uint64_t last_timestamp = std::numeric_limits<uint64_t>::min(); | |
121 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) { | |
122 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i); | |
123 if (event_type == ParsedRtcEventLog::EventType::VIDEO_RECEIVER_CONFIG_EVENT) | |
124 continue; | |
125 if (event_type == ParsedRtcEventLog::EventType::VIDEO_SENDER_CONFIG_EVENT) | |
126 continue; | |
127 if (event_type == ParsedRtcEventLog::EventType::AUDIO_RECEIVER_CONFIG_EVENT) | |
128 continue; | |
129 if (event_type == ParsedRtcEventLog::EventType::AUDIO_SENDER_CONFIG_EVENT) | |
130 continue; | |
131 uint64_t timestamp = parsed_log_.GetTimestamp(i); | |
132 first_timestamp = std::min(first_timestamp, timestamp); | |
133 last_timestamp = std::max(last_timestamp, timestamp); | |
134 } | |
135 if (last_timestamp < first_timestamp) { | |
136 // No useful events in the log. | |
137 first_timestamp = last_timestamp = 0; | |
138 } | |
139 begin_time_ = first_timestamp; | |
140 end_time_ = last_timestamp; | |
141 window_duration_ = 250000; | |
142 step_ = 10000; | |
stefan-webrtc
2016/06/29 11:13:04
These two members should be set in the initializer
terelius
2016/07/01 16:54:45
Done.
| |
143 } | |
144 | |
145 void EventLogAnalyzer::CreatePacketGraph(PacketDirection desired_direction, | |
146 Plot* plot) { | |
147 std::map<uint32_t, TimeSeries> time_series; | |
148 | |
149 PacketDirection direction; | |
150 MediaType media_type; | |
151 uint8_t header[IP_PACKET_SIZE]; | |
152 size_t header_length, total_length; | |
153 float max_y = 0; | |
154 | |
155 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) { | |
156 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i); | |
157 if (event_type == ParsedRtcEventLog::RTP_EVENT) { | |
158 parsed_log_.GetRtpHeader(i, &direction, &media_type, header, | |
159 &header_length, &total_length); | |
160 if (direction == desired_direction) { | |
161 // Parse header to get SSRC. | |
162 RtpUtility::RtpHeaderParser rtp_parser(header, header_length); | |
163 RTPHeader parsed_header; | |
164 rtp_parser.Parse(&parsed_header); | |
165 // Filter on SSRC. | |
166 if (MatchingSsrc(parsed_header.ssrc, desired_ssrc_)) { | |
167 uint64_t timestamp = parsed_log_.GetTimestamp(i); | |
168 float x = static_cast<float>(timestamp - begin_time_) / 1000000; | |
169 float y = total_length; | |
170 max_y = std::max(max_y, y); | |
171 std::string message; | |
172 if (extra_point_info_) { | |
173 message = HeaderToString(parsed_header); | |
174 } | |
175 time_series[parsed_header.ssrc].points.push_back( | |
176 TimeSeriesPoint(x, y, message)); | |
177 } | |
178 } | |
179 } | |
180 } | |
181 | |
182 // Set labels and put in graph. | |
183 for (auto& kv : time_series) { | |
184 kv.second.label = SsrcToString(kv.first); | |
185 kv.second.style = BAR_GRAPH; | |
186 plot->series.push_back(TimeSeries()); | |
187 plot->series.back().swap(kv.second); | |
stefan-webrtc
2016/06/29 11:13:04
Why not simply push_back(kv.second)?
terelius
2016/07/01 16:54:45
Because I don't want to copy all the data in the T
| |
188 } | |
189 | |
190 plot->xaxis_min = kDefaultXMin; | |
191 plot->xaxis_max = (end_time_ - begin_time_) / 1000000 * kXMargin; | |
192 plot->xaxis_label = "Time (s)"; | |
193 plot->yaxis_min = kDefaultYMin; | |
194 plot->yaxis_max = max_y * kYMargin; | |
195 plot->yaxis_label = "Packet size (bytes)"; | |
196 if (desired_direction == webrtc::PacketDirection::kIncomingPacket) { | |
197 plot->title = "Incoming RTP packets"; | |
198 } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) { | |
199 plot->title = "Outgoing RTP packets"; | |
200 } | |
201 } | |
202 | |
203 // For each SSRC, plot the time between the consecutive playouts. | |
204 void EventLogAnalyzer::CreatePlayoutGraph(Plot* plot) { | |
205 std::map<uint32_t, TimeSeries> time_series; | |
206 std::map<uint32_t, uint64_t> last_playout; | |
207 | |
208 uint32_t ssrc; | |
209 float max_y = 0; | |
210 | |
211 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) { | |
212 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i); | |
213 if (event_type == ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT) { | |
214 parsed_log_.GetAudioPlayout(i, &ssrc); | |
215 uint64_t timestamp = parsed_log_.GetTimestamp(i); | |
216 if (MatchingSsrc(ssrc, desired_ssrc_)) { | |
217 float x = static_cast<float>(timestamp - begin_time_) / 1000000; | |
218 float y = static_cast<float>(timestamp - last_playout[ssrc]) / 1000; | |
219 if (time_series[ssrc].points.size() == 0) { | |
220 // There were no previusly logged playout for this SSRC. | |
221 // Generate a point, but place it on the x-axis. | |
222 y = 0; | |
223 } | |
224 max_y = std::max(max_y, y); | |
225 time_series[ssrc].points.push_back(TimeSeriesPoint(x, y, "")); | |
226 last_playout[ssrc] = timestamp; | |
227 } | |
228 } | |
229 } | |
230 | |
231 // Set labels and put in graph. | |
232 for (auto& kv : time_series) { | |
233 kv.second.label = SsrcToString(kv.first); | |
234 kv.second.style = BAR_GRAPH; | |
235 plot->series.push_back(TimeSeries()); | |
236 plot->series.back().swap(kv.second); | |
237 } | |
238 | |
239 plot->xaxis_min = kDefaultXMin; | |
240 plot->xaxis_max = (end_time_ - begin_time_) / 1000000 * kXMargin; | |
241 plot->xaxis_label = "Time (s)"; | |
242 plot->yaxis_min = kDefaultYMin; | |
243 plot->yaxis_max = max_y * kYMargin; | |
244 plot->yaxis_label = "Time since last playout (ms)"; | |
245 plot->title = "Audio playout"; | |
246 } | |
247 | |
248 // For each SSRC, plot the time between the consecutive playouts. | |
249 void EventLogAnalyzer::CreateSequenceNumberGraph(Plot* plot) { | |
250 std::map<uint32_t, TimeSeries> time_series; | |
251 std::map<uint32_t, uint16_t> last_seqno; | |
252 | |
253 PacketDirection direction; | |
254 MediaType media_type; | |
255 uint8_t header[IP_PACKET_SIZE]; | |
256 size_t header_length, total_length; | |
257 | |
258 int max_y = 1; | |
259 int min_y = 0; | |
260 | |
261 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) { | |
262 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i); | |
263 if (event_type == ParsedRtcEventLog::RTP_EVENT) { | |
264 parsed_log_.GetRtpHeader(i, &direction, &media_type, header, | |
265 &header_length, &total_length); | |
266 uint64_t timestamp = parsed_log_.GetTimestamp(i); | |
267 if (direction == PacketDirection::kIncomingPacket) { | |
268 // Parse header to get SSRC. | |
269 RtpUtility::RtpHeaderParser rtp_parser(header, header_length); | |
270 RTPHeader parsed_header; | |
271 rtp_parser.Parse(&parsed_header); | |
272 // Filter on SSRC. | |
273 if (MatchingSsrc(parsed_header.ssrc, desired_ssrc_)) { | |
274 float x = static_cast<float>(timestamp - begin_time_) / 1000000; | |
275 int y = WrappingDifference(parsed_header.sequenceNumber, | |
276 last_seqno[parsed_header.ssrc], 1ul << 16); | |
277 if (time_series[parsed_header.ssrc].points.size() == 0) { | |
278 // There were no previusly logged playout for this SSRC. | |
279 // Generate a point, but place it on the x-axis. | |
280 y = 0; | |
281 } | |
282 max_y = std::max(max_y, y); | |
283 min_y = std::min(min_y, y); | |
284 time_series[parsed_header.ssrc].points.push_back( | |
285 TimeSeriesPoint(x, y, "")); | |
286 last_seqno[parsed_header.ssrc] = parsed_header.sequenceNumber; | |
287 } | |
288 } | |
289 } | |
290 } | |
291 | |
292 // Set labels and put in graph. | |
293 for (auto& kv : time_series) { | |
294 kv.second.label = SsrcToString(kv.first); | |
295 kv.second.style = BAR_GRAPH; | |
296 plot->series.push_back(TimeSeries()); | |
297 plot->series.back().swap(kv.second); | |
298 } | |
299 | |
300 plot->xaxis_min = kDefaultXMin; | |
301 plot->xaxis_max = (end_time_ - begin_time_) / 1000000 * kXMargin; | |
302 plot->xaxis_label = "Time (s)"; | |
303 plot->yaxis_min = min_y - (kYMargin - 1) / 2 * (max_y - min_y); | |
304 plot->yaxis_max = max_y + (kYMargin - 1) / 2 * (max_y - min_y); | |
305 plot->yaxis_label = "Difference since last packet"; | |
306 plot->title = "Sequence number"; | |
307 } | |
308 | |
309 void EventLogAnalyzer::CreateDelayChangeGraph(Plot* plot) { | |
310 // Maps a stream identifier consisting of ssrc, direction and MediaType | |
311 // to the header extensions used by that stream, | |
312 std::map<uint64_t, RtpHeaderExtensionMap> extension_maps; | |
313 | |
314 struct SendReceiveTime { | |
315 SendReceiveTime() = default; | |
316 SendReceiveTime(uint32_t send_time, uint64_t recv_time) | |
317 : absolute_send_time(send_time), receive_timestamp(recv_time) {} | |
318 uint32_t absolute_send_time; // 24-bit value in units of 2^-18 seconds. | |
319 uint64_t receive_timestamp; // In microseconds. | |
320 }; | |
321 std::map<uint64_t, SendReceiveTime> last_packet; | |
322 std::map<uint64_t, TimeSeries> time_series; | |
323 | |
324 PacketDirection direction; | |
325 MediaType media_type; | |
326 uint8_t header[IP_PACKET_SIZE]; | |
327 size_t header_length, total_length; | |
328 | |
329 double max_y = 10; | |
330 double min_y = 0; | |
331 | |
332 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) { | |
333 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i); | |
334 if (event_type == ParsedRtcEventLog::VIDEO_RECEIVER_CONFIG_EVENT) { | |
stefan-webrtc
2016/06/29 11:13:04
Handling of config events seems to happen in many
terelius
2016/07/01 16:54:45
Config events are used in CreateDelayChangeGraph a
stefan-webrtc
2016/07/05 08:58:11
If it becomes better in a follow up I think I'm ok
| |
335 VideoReceiveStream::Config config(nullptr); | |
336 parsed_log_.GetVideoReceiveConfig(i, &config); | |
337 uint64_t stream = GetStreamId(config.rtp.remote_ssrc, kIncomingPacket, | |
338 MediaType::VIDEO); | |
339 extension_maps[stream].Erase(); | |
340 for (size_t j = 0; j < config.rtp.extensions.size(); ++j) { | |
341 const std::string& extension = config.rtp.extensions[j].uri; | |
342 int id = config.rtp.extensions[j].id; | |
343 extension_maps[stream].Register(StringToRtpExtensionType(extension), | |
344 id); | |
345 } | |
346 } else if (event_type == ParsedRtcEventLog::VIDEO_SENDER_CONFIG_EVENT) { | |
347 VideoSendStream::Config config(nullptr); | |
348 parsed_log_.GetVideoSendConfig(i, &config); | |
349 for (auto ssrc : config.rtp.ssrcs) { | |
350 uint64_t stream = GetStreamId(ssrc, kIncomingPacket, MediaType::VIDEO); | |
351 extension_maps[stream].Erase(); | |
352 for (size_t j = 0; j < config.rtp.extensions.size(); ++j) { | |
353 const std::string& extension = config.rtp.extensions[j].uri; | |
354 int id = config.rtp.extensions[j].id; | |
355 extension_maps[stream].Register(StringToRtpExtensionType(extension), | |
356 id); | |
357 } | |
358 } | |
359 } else if (event_type == ParsedRtcEventLog::AUDIO_RECEIVER_CONFIG_EVENT) { | |
360 AudioReceiveStream::Config config; | |
361 // TODO(terelius): Parse the audio configs once we have them | |
362 } else if (event_type == ParsedRtcEventLog::AUDIO_SENDER_CONFIG_EVENT) { | |
363 AudioSendStream::Config config(nullptr); | |
364 // TODO(terelius): Parse the audio configs once we have them | |
365 } else if (event_type == ParsedRtcEventLog::RTP_EVENT) { | |
366 parsed_log_.GetRtpHeader(i, &direction, &media_type, header, | |
367 &header_length, &total_length); | |
368 if (direction == kIncomingPacket) { | |
369 // Parse header to get SSRC. | |
370 RtpUtility::RtpHeaderParser rtp_parser(header, header_length); | |
371 RTPHeader parsed_header; | |
372 rtp_parser.Parse(&parsed_header); | |
373 // Filter on SSRC. | |
374 if (MatchingSsrc(parsed_header.ssrc, desired_ssrc_)) { | |
stefan-webrtc
2016/06/29 11:13:05
Maybe we could have a method similar to:
GetEvents
terelius
2016/07/01 16:54:45
The follow up CL that caches a map from streams to
stefan-webrtc
2016/07/05 08:58:11
Acknowledged.
| |
375 uint64_t stream = | |
376 GetStreamId(parsed_header.ssrc, direction, media_type); | |
377 // Look up the extension_map and parse it again to get the extensions. | |
378 if (extension_maps.count(stream) == 1) { | |
379 RtpHeaderExtensionMap* extension_map = &extension_maps[stream]; | |
380 rtp_parser.Parse(&parsed_header, extension_map); | |
381 if (parsed_header.extension.hasAbsoluteSendTime) { | |
382 uint64_t timestamp = parsed_log_.GetTimestamp(i); | |
383 int64_t send_time_diff = WrappingDifference( | |
384 parsed_header.extension.absoluteSendTime, | |
385 last_packet[stream].absolute_send_time, 1ul << 24); | |
386 int64_t recv_time_diff = | |
387 timestamp - last_packet[stream].receive_timestamp; | |
388 | |
389 float x = static_cast<float>(timestamp - begin_time_) / 1000000; | |
390 double y = static_cast<double>( | |
391 recv_time_diff - | |
392 AbsSendTimeToMicroseconds(send_time_diff)) / | |
393 1000; | |
394 if (time_series[stream].points.size() == 0) { | |
395 // There were no previusly logged playout for this SSRC. | |
396 // Generate a point, but place it on the x-axis. | |
397 y = 0; | |
398 } | |
399 max_y = std::max(max_y, y); | |
400 min_y = std::min(min_y, y); | |
401 time_series[stream].points.push_back(TimeSeriesPoint(x, y, "")); | |
402 last_packet[stream] = SendReceiveTime( | |
403 parsed_header.extension.absoluteSendTime, timestamp); | |
404 } | |
405 } | |
406 } | |
407 } | |
408 } | |
409 } | |
410 | |
411 // Set labels and put in graph. | |
412 for (auto& kv : time_series) { | |
413 kv.second.label = SsrcToString(GetSsrcFromStreamId(kv.first)); | |
414 kv.second.style = BAR_GRAPH; | |
415 plot->series.push_back(TimeSeries()); | |
416 plot->series.back().swap(kv.second); | |
417 } | |
418 | |
419 plot->xaxis_min = kDefaultXMin; | |
420 plot->xaxis_max = (end_time_ - begin_time_) / 1000000 * kXMargin; | |
421 plot->xaxis_label = "Time (s)"; | |
422 plot->yaxis_min = min_y - (kYMargin - 1) / 2 * (max_y - min_y); | |
423 plot->yaxis_max = max_y + (kYMargin - 1) / 2 * (max_y - min_y); | |
424 plot->yaxis_label = "Latency change (ms)"; | |
425 plot->title = "Network latency change between consecutive packets"; | |
426 } | |
427 | |
428 void EventLogAnalyzer::CreateAccumulatedDelayChangeGraph(Plot* plot) { | |
429 // TODO(terelius): Refactor | |
430 | |
431 // Maps a stream identifier consisting of ssrc, direction and MediaType | |
432 // to the header extensions used by that stream. | |
433 std::map<uint64_t, RtpHeaderExtensionMap> extension_maps; | |
434 | |
435 struct SendReceiveTime { | |
436 SendReceiveTime() = default; | |
437 SendReceiveTime(uint32_t send_time, uint64_t recv_time, double accumulated) | |
438 : absolute_send_time(send_time), | |
439 receive_timestamp(recv_time), | |
440 accumulated_delay(accumulated) {} | |
441 uint32_t absolute_send_time; // 24-bit value in units of 2^-18 seconds. | |
442 uint64_t receive_timestamp; // In microseconds. | |
443 double accumulated_delay; // In milliseconds. | |
444 }; | |
445 std::map<uint64_t, SendReceiveTime> last_packet; | |
446 std::map<uint64_t, TimeSeries> time_series; | |
447 | |
448 PacketDirection direction; | |
449 MediaType media_type; | |
450 uint8_t header[IP_PACKET_SIZE]; | |
451 size_t header_length, total_length; | |
452 | |
453 double max_y = 10; | |
454 double min_y = 0; | |
455 | |
456 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) { | |
457 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i); | |
458 if (event_type == ParsedRtcEventLog::VIDEO_RECEIVER_CONFIG_EVENT) { | |
459 VideoReceiveStream::Config config(nullptr); | |
460 parsed_log_.GetVideoReceiveConfig(i, &config); | |
461 uint64_t stream = GetStreamId(config.rtp.remote_ssrc, kIncomingPacket, | |
462 MediaType::VIDEO); | |
463 extension_maps[stream].Erase(); | |
464 for (size_t j = 0; j < config.rtp.extensions.size(); ++j) { | |
465 const std::string& extension = config.rtp.extensions[j].uri; | |
466 int id = config.rtp.extensions[j].id; | |
467 extension_maps[stream].Register(StringToRtpExtensionType(extension), | |
468 id); | |
469 } | |
470 } else if (event_type == ParsedRtcEventLog::VIDEO_SENDER_CONFIG_EVENT) { | |
471 VideoSendStream::Config config(nullptr); | |
472 parsed_log_.GetVideoSendConfig(i, &config); | |
473 for (auto ssrc : config.rtp.ssrcs) { | |
474 uint64_t stream = GetStreamId(ssrc, kIncomingPacket, MediaType::VIDEO); | |
475 extension_maps[stream].Erase(); | |
476 for (size_t j = 0; j < config.rtp.extensions.size(); ++j) { | |
477 const std::string& extension = config.rtp.extensions[j].uri; | |
478 int id = config.rtp.extensions[j].id; | |
479 extension_maps[stream].Register(StringToRtpExtensionType(extension), | |
480 id); | |
481 } | |
482 } | |
483 } else if (event_type == ParsedRtcEventLog::AUDIO_RECEIVER_CONFIG_EVENT) { | |
484 AudioReceiveStream::Config config; | |
485 // TODO(terelius): Parse the audio configs once we have them | |
486 } else if (event_type == ParsedRtcEventLog::AUDIO_SENDER_CONFIG_EVENT) { | |
487 AudioSendStream::Config config(nullptr); | |
488 // TODO(terelius): Parse the audio configs once we have them | |
489 } else if (event_type == ParsedRtcEventLog::RTP_EVENT) { | |
490 parsed_log_.GetRtpHeader(i, &direction, &media_type, header, | |
491 &header_length, &total_length); | |
492 if (direction == kIncomingPacket) { | |
493 // Parse header to get SSRC. | |
494 RtpUtility::RtpHeaderParser rtp_parser(header, header_length); | |
495 RTPHeader parsed_header; | |
496 rtp_parser.Parse(&parsed_header); | |
497 // Filter on SSRC. | |
498 if (MatchingSsrc(parsed_header.ssrc, desired_ssrc_)) { | |
499 uint64_t stream = | |
500 GetStreamId(parsed_header.ssrc, direction, media_type); | |
501 // Look up the extension_map and parse it again to get the extensions. | |
502 if (extension_maps.count(stream) == 1) { | |
503 RtpHeaderExtensionMap* extension_map = &extension_maps[stream]; | |
504 rtp_parser.Parse(&parsed_header, extension_map); | |
505 if (parsed_header.extension.hasAbsoluteSendTime) { | |
506 uint64_t timestamp = parsed_log_.GetTimestamp(i); | |
507 int64_t send_time_diff = WrappingDifference( | |
508 parsed_header.extension.absoluteSendTime, | |
509 last_packet[stream].absolute_send_time, 1ul << 24); | |
510 int64_t recv_time_diff = | |
511 timestamp - last_packet[stream].receive_timestamp; | |
512 | |
513 float x = static_cast<float>(timestamp - begin_time_) / 1000000; | |
514 double y = last_packet[stream].accumulated_delay + | |
515 static_cast<double>( | |
516 recv_time_diff - | |
517 AbsSendTimeToMicroseconds(send_time_diff)) / | |
518 1000; | |
519 if (time_series[stream].points.size() == 0) { | |
520 // There were no previusly logged playout for this SSRC. | |
521 // Generate a point, but place it on the x-axis. | |
522 y = 0; | |
523 } | |
524 max_y = std::max(max_y, y); | |
525 min_y = std::min(min_y, y); | |
526 time_series[stream].points.push_back(TimeSeriesPoint(x, y, "")); | |
527 last_packet[stream] = SendReceiveTime( | |
528 parsed_header.extension.absoluteSendTime, timestamp, y); | |
529 } | |
530 } | |
531 } | |
532 } | |
533 } | |
534 } | |
535 | |
536 // Set labels and put in graph. | |
537 for (auto& kv : time_series) { | |
538 kv.second.label = SsrcToString(GetSsrcFromStreamId(kv.first)); | |
539 kv.second.style = LINE_GRAPH; | |
540 plot->series.push_back(TimeSeries()); | |
541 plot->series.back().swap(kv.second); | |
542 } | |
543 | |
544 plot->xaxis_min = kDefaultXMin; | |
545 plot->xaxis_max = (end_time_ - begin_time_) / 1000000 * kXMargin; | |
546 plot->xaxis_label = "Time (s)"; | |
547 plot->yaxis_min = min_y - (kYMargin - 1) / 2 * (max_y - min_y); | |
548 plot->yaxis_max = max_y + (kYMargin - 1) / 2 * (max_y - min_y); | |
549 plot->yaxis_label = "Latency change (ms)"; | |
550 plot->title = "Accumulated network latency change"; | |
551 } | |
552 | |
553 // Plot the total bandwitch used by all RTP streams. | |
554 void EventLogAnalyzer::CreateTotalBitrateGraph( | |
555 PacketDirection desired_direction, | |
556 Plot* plot) { | |
557 struct TimestampSize { | |
558 TimestampSize(uint64_t t, size_t s) : timestamp(t), size(s) {} | |
559 uint64_t timestamp; | |
560 size_t size; | |
561 }; | |
562 std::vector<TimestampSize> packets; | |
563 | |
564 PacketDirection direction; | |
565 size_t total_length; | |
566 | |
567 // Extract timestamps and sizes for the relevant packets. | |
568 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) { | |
569 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i); | |
570 if (event_type == ParsedRtcEventLog::RTP_EVENT) { | |
571 parsed_log_.GetRtpHeader(i, &direction, nullptr, nullptr, nullptr, | |
572 &total_length); | |
573 if (direction == desired_direction) { | |
574 uint64_t timestamp = parsed_log_.GetTimestamp(i); | |
575 packets.push_back(TimestampSize(timestamp, total_length)); | |
576 } | |
577 } | |
578 } | |
579 | |
580 size_t window_index_begin = 0; | |
581 size_t window_index_end = 0; | |
582 size_t bytes_in_window = 0; | |
583 float max_y = 0; | |
584 | |
585 // Calculate a moving average of the bitrate and store in a TimeSeries. | |
586 plot->series.push_back(TimeSeries()); | |
587 for (uint64_t time = begin_time_; time < end_time_ + step_; time += step_) { | |
588 while (window_index_end < packets.size() && | |
589 packets[window_index_end].timestamp < time) { | |
590 bytes_in_window += packets[window_index_end].size; | |
591 window_index_end++; | |
592 } | |
593 while (window_index_begin < packets.size() && | |
594 packets[window_index_begin].timestamp < time - window_duration_) { | |
595 bytes_in_window -= packets[window_index_begin].size; | |
596 window_index_begin++; | |
597 } | |
598 RTC_DCHECK_LE(0ul, bytes_in_window); | |
599 float window_duration_in_seconds = | |
600 static_cast<float>(window_duration_) / 1000000; | |
601 float x = static_cast<float>(time - begin_time_) / 1000000; | |
602 float y = bytes_in_window * 8 / window_duration_in_seconds / 1000; | |
603 max_y = std::max(max_y, y); | |
604 plot->series.back().points.push_back(TimeSeriesPoint(x, y)); | |
605 } | |
606 | |
607 // Set labels. | |
608 if (desired_direction == webrtc::PacketDirection::kIncomingPacket) { | |
609 plot->series.back().label = "Incoming bitrate"; | |
610 } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) { | |
611 plot->series.back().label = "Outgoing bitrate"; | |
612 } | |
613 plot->series.back().style = LINE_GRAPH; | |
614 | |
615 plot->xaxis_min = kDefaultXMin; | |
616 plot->xaxis_max = (end_time_ - begin_time_) / 1000000 * kXMargin; | |
617 plot->xaxis_label = "Time (s)"; | |
618 plot->yaxis_min = kDefaultYMin; | |
619 plot->yaxis_max = max_y * kYMargin; | |
620 plot->yaxis_label = "Bitrate (kbps)"; | |
621 if (desired_direction == webrtc::PacketDirection::kIncomingPacket) { | |
622 plot->title = "Incoming RTP bitrate"; | |
623 } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) { | |
624 plot->title = "Outgoing RTP bitrate"; | |
625 } | |
626 } | |
627 | |
628 // For each SSRC, plot the bandwitch used by that stream. | |
stefan-webrtc
2016/06/29 11:13:04
bandwidth
terelius
2016/07/01 16:54:45
Done. :)
stefan-webrtc
2016/07/05 08:58:11
Nope, still there... :)
terelius
2016/07/06 15:15:12
Done.
| |
629 void EventLogAnalyzer::CreateStreamBitrateGraph( | |
630 PacketDirection desired_direction, | |
631 Plot* plot) { | |
632 struct TimestampSize { | |
633 TimestampSize(uint64_t t, size_t s) : timestamp(t), size(s) {} | |
634 uint64_t timestamp; | |
635 size_t size; | |
636 }; | |
637 std::map<uint32_t, std::vector<TimestampSize> > packets; | |
638 | |
639 PacketDirection direction; | |
640 MediaType media_type; | |
641 uint8_t header[IP_PACKET_SIZE]; | |
642 size_t header_length, total_length; | |
643 | |
644 // Extract timestamps and sizes for the relevant packets. | |
645 for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) { | |
646 ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i); | |
647 if (event_type == ParsedRtcEventLog::RTP_EVENT) { | |
648 parsed_log_.GetRtpHeader(i, &direction, &media_type, header, | |
649 &header_length, &total_length); | |
650 if (direction == desired_direction) { | |
651 // Parse header to get SSRC. | |
652 RtpUtility::RtpHeaderParser rtp_parser(header, header_length); | |
653 RTPHeader parsed_header; | |
654 rtp_parser.Parse(&parsed_header); | |
655 // Filter on SSRC. | |
656 if (MatchingSsrc(parsed_header.ssrc, desired_ssrc_)) { | |
657 uint64_t timestamp = parsed_log_.GetTimestamp(i); | |
658 packets[parsed_header.ssrc].push_back( | |
659 TimestampSize(timestamp, total_length)); | |
660 } | |
661 } | |
662 } | |
663 } | |
664 | |
665 float max_y = 0; | |
666 | |
667 for (auto& kv : packets) { | |
668 size_t window_index_begin = 0; | |
669 size_t window_index_end = 0; | |
670 size_t bytes_in_window = 0; | |
671 | |
672 // Calculate a moving average of the bitrate and store in a TimeSeries. | |
673 plot->series.push_back(TimeSeries()); | |
674 for (uint64_t time = begin_time_; time < end_time_ + step_; time += step_) { | |
675 while (window_index_end < kv.second.size() && | |
676 kv.second[window_index_end].timestamp < time) { | |
677 bytes_in_window += kv.second[window_index_end].size; | |
678 window_index_end++; | |
679 } | |
680 while (window_index_begin < kv.second.size() && | |
681 kv.second[window_index_begin].timestamp < | |
682 time - window_duration_) { | |
683 bytes_in_window -= kv.second[window_index_begin].size; | |
684 window_index_begin++; | |
685 } | |
686 RTC_DCHECK_LE(0ul, bytes_in_window); | |
687 float window_duration_in_seconds = | |
688 static_cast<float>(window_duration_) / 1000000; | |
689 float x = static_cast<float>(time - begin_time_) / 1000000; | |
690 float y = bytes_in_window * 8 / window_duration_in_seconds / 1000; | |
691 max_y = std::max(max_y, y); | |
692 plot->series.back().points.push_back(TimeSeriesPoint(x, y)); | |
693 } | |
694 | |
695 // Set labels. | |
696 plot->series.back().label = SsrcToString(kv.first); | |
697 plot->series.back().style = LINE_GRAPH; | |
698 } | |
699 | |
700 plot->xaxis_min = kDefaultXMin; | |
701 plot->xaxis_max = (end_time_ - begin_time_) / 1000000 * kXMargin; | |
702 plot->xaxis_label = "Time (s)"; | |
703 plot->yaxis_min = kDefaultYMin; | |
704 plot->yaxis_max = max_y * kYMargin; | |
705 plot->yaxis_label = "Bitrate (kbps)"; | |
706 if (desired_direction == webrtc::PacketDirection::kIncomingPacket) { | |
707 plot->title = "Incoming bitrate per stream"; | |
708 } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) { | |
709 plot->title = "Outgoing bitrate per stream"; | |
710 } | |
711 } | |
712 | |
713 } // namespace plotting | |
714 } // namespace webrtc | |
OLD | NEW |