Jelajahi Sumber

Merge pull request #1701 from jchavanton/rtp_media_server

rtp_media_server: adding module
Julien Chavanton 6 tahun lalu
induk
melakukan
32c363e105

+ 5 - 1
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)
 
 
 

+ 13 - 0
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

+ 56 - 0
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_files/OSR_us_000_0010_8k.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;
+}
+

+ 4 - 0
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

+ 42 - 0
src/modules/rtp_media_server/doc/rtp_media_server.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+	<bookinfo>
+	<title>rtp_media_server Module</title>
+	<productname class="trade">&kamailio;</productname>
+	<authorgroup>
+        <author>
+        <firstname>Julien</firstname>
+        <surname>Chavanton</surname>
+        <email>[email protected]</email>
+        </author>
+		<author>
+		<firstname>Julien</firstname>
+		<surname>Chavanton</surname>
+		<affiliation><orgname>flowroute.com</orgname></affiliation>
+		<email>[email protected]</email>
+		</author>
+		<editor>
+		<firstname>Julien</firstname>
+		<surname>Chavanton</surname>
+		<affiliation><orgname>flowroute.com</orgname></affiliation>
+		<email>[email protected]</email>
+		</editor>
+	</authorgroup>
+	<copyright>
+		<year>2017-2018</year>
+		<holder>Flowroute.com</holder>
+	</copyright>
+	</bookinfo>
+	<toc></toc>
+
+	<xi:include href="rtp_media_server_admin.xml"/>
+</book>

+ 205 - 0
src/modules/rtp_media_server/doc/rtp_media_server_admin.xml

@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<chapter>
+	<title>&adminguide;</title>
+
+<section>
+	<title>Overview</title>
+	<para>
+		rtp_media_server module is adding RTP and media processing functionalities to Kamailio
+	</para>
+	<para>
+		Kamailio is providing SIP signaling including and enpoint with Dialog state, SDP parsing and scripting language
+	</para><para>
+		oRTP: is providing Real-time Transport Protocol (RFC 3550)
+	</para><para>
+		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.
+	</para><para>
+		mediastreamer2 is also providing a framework to create custom mediaprocessing modules.
+	</para>
+</section>
+
+<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The module depends on the following modules (in the other words
+		the listed modules must be loaded before this module):
+		<itemizedlist>
+			<listitem>
+			<para><emphasis>tm</emphasis> - accounting module</para>
+			</listitem>
+		</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed
+		before running &kamailio; with this module loaded:
+		</para><para>
+		If you want to build oRTP and mediastreamer from source, you can use the provided script for Debian "install_bc.sh".
+		</para>
+		<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>oRTP</emphasis> git://git.linphone.org/ortp.git
+			</para>
+			<para>
+				oRTP is a library implemeting Real-time Transport Protocol (RFC 3550), distributed under GNU GPLv2 or proprietary license.
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+				<emphasis>mediastreamer2</emphasis> git clone git://git.linphone.org/mediastreamer2.git
+			</para>
+			<para>
+				Mediastreamer2 is a powerful and lightweight streaming engine specialized for voice/video telephony applications.
+			</para>
+			</listitem>
+		</itemizedlist>
+	</section>
+</section>
+
+<section>
+	<title>Parameters</title>
+	<section>
+	<title><varname>log_file_name</varname> (string)</title>
+		<para>
+		oRTP and MediaStreamer2 log file settings
+		the log mask is not configurable :
+		MESSAGE | WARNING | ERROR | FATAL
+		levels are activated.
+		</para>
+		<para>
+		Default value is not-set (no logging to file).
+		</para>
+		<example>
+		<title>log_file_name example</title>
+		<programlisting format="linespecific">
+...
+modparam("rtp_media_server", "log_file_name", "/var/log/rms/rms_ortp.log")
+...
+		</programlisting></example>
+	</section>
+</section>
+
+<section>
+	<title>Functions</title>
+
+	<section id="rtp_media_server.f.rms_answer">
+		<title><varname>rms_answer</varname> ()</title>
+		<para>
+		Create a session and a call leg and call the event_route[rms:start]
+		config example
+		</para>
+		<para>
+		This function can be used from REQUEST_ROUTE, REPLY_ROUTE and FAILURE_ROUTE.
+		</para>
+		<example>
+		<title>usage example</title>
+		<programlisting format="linespecific">
+...
+event_route[rms:start] {
+	xnotice("[rms:start] play ...\n");
+	rms_play("/tmp/reference_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();
+	if (is_method("INVITE") &amp;&amp; !has_totag()) {
+		if (!rms_answer()) {
+			t_reply("503", "server error");
+		}
+	}
+
+	if (is_method("BYE")){
+		xnotice("BYE RECEIVED [$ci]\n");
+		rms_media_stop();
+	}
+...
+		</programlisting></example>
+	</section>
+
+	<section id="rtp_media_server.f.rms_hangup">
+		<title><varname>rms_hangup</varname> ()</title>
+		<para>
+		Send a BYE, delete the RTP session and the media ressources.
+		</para>
+		<para>
+		This function can be used from EVENT_ROUTE.
+		</para>
+		<example>
+		<title>usage example</title>
+		<programlisting format="linespecific">
+...
+	rms_hangup();
+...
+		</programlisting></example>
+	</section>
+
+	<section id="rtp_media_server.f.rms_media_stop">
+		<title><varname>rms_media_stop</varname> ()</title>
+		<para>
+		This should be called on reception of a BYE, this will
+		delete the RTP session and the media ressources.
+		and reply "200 OK".
+		</para>
+		<para>
+		If the SIP session is not found "481 Call/Transaction Does Not Exist"
+		is returned.
+		</para>
+		<para>
+		This function can be used from REQUEST_ROUTE, REPLY_ROUTE and FAILURE_ROUTE.
+		</para>
+		<example>
+		<title>usage example</title>
+		<programlisting format="linespecific">
+...
+	if (is_method("BYE")){
+		rms_media_stop();
+	}
+...
+		</programlisting></example>
+	</section>
+
+	<section id="rtp_media_server.f.rms_play">
+		<title><varname>rms_play</varname> ()</title>
+		<para>
+		Play a wav file, a resampler is automaticaly configured to resample
+		and convert stereo to mono if needed.
+		</para><para>
+		The second parameter is the event route that will be called when the file was played.
+		</para>
+		<para>
+		This function can be used from EVENT_ROUTE.
+		</para>
+		<example>
+		<title>usage example</title>
+		<programlisting format="linespecific">
+...
+	rms_play("file.wav", "event_route_name");
+...
+		</programlisting></example>
+	</section>
+</section>
+</chapter>

+ 52 - 0
src/modules/rtp_media_server/install_bc.sh

@@ -0,0 +1,52 @@
+# 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
+
+# download sample voice file
+mkdir -p voice_files
+cd voice_files
+wget http://www.voiptroubleshooter.com/open_speech/american/OSR_us_000_0010_8k.wav
+cd ..
+

+ 326 - 0
src/modules/rtp_media_server/rms_media.c

@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton [email protected]
+ *
+ * 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;
+
+
+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();
+	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(&params);
+}
+
+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_action_t *a = (rms_action_t *)user_data;
+		a->type = 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_get_dtmf(call_leg_media_t *m, char dtmf) {
+//	static void tone_detected_cb(void *data, MSFilter *f, unsigned int event_id, MSToneDetectorEvent *ev) {
+//			MS_UNUSED(data), MS_UNUSED(f), MS_UNUSED(event_id), MS_UNUSED(ev);
+//				ms_tester_tone_detected = TRUE;
+//	}
+	return 1;
+}
+
+int rms_playfile(call_leg_media_t *m, rms_action_t *a)
+{
+	int file_sample_rate = 8000;
+	if(!m->ms_player)
+		return 0;
+	ms_filter_add_notify_callback(m->ms_player, rms_player_eof, a, TRUE);
+	ms_filter_call_method(m->ms_player, MS_FILE_PLAYER_OPEN, (void *)a->param.s);
+	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;
+}

+ 102 - 0
src/modules/rtp_media_server/rms_media.h

@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton [email protected]
+ *
+ * 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 <mediastreamer2/mediastream.h>
+#include <mediastreamer2/msrtp.h>
+#include <mediastreamer2/dtmfgen.h>
+#include <mediastreamer2/msfileplayer.h>
+#include <mediastreamer2/msfilerec.h>
+#include <mediastreamer2/msrtp.h>
+#include <mediastreamer2/mstonedetector.h>
+#include <mediastreamer2/msfilter.h>
+#include <mediastreamer2/mscommon.h>
+#include <ortp/ortp.h>
+#include <ortp/port.h>
+
+struct rms_session_info;
+typedef struct rms_action rms_action_t;
+
+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, rms_action_t *a);
+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

+ 291 - 0
src/modules/rtp_media_server/rms_sdp.c

@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton [email protected]
+ *
+ * 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;
+}

+ 45 - 0
src/modules/rtp_media_server/rms_sdp.h

@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton [email protected]
+ *
+ * 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 <mediastreamer2/mediastream.h>
+
+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

+ 73 - 0
src/modules/rtp_media_server/rms_util.h

@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton [email protected]
+ *
+ * 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

+ 788 - 0
src/modules/rtp_media_server/rtp_media_server.c

@@ -0,0 +1,788 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton [email protected]
+ *
+ * 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, EVENT_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, REQUEST_ROUTE
+				| FAILURE_ROUTE | ONREPLY_ROUTE},
+		{"rms_media_stop", (cmd_function)rms_media_stop_f, 0, 0, 0, REQUEST_ROUTE
+				| FAILURE_ROUTE | ONREPLY_ROUTE},
+		{"rms_hangup", (cmd_function)rms_hangup_f, 0, 0, 0, EVENT_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);
+}
+
+static rms_session_info_t* rms_session_action_check(rms_session_info_t *si)
+{
+	rms_action_t *a;
+	clist_foreach(&si->action, a, next)
+	{
+		if(a->type == RMS_HANGUP) {
+			LM_INFO("session action RMS_HANGUP [%s]\n", si->callid.s);
+			rms_hangup_call(si);
+			a->type = RMS_STOP;
+			return si;
+		} else if(a->type == RMS_STOP) {
+			LM_INFO("session action RMS_STOP [%s][%p|%p]\n", si->callid.s, si, si->prev);
+			rms_stop_media(&si->caller_media);
+			rms_session_info_t *tmp = si->prev;
+			clist_rm(si, next, prev);
+			rms_session_free(si);
+			si = tmp;
+			return si;
+		} else if(a->type == RMS_PLAY) {
+			LM_INFO("session action RMS_PLAY [%s]\n", si->callid.s);
+			rms_playfile(&si->caller_media, a);
+			a->type = RMS_NONE;
+		} else if(a->type == RMS_DONE) {
+			LM_INFO("session action RMS_DONE [%s][%s]\n", si->callid.s,
+					a->route.s);
+			if(a->route.s) {
+				run_action_route(si, a->route.s);
+				rms_action_t *tmp = a->prev;
+				clist_rm(a, next, prev);
+				shm_free(a);
+				a = tmp;
+			} else {
+				a->type = RMS_HANGUP;
+			}
+			return si;
+		} else if(a->type == 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, a->param.s);
+			run_action_route(si, "rms:start");
+			a->type = RMS_NONE;
+			return si;
+		}
+	}
+	return si;
+}
+
+/**
+ * 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)
+		{
+			si = rms_session_action_check(si);
+		}
+		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: <sip:rms@%s:%d>\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 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 void rms_action_free(rms_session_info_t *si)
+{
+	rms_action_t *a, *tmp;
+	clist_foreach(&si->action, a, next) {
+		tmp = a;
+		a = a->prev;
+		clist_rm(tmp, next, prev);
+		shm_free(tmp);
+	}
+}
+
+static int rms_session_free(rms_session_info_t *si)
+{
+	rms_action_free(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_action_t *rms_action_new(rms_action_type_t t) {
+	rms_action_t *a = shm_malloc(sizeof(rms_action_t));
+	if (!a) return NULL;
+	memset(a, 0, sizeof(rms_action_t));
+	a->type = t;
+	return a;
+}
+
+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;
+	}
+	clist_init(&si->action, next, prev);
+	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_action_add(rms_session_info_t *si, rms_action_t *a) {
+	clist_append(&si->action, a, next, prev);
+}
+
+static void rms_action_add_sync(rms_session_info_t *si, rms_action_t *a) {
+	lock(&session_list_mutex);
+	rms_action_add(si, a);
+	unlock(&session_list_mutex);
+}
+
+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 rms_session_info_t *rms_session_search(char *callid, int len)
+{
+	rms_session_info_t *si;
+	clist_foreach(rms_session_list, si, next)
+	{
+		if(strncmp(callid, si->callid.s, len) == 0) {
+			return si;
+		}
+	}
+	return NULL;
+}
+
+static rms_session_info_t *rms_session_search_sync(char *callid, int len)
+{
+	lock(&session_list_mutex);
+	rms_session_info_t *si = rms_session_search(callid, len);
+	unlock(&session_list_mutex);
+	return si;
+}
+
+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_sync(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_sync(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;
+	}
+	rms_action_t *a = rms_action_new(RMS_STOP);
+	if(!a) return -1;
+	rms_action_add_sync(si, a);
+	if(!tmb.t_reply(msg, 200, "OK")) {
+		return -1;
+	}
+	return 0;
+}
+
+//static int rms_action_dtmf_f(struct sip_msg *msg, char dtmf, str *route)
+//	rms_session_info_t *si =
+//			rms_session_search(msg->callid->body.s, msg->callid->body.len);
+//	if(!si)
+//		return -1;
+//	rms_playfile();
+//	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);
+
+	rms_action_t *a = rms_action_new(RMS_PLAY);
+	if(!a) return -1;
+	a->param.len = playback_fn->len;
+	a->param.s = playback_fn->s;
+	a->route.len = route->len;
+	a->route.s = route->s;
+	rms_action_add(si, a);
+	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;
+	rms_action_t *a = rms_action_new(RMS_HANGUP);
+	if(!a) return -1;
+	rms_action_add(si, a);
+	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);
+	rms_action_t *a = rms_action_new(RMS_START);
+	if(!a) return -1;
+	rms_action_add(si, a);
+	return 1;
+error:
+	rms_session_rm(si);
+	rms_session_free(si);
+	return -1;
+}

+ 100 - 0
src/modules/rtp_media_server/rtp_media_server.h

@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017-2018 Julien Chavanton [email protected]
+ *
+ * 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_type
+{
+	RMS_NONE,
+	RMS_START,
+	RMS_STOP,
+	RMS_HANGUP,
+	RMS_PLAY,
+	RMS_DONE,
+} rms_action_type_t;
+
+typedef struct rms_action
+{
+	struct rms_action *next;
+	struct rms_action *prev;
+	str param;
+	str route;
+	rms_action_type_t type;
+} 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;
+} rms_session_info_t;
+
+#endif