module RTP_Emulation { /* Functionalities that we want this module to imeplement: * * act as a RTP source that generates a RTP Stream * * act asaa RTP sink that consumes a RTP Stream * * for all of the above, we want to be able to * * specify the payload type * * specify the interval / sample rate * * create drop-outs in the stream * * detect reordered or lost frames * * validate if the size of the frames matches epectations * * play back real audio (at least some tones?) * * enable/disable generation/verification of RTCP */ import from General_Types all; import from Osmocom_Types all; import from IPL4asp_Types all; import from RTP_Types all; import from RTP_CodecPort all; import from RTP_CodecPort_CtrlFunct all; import from IuUP_Types all; import from IuUP_Emulation all; type component RTP_Emulation_CT { /* down-facing ports for RTP and RTCP codec ports on top of IPL4asp */ port RTP_CODEC_PT RTP; var integer g_rtp_conn_id := -1; port RTP_CODEC_PT RTCP; var integer g_rtcp_conn_id := -1; /* user-facing port for controlling the binding */ port RTPEM_CTRL_PT CTRL; /* configurable by user, should be fixed */ var RtpemConfig g_cfg := c_RtpemDefaultCfg; var HostName g_remote_host; var PortNumber g_remote_port; var HostName g_local_host; var PortNumber g_local_port; /* state variables, change over time */ var boolean g_rx_enabled := false; var LIN2_BO_LAST g_tx_next_seq := 0; var uint32_t g_tx_next_ts := 0; var INT7b g_rx_payload_type := 0; var LIN2_BO_LAST g_rx_last_seq; var uint32_t g_rx_last_ts; var IuUP_Entity g_iuup_ent; // := valueof(t_IuUP_Entity(1)); } type enumerated RtpemMode { RTPEM_MODE_NONE, RTPEM_MODE_TXONLY, RTPEM_MODE_RXONLY, RTPEM_MODE_BIDIR }; type record RtpemConfig { INT7b tx_payload_type, integer tx_samplerate_hz, integer tx_duration_ms, BIT32_BO_LAST tx_ssrc, octetstring tx_fixed_payload optional, boolean iuup_mode, boolean iuup_tx_init }; const RtpemConfig c_RtpemDefaultCfg := { tx_payload_type := 0, tx_samplerate_hz := 8000, tx_duration_ms := 20, tx_ssrc := '11011110101011011011111011101111'B, tx_fixed_payload := '01020304'O, iuup_mode := false, iuup_tx_init := true } signature RTPEM_bind(in HostName local_host, inout PortNumber local_port); signature RTPEM_connect(in HostName remote_host, in PortNumber remote_port); signature RTPEM_mode(in RtpemMode mode); signature RTPEM_configure(in RtpemConfig cfg); type port RTPEM_CTRL_PT procedure { inout RTPEM_bind, RTPEM_connect, RTPEM_mode, RTPEM_configure; } with { extension "internal" }; template PDU_RTP ts_RTP(BIT32_BO_LAST ssrc, INT7b pt, LIN2_BO_LAST seq, uint32_t ts, octetstring payload, BIT1 marker := '0'B) := { version := 2, padding_ind := '0'B, extension_ind := '0'B, CSRC_count := 0, marker_bit := marker, payload_type := pt, sequence_number := seq, time_stamp := int2bit(ts, 32), SSRC_id := ssrc, CSRCs := omit, ext_header := omit, data := payload } private function f_tx_rtp(octetstring payload, BIT1 marker := '0'B) runs on RTP_Emulation_CT { if (g_cfg.iuup_mode) { payload := f_IuUP_Em_tx_encap(g_iuup_ent, payload); } var PDU_RTP rtp := valueof(ts_RTP(g_cfg.tx_ssrc, g_cfg.tx_payload_type, g_tx_next_seq, g_tx_next_ts, payload, marker)); RTP.send(t_RTP_Send(g_rtp_conn_id, RTP_messages_union:{rtp:=rtp})); /* increment sequence + timestamp for next transmit */ g_tx_next_seq := g_tx_next_seq + 1; g_tx_next_ts := g_tx_next_ts + (g_cfg.tx_samplerate_hz / (1000 / g_cfg.tx_duration_ms)); } function f_main() runs on RTP_Emulation_CT { var Result res; timer T_transmit := int2float(g_cfg.tx_duration_ms)/1000.0; var RTP_RecvFrom rx_rtp; var RtpemConfig cfg; var template RTP_RecvFrom tr := { connId := ?, remName := ?, remPort := ?, locName := ?, locPort := ?, msg := ? }; var template RTP_RecvFrom tr_rtp := tr; var template RTP_RecvFrom tr_rtcp := tr; tr_rtp.connId := g_rtp_conn_id; tr_rtp.msg := { rtp := ? }; tr_rtp.connId := g_rtcp_conn_id; tr_rtcp.msg := { rtcp := ? }; g_iuup_ent := valueof(t_IuUP_Entity(g_cfg.iuup_tx_init)); while (true) { alt { /* control procedures (calls) from the user */ [] CTRL.getcall(RTPEM_bind:{?,?}) -> param(g_local_host, g_local_port) { if (g_local_port rem 2 == 1) { //CTRL.raise(RTPEM_bind, "Local Port is not an even port number!"); log("Local Port is not an even port number!"); continue; } res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTP, g_local_host, g_local_port, {udp:={}}); g_rtp_conn_id := res.connId; res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTP, g_local_host, g_local_port+1, {udp:={}}); g_rtcp_conn_id := res.connId; CTRL.reply(RTPEM_bind:{g_local_host, g_local_port}); } [] CTRL.getcall(RTPEM_connect:{?,?}) -> param (g_remote_host, g_remote_port) { if (g_remote_port rem 2 == 1) { //CTRL.raise(RTPEM_connect, "Remote Port is not an even number!"); log("Remote Port is not an even number!"); continue; } res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTP, g_remote_host, g_remote_port, g_local_host, g_local_port, g_rtp_conn_id, {udp:={}}); res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTCP, g_remote_host, g_remote_port+1, g_local_host, g_local_port+1, g_rtcp_conn_id, {udp:={}}); CTRL.reply(RTPEM_connect:{g_remote_host, g_remote_port}); } [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_NONE}) { T_transmit.stop; g_rx_enabled := false; CTRL.reply(RTPEM_mode:{RTPEM_MODE_NONE}); } [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_TXONLY}) { /* start transmit timer */ T_transmit.start; g_rx_enabled := false; CTRL.reply(RTPEM_mode:{RTPEM_MODE_TXONLY}); } [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_RXONLY}) { T_transmit.stop; if (g_rx_enabled == false) { /* flush queues */ RTP.clear; RTCP.clear; g_rx_enabled := true; } CTRL.reply(RTPEM_mode:{RTPEM_MODE_RXONLY}); } [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_BIDIR}) { T_transmit.start; if (g_rx_enabled == false) { /* flush queues */ RTP.clear; RTCP.clear; g_rx_enabled := true; } CTRL.reply(RTPEM_mode:{RTPEM_MODE_BIDIR}); } [] CTRL.getcall(RTPEM_configure:{?}) -> param (cfg) { g_cfg := cfg; g_iuup_ent.cfg.active_init := g_cfg.iuup_tx_init; CTRL.reply(RTPEM_configure:{cfg}); } /* simply ignore any RTTP/RTCP if receiver not enabled */ [g_rx_enabled==false] RTP.receive(tr_rtp) { } [g_rx_enabled==false] RTCP.receive(tr_rtp) { } /* process received RTCP/RTP if receiver enabled */ [g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp { log("RX RTP: ", rx_rtp); if (g_cfg.iuup_mode) { rx_rtp.msg.rtp.data := f_IuUP_Em_rx_decaps(g_iuup_ent, rx_rtp.msg.rtp.data); } } [g_rx_enabled] RTCP.receive(tr_rtcp) -> value rx_rtp { log("RX RTCP: ", rx_rtp); } /* transmit if timer has expired */ [] T_transmit.timeout { /* send one RTP frame, re-start timer */ f_tx_rtp(g_cfg.tx_fixed_payload); T_transmit.start; } /* fail on any unexpected messages */ [] RTP.receive { setverdict(fail, "Received unexpected type from RTP"); } [] RTCP.receive { setverdict(fail, "Received unexpected type from RTCP"); } } } } }