diff --git a/src/Makefile.groups b/src/Makefile.groups
index 04a5ae730fc..1e8227dadce 100644
--- a/src/Makefile.groups
+++ b/src/Makefile.groups
@@ -207,6 +207,9 @@ mod_list_rabbitmq=rabbitmq
# - modules depending on libphonenumber library
mod_list_phonenum=phonenum
+# - modules depending on oRTP and mediastreamer2 libraries
+mod_list_rtp_media=rtp_media_server
+
# - all modules
mod_list_all=$(sort $(mod_list_basic) $(mod_list_extra) \
$(mod_list_db) $(mod_list_dbuid) \
@@ -239,7 +242,8 @@ mod_list_all=$(sort $(mod_list_basic) $(mod_list_extra) \
$(mod_list_erlang) $(mod_list_systemd) \
$(mod_list_http_async) $(mod_list_nsq) \
$(mod_list_rabbitmq) $(mod_list_jsdt)) \
- $(mod_list_phonenum)
+ $(mod_list_phonenum) \
+ $(mod_list_rtp_media)
diff --git a/src/modules/rtp_media_server/Makefile b/src/modules/rtp_media_server/Makefile
new file mode 100644
index 00000000000..d2800c44e6a
--- /dev/null
+++ b/src/modules/rtp_media_server/Makefile
@@ -0,0 +1,13 @@
+include ../../Makefile.defs
+auto_gen=
+NAME=rtp_media_server.so
+
+DEFS+=-I$(LOCALBASE)/lib
+
+ORTPLIBS=-lortp
+BCUNITLIBS=-lbcunit
+MS2LIBS=-lmediastreamer_voip -lmediastreamer_base
+
+LIBS=$(ORTPLIBS) $(BCUNITLIBS) $(MS2LIBS)
+DEFS+=-DKAMAILIO_MOD_INTERFACE
+include ../../Makefile.modules
diff --git a/src/modules/rtp_media_server/config_example/kamailio.cfg b/src/modules/rtp_media_server/config_example/kamailio.cfg
new file mode 100755
index 00000000000..6ddb36567da
--- /dev/null
+++ b/src/modules/rtp_media_server/config_example/kamailio.cfg
@@ -0,0 +1,56 @@
+
+debug=3
+log_stderror=yes
+memdbg=5
+memlog=5
+
+# number of SIP routing processes
+children=5
+
+loadmodule "ctl"
+loadmodule "tm"
+loadmodule "tmx"
+loadmodule "sl"
+loadmodule "rr"
+loadmodule "pv"
+loadmodule "textops"
+loadmodule "siputils"
+loadmodule "xlog"
+loadmodule "nathelper"
+loadmodule "rtp_media_server"
+modparam("rtp_media_server", "log_file_name", "/tmp/rms_transfer.log")
+modparam("tm", "wt_timer", 1000)
+listen=udp:147.75.39.121:5090
+
+event_route[rms:start] {
+ xnotice("[rms:start] play ...\n");
+ rms_play("./voice_file/Bach_10s_8000.wav", "rms:after_play");
+};
+
+event_route[rms:after_play] {
+ xnotice("[rms:after_play] play done...\n");
+ rms_hangup();
+};
+
+route {
+ if (t_precheck_trans()) {
+ t_check_trans();
+ exit;
+ }
+ t_check_trans();
+
+ xnotice("[$rm][$ci]\n");
+ if (is_method("INVITE") && !has_totag()) {
+ fix_nated_contact();
+ if (!rms_answer()) {
+ t_reply("503", "server error");
+ xerr("rtp_media_server error!\n");
+ exit;
+ }
+ }
+ if (is_method("BYE")){
+ rms_media_stop();
+ }
+ exit;
+}
+
diff --git a/src/modules/rtp_media_server/doc/Makefile b/src/modules/rtp_media_server/doc/Makefile
new file mode 100644
index 00000000000..b3040f9da41
--- /dev/null
+++ b/src/modules/rtp_media_server/doc/Makefile
@@ -0,0 +1,4 @@
+docs = rtp_media_server.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module
diff --git a/src/modules/rtp_media_server/doc/rtp_media_server.xml b/src/modules/rtp_media_server/doc/rtp_media_server.xml
new file mode 100644
index 00000000000..1f043a3842f
--- /dev/null
+++ b/src/modules/rtp_media_server/doc/rtp_media_server.xml
@@ -0,0 +1,42 @@
+
+
+
+%docentities;
+
+]>
+
+
+
+ rtp_media_server Module
+ &kamailio;
+
+
+ Julien
+ Chavanton
+ jchavanton@gmail.com
+
+
+ Julien
+ Chavanton
+ flowroute.com
+ jchavanton@gmail.com
+
+
+ Julien
+ Chavanton
+ flowroute.com
+ jchavanton@gmail.com
+
+
+
+ 2017-2018
+ Flowroute.com
+
+
+
+
+
+
diff --git a/src/modules/rtp_media_server/doc/rtp_media_server_admin.xml b/src/modules/rtp_media_server/doc/rtp_media_server_admin.xml
new file mode 100644
index 00000000000..adaf1a91560
--- /dev/null
+++ b/src/modules/rtp_media_server/doc/rtp_media_server_admin.xml
@@ -0,0 +1,193 @@
+
+
+
+%docentities;
+
+]>
+
+
+ &adminguide;
+
+
+ Overview
+
+ rtp_media_server module is adding RTP and media processing functionalities to Kamailio
+
+
+ Kamailio is providing SIP signaling including and enpoint with Dialog state, SDP parsing and scripting language
+
+ oRTP: is providing Real-time Transport Protocol (RFC 3550)
+
+ mediastreamer2: is providing mediaprocessing functionnalities using graphs and filters, many modules are available
+ to support various features, it should be relatively simple to integrated them.
+
+ mediastreamer2 is also providing a framework to create custom mediaprocessing modules.
+
+
+
+
+ Dependencies
+
+ &kamailio; Modules
+
+ The module depends on the following modules (in the other words
+ the listed modules must be loaded before this module):
+
+
+ tm - accounting module
+
+
+
+
+
+ External Libraries or Applications
+
+ The following libraries or applications must be installed
+ before running &kamailio; with this module loaded:
+
+ If you want to build oRTP and mediastreamer from source, you can use the provided script for Debian "install_bc.sh".
+
+
+
+
+ oRTP git://git.linphone.org/ortp.git
+
+
+ oRTP is a library implemeting Real-time Transport Protocol (RFC 3550), distributed under GNU GPLv2 or proprietary license.
+
+
+
+
+ mediastreamer2 git clone git://git.linphone.org/mediastreamer2.git
+
+
+ Mediastreamer2 is a powerful and lightweight streaming engine specialized for voice/video telephony applications.
+
+
+
+
+
+
+
+ Parameters
+
+ log_file_name (string)
+
+ oRTP and MediaStreamer2 log file settings
+ the log mask is not configurable :
+ MESSAGE | WARNING | ERROR | FATAL
+ levels are activated.
+
+
+ Default value is not-set (no logging to file).
+
+
+ log_file_name example
+
+...
+modparam("rtp_media_server", "log_file_name", "/var/log/rms/rms_ortp.log")
+...
+
+
+
+
+
+ Functions
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/rtp_media_server/install_bc.sh b/src/modules/rtp_media_server/install_bc.sh
new file mode 100755
index 00000000000..02cbaf1e9c1
--- /dev/null
+++ b/src/modules/rtp_media_server/install_bc.sh
@@ -0,0 +1,45 @@
+# https://github.com/jchavanton/rtp_media_server.git
+apt-get install automake autogen autoconf libtool pkg-config
+# bcunit
+git clone https://github.com/BelledonneCommunications/bcunit.git
+cd bcunit
+git checkout 29c556fa8ac1ab21fba1291231ffa8dea43cf32a
+./autogen.sh
+./configure
+make
+make install
+cd ..
+
+# bctoolbox
+apt-get install libmbedtls-dev
+git clone https://github.com/BelledonneCommunications/bctoolbox.git
+cd bctoolbox
+git checkout 971953a9fa4058e9c8a40ca4a3fa12d832445255
+./autogen.sh
+./configure
+make
+make install
+cd ..
+
+# oRTP
+git clone https://github.com/BelledonneCommunications/ortp.git
+git checkout 6e13ef49a55cdd19dae395c38cfff7ffa518a089
+cd ortp
+./autogen.sh
+./configure
+make
+make install
+cd ..
+
+# mediastreamer2
+apt-get install intltool libspeex-dev libspeexdsp-dev
+git clone https://github.com/BelledonneCommunications/mediastreamer2.git
+cd mediastreamer2
+git checkout d935123fc497d19a24019c6e7ae4fe0c5f19d55a
+./autogen.sh
+./configure --disable-sound --disable-video --enable-tools=no --disable-tests
+make
+make install
+cd ..
+
+ldconfig
diff --git a/src/modules/rtp_media_server/rms_media.c b/src/modules/rtp_media_server/rms_media.c
new file mode 100644
index 00000000000..747c56c39f7
--- /dev/null
+++ b/src/modules/rtp_media_server/rms_media.c
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton jchavanton@gmail.com
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "../../core/mem/shm.h"
+#include "../../core/sr_module.h"
+#include "rtp_media_server.h"
+
+inline static void *ptr_shm_malloc(size_t size)
+{
+ return shm_malloc(size);
+}
+inline static void *ptr_shm_realloc(void *ptr, size_t size)
+{
+ return shm_realloc(ptr, size);
+}
+inline static void ptr_shm_free(void *ptr)
+{
+ shm_free(ptr);
+}
+
+typedef struct shared_global_vars
+{
+ MSFactory *ms_factory;
+ gen_lock_t lock;
+} shared_global_vars_t;
+
+static shared_global_vars_t *vars;
+
+MSFilterDesc *rms_ms_filter_descs[] = {&ms_alaw_dec_desc, &ms_alaw_enc_desc,
+ &ms_ulaw_dec_desc, &ms_ulaw_enc_desc, &ms_rtp_send_desc,
+ &ms_rtp_recv_desc, &ms_dtmf_gen_desc, &ms_volume_desc,
+ &ms_equalizer_desc, &ms_channel_adapter_desc, &ms_audio_mixer_desc,
+ &ms_tone_detector_desc, &ms_speex_dec_desc, &ms_speex_enc_desc,
+ &ms_speex_ec_desc, &ms_file_player_desc, &ms_file_rec_desc,
+ &ms_resample_desc,
+ // &ms_opus_dec_desc,
+ // &ms_opus_enc_desc,
+ NULL};
+
+static MSFactory *rms_create_factory()
+{
+ MSFactory *f = ms_factory_new();
+ int i;
+ for(i = 0; rms_ms_filter_descs[i] != NULL; i++) {
+ ms_factory_register_filter(f, rms_ms_filter_descs[i]);
+ }
+ ms_factory_init_plugins(f);
+ ms_factory_enable_statistics(f, TRUE);
+ ms_factory_reset_statistics(f);
+ return f;
+}
+
+int rms_media_init()
+{
+ OrtpMemoryFunctions ortp_memory_functions;
+ ortp_memory_functions.malloc_fun = ptr_shm_malloc;
+ ortp_memory_functions.realloc_fun = ptr_shm_realloc;
+ ortp_memory_functions.free_fun = ptr_shm_free;
+ ortp_set_memory_functions(&ortp_memory_functions);
+ ortp_init();
+ vars = shm_malloc(sizeof(shared_global_vars_t));
+ return 1;
+}
+
+static MSTicker *rms_create_ticker(char *name)
+{
+ MSTickerParams params;
+ params.name = name;
+ params.prio = MS_TICKER_PRIO_NORMAL;
+ return ms_ticker_new_with_params(¶ms);
+}
+
+void rms_media_destroy(call_leg_media_t *m)
+{
+ LM_INFO("rtp_session_destroy[%p]\n", m->rtps);
+ rtp_session_destroy(m->rtps);
+ m->rtps = NULL;
+ LM_INFO("ms_ticker[%p]\n", m->ms_ticker);
+ ms_ticker_destroy(m->ms_ticker);
+ m->ms_ticker = NULL;
+ LM_INFO("ms_factory_destroy[%p]\n", m->ms_factory);
+ ms_factory_destroy(m->ms_factory);
+ m->ms_factory = NULL;
+}
+
+int create_call_leg_media(call_leg_media_t *m)
+{
+ m->ms_factory = rms_create_factory();
+ // create caller RTP session
+ LM_INFO("RTP session [%s:%d]<>[%s:%d]\n", m->local_ip.s, m->local_port,
+ m->remote_ip.s, m->remote_port);
+ m->rtps = ms_create_duplex_rtp_session(m->local_ip.s, m->local_port,
+ m->local_port + 1, ms_factory_get_mtu(m->ms_factory));
+ rtp_session_set_remote_addr_full(m->rtps, m->remote_ip.s, m->remote_port,
+ m->remote_ip.s, m->remote_port + 1);
+ rtp_session_set_payload_type(m->rtps, m->pt->type);
+ rtp_session_enable_rtcp(m->rtps, FALSE);
+ // create caller filters : rtprecv1/rtpsend1/encoder1/decoder1
+ m->ms_rtprecv = ms_factory_create_filter(m->ms_factory, MS_RTP_RECV_ID);
+ m->ms_rtpsend = ms_factory_create_filter(m->ms_factory, MS_RTP_SEND_ID);
+
+ LM_INFO("codec[%s]\n", m->pt->mime_type);
+ m->ms_encoder = ms_factory_create_encoder(m->ms_factory, m->pt->mime_type);
+ if(!m->ms_encoder) {
+ LM_ERR("creating encoder failed.\n");
+ return 0;
+ }
+ m->ms_decoder = ms_factory_create_decoder(m->ms_factory, m->pt->mime_type);
+
+ /* set filter params */
+ ms_filter_call_method(m->ms_rtpsend, MS_RTP_SEND_SET_SESSION, m->rtps);
+ ms_filter_call_method(m->ms_rtprecv, MS_RTP_RECV_SET_SESSION, m->rtps);
+ return 1;
+}
+
+int rms_bridge(call_leg_media_t *m1, call_leg_media_t *m2)
+{
+ MSConnectionHelper h;
+ m1->ms_ticker = rms_create_ticker(NULL);
+
+ // direction 1
+ ms_connection_helper_start(&h);
+ ms_connection_helper_link(&h, m1->ms_rtprecv, -1, 0);
+ ms_connection_helper_link(&h, m2->ms_rtpsend, 0, -1);
+
+ // direction 2
+ ms_connection_helper_start(&h);
+ ms_connection_helper_link(&h, m2->ms_rtprecv, -1, 0);
+ ms_connection_helper_link(&h, m1->ms_rtpsend, 0, -1);
+
+ ms_ticker_attach_multiple(
+ m1->ms_ticker, m1->ms_rtprecv, m2->ms_rtprecv, NULL);
+
+ return 1;
+}
+
+static void rms_player_eof(
+ void *user_data, MSFilter *f, unsigned int event, void *event_data)
+{
+ if(event == MS_FILE_PLAYER_EOF) {
+ rms_session_info_t *si = (rms_session_info_t *)user_data;
+ si->action = RMS_DONE;
+ }
+ MS_UNUSED(f), MS_UNUSED(event_data);
+}
+
+int rms_stop_bridge(call_leg_media_t *m1, call_leg_media_t *m2)
+{
+ MSConnectionHelper h;
+ if(!m1->ms_ticker)
+ return -1;
+ if(m1->ms_rtpsend)
+ ms_ticker_detach(m1->ms_ticker, m1->ms_rtpsend);
+ if(m1->ms_rtprecv)
+ ms_ticker_detach(m1->ms_ticker, m1->ms_rtprecv);
+ if(m2->ms_rtpsend)
+ ms_ticker_detach(m1->ms_ticker, m2->ms_rtpsend);
+ if(m2->ms_rtprecv)
+ ms_ticker_detach(m1->ms_ticker, m2->ms_rtprecv);
+ rtp_stats_display(rtp_session_get_stats(m1->rtps),
+ " AUDIO BRIDGE offer RTP STATISTICS ");
+ rtp_stats_display(rtp_session_get_stats(m2->rtps),
+ " AUDIO BRIDGE answer RTP STATISTICS ");
+ ms_factory_log_statistics(m1->ms_factory);
+
+ ms_connection_helper_start(&h);
+ if(m1->ms_rtprecv)
+ ms_connection_helper_unlink(&h, m1->ms_rtprecv, -1, 0);
+ if(m2->ms_rtpsend)
+ ms_connection_helper_unlink(&h, m2->ms_rtpsend, 0, -1);
+
+ ms_connection_helper_start(&h);
+ if(m2->ms_rtprecv)
+ ms_connection_helper_unlink(&h, m2->ms_rtprecv, -1, 0);
+ if(m1->ms_rtpsend)
+ ms_connection_helper_unlink(&h, m1->ms_rtpsend, 0, -1);
+
+ if(m1->ms_rtpsend)
+ ms_filter_destroy(m1->ms_rtpsend);
+ if(m1->ms_rtprecv)
+ ms_filter_destroy(m1->ms_rtprecv);
+ if(m2->ms_rtpsend)
+ ms_filter_destroy(m2->ms_rtpsend);
+ if(m2->ms_rtprecv)
+ ms_filter_destroy(m2->ms_rtprecv);
+ return 1;
+}
+
+int rms_playfile(call_leg_media_t *m, char *file_name)
+{
+ int file_sample_rate = 8000;
+ if(!m->ms_player)
+ return 0;
+ ms_filter_add_notify_callback(m->ms_player, rms_player_eof, m->si, TRUE);
+ ms_filter_call_method(m->ms_player, MS_FILE_PLAYER_OPEN, (void *)file_name);
+ ms_filter_call_method(m->ms_player, MS_FILE_PLAYER_START, NULL);
+ ms_filter_call_method(m->ms_player, MS_FILTER_GET_SAMPLE_RATE, &file_sample_rate);
+ ms_filter_call_method(m->ms_resampler, MS_FILTER_SET_SAMPLE_RATE, &file_sample_rate);
+ ms_filter_call_method(m->ms_resampler, MS_FILTER_SET_OUTPUT_SAMPLE_RATE, &m->pt->clock_rate);
+ ms_filter_call_method(m->ms_resampler, MS_FILTER_SET_OUTPUT_NCHANNELS, &m->pt->channels);
+ LM_INFO("clock[%d][%d]\n", m->pt->clock_rate, file_sample_rate);
+ return 1;
+}
+
+int rms_start_media(call_leg_media_t *m, char *file_name)
+{
+ MSConnectionHelper h;
+ int channels = 1;
+ int file_sample_rate = 8000;
+ m->ms_ticker = rms_create_ticker(NULL);
+ if(!m->ms_ticker) goto error;
+ m->ms_player = ms_factory_create_filter(m->ms_factory, MS_FILE_PLAYER_ID);
+ if(!m->ms_player) goto error;
+ m->ms_resampler = ms_factory_create_filter(m->ms_factory, MS_RESAMPLE_ID);
+ if(!m->ms_resampler) goto error;
+ // m->ms_recorder = ms_factory_create_filter(m->ms_factory,
+ // MS_FILE_PLAYER_ID);
+ m->ms_voidsink = ms_factory_create_filter(m->ms_factory, MS_VOID_SINK_ID);
+ if(!m->ms_voidsink) goto error;
+ LM_INFO("m[%p]call-id[%p]\n", m, m->si->callid.s);
+
+ ms_filter_call_method(
+ m->ms_player, MS_FILTER_SET_OUTPUT_NCHANNELS, &channels);
+ ms_filter_call_method_noarg(m->ms_player, MS_FILE_PLAYER_START);
+ ms_filter_call_method(m->ms_player, MS_FILTER_GET_SAMPLE_RATE, &file_sample_rate);
+ ms_filter_call_method(m->ms_resampler, MS_FILTER_SET_SAMPLE_RATE, &file_sample_rate);
+ ms_filter_call_method(m->ms_resampler, MS_FILTER_SET_OUTPUT_SAMPLE_RATE, &m->pt->clock_rate);
+
+ // sending graph
+ ms_connection_helper_start(&h);
+ ms_connection_helper_link(&h, m->ms_player, -1, 0);
+
+ ms_connection_helper_link(&h, m->ms_resampler, 0, 0);
+ ms_connection_helper_link(&h, m->ms_encoder, 0, 0);
+ ms_connection_helper_link(&h, m->ms_rtpsend, 0, -1);
+
+ // receiving graph
+ ms_connection_helper_start(&h);
+ ms_connection_helper_link(&h, m->ms_rtprecv, -1, 0);
+ // ms_connection_helper_link(&h, m->ms_decoder, 0, 0);
+ ms_connection_helper_link(&h, m->ms_voidsink, 0, -1);
+
+ ms_ticker_attach_multiple(m->ms_ticker, m->ms_player, m->ms_rtprecv, NULL);
+ return 1;
+error:
+ LM_ERR(" can not start media!\n");
+ return 0;
+}
+
+int rms_stop_media(call_leg_media_t *m)
+{
+ MSConnectionHelper h;
+ if(!m->ms_ticker) {
+ LM_ERR("RMS STOP MEDIA\n");
+ return -1;
+ }
+ if(m->ms_player)
+ ms_ticker_detach(m->ms_ticker, m->ms_player);
+ if(m->ms_rtprecv)
+ ms_ticker_detach(m->ms_ticker, m->ms_rtprecv);
+
+ rtp_stats_display(
+ rtp_session_get_stats(m->rtps), " AUDIO SESSION'S RTP STATISTICS ");
+ ms_factory_log_statistics(m->ms_factory);
+
+ /*dismantle the sending graph*/
+ ms_connection_helper_start(&h);
+ if(m->ms_player)
+ ms_connection_helper_unlink(&h, m->ms_player, -1, 0);
+ if(m->ms_resampler)
+ ms_connection_helper_unlink(&h, m->ms_resampler, 0, 0);
+ if(m->ms_encoder)
+ ms_connection_helper_unlink(&h, m->ms_encoder, 0, 0);
+ if(m->ms_rtpsend)
+ ms_connection_helper_unlink(&h, m->ms_rtpsend, 0, -1);
+ /*dismantle the receiving graph*/
+ ms_connection_helper_start(&h);
+ if(m->ms_rtprecv)
+ ms_connection_helper_unlink(&h, m->ms_rtprecv, -1, 0);
+ if(m->ms_voidsink)
+ ms_connection_helper_unlink(&h, m->ms_voidsink, 0, -1);
+
+ if(m->ms_player)
+ ms_filter_destroy(m->ms_player);
+ if(m->ms_resampler)
+ ms_filter_destroy(m->ms_resampler);
+ if(m->ms_encoder)
+ ms_filter_destroy(m->ms_encoder);
+ if(m->ms_rtpsend) {
+ LM_ERR("detroy rtpsend\n");
+ ms_filter_destroy(m->ms_rtpsend);
+ } else {
+ LM_ERR("no rtpsend\n");
+ }
+ if(m->ms_rtprecv)
+ ms_filter_destroy(m->ms_rtprecv);
+ if(m->ms_voidsink)
+ ms_filter_destroy(m->ms_voidsink);
+
+ rms_media_destroy(m);
+ return 1;
+}
diff --git a/src/modules/rtp_media_server/rms_media.h b/src/modules/rtp_media_server/rms_media.h
new file mode 100644
index 00000000000..cc4499c0b80
--- /dev/null
+++ b/src/modules/rtp_media_server/rms_media.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton jchavanton@gmail.com
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef rms_media_h
+#define rms_media_h
+
+#include "../../core/mem/shm.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct rms_session_info;
+
+typedef struct call_leg_media
+{
+ MSFactory *ms_factory;
+ RtpSession *rtps;
+ PayloadType *pt;
+ MSTicker *ms_ticker;
+ MSFilter *ms_encoder;
+ MSFilter *ms_decoder;
+ MSFilter *ms_rtprecv;
+ MSFilter *ms_rtpsend;
+ MSFilter *ms_resampler;
+ MSFilter *ms_player;
+ MSFilter *ms_recorder;
+ MSFilter *ms_dtmfgen;
+ MSFilter *ms_tonedet;
+ MSFilter *ms_voidsource;
+ MSFilter *ms_voidsink;
+ str local_ip;
+ int local_port;
+ str remote_ip;
+ int remote_port;
+ struct rms_session_info *si;
+} call_leg_media_t;
+
+int create_call_leg_media(call_leg_media_t *m);
+
+int rms_media_init();
+void rms_media_destroy();
+
+MSFactory *rms_get_factory();
+
+int rms_stop_media(call_leg_media_t *m);
+int rms_playfile(call_leg_media_t *m, char *file_name);
+int rms_start_media(call_leg_media_t *m, char *file_name);
+int rms_bridge(call_leg_media_t *m1, call_leg_media_t *m2);
+int rms_stop_bridge(call_leg_media_t *m1, call_leg_media_t *m2);
+
+extern MSFilterDesc ms_pcap_file_player_desc;
+extern MSFilterDesc ms_rtp_send_desc;
+extern MSFilterDesc ms_rtp_recv_desc;
+extern MSFilterDesc ms_udp_send_desc;
+extern MSFilterDesc ms_alaw_dec_desc;
+extern MSFilterDesc ms_alaw_enc_desc;
+extern MSFilterDesc ms_ulaw_dec_desc;
+extern MSFilterDesc ms_ulaw_enc_desc;
+extern MSFilterDesc ms_dtmf_gen_desc;
+extern MSFilterDesc ms_volume_desc;
+extern MSFilterDesc ms_equalizer_desc;
+extern MSFilterDesc ms_channel_adapter_desc;
+extern MSFilterDesc ms_audio_mixer_desc;
+extern MSFilterDesc ms_tone_detector_desc;
+extern MSFilterDesc ms_genericplc_desc;
+extern MSFilterDesc ms_file_player_desc;
+extern MSFilterDesc ms_file_rec_desc;
+extern MSFilterDesc ms_vad_dtx_desc;
+extern MSFilterDesc ms_speex_dec_desc;
+extern MSFilterDesc ms_speex_enc_desc;
+extern MSFilterDesc ms_speex_ec_desc;
+extern MSFilterDesc ms_opus_enc_desc;
+extern MSFilterDesc ms_opus_dec_desc;
+extern MSFilterDesc ms_resample_desc;
+
+#endif
diff --git a/src/modules/rtp_media_server/rms_sdp.c b/src/modules/rtp_media_server/rms_sdp.c
new file mode 100644
index 00000000000..06bbf45aa22
--- /dev/null
+++ b/src/modules/rtp_media_server/rms_sdp.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton jchavanton@gmail.com
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "rms_sdp.h"
+#include "rms_util.h"
+#include "../../core/data_lump.h"
+#include "../../core/parser/parse_content.h"
+
+// https://tools.ietf.org/html/rfc4566
+// (protocol version)
+const char *sdp_v = "v=0\r\n";
+// (session name)
+const char *sdp_s = "s=-\r\n";
+// (time the session is active)
+const char *sdp_t = "t=0 0\r\n";
+//"a=rtpmap:101 telephone-event/8000\r\n"
+//"a=fmtp:101 0-15\r\n";
+//"a=rtpmap:0 PCMU/8000\r\n"
+//"a=rtpmap:8 PCMA/8000\r\n"
+//"a=rtpmap:96 opus/48000/2\r\n"
+//"a=fmtp:96 useinbandfec=1\r\n";
+
+static char *rms_sdp_get_rtpmap(str body, int type_number)
+{
+ char *pos = body.s;
+ while((pos = strstr(pos, "a=rtpmap:"))) {
+ int id;
+ int sampling_rate;
+ char codec[64];
+ sscanf(pos, "a=rtpmap:%d %s/%d", &id, codec, &sampling_rate);
+ if(id == type_number) {
+ LM_INFO("[%d][%s/%d]\n", id, codec, sampling_rate);
+ return rms_char_dup(codec, 1);
+ }
+ pos++;
+ }
+ return NULL;
+}
+
+void rms_sdp_info_init(rms_sdp_info_t *sdp_info)
+{
+ memset(sdp_info, 0, sizeof(rms_sdp_info_t));
+}
+
+void rms_sdp_info_free(rms_sdp_info_t *sdp_info)
+{
+ if(sdp_info->remote_ip.s) {
+ shm_free(sdp_info->remote_ip.s);
+ sdp_info->remote_ip.s = NULL;
+ }
+ if(sdp_info->payloads.s) {
+ shm_free(sdp_info->payloads.s);
+ sdp_info->payloads.s = NULL;
+ }
+ if(sdp_info->new_body.s) {
+ shm_free(sdp_info->new_body.s);
+ sdp_info->new_body.s = NULL;
+ }
+}
+
+int rms_sdp_prepare_new_body(rms_sdp_info_t *sdp_info, int payload_type_number)
+{
+ if(sdp_info->new_body.s)
+ return 0;
+
+ str *body = &sdp_info->new_body;
+ body->len = strlen(sdp_v) + strlen(sdp_s) + strlen(sdp_t);
+
+ // (originator and session identifier)
+ char sdp_o[128];
+ snprintf(
+ sdp_o, 128, "o=- 1028316687 1 IN IP4 %s\r\n", sdp_info->local_ip.s);
+ body->len += strlen(sdp_o);
+
+ // (connection information -- not required if included in all media)
+ char sdp_c[128];
+ snprintf(sdp_c, 128, "c=IN IP4 %s\r\n", sdp_info->local_ip.s);
+ body->len += strlen(sdp_c);
+
+ char sdp_m[128];
+ snprintf(sdp_m, 128, "m=audio %d RTP/AVP %d\r\n", sdp_info->udp_local_port,
+ payload_type_number);
+ body->len += strlen(sdp_m);
+
+ body->s = pkg_malloc(body->len + 1);
+ if (!body->s) return 0;
+ strcpy(body->s, sdp_v);
+ strcat(body->s, sdp_o);
+ strcat(body->s, sdp_s);
+ strcat(body->s, sdp_c);
+ strcat(body->s, sdp_t);
+ strcat(body->s, sdp_m);
+ return 1;
+}
+
+PayloadType *rms_sdp_check_payload(rms_sdp_info_t *sdp)
+{
+ // https://tools.ietf.org/html/rfc3551
+ LM_INFO("payloads[%s]\n", sdp->payloads.s); // 0 8
+ PayloadType *pt = payload_type_new();
+ char *payloads = sdp->payloads.s;
+ char *payload_type_number = strtok(payloads, " ");
+ if(!payload_type_number) {
+ payload_type_destroy(pt);
+ return NULL;
+ }
+ pt->type = atoi(payload_type_number);
+ pt->clock_rate = 8000;
+ pt->channels = 1;
+ pt->mime_type = NULL;
+ while(!pt->mime_type) {
+ if(pt->type > 127) {
+ return NULL;
+ // } else if (pt->type >= 96) {
+ // continue;
+ // char *rtpmap =
+ // rms_sdp_get_rtpmap(sdp->recv_body, pt->type);
+ // pt->mime_type = rms_char_dup(strtok(rtpmap,
+ //"/"),1);
+ // if (strcasecmp(pt->mime_type,"opus") == 0) {
+ // pt->clock_rate = atoi(strtok(NULL,
+ //"/"));
+ // pt->channels = atoi(strtok(NULL, "/"));
+ // shm_free(rtpmap);
+ // return pt;
+ // }
+ // shm_free(pt->mime_type);
+ // pt->mime_type=NULL;
+ // shm_free(rtpmap);
+ } else if(pt->type == 0) {
+ pt->mime_type = rms_char_dup("pcmu", 1); /* ia=rtpmap:0 PCMU/8000*/
+ } else if(pt->type == 8) {
+ pt->mime_type = rms_char_dup("pcma", 1);
+ // } else if (pt->type == 9) {
+ // pt->mime_type=rms_char_dup("g722", 1);
+ // } else if (pt->type == 18) {
+ // pt->mime_type=rms_char_dup("g729", 1);
+ }
+ if(pt->mime_type)
+ break;
+ payload_type_number = strtok(NULL, " ");
+ if(!payload_type_number) {
+ payload_type_destroy(pt);
+ return NULL;
+ }
+ pt->type = atoi(payload_type_number);
+ }
+ LM_INFO("payload_type:%d %s/%d/%d\n", pt->type, pt->mime_type,
+ pt->clock_rate, pt->channels);
+ return pt;
+}
+
+
+
+int rms_sdp_set_body(struct sip_msg *msg, str *new_body)
+{
+ struct lump *anchor;
+ char *buf;
+ int len;
+ char *value_s;
+ int value_len;
+ str body = {0, 0};
+ str content_type_sdp = str_init("application/sdp");
+
+ if(!new_body->s || new_body->len == 0) {
+ LM_ERR("invalid body parameter\n");
+ return -1;
+ }
+
+ body.len = 0;
+ body.s = get_body(msg);
+ if(body.s == 0) {
+ LM_ERR("malformed sip message\n");
+ return -1;
+ }
+
+ del_nonshm_lump(&(msg->body_lumps));
+ msg->body_lumps = NULL;
+
+ if(msg->content_length) {
+ body.len = get_content_length(msg);
+ if(body.len > 0) {
+ if(body.s + body.len > msg->buf + msg->len) {
+ LM_ERR("invalid content length: %d\n", body.len);
+ return -1;
+ }
+ if(del_lump(msg, body.s - msg->buf, body.len, 0) == 0) {
+ LM_ERR("cannot delete existing body");
+ return -1;
+ }
+ }
+ }
+
+ anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, 0);
+ if(!anchor) {
+ LM_ERR("failed to get anchor\n");
+ return -1;
+ }
+
+ if(msg->content_length == 0) {
+ /* need to add Content-Length */
+ len = new_body->len;
+ value_s = int2str(len, &value_len);
+ LM_DBG("content-length: %d (%s)\n", value_len, value_s);
+
+ len = CONTENT_LENGTH_LEN + value_len + CRLF_LEN;
+ buf = pkg_malloc(sizeof(char) * (len));
+ if(!buf) {
+ LM_ERR("out of pkg memory\n");
+ return -1;
+ }
+
+ memcpy(buf, CONTENT_LENGTH, CONTENT_LENGTH_LEN);
+ memcpy(buf + CONTENT_LENGTH_LEN, value_s, value_len);
+ memcpy(buf + CONTENT_LENGTH_LEN + value_len, CRLF, CRLF_LEN);
+ if(insert_new_lump_after(anchor, buf, len, 0) == 0) {
+ LM_ERR("failed to insert content-length lump\n");
+ pkg_free(buf);
+ return -1;
+ }
+ }
+
+ /* add content-type */
+ if(msg->content_type == NULL
+ || msg->content_type->body.len != content_type_sdp.len
+ || strncmp(msg->content_type->body.s, content_type_sdp.s,
+ content_type_sdp.len)
+ != 0) {
+ if(msg->content_type != NULL) {
+ if(del_lump(msg, msg->content_type->name.s - msg->buf,
+ msg->content_type->len, 0)
+ == 0) {
+ LM_ERR("failed to delete content type\n");
+ return -1;
+ }
+ }
+ value_len = content_type_sdp.len;
+ len = sizeof("Content-Type: ") - 1 + value_len + CRLF_LEN;
+ buf = pkg_malloc(sizeof(char) * (len));
+ if(!buf) {
+ LM_ERR("out of pkg memory\n");
+ return -1;
+ }
+ memcpy(buf, "Content-Type: ", sizeof("Content-Type: ") - 1);
+ memcpy(buf + sizeof("Content-Type: ") - 1, content_type_sdp.s,
+ value_len);
+ memcpy(buf + sizeof("Content-Type: ") - 1 + value_len, CRLF, CRLF_LEN);
+ if(insert_new_lump_after(anchor, buf, len, 0) == 0) {
+ LM_ERR("failed to insert content-type lump\n");
+ pkg_free(buf);
+ return -1;
+ }
+ }
+ anchor = anchor_lump(msg, body.s - msg->buf, 0, 0);
+
+ if(anchor == 0) {
+ LM_ERR("failed to get body anchor\n");
+ return -1;
+ }
+
+ buf = pkg_malloc(sizeof(char) * (new_body->len));
+ if(!buf) {
+ LM_ERR("out of pkg memory\n");
+ return -1;
+ }
+ memcpy(buf, new_body->s, new_body->len);
+ if(!insert_new_lump_after(anchor, buf, new_body->len, 0)) {
+ LM_ERR("failed to insert body lump\n");
+ pkg_free(buf);
+ return -1;
+ }
+ LM_DBG("new body: [%.*s]", new_body->len, new_body->s);
+ return 1;
+}
diff --git a/src/modules/rtp_media_server/rms_sdp.h b/src/modules/rtp_media_server/rms_sdp.h
new file mode 100644
index 00000000000..5088630d6af
--- /dev/null
+++ b/src/modules/rtp_media_server/rms_sdp.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton jchavanton@gmail.com
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef rms_sdp_h
+#define rms_sdp_h
+
+#include "../../core/sr_module.h"
+#include
+
+typedef struct rms_sdp_info
+{
+ str remote_ip;
+ str local_ip;
+ str payloads;
+ int remote_port;
+ int ipv6;
+ str new_body;
+ str recv_body;
+ int udp_local_port;
+} rms_sdp_info_t;
+
+int rms_sdp_set_body(struct sip_msg *msg, str *new_body);
+int rms_sdp_prepare_new_body(rms_sdp_info_t *, int payload_type_number);
+void rms_sdp_info_init(rms_sdp_info_t *sdp_info);
+void rms_sdp_info_free(rms_sdp_info_t *sdp_info);
+PayloadType *rms_sdp_check_payload(rms_sdp_info_t *);
+
+#endif
diff --git a/src/modules/rtp_media_server/rms_util.h b/src/modules/rtp_media_server/rms_util.h
new file mode 100644
index 00000000000..6bc82025c8c
--- /dev/null
+++ b/src/modules/rtp_media_server/rms_util.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton jchavanton@gmail.com
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef rms_util_h
+#define rms_util_h
+
+/**
+ * \brief Make a copy of a str structure using shm_malloc/pkg_malloc
+ * garanty to return a null terminated string, even if the source is not
+ * \param dst destination
+ * \param src source
+ * \param shared use shared memory
+ * \return 0 on success, -1 on failure
+ */
+static inline int rms_str_dup(str *dst, str *src, int shared)
+{
+ if(!dst) {
+ LM_ERR("dst null\n");
+ return -1;
+ }
+ dst->len = 0;
+ dst->s = NULL;
+ if(!src || !src->s || src->len < 0) {
+ LM_ERR("src null or invalid\n");
+ return 0;
+ }
+ if(src->len == 0)
+ return 1;
+ dst->len = src->len;
+ if(shared) {
+ dst->s = shm_malloc(dst->len+1);
+ } else {
+ dst->s = pkg_malloc(dst->len+1);
+ }
+ if(!dst->s) {
+ LM_ERR("%s_malloc: can't allocate memory (%d bytes)\n",
+ shared ? "shm" : "pkg", src->len);
+ return -1;
+ }
+ memcpy(dst->s, src->s, src->len);
+ dst->s[dst->len] = '\0';
+ return 1;
+}
+
+static inline char* rms_char_dup(char *s, int shared)
+{
+ str src;
+ str dst;
+ src.s = s;
+ src.len = strlen(s);
+ if(!rms_str_dup(&dst, &src, shared))
+ return NULL;
+ return dst.s;
+}
+
+#endif
diff --git a/src/modules/rtp_media_server/rtp_media_server.c b/src/modules/rtp_media_server/rtp_media_server.c
new file mode 100644
index 00000000000..538af1596c5
--- /dev/null
+++ b/src/modules/rtp_media_server/rtp_media_server.c
@@ -0,0 +1,716 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton jchavanton@gmail.com
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "rtp_media_server.h"
+#include "../../core/fmsg.h"
+
+MODULE_VERSION
+
+static int mod_init(void);
+static void mod_destroy(void);
+static int child_init(int);
+
+static rms_session_info_t *rms_session_list;
+str playback_fn = {0, 0};
+str log_fn = {0, 0};
+
+static rms_t rms;
+
+static int rms_session_free(rms_session_info_t *si);
+static rms_session_info_t *rms_session_search(char *callid, int len);
+static int fixup_rms_action_play(void **param, int param_no);
+static int rms_hangup_call(rms_session_info_t *si);
+
+static int rms_answer_f(struct sip_msg *);
+static int rms_action_play_f(struct sip_msg *, str *, str *);
+static int rms_sdp_offer_f(struct sip_msg *, char *, char *);
+static int rms_sdp_answer_f(struct sip_msg *, char *, char *);
+static int rms_media_stop_f(struct sip_msg *, char *, char *);
+static int rms_hangup_f(struct sip_msg *);
+static int rms_sessions_dump_f(struct sip_msg *, char *, char *);
+
+static cmd_export_t cmds[] = {
+ {"rms_answer", (cmd_function)rms_answer_f, 0, 0, 0, ANY_ROUTE},
+ {"rms_play", (cmd_function)rms_action_play_f, 2, fixup_rms_action_play, 0,
+ ANY_ROUTE},
+ {"rms_sdp_offer", (cmd_function)rms_sdp_offer_f, 0, 0, 0, ANY_ROUTE},
+ {"rms_sdp_answer", (cmd_function)rms_sdp_answer_f, 0, 0, 0, ANY_ROUTE},
+ {"rms_media_stop", (cmd_function)rms_media_stop_f, 0, 0, 0, ANY_ROUTE},
+ {"rms_hangup", (cmd_function)rms_hangup_f, 0, 0, 0, ANY_ROUTE},
+ {"rms_sessions_dump", (cmd_function)rms_sessions_dump_f, 0, 0, 0,
+ ANY_ROUTE},
+ {0, 0, 0, 0, 0, 0}};
+
+static param_export_t mod_params[] = {
+ {"log_file_name", PARAM_STR, &log_fn}, {0, 0, 0}};
+
+struct module_exports exports = {
+ "rtp_media_server", DEFAULT_DLFLAGS, /* dlopen flags */
+ cmds, mod_params, 0, /* RPC export */
+ 0, 0, mod_init, child_init, mod_destroy,
+};
+
+static void run_action_route(rms_session_info_t *si, char *route)
+{
+ int rt, backup_rt;
+ struct run_act_ctx ctx;
+ sip_msg_t *fmsg;
+
+ if(route == NULL) {
+ LM_ERR("bad route\n");
+ return;
+ }
+ rt = -1;
+ rt = route_lookup(&event_rt, route);
+ if(rt < 0 || event_rt.rlist[rt] == NULL) {
+ LM_DBG("route does not exist");
+ return;
+ }
+ if(faked_msg_init() < 0) {
+ LM_ERR("faked_msg_init() failed\n");
+ return;
+ }
+ fmsg = faked_msg_next();
+ struct hdr_field callid;
+ callid.body.s = si->callid.s;
+ callid.body.len = si->callid.len;
+
+ fmsg->callid = &callid;
+
+ backup_rt = get_route_type();
+ set_route_type(EVENT_ROUTE);
+ init_run_actions_ctx(&ctx);
+ if(rt >= 0) {
+ run_top_route(event_rt.rlist[rt], fmsg, 0);
+ }
+ set_route_type(backup_rt);
+}
+
+static int fixup_rms_action_play(void **param, int param_no)
+{
+ if(param_no == 1)
+ return fixup_spve_null(param, 1);
+ if(param_no == 2)
+ return fixup_spve_null(param, 1);
+ LM_ERR("invalid parameter count [%d]\n", param_no);
+ return -1;
+}
+
+/**
+ * @return 0 to continue to load the OpenSER, -1 to stop the loading
+ * and abort OpenSER.
+ */
+static int mod_init(void)
+{
+ LM_INFO("RTP media server module init\n");
+ rms.udp_start_port = 50000;
+ rms.udp_end_port = 60000;
+ rms.udp_last_port = 50000;
+ rms_media_init();
+ rms_session_list = shm_malloc(sizeof(rms_session_info_t));
+ clist_init(rms_session_list, next, prev);
+
+ register_procs(1);
+ if(load_tm_api(&tmb) != 0) {
+ LM_ERR("can't load TM API\n");
+ return -1;
+ }
+ FILE *log_file = fopen(log_fn.s, "w+");
+ if(log_file) {
+ LM_INFO("ortp logs are redirected [%s]\n", log_fn.s);
+ } else {
+ log_file = stdout;
+ LM_INFO("ortp can not open logs file [%s]\n", log_fn.s);
+ }
+ ortp_set_log_file(log_file);
+ ortp_set_log_level_mask(
+ NULL, ORTP_MESSAGE | ORTP_WARNING | ORTP_ERROR | ORTP_FATAL);
+ return (0);
+}
+
+/**
+ * Called only once when OpenSER is shuting down to clean up module
+ * resources.
+ */
+static void mod_destroy()
+{
+ rms_media_destroy();
+ LM_INFO("RTP media server module destroy\n");
+ return;
+}
+
+void rms_signal_handler(int signum)
+{
+ LM_INFO("signal received [%d]\n", signum);
+}
+
+/**
+ * Most interaction with the session and media streams that are controlled
+ * in this function this is safer in the event where a library is using non shared memory
+ * all the mediastreamer2 ticker threads are spawned from here.
+ */
+static void rms_session_manage_loop()
+{
+ while(1) {
+ lock(&session_list_mutex);
+ rms_session_info_t *si;
+ clist_foreach(rms_session_list, si, next)
+ {
+ if(si->action == RMS_HANGUP) {
+ LM_INFO("session action RMS_HANGUP [%s]\n", si->callid.s);
+ rms_hangup_call(si);
+ si->action = RMS_STOP;
+ } else if(si->action == RMS_STOP) {
+ LM_INFO("session action RMS_STOP [%s]\n", si->callid.s);
+ rms_stop_media(&si->caller_media);
+ rms_session_info_t *tmp = si->prev;
+ clist_rm(si, next, prev);
+ rms_session_free(si);
+ si = tmp;
+ } else if(si->action == RMS_PLAY) {
+ LM_INFO("session action RMS_PLAY [%s]\n", si->callid.s);
+ rms_playfile(&si->caller_media, si->action_param.s);
+ si->action = RMS_NONE;
+ } else if(si->action == RMS_DONE) {
+ LM_INFO("session action RMS_DONE [%s][%s]\n", si->callid.s,
+ si->action_route.s);
+ if(si->action_route.s) {
+ run_action_route(si, si->action_route.s);
+ } else {
+ si->action = RMS_HANGUP;
+ }
+ } else if(si->action == RMS_START) {
+ create_call_leg_media(&si->caller_media);
+ LM_INFO("session action RMS_START [%s]\n", si->callid.s);
+ rms_start_media(&si->caller_media, si->action_param.s);
+ si->action = RMS_NONE;
+ run_action_route(si, "rms:start");
+ }
+ }
+ unlock(&session_list_mutex);
+ usleep(2000);
+ }
+}
+
+/**
+ * The rank will be o for the main process calling this function,
+ * or 1 through n for each listener process. The rank can have a negative
+ * value if it is a special process calling the child init function.
+ * Other then the listeners, the rank will equal one of these values:
+ * PROC_MAIN 0 Main ser process
+ * PROC_TIMER -1 Timer attendant process
+ * PROC_FIFO -2 FIFO attendant process
+ * PROC_TCP_MAIN -4 TCP main process
+ * PROC_UNIXSOCK -5 Unix domain socket server processes
+ *
+ * If this function returns a nonzero value the loading of OpenSER will
+ * stop.
+ */
+static int child_init(int rank)
+{
+ if(rank == PROC_MAIN) {
+ int pid;
+ pid = fork_process(PROC_XWORKER, "RTP_media_server", 1);
+ if(pid < 0)
+ return -1;
+ if(pid == 0) {
+ rms_session_manage_loop();
+ return 0;
+ }
+ }
+ int rtn = 0;
+ return (rtn);
+}
+
+static int rms_get_sdp_info(rms_sdp_info_t *sdp_info, struct sip_msg *msg)
+{
+ sdp_session_cell_t *sdp_session;
+ sdp_stream_cell_t *sdp_stream;
+ str media_ip, media_port;
+ int sdp_session_num = 0;
+ int sdp_stream_num = get_sdp_stream_num(msg);
+ if(parse_sdp(msg) < 0) {
+ LM_INFO("can not parse sdp\n");
+ return 0;
+ }
+ sdp_info_t *sdp = (sdp_info_t *)msg->body;
+ if(!sdp) {
+ LM_INFO("sdp null\n");
+ return 0;
+ }
+ rms_str_dup(&sdp_info->recv_body, &sdp->text, 1);
+ if(!sdp_info->recv_body.s)
+ goto error;
+ LM_INFO("sdp body - type[%d]\n", sdp->type);
+ if(sdp_stream_num > 1 || !sdp_stream_num) {
+ LM_INFO("only support one stream[%d]\n", sdp_stream_num);
+ }
+ sdp_stream_num = 0;
+ sdp_session = get_sdp_session(msg, sdp_session_num);
+ if(!sdp_session) {
+ return 0;
+ } else {
+ int sdp_stream_num = 0;
+ sdp_stream = get_sdp_stream(msg, sdp_session_num, sdp_stream_num);
+ if(!sdp_stream) {
+ LM_INFO("can not get the sdp stream\n");
+ return 0;
+ } else {
+ rms_str_dup(&sdp_info->payloads, &sdp_stream->payloads, 1);
+ if(!sdp_info->payloads.s)
+ goto error;
+ }
+ }
+ if(sdp_stream->ip_addr.s && sdp_stream->ip_addr.len > 0) {
+ media_ip = sdp_stream->ip_addr;
+ } else {
+ media_ip = sdp_session->ip_addr;
+ }
+ rms_str_dup(&sdp_info->remote_ip, &media_ip, 1);
+ if(!sdp_info->remote_ip.s)
+ goto error;
+ rms_str_dup(&media_port, &sdp_stream->port, 0);
+ if(!media_port.s)
+ goto error;
+ sdp_info->remote_port = atoi(media_port.s);
+ pkg_free(media_port.s);
+ return 1;
+error:
+ rms_sdp_info_free(sdp_info);
+ return 0;
+}
+
+static int rms_relay_call(struct sip_msg *msg)
+{
+ if(!tmb.t_relay(msg, NULL, NULL)) {
+ LM_INFO("t_ralay error\n");
+ return -1;
+ }
+ return 1;
+}
+
+static int parse_from(struct sip_msg *msg, rms_session_info_t *si)
+{
+ struct to_body *from = get_from(msg);
+ LM_INFO("from[%.*s]tag[%.*s]\n", from->uri.len, from->uri.s,
+ from->tag_value.len, from->tag_value.s);
+ rms_str_dup(&si->remote_tag, &from->tag_value, 1);
+ return 1;
+}
+
+static int rms_answer_call(struct sip_msg *msg, rms_session_info_t *si)
+{
+ char buffer[128];
+ str to_tag;
+ str reason = str_init("OK");
+ str contact_hdr;
+
+ rms_sdp_info_t *sdp_info = &si->sdp_info_offer;
+
+ if(msg->REQ_METHOD != METHOD_INVITE) {
+ LM_ERR("only invite is supported for offer \n");
+ return 0;
+ }
+
+ parse_from(msg, si);
+
+ if(si->remote_tag.len == 0) {
+ LM_ERR("can not find from tag\n");
+ return 0;
+ }
+
+ sdp_info->local_ip.s = si->local_ip.s;
+ sdp_info->local_ip.len = si->local_ip.len;
+ if(!rms_sdp_prepare_new_body(sdp_info, si->caller_media.pt->type)) {
+ LM_ERR("error preparing SDP body\n");
+ return 0;
+ }
+
+ tmb.t_get_reply_totag(msg, &to_tag);
+ rms_str_dup(&si->local_tag, &to_tag, 1);
+ LM_INFO("local_uri[%s]local_tag[%s]\n", si->local_uri.s, si->local_tag.s);
+
+ snprintf(buffer, 128,
+ "Contact: \r\nContent-Type: application/sdp\r\n",
+ si->local_ip.s, msg->rcv.dst_port);
+ contact_hdr.len = strlen(buffer);
+ contact_hdr.s = buffer;
+
+ if(!tmb.t_reply_with_body(tmb.t_gett(), 200, &reason, &sdp_info->new_body,
+ &contact_hdr, &si->local_tag)) {
+ LM_ERR("t_reply error");
+ return 0;
+ }
+ LM_DBG("answered\n");
+ return 1;
+}
+
+static rms_session_info_t *rms_session_search(char *callid, int len)
+{
+ // lock(&session_list_mutex);
+ rms_session_info_t *si;
+ clist_foreach(rms_session_list, si, next)
+ {
+ if(strncmp(callid, si->callid.s, len) == 0) {
+ unlock(&session_list_mutex);
+ return si;
+ }
+ }
+ // unlock(&session_list_mutex);
+ return NULL;
+}
+
+static int rms_hangup_call(rms_session_info_t *si)
+{
+ uac_req_t uac_r;
+ int result;
+ str headers = str_init("Max-Forwards: 70" CRLF);
+ str method_bye = str_init("BYE");
+
+ LM_INFO("rms_hangup_call[%.*s]remote_uri[%s]local_uri[%s]\n",
+ si->callid.len, si->callid.s, si->remote_uri.s, si->local_uri.s);
+ LM_INFO("contact[%.*s]\n", si->contact_uri.len, si->contact_uri.s);
+ dlg_t *dialog = NULL;
+ if(tmb.new_dlg_uac(&si->callid, &si->local_tag, si->cseq, &si->local_uri,
+ &si->remote_uri, &dialog)
+ < 0) {
+ LM_ERR("error in tmb.new_dlg_uac\n");
+ return -1;
+ }
+ dialog->id.rem_tag.s = si->remote_tag.s;
+ dialog->id.rem_tag.len = si->remote_tag.len;
+ dialog->rem_target.s = si->contact_uri.s;
+ dialog->rem_target.len = si->contact_uri.len;
+ set_uac_req(&uac_r, &method_bye, &headers, NULL, dialog,
+ TMCB_LOCAL_COMPLETED, NULL, NULL);
+ result = tmb.t_request_within(&uac_r);
+ if(result < 0) {
+ LM_ERR("error in tmb.t_request\n");
+ return -1;
+ } else {
+ LM_ERR("tmb.t_request_within ok\n");
+ }
+ return 1;
+}
+
+static int rms_check_msg(struct sip_msg *msg)
+{
+ if(!msg || !msg->callid || !msg->callid->body.s) {
+ LM_INFO("no callid ?\n");
+ return -1;
+ }
+ if(rms_session_search(msg->callid->body.s, msg->callid->body.len))
+ return -1;
+ return 1;
+}
+
+static int rms_session_free(rms_session_info_t *si)
+{
+ rms_sdp_info_free(&si->sdp_info_offer);
+ rms_sdp_info_free(&si->sdp_info_answer);
+ if(si->caller_media.pt) {
+ payload_type_destroy(si->caller_media.pt);
+ si->caller_media.pt = NULL;
+ }
+ if(si->callee_media.pt) {
+ payload_type_destroy(si->callee_media.pt);
+ si->callee_media.pt = NULL;
+ }
+ if(si->callid.s) {
+ shm_free(si->callid.s);
+ si->callid.s = NULL;
+ }
+ if(si->contact_uri.s) {
+ shm_free(si->contact_uri.s);
+ si->contact_uri.s = NULL;
+ }
+ if(si->local_ip.s) {
+ shm_free(si->local_ip.s);
+ si->local_ip.s = NULL;
+ }
+ if(si->remote_uri.s) {
+ shm_free(si->remote_uri.s);
+ si->remote_uri.s = NULL;
+ }
+ if (si->local_uri.s) {
+ shm_free(si->local_uri.s);
+ si->local_uri.s = NULL;
+ }
+ shm_free(si);
+ si = NULL;
+ return 1;
+}
+
+rms_session_info_t *rms_session_new(struct sip_msg *msg)
+{
+ struct hdr_field *hdr = NULL;
+
+ if(!rms_check_msg(msg))
+ return NULL;
+ rms_session_info_t *si = shm_malloc(sizeof(rms_session_info_t));
+ if(!si) {
+ LM_ERR("can not allocate session info !\n");
+ goto error;
+ }
+ memset(si, 0, sizeof(rms_session_info_t));
+
+ if(!rms_str_dup(&si->callid, &msg->callid->body, 1)) {
+ LM_ERR("can not get callid .\n");
+ goto error;
+ }
+ if(!rms_str_dup(&si->remote_uri, &msg->from->body, 1))
+ goto error;
+ if(!rms_str_dup(&si->local_uri, &msg->to->body, 1))
+ goto error;
+ str ip;
+ ip.s = ip_addr2a(&msg->rcv.dst_ip);
+ ip.len = strlen(ip.s);
+ if(!rms_str_dup(&si->local_ip, &ip, 1))
+ goto error;
+ hdr = msg->contact;
+ if(parse_contact(hdr) < 0)
+ goto error;
+ contact_body_t *contact = hdr->parsed;
+ if(!rms_str_dup(&si->contact_uri, &contact->contacts->uri, 1))
+ goto error;
+ LM_INFO(
+ "[contact offer] [%.*s]\n", si->contact_uri.len, si->contact_uri.s);
+ si->cseq = atoi(msg->cseq->body.s);
+
+ rms_sdp_info_t *sdp_info = &si->sdp_info_offer;
+ if(!rms_get_sdp_info(sdp_info, msg))
+ goto error;
+ si->caller_media.pt = rms_sdp_check_payload(sdp_info);
+ if(!si->caller_media.pt) {
+ tmb.t_reply(msg, 488, "incompatible media format");
+ goto error;
+ }
+
+ return si;
+error:
+ rms_session_free(si);
+ return NULL;
+}
+
+
+static int rms_get_udp_port(void)
+{
+ // RTP UDP port
+ LM_INFO("last port[%d]\n", rms.udp_last_port);
+ rms.udp_last_port += 2;
+ if(rms.udp_last_port > rms.udp_end_port)
+ rms.udp_last_port = rms.udp_start_port;
+ LM_INFO("last port[%d]\n", rms.udp_last_port);
+ return rms.udp_last_port;
+}
+
+static int rms_create_call_leg(struct sip_msg *msg, rms_session_info_t *si,
+ call_leg_media_t *m, rms_sdp_info_t *sdp_info)
+{
+ m->local_port = rms_get_udp_port();
+ sdp_info->udp_local_port = m->local_port;
+ m->local_ip.s = si->local_ip.s;
+ m->local_ip.len = si->local_ip.len;
+ m->remote_port = sdp_info->remote_port;
+ m->remote_ip.s = sdp_info->remote_ip.s;
+ m->remote_ip.len = sdp_info->remote_ip.len;
+ m->si = si;
+
+ LM_DBG("remote_socket[%s:%d] local_socket[%s:%d] pt[%s]\n",
+ sdp_info->remote_ip.s, sdp_info->remote_port, m->local_ip.s,
+ m->local_port, si->caller_media.pt->mime_type);
+ return 1;
+}
+
+static int rms_create_trans(struct sip_msg *msg) {
+ int status = tmb.t_newtran(msg);
+ LM_INFO("invite new transaction[%d]\n", status);
+ if(status < 0) {
+ LM_ERR("error creating transaction \n");
+ return -1;
+ } else if(status == 0) {
+ LM_DBG("retransmission");
+ return 0;
+ }
+ return 1;
+}
+
+static void rms_session_add(rms_session_info_t *si) {
+ lock(&session_list_mutex);
+ clist_append(rms_session_list, si, next, prev);
+ unlock(&session_list_mutex);
+}
+
+static void rms_session_rm(rms_session_info_t *si) {
+ lock(&session_list_mutex);
+ clist_rm(si, next, prev);
+ unlock(&session_list_mutex);
+}
+
+static int rms_sdp_offer_f(struct sip_msg *msg, char *param1, char *param2)
+{
+ int status = rms_create_trans(msg);
+ if(status<1) return status;
+
+ rms_session_info_t *si = rms_session_new(msg);
+ if(!si)
+ return -1;
+ rms_session_add(si);
+ rms_sdp_info_t *sdp_info = &si->sdp_info_offer;
+ if(!rms_create_call_leg(msg, si, &si->caller_media, sdp_info))
+ goto error;
+ rms_sdp_prepare_new_body(sdp_info, si->caller_media.pt->type);
+ rms_sdp_set_body(msg, &sdp_info->new_body);
+ if(!rms_relay_call(msg))
+ goto error;
+ return 1;
+error:
+ rms_session_rm(si);
+ rms_session_free(si);
+ return -1;
+}
+
+static int rms_sdp_answer_f(struct sip_msg *msg, char *param1, char *param2)
+{
+ rms_session_info_t *si;
+
+ if(!msg || !msg->callid || !msg->callid->body.s) {
+ LM_INFO("no callid ?\n");
+ return -1;
+ }
+ si = rms_session_search(msg->callid->body.s, msg->callid->body.len);
+ if(!si) {
+ LM_INFO("session not found ci[%.*s]\n", msg->callid->body.len,
+ msg->callid->body.s);
+ return 1;
+ }
+ LM_INFO("session found [%s] bridging\n", si->callid.s);
+ rms_sdp_info_t *sdp_info = &si->sdp_info_answer;
+ if(!rms_get_sdp_info(sdp_info, msg)) {
+ LM_ERR("can not get SDP information\n");
+ return -1;
+ }
+ si->callee_media.pt = rms_sdp_check_payload(sdp_info);
+ if(!rms_create_call_leg(msg, si, &si->callee_media, sdp_info)) {
+ rms_session_rm(si);
+ rms_session_free(si);
+ return -1;
+ }
+ rms_sdp_prepare_new_body(sdp_info, si->callee_media.pt->type);
+ rms_sdp_set_body(msg, &sdp_info->new_body);
+ rms_bridge(&si->caller_media, &si->callee_media);
+ return 1;
+}
+
+static int rms_sessions_dump_f(struct sip_msg *msg, char *param1, char *param2)
+{
+ int x = 1;
+ rms_session_info_t *si;
+ clist_foreach(rms_session_list, si, next)
+ {
+ LM_INFO("[%d]callid[%s]remote_uri[%s]local_uri[%s]cseq[%d]\n", x,
+ si->callid.s, si->remote_uri.s, si->local_uri.s, si->cseq);
+ x++;
+ }
+ return 1;
+}
+
+static int rms_media_stop_f(struct sip_msg *msg, char *param1, char *param2)
+{
+ int status = rms_create_trans(msg);
+ if(status<1) return status;
+
+ rms_session_info_t *si;
+ if(!msg || !msg->callid || !msg->callid->body.s) {
+ LM_ERR("no callid\n");
+ return -1;
+ }
+ si = rms_session_search(msg->callid->body.s, msg->callid->body.len);
+ if(!si) {
+ LM_INFO("session not found ci[%.*s]\n", msg->callid->body.len,
+ msg->callid->body.s);
+ if(!tmb.t_reply(msg, 481, "Call/Transaction Does Not Exist")) {
+ return -1;
+ }
+ return 0;
+ }
+ si->action = RMS_STOP;
+ if(!tmb.t_reply(msg, 200, "OK")) {
+ return -1;
+ }
+ return 0;
+}
+
+static int rms_action_play_f(struct sip_msg *msg, str *playback_fn, str *route)
+{
+ rms_session_info_t *si =
+ rms_session_search(msg->callid->body.s, msg->callid->body.len);
+ if(!si)
+ return -1;
+ LM_INFO("RTP session [%s:%d]<>[%s:%d]\n", si->caller_media.local_ip.s,
+ si->caller_media.local_port, si->caller_media.remote_ip.s,
+ si->caller_media.remote_port);
+ si->action = RMS_PLAY;
+ si->action_param.len = playback_fn->len;
+ si->action_param.s = playback_fn->s;
+ si->action_route.len = route->len;
+ si->action_route.s = route->s;
+ return 0;
+}
+
+static int rms_hangup_f(struct sip_msg *msg)
+{
+ rms_session_info_t *si =
+ rms_session_search(msg->callid->body.s, msg->callid->body.len);
+ if(!si)
+ return -1;
+ si->action = RMS_HANGUP;
+ return 0;
+}
+
+
+
+static int rms_answer_f(struct sip_msg *msg)
+{
+ int status = rms_create_trans(msg);
+ if(status<1) return status;
+
+ if(rms_session_search(msg->callid->body.s, msg->callid->body.len))
+ return -1;
+ rms_session_info_t *si = rms_session_new(msg);
+ if(!si)
+ return -1;
+ rms_session_add(si);
+ rms_sdp_info_t *sdp_info = &si->sdp_info_offer;
+ if(rms_create_call_leg(msg, si, &si->caller_media, sdp_info) < 1)
+ goto error;
+ if(rms_answer_call(msg, si) < 1) {
+ goto error;
+ }
+ LM_INFO("RTP session [%s:%d]<>[%s:%d]\n", si->caller_media.local_ip.s,
+ si->caller_media.local_port, si->caller_media.remote_ip.s,
+ si->caller_media.remote_port);
+ si->action = RMS_START;
+ return 1;
+error:
+ rms_session_rm(si);
+ rms_session_free(si);
+ return -1;
+}
diff --git a/src/modules/rtp_media_server/rtp_media_server.h b/src/modules/rtp_media_server/rtp_media_server.h
new file mode 100644
index 00000000000..fe8391223e0
--- /dev/null
+++ b/src/modules/rtp_media_server/rtp_media_server.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton jchavanton@gmail.com
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef rms_h
+#define rms_h
+
+#include "../../core/data_lump.h"
+#include "../../core/sr_module.h"
+#include "../../core/mod_fix.h"
+#include "../../core/parser/sdp/sdp_helpr_funcs.h"
+#include "../../core/parser/parse_from.h"
+#include "../../core/parser/parse_content.h"
+#include "../../core/data_lump_rpl.h"
+#include "../../core/clist.h"
+#include "../../core/parser/contact/parse_contact.h"
+
+#include "../tm/tm_load.h"
+#include "../sdpops/api.h"
+
+#include "rms_util.h"
+#include "rms_sdp.h"
+#include "rms_media.h"
+
+
+ser_lock_t session_list_mutex;
+
+
+typedef struct rms
+{
+ int udp_start_port;
+ int udp_end_port;
+ int udp_last_port;
+ char *local_ip;
+} rms_t;
+
+struct tm_binds tmb;
+
+typedef struct ms_res
+{
+ AudioStream *audio_stream;
+ RtpProfile *rtp_profile;
+} ms_res_t;
+
+typedef enum rms_action {
+ RMS_NONE,
+ RMS_START,
+ RMS_STOP,
+ RMS_HANGUP,
+ RMS_PLAY,
+ RMS_DONE,
+} rms_action_t;
+
+typedef struct rms_session_info
+{
+ struct rms_session_info *next;
+ struct rms_session_info *prev;
+ rms_sdp_info_t sdp_info_offer;
+ rms_sdp_info_t sdp_info_answer;
+ str callid;
+ str local_ip;
+ str local_uri;
+ str local_tag;
+ str remote_uri;
+ str remote_tag;
+ str contact_uri;
+ int cseq;
+ ms_res_t ms;
+ call_leg_media_t caller_media;
+ call_leg_media_t callee_media;
+ rms_action_t action;
+ str action_param;
+ str action_route;
+} rms_session_info_t;
+
+#endif
diff --git a/src/modules/rtp_media_server/voice_file/Bach_10s_8000.wav b/src/modules/rtp_media_server/voice_file/Bach_10s_8000.wav
new file mode 100644
index 00000000000..f4bbac85f51
Binary files /dev/null and b/src/modules/rtp_media_server/voice_file/Bach_10s_8000.wav differ