blob: 4b8c39770a5a51a744f8305ef5860406ead1fdcb [file] [log] [blame]
Harald Welte067d66e2017-12-13 17:24:03 +01001module RTP_Emulation {
2
3/* Functionalities that we want this module to imeplement:
4 * * act as a RTP source that generates a RTP Stream
5 * * act asaa RTP sink that consumes a RTP Stream
6 *
7 * for all of the above, we want to be able to
8 * * specify the payload type
9 * * specify the interval / sample rate
10 * * create drop-outs in the stream
11 * * detect reordered or lost frames
12 * * validate if the size of the frames matches epectations
13 * * play back real audio (at least some tones?)
14 * * enable/disable generation/verification of RTCP
15 */
16
Harald Weltea4ca4462018-02-09 00:17:14 +010017/* Ideas:
18
19* each component consists of transmitter and receiver
20* transmitters and receivers can be operated as tuple?
21* high-level operation
22** set-up config at transmitter + receiver
23** transmit sequence of payloads
24** verify receiption of those payloads
25* can operate full-duplex/bi-directional as needed
26
27* transmitter
28** trigger transmission of n number of packets
29** transmit them at normal ptime interval
30** payload size configurable
31** payload contents PRBS or the like
32
33* receiver
34** count number of related packets at receiver
35** check received payload type
36** check received timestamp increments
37** check received seq_nr increments
38** (optionally) check for SSRC
39** (optionally) check for payload size
40** (optionally) check for payload contents
41
42* later
43** how to test transcoding?
44** how to test pure play-out endpoints (rx only)?
45** how to test "Rx from wrong IP/port" scenarios?
46** how to test RTCP?
47** maybe keep ports un-connected to show wrong src -lrt
48
49*/
50
51
52
53
Harald Welte067d66e2017-12-13 17:24:03 +010054import from General_Types all;
55import from Osmocom_Types all;
56import from IPL4asp_Types all;
57import from RTP_Types all;
58import from RTP_CodecPort all;
59import from RTP_CodecPort_CtrlFunct all;
60
Harald Welte80981642017-12-24 23:59:47 +010061import from IuUP_Types all;
62import from IuUP_Emulation all;
63
Harald Welte067d66e2017-12-13 17:24:03 +010064type component RTP_Emulation_CT {
65 /* down-facing ports for RTP and RTCP codec ports on top of IPL4asp */
66 port RTP_CODEC_PT RTP;
67 var integer g_rtp_conn_id := -1;
68 port RTP_CODEC_PT RTCP;
69 var integer g_rtcp_conn_id := -1;
70
71 /* user-facing port for controlling the binding */
72 port RTPEM_CTRL_PT CTRL;
73
74 /* configurable by user, should be fixed */
Harald Welte80981642017-12-24 23:59:47 +010075 var RtpemConfig g_cfg := c_RtpemDefaultCfg;
Harald Welte067d66e2017-12-13 17:24:03 +010076
Harald Weltecb8b4272018-03-28 19:57:58 +020077 /* statistics */
78 var RtpemStats g_stats_rtp := c_RtpemStatsReset;
79 var RtpemStats g_stats_rtcp := c_RtpemStatsReset;
80
Harald Welte067d66e2017-12-13 17:24:03 +010081 var HostName g_remote_host;
82 var PortNumber g_remote_port;
83 var HostName g_local_host;
84 var PortNumber g_local_port;
85
86 /* state variables, change over time */
87 var boolean g_rx_enabled := false;
88 var LIN2_BO_LAST g_tx_next_seq := 0;
89 var uint32_t g_tx_next_ts := 0;
90
91 var INT7b g_rx_payload_type := 0;
92 var LIN2_BO_LAST g_rx_last_seq;
93 var uint32_t g_rx_last_ts;
Harald Welte80981642017-12-24 23:59:47 +010094
95 var IuUP_Entity g_iuup_ent; // := valueof(t_IuUP_Entity(1));
Harald Welte067d66e2017-12-13 17:24:03 +010096}
97
98type enumerated RtpemMode {
99 RTPEM_MODE_NONE,
100 RTPEM_MODE_TXONLY,
101 RTPEM_MODE_RXONLY,
102 RTPEM_MODE_BIDIR
103};
104
Harald Weltecb8b4272018-03-28 19:57:58 +0200105type record RtpemStats {
106 /* number of packets transmitted */
107 integer num_pkts_tx,
108 /* number of RTP payload bytes transmitted */
109 integer bytes_payload_tx,
110
111 /* number of packets received */
112 integer num_pkts_rx,
113 /* number of RTP payload bytes received */
114 integer bytes_payload_rx,
115 /* number of packets received out-of-sequence */
116 integer num_pkts_rx_err_seq,
117 /* number of packets received wrong timestamp */
118 integer num_pkts_rx_err_ts,
119 /* number of packets received during Rx disable */
120 integer num_pkts_rx_err_disabled
121}
122
123const RtpemStats c_RtpemStatsReset := {
124 num_pkts_tx := 0,
125 bytes_payload_tx := 0,
126 num_pkts_rx := 0,
127 bytes_payload_rx := 0,
128 num_pkts_rx_err_seq := 0,
129 num_pkts_rx_err_ts := 0,
130 num_pkts_rx_err_disabled := 0
131}
132
Harald Welte3f6f48f2017-12-24 21:48:33 +0100133type record RtpemConfig {
134 INT7b tx_payload_type,
135 integer tx_samplerate_hz,
136 integer tx_duration_ms,
137 BIT32_BO_LAST tx_ssrc,
Harald Welte80981642017-12-24 23:59:47 +0100138 octetstring tx_fixed_payload optional,
139 boolean iuup_mode,
140 boolean iuup_tx_init
Harald Welte3f6f48f2017-12-24 21:48:33 +0100141};
142
Harald Welte80981642017-12-24 23:59:47 +0100143const RtpemConfig c_RtpemDefaultCfg := {
Harald Welte3f6f48f2017-12-24 21:48:33 +0100144 tx_payload_type := 0,
145 tx_samplerate_hz := 8000,
146 tx_duration_ms := 20,
147 tx_ssrc := '11011110101011011011111011101111'B,
Harald Welte80981642017-12-24 23:59:47 +0100148 tx_fixed_payload := '01020304'O,
149 iuup_mode := false,
150 iuup_tx_init := true
Harald Welte3f6f48f2017-12-24 21:48:33 +0100151}
152
Harald Welte067d66e2017-12-13 17:24:03 +0100153signature RTPEM_bind(in HostName local_host, inout PortNumber local_port);
154signature RTPEM_connect(in HostName remote_host, in PortNumber remote_port);
155signature RTPEM_mode(in RtpemMode mode);
Harald Welte3f6f48f2017-12-24 21:48:33 +0100156signature RTPEM_configure(in RtpemConfig cfg);
Harald Weltecb8b4272018-03-28 19:57:58 +0200157signature RTPEM_stats_get(out RtpemStats stats, in boolean rtcp);
Harald Welte067d66e2017-12-13 17:24:03 +0100158
159type port RTPEM_CTRL_PT procedure {
Harald Weltecb8b4272018-03-28 19:57:58 +0200160 inout RTPEM_bind, RTPEM_connect, RTPEM_mode, RTPEM_configure, RTPEM_stats_get;
Harald Welte067d66e2017-12-13 17:24:03 +0100161} with { extension "internal" };
162
163template PDU_RTP ts_RTP(BIT32_BO_LAST ssrc, INT7b pt, LIN2_BO_LAST seq, uint32_t ts,
164 octetstring payload, BIT1 marker := '0'B) := {
165 version := 2,
166 padding_ind := '0'B,
167 extension_ind := '0'B,
168 CSRC_count := 0,
169 marker_bit := marker,
170 payload_type := pt,
171 sequence_number := seq,
Harald Welte8a4d3952017-12-24 21:30:23 +0100172 time_stamp := int2bit(ts, 32),
Harald Welte067d66e2017-12-13 17:24:03 +0100173 SSRC_id := ssrc,
174 CSRCs := omit,
175 ext_header := omit,
176 data := payload
177}
178
179private function f_tx_rtp(octetstring payload, BIT1 marker := '0'B) runs on RTP_Emulation_CT {
Harald Welte80981642017-12-24 23:59:47 +0100180 if (g_cfg.iuup_mode) {
181 payload := f_IuUP_Em_tx_encap(g_iuup_ent, payload);
182 }
Harald Welte3f6f48f2017-12-24 21:48:33 +0100183 var PDU_RTP rtp := valueof(ts_RTP(g_cfg.tx_ssrc, g_cfg.tx_payload_type, g_tx_next_seq,
Harald Welte067d66e2017-12-13 17:24:03 +0100184 g_tx_next_ts, payload, marker));
185 RTP.send(t_RTP_Send(g_rtp_conn_id, RTP_messages_union:{rtp:=rtp}));
186 /* increment sequence + timestamp for next transmit */
187 g_tx_next_seq := g_tx_next_seq + 1;
Harald Welte3f6f48f2017-12-24 21:48:33 +0100188 g_tx_next_ts := g_tx_next_ts + (g_cfg.tx_samplerate_hz / (1000 / g_cfg.tx_duration_ms));
Harald Welte067d66e2017-12-13 17:24:03 +0100189}
190
191function f_main() runs on RTP_Emulation_CT
192{
193 var Result res;
Harald Weltecb8b4272018-03-28 19:57:58 +0200194 var boolean is_rtcp
Harald Welte067d66e2017-12-13 17:24:03 +0100195
Harald Welte3f6f48f2017-12-24 21:48:33 +0100196 timer T_transmit := int2float(g_cfg.tx_duration_ms)/1000.0;
Harald Welte067d66e2017-12-13 17:24:03 +0100197 var RTP_RecvFrom rx_rtp;
Harald Welte3f6f48f2017-12-24 21:48:33 +0100198 var RtpemConfig cfg;
Harald Welte067d66e2017-12-13 17:24:03 +0100199 var template RTP_RecvFrom tr := {
200 connId := ?,
201 remName := ?,
202 remPort := ?,
203 locName := ?,
204 locPort := ?,
205 msg := ?
206 };
207 var template RTP_RecvFrom tr_rtp := tr;
208 var template RTP_RecvFrom tr_rtcp := tr;
Harald Welte067d66e2017-12-13 17:24:03 +0100209 tr_rtp.msg := { rtp := ? };
Harald Welte067d66e2017-12-13 17:24:03 +0100210 tr_rtcp.msg := { rtcp := ? };
211
Harald Welte80981642017-12-24 23:59:47 +0100212 g_iuup_ent := valueof(t_IuUP_Entity(g_cfg.iuup_tx_init));
213
Harald Welte067d66e2017-12-13 17:24:03 +0100214 while (true) {
215 alt {
216 /* control procedures (calls) from the user */
217 [] CTRL.getcall(RTPEM_bind:{?,?}) -> param(g_local_host, g_local_port) {
218 if (g_local_port rem 2 == 1) {
219 //CTRL.raise(RTPEM_bind, "Local Port is not an even port number!");
220 log("Local Port is not an even port number!");
221 continue;
222 }
223 res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTP, g_local_host,
224 g_local_port, {udp:={}});
225 g_rtp_conn_id := res.connId;
Harald Welte9774e7a2018-03-29 08:49:38 +0200226 tr_rtp.connId := g_rtp_conn_id;
Harald Welte067d66e2017-12-13 17:24:03 +0100227 res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTP, g_local_host,
228 g_local_port+1, {udp:={}});
229 g_rtcp_conn_id := res.connId;
Harald Welte9774e7a2018-03-29 08:49:38 +0200230 tr_rtcp.connId := g_rtcp_conn_id;
Harald Welte067d66e2017-12-13 17:24:03 +0100231 CTRL.reply(RTPEM_bind:{g_local_host, g_local_port});
232 }
233 [] CTRL.getcall(RTPEM_connect:{?,?}) -> param (g_remote_host, g_remote_port) {
234 if (g_remote_port rem 2 == 1) {
235 //CTRL.raise(RTPEM_connect, "Remote Port is not an even number!");
236 log("Remote Port is not an even number!");
237 continue;
238 }
239 res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTP, g_remote_host,
240 g_remote_port,
241 g_local_host, g_local_port,
242 g_rtp_conn_id, {udp:={}});
243 res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTCP, g_remote_host,
244 g_remote_port+1,
245 g_local_host, g_local_port+1,
246 g_rtcp_conn_id, {udp:={}});
247 CTRL.reply(RTPEM_connect:{g_remote_host, g_remote_port});
248 }
249 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_NONE}) {
250 T_transmit.stop;
251 g_rx_enabled := false;
Harald Welte80981642017-12-24 23:59:47 +0100252 CTRL.reply(RTPEM_mode:{RTPEM_MODE_NONE});
Harald Welte067d66e2017-12-13 17:24:03 +0100253 }
254 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_TXONLY}) {
255 /* start transmit timer */
256 T_transmit.start;
257 g_rx_enabled := false;
Harald Welte80981642017-12-24 23:59:47 +0100258 CTRL.reply(RTPEM_mode:{RTPEM_MODE_TXONLY});
Harald Welte067d66e2017-12-13 17:24:03 +0100259 }
260 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_RXONLY}) {
261
262 T_transmit.stop;
263 if (g_rx_enabled == false) {
264 /* flush queues */
265 RTP.clear;
266 RTCP.clear;
267 g_rx_enabled := true;
268 }
Harald Welte80981642017-12-24 23:59:47 +0100269 CTRL.reply(RTPEM_mode:{RTPEM_MODE_RXONLY});
Harald Welte067d66e2017-12-13 17:24:03 +0100270 }
271 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_BIDIR}) {
272 T_transmit.start;
273 if (g_rx_enabled == false) {
274 /* flush queues */
275 RTP.clear;
276 RTCP.clear;
277 g_rx_enabled := true;
278 }
Harald Welte80981642017-12-24 23:59:47 +0100279 CTRL.reply(RTPEM_mode:{RTPEM_MODE_BIDIR});
Harald Welte067d66e2017-12-13 17:24:03 +0100280 }
Harald Welte3f6f48f2017-12-24 21:48:33 +0100281 [] CTRL.getcall(RTPEM_configure:{?}) -> param (cfg) {
282 g_cfg := cfg;
Harald Welte80981642017-12-24 23:59:47 +0100283 g_iuup_ent.cfg.active_init := g_cfg.iuup_tx_init;
284 CTRL.reply(RTPEM_configure:{cfg});
Harald Welte3f6f48f2017-12-24 21:48:33 +0100285 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200286 [] CTRL.getcall(RTPEM_stats_get:{?, ?}) -> param (is_rtcp) {
287 if (is_rtcp) {
288 CTRL.reply(RTPEM_stats_get:{g_stats_rtcp, is_rtcp});
289 } else {
290 CTRL.reply(RTPEM_stats_get:{g_stats_rtp, is_rtcp});
291 }
292 }
Harald Welte067d66e2017-12-13 17:24:03 +0100293
Harald Weltecb8b4272018-03-28 19:57:58 +0200294
295
296 /* simply ignore any RTTP/RTP if receiver not enabled */
297 [g_rx_enabled==false] RTP.receive(tr_rtp) {
298 g_stats_rtp.num_pkts_rx_err_disabled := g_stats_rtp.num_pkts_rx_err_disabled+1;
299 }
300 [g_rx_enabled==false] RTCP.receive(tr_rtp) {
301 g_stats_rtcp.num_pkts_rx_err_disabled := g_stats_rtcp.num_pkts_rx_err_disabled+1;
302 }
Harald Welte067d66e2017-12-13 17:24:03 +0100303
304 /* process received RTCP/RTP if receiver enabled */
305 [g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp {
Harald Welte8d3ea0e2018-03-29 08:50:18 +0200306 //log("RX RTP: ", rx_rtp);
Harald Weltecb8b4272018-03-28 19:57:58 +0200307 /* increment counters */
308 g_stats_rtp.num_pkts_rx := g_stats_rtp.num_pkts_rx+1;
309 g_stats_rtp.bytes_payload_rx := g_stats_rtp.bytes_payload_rx +
310 lengthof(rx_rtp.msg.rtp.data);
Harald Welte80981642017-12-24 23:59:47 +0100311 if (g_cfg.iuup_mode) {
312 rx_rtp.msg.rtp.data := f_IuUP_Em_rx_decaps(g_iuup_ent, rx_rtp.msg.rtp.data);
313 }
Harald Welte067d66e2017-12-13 17:24:03 +0100314 }
315 [g_rx_enabled] RTCP.receive(tr_rtcp) -> value rx_rtp {
Harald Welte8d3ea0e2018-03-29 08:50:18 +0200316 //log("RX RTCP: ", rx_rtp);
Harald Weltecb8b4272018-03-28 19:57:58 +0200317 g_stats_rtcp.num_pkts_rx := g_stats_rtcp.num_pkts_rx+1;
Harald Welte067d66e2017-12-13 17:24:03 +0100318 }
319
320 /* transmit if timer has expired */
321 [] T_transmit.timeout {
322 /* send one RTP frame, re-start timer */
Harald Welte3f6f48f2017-12-24 21:48:33 +0100323 f_tx_rtp(g_cfg.tx_fixed_payload);
Harald Welte067d66e2017-12-13 17:24:03 +0100324 T_transmit.start;
Harald Weltecb8b4272018-03-28 19:57:58 +0200325 /* update counters */
326 g_stats_rtp.num_pkts_tx := g_stats_rtp.num_pkts_tx+1;
327 g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx +
328 lengthof(g_cfg.tx_fixed_payload);
Harald Welte067d66e2017-12-13 17:24:03 +0100329 }
330
331 /* fail on any unexpected messages */
332 [] RTP.receive {
333 setverdict(fail, "Received unexpected type from RTP");
334 }
335 [] RTCP.receive {
336 setverdict(fail, "Received unexpected type from RTCP");
337 }
338 }
339 }
340}
341
342
343}