1function 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
18[SeqNo,TimeStamp,ArrTime,Size,PT,M,SSRC] = importfile(input_file);
19
20%% Filter out RTCP packets.
21% These appear as RTP packets having payload types 72 through 76.
22ix = not(ismember(PT, 72:76));
23fprintf('Removing %i RTCP packets\n', length(SeqNo) - sum(ix));
24SeqNo = SeqNo(ix);
25TimeStamp = TimeStamp(ix);
26ArrTime = ArrTime(ix);
27Size = Size(ix);
28PT = PT(ix);
29M = M(ix);
30SSRC = SSRC(ix);
31
32%% Find streams.
33[uSSRC, ~, uix] = unique(SSRC);
34
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.
38if 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
52    ix = find(uix == sel);
53    % This is where the data vectors are trimmed.
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);
61end
62
63%% Unwrap SeqNo and TimeStamp.
64SeqNoUW = maxUnwrap(SeqNo, 65535);
65TimeStampUW = maxUnwrap(TimeStamp, 4294967295);
66
67%% Generate some stats for the stream.
68fprintf('Statistics:\n');
69fprintf('SSRC: %s\n', SSRC{1});
70uPT = unique(PT);
71if length(uPT) > 1
72    warning('This tool cannot yet handle changes in codec sample rate');
73end
74fprintf('Payload type(s): %i', uPT(1));
75if length(uPT) > 1
76    fprintf(', %i', uPT(2:end));
77end
78fprintf('\n');
79fprintf('Packets: %i\n', length(SeqNo));
80SortSeqNo = sort(SeqNoUW);
81fprintf('Missing sequence numbers: %i\n', ...
82    length(find(diff(SortSeqNo) > 1)));
83fprintf('Duplicated packets: %i\n', length(find(diff(SortSeqNo) == 0)));
84reorderIx = findReorderedPackets(SeqNoUW);
85fprintf('Reordered packets: %i\n', length(reorderIx));
86tsdiff = diff(TimeStampUW);
87tsdiff = tsdiff(diff(SeqNoUW) == 1);
88[utsdiff, ~, ixtsdiff] = unique(tsdiff);
89fprintf('Common packet sizes:\n');
90for i = 1:length(utsdiff)
91    fprintf('  %i samples (%i%%)\n', ...
92        utsdiff(i), ...
93        round(100 * length(find(ixtsdiff == i))/length(ixtsdiff)));
94end
95
96%% Trying to figure out sample rate.
97fs_est = (TimeStampUW(end) - TimeStampUW(1)) / (ArrTime(end) - ArrTime(1));
98fs_vec = [8, 16, 32, 48];
99fs = 0;
100for f = fs_vec
101    if abs((fs_est-f)/f) < 0.05  % 5% margin
102        fs = f;
103        break;
104    end
105end
106if 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): ');
110else
111    fprintf('Sample rate estimated to %i kHz\n', fs);
112end
113
114SendTimeMs = (TimeStampUW - TimeStampUW(1)) / fs;
115
116fprintf('Stream duration at sender: %.1f seconds\n', ...
117    (SendTimeMs(end) - SendTimeMs(1)) / 1000);
118
119fprintf('Stream duration at receiver: %.1f seconds\n', ...
120    (ArrTime(end) - ArrTime(1)) / 1000);
121
122fprintf('Clock drift: %.2f%%\n', ...
123    100 * ((ArrTime(end) - ArrTime(1)) / ...
124    (SendTimeMs(end) - SendTimeMs(1)) - 1));
125
126fprintf('Sent average bitrate: %i kbps\n', ...
127    round(sum(Size) * 8 / (SendTimeMs(end)-SendTimeMs(1))));
128
129fprintf('Received average bitrate: %i kbps\n', ...
130    round(sum(Size) * 8 / (ArrTime(end)-ArrTime(1))));
131
132%% Plots.
133delay = ArrTime - SendTimeMs;
134delay = delay - min(delay);
135delayOrdered = delay;
136delayOrdered(reorderIx) = nan;  % Set reordered packets to NaN.
137delayReordered = delay(reorderIx);  % Pick the reordered packets.
138sendTimeMsReordered = SendTimeMs(reorderIx);
139
140% Sort time arrays in packet send order.
141[~, sortix] = sort(SeqNoUW);
142SendTimeMs = SendTimeMs(sortix);
143Size = Size(sortix);
144delayOrdered = delayOrdered(sortix);
145
146figure
147plot(SendTimeMs / 1000, delayOrdered, ...
148    sendTimeMsReordered / 1000, delayReordered, 'r.');
149xlabel('Send time [s]');
150ylabel('Relative transport delay [ms]');
151title(sprintf('SSRC: %s', SSRC{1}));
152
153SendBitrateKbps = 8 * Size(1:end-1) ./ diff(SendTimeMs);
154figure
155plot(SendTimeMs(1:end-1)/1000, SendBitrateKbps);
156xlabel('Send time [s]');
157ylabel('Send bitrate [kbps]');
158end
159
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.
165function reorderIx = findReorderedPackets(seqNo)
166largestSeqNo = seqNo(1);
167reorderIx = [];
168for i = 2:length(seqNo)
169    if seqNo(i) < largestSeqNo
170        reorderIx = [reorderIx; i]; %#ok<AGROW>
171    else
172        largestSeqNo = seqNo(i);
173    end
174end
175end
176
177%% Auto-generated subfunction.
178function [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.
197if nargin<=2
198    startRow = 2;
199    endRow = inf;
200end
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.
211formatSpec = '%5f%11f%11f%6f%6f%3f%s%[^\n\r]';
212
213%% Open the text file.
214fileID = 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.
220dataArray = textscan(fileID, formatSpec, endRow(1)-startRow(1)+1, ...
221    'Delimiter', '', 'WhiteSpace', '', 'HeaderLines', startRow(1)-1, ...
222    'ReturnOnError', false);
223for 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
231end
232
233%% Close the text file.
234fclose(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
243SeqNo = dataArray{:, 1};
244TimeStamp = dataArray{:, 2};
245SendTime = dataArray{:, 3};
246Size = dataArray{:, 4};
247PT = dataArray{:, 5};
248M = dataArray{:, 6};
249SSRC = dataArray{:, 7};
250end
251
252