Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 1 | function rtpAnalyze( input_file ) |
| 2 | %RTP_ANALYZE Analyze RTP stream(s) from a txt file |
| 3 | % The function takes the output from the command line tool rtp_analyze |
| 4 | % and analyzes the stream(s) therein. First, process your rtpdump file |
| 5 | % through rtp_analyze (from command line): |
| 6 | % $ out/Debug/rtp_analyze my_file.rtp my_file.txt |
| 7 | % Then load it with this function (in Matlab): |
| 8 | % >> rtpAnalyze('my_file.txt') |
| 9 | |
| 10 | % Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. |
| 11 | % |
| 12 | % Use of this source code is governed by a BSD-style license |
| 13 | % that can be found in the LICENSE file in the root of the source |
| 14 | % tree. An additional intellectual property rights grant can be found |
| 15 | % in the file PATENTS. All contributing project authors may |
| 16 | % be found in the AUTHORS file in the root of the source tree. |
| 17 | |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 18 | [SeqNo,TimeStamp,ArrTime,Size,PT,M,SSRC] = importfile(input_file); |
| 19 | |
henrik.lundin | d84dcbd | 2015-08-18 04:46:46 -0700 | [diff] [blame] | 20 | %% Filter out RTCP packets. |
| 21 | % These appear as RTP packets having payload types 72 through 76. |
| 22 | ix = not(ismember(PT, 72:76)); |
| 23 | fprintf('Removing %i RTCP packets\n', length(SeqNo) - sum(ix)); |
| 24 | SeqNo = SeqNo(ix); |
| 25 | TimeStamp = TimeStamp(ix); |
| 26 | ArrTime = ArrTime(ix); |
| 27 | Size = Size(ix); |
| 28 | PT = PT(ix); |
| 29 | M = M(ix); |
| 30 | SSRC = SSRC(ix); |
| 31 | |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 32 | %% Find streams. |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 33 | [uSSRC, ~, uix] = unique(SSRC); |
| 34 | |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 35 | % If there are multiple streams, select one and purge the other |
| 36 | % streams from the data vectors. If there is only one stream, the |
| 37 | % vectors are good to use as they are. |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 38 | if length(uSSRC) > 1 |
| 39 | for i=1:length(uSSRC) |
| 40 | uPT = unique(PT(uix == i)); |
| 41 | fprintf('%i: %s (%d packets, pt: %i', i, uSSRC{i}, ... |
| 42 | length(find(uix==i)), uPT(1)); |
| 43 | if length(uPT) > 1 |
| 44 | fprintf(', %i', uPT(2:end)); |
| 45 | end |
| 46 | fprintf(')\n'); |
| 47 | end |
| 48 | sel = input('Select stream number: '); |
| 49 | if sel < 1 || sel > length(uSSRC) |
| 50 | error('Out of range'); |
| 51 | end |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 52 | ix = find(uix == sel); |
| 53 | % This is where the data vectors are trimmed. |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 54 | SeqNo = SeqNo(ix); |
| 55 | TimeStamp = TimeStamp(ix); |
| 56 | ArrTime = ArrTime(ix); |
| 57 | Size = Size(ix); |
| 58 | PT = PT(ix); |
| 59 | M = M(ix); |
| 60 | SSRC = SSRC(ix); |
| 61 | end |
| 62 | |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 63 | %% Unwrap SeqNo and TimeStamp. |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 64 | SeqNoUW = maxUnwrap(SeqNo, 65535); |
| 65 | TimeStampUW = maxUnwrap(TimeStamp, 4294967295); |
| 66 | |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 67 | %% Generate some stats for the stream. |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 68 | fprintf('Statistics:\n'); |
| 69 | fprintf('SSRC: %s\n', SSRC{1}); |
| 70 | uPT = unique(PT); |
| 71 | if length(uPT) > 1 |
| 72 | warning('This tool cannot yet handle changes in codec sample rate'); |
| 73 | end |
| 74 | fprintf('Payload type(s): %i', uPT(1)); |
| 75 | if length(uPT) > 1 |
| 76 | fprintf(', %i', uPT(2:end)); |
| 77 | end |
| 78 | fprintf('\n'); |
| 79 | fprintf('Packets: %i\n', length(SeqNo)); |
Henrik Lundin | 76381d9 | 2015-06-16 09:28:09 +0200 | [diff] [blame] | 80 | SortSeqNo = sort(SeqNoUW); |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 81 | fprintf('Missing sequence numbers: %i\n', ... |
Henrik Lundin | 76381d9 | 2015-06-16 09:28:09 +0200 | [diff] [blame] | 82 | length(find(diff(SortSeqNo) > 1))); |
| 83 | fprintf('Duplicated packets: %i\n', length(find(diff(SortSeqNo) == 0))); |
| 84 | reorderIx = findReorderedPackets(SeqNoUW); |
| 85 | fprintf('Reordered packets: %i\n', length(reorderIx)); |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 86 | tsdiff = diff(TimeStampUW); |
| 87 | tsdiff = tsdiff(diff(SeqNoUW) == 1); |
| 88 | [utsdiff, ~, ixtsdiff] = unique(tsdiff); |
| 89 | fprintf('Common packet sizes:\n'); |
| 90 | for i = 1:length(utsdiff) |
| 91 | fprintf(' %i samples (%i%%)\n', ... |
| 92 | utsdiff(i), ... |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 93 | round(100 * length(find(ixtsdiff == i))/length(ixtsdiff))); |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 94 | end |
| 95 | |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 96 | %% Trying to figure out sample rate. |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 97 | fs_est = (TimeStampUW(end) - TimeStampUW(1)) / (ArrTime(end) - ArrTime(1)); |
| 98 | fs_vec = [8, 16, 32, 48]; |
| 99 | fs = 0; |
| 100 | for f = fs_vec |
| 101 | if abs((fs_est-f)/f) < 0.05 % 5% margin |
| 102 | fs = f; |
| 103 | break; |
| 104 | end |
| 105 | end |
| 106 | if fs == 0 |
| 107 | fprintf('Cannot determine sample rate. I get it to %.2f kHz\n', ... |
| 108 | fs_est); |
| 109 | fs = input('Please, input a sample rate (in kHz): '); |
| 110 | else |
| 111 | fprintf('Sample rate estimated to %i kHz\n', fs); |
| 112 | end |
| 113 | |
| 114 | SendTimeMs = (TimeStampUW - TimeStampUW(1)) / fs; |
| 115 | |
| 116 | fprintf('Stream duration at sender: %.1f seconds\n', ... |
| 117 | (SendTimeMs(end) - SendTimeMs(1)) / 1000); |
| 118 | |
| 119 | fprintf('Stream duration at receiver: %.1f seconds\n', ... |
| 120 | (ArrTime(end) - ArrTime(1)) / 1000); |
| 121 | |
| 122 | fprintf('Clock drift: %.2f%%\n', ... |
| 123 | 100 * ((ArrTime(end) - ArrTime(1)) / ... |
| 124 | (SendTimeMs(end) - SendTimeMs(1)) - 1)); |
| 125 | |
| 126 | fprintf('Sent average bitrate: %i kbps\n', ... |
| 127 | round(sum(Size) * 8 / (SendTimeMs(end)-SendTimeMs(1)))); |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 128 | |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 129 | fprintf('Received average bitrate: %i kbps\n', ... |
| 130 | round(sum(Size) * 8 / (ArrTime(end)-ArrTime(1)))); |
| 131 | |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 132 | %% Plots. |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 133 | delay = ArrTime - SendTimeMs; |
| 134 | delay = delay - min(delay); |
Henrik Lundin | 76381d9 | 2015-06-16 09:28:09 +0200 | [diff] [blame] | 135 | delayOrdered = delay; |
| 136 | delayOrdered(reorderIx) = nan; % Set reordered packets to NaN. |
| 137 | delayReordered = delay(reorderIx); % Pick the reordered packets. |
| 138 | sendTimeMsReordered = SendTimeMs(reorderIx); |
| 139 | |
| 140 | % Sort time arrays in packet send order. |
| 141 | [~, sortix] = sort(SeqNoUW); |
| 142 | SendTimeMs = SendTimeMs(sortix); |
| 143 | Size = Size(sortix); |
| 144 | delayOrdered = delayOrdered(sortix); |
| 145 | |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 146 | figure |
Henrik Lundin | 76381d9 | 2015-06-16 09:28:09 +0200 | [diff] [blame] | 147 | plot(SendTimeMs / 1000, delayOrdered, ... |
| 148 | sendTimeMsReordered / 1000, delayReordered, 'r.'); |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 149 | xlabel('Send time [s]'); |
| 150 | ylabel('Relative transport delay [ms]'); |
| 151 | title(sprintf('SSRC: %s', SSRC{1})); |
| 152 | |
| 153 | SendBitrateKbps = 8 * Size(1:end-1) ./ diff(SendTimeMs); |
| 154 | figure |
| 155 | plot(SendTimeMs(1:end-1)/1000, SendBitrateKbps); |
| 156 | xlabel('Send time [s]'); |
| 157 | ylabel('Send bitrate [kbps]'); |
| 158 | end |
| 159 | |
Henrik Lundin | 76381d9 | 2015-06-16 09:28:09 +0200 | [diff] [blame] | 160 | %% Subfunctions. |
| 161 | |
| 162 | % findReorderedPackets returns the index to all packets that are considered |
| 163 | % old compared with the largest seen sequence number. The input seqNo must |
| 164 | % be unwrapped for this to work. |
| 165 | function reorderIx = findReorderedPackets(seqNo) |
| 166 | largestSeqNo = seqNo(1); |
| 167 | reorderIx = []; |
| 168 | for i = 2:length(seqNo) |
| 169 | if seqNo(i) < largestSeqNo |
| 170 | reorderIx = [reorderIx; i]; %#ok<AGROW> |
| 171 | else |
| 172 | largestSeqNo = seqNo(i); |
| 173 | end |
| 174 | end |
| 175 | end |
| 176 | |
Henrik Lundin | 60508f8 | 2015-06-03 09:38:23 +0200 | [diff] [blame] | 177 | %% Auto-generated subfunction. |
Henrik Lundin | 8051832 | 2015-05-28 12:37:46 +0200 | [diff] [blame] | 178 | function [SeqNo,TimeStamp,SendTime,Size,PT,M,SSRC] = ... |
| 179 | importfile(filename, startRow, endRow) |
| 180 | %IMPORTFILE Import numeric data from a text file as column vectors. |
| 181 | % [SEQNO,TIMESTAMP,SENDTIME,SIZE,PT,M,SSRC] = IMPORTFILE(FILENAME) Reads |
| 182 | % data from text file FILENAME for the default selection. |
| 183 | % |
| 184 | % [SEQNO,TIMESTAMP,SENDTIME,SIZE,PT,M,SSRC] = IMPORTFILE(FILENAME, |
| 185 | % STARTROW, ENDROW) Reads data from rows STARTROW through ENDROW of text |
| 186 | % file FILENAME. |
| 187 | % |
| 188 | % Example: |
| 189 | % [SeqNo,TimeStamp,SendTime,Size,PT,M,SSRC] = |
| 190 | % importfile('rtpdump_recv.txt',2, 123); |
| 191 | % |
| 192 | % See also TEXTSCAN. |
| 193 | |
| 194 | % Auto-generated by MATLAB on 2015/05/28 09:55:50 |
| 195 | |
| 196 | %% Initialize variables. |
| 197 | if nargin<=2 |
| 198 | startRow = 2; |
| 199 | endRow = inf; |
| 200 | end |
| 201 | |
| 202 | %% Format string for each line of text: |
| 203 | % column1: double (%f) |
| 204 | % column2: double (%f) |
| 205 | % column3: double (%f) |
| 206 | % column4: double (%f) |
| 207 | % column5: double (%f) |
| 208 | % column6: double (%f) |
| 209 | % column7: text (%s) |
| 210 | % For more information, see the TEXTSCAN documentation. |
| 211 | formatSpec = '%5f%11f%11f%6f%6f%3f%s%[^\n\r]'; |
| 212 | |
| 213 | %% Open the text file. |
| 214 | fileID = fopen(filename,'r'); |
| 215 | |
| 216 | %% Read columns of data according to format string. |
| 217 | % This call is based on the structure of the file used to generate this |
| 218 | % code. If an error occurs for a different file, try regenerating the code |
| 219 | % from the Import Tool. |
| 220 | dataArray = textscan(fileID, formatSpec, endRow(1)-startRow(1)+1, ... |
| 221 | 'Delimiter', '', 'WhiteSpace', '', 'HeaderLines', startRow(1)-1, ... |
| 222 | 'ReturnOnError', false); |
| 223 | for block=2:length(startRow) |
| 224 | frewind(fileID); |
| 225 | dataArrayBlock = textscan(fileID, formatSpec, ... |
| 226 | endRow(block)-startRow(block)+1, 'Delimiter', '', 'WhiteSpace', ... |
| 227 | '', 'HeaderLines', startRow(block)-1, 'ReturnOnError', false); |
| 228 | for col=1:length(dataArray) |
| 229 | dataArray{col} = [dataArray{col};dataArrayBlock{col}]; |
| 230 | end |
| 231 | end |
| 232 | |
| 233 | %% Close the text file. |
| 234 | fclose(fileID); |
| 235 | |
| 236 | %% Post processing for unimportable data. |
| 237 | % No unimportable data rules were applied during the import, so no post |
| 238 | % processing code is included. To generate code which works for |
| 239 | % unimportable data, select unimportable cells in a file and regenerate the |
| 240 | % script. |
| 241 | |
| 242 | %% Allocate imported array to column variable names |
| 243 | SeqNo = dataArray{:, 1}; |
| 244 | TimeStamp = dataArray{:, 2}; |
| 245 | SendTime = dataArray{:, 3}; |
| 246 | Size = dataArray{:, 4}; |
| 247 | PT = dataArray{:, 5}; |
| 248 | M = dataArray{:, 6}; |
| 249 | SSRC = dataArray{:, 7}; |
| 250 | end |
| 251 | |