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 +
+ <varname>log_file_name</varname> (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 + +
+ <varname>rms_answer</varname> () + + Create a session and a call leg and call the event_route[rms:start] + config example + + + usage example + +... +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") && !has_totag()) { + if (!rms_answer()) { + t_reply("503", "server error"); + } + } + + if (is_method("BYE")){ + xnotice("BYE RECEIVED [$ci]\n"); + rms_media_stop(); + } +... + +
+ +
+ <varname>rms_hangup</varname> () + + Send a BYE, delete the RTP session and the media ressources. + + + usage example + +... + rms_hangup(); +... + +
+ +
+ <varname>rms_media_stop</varname> () + + This should be called on reception of a BYE, this will + delete the RTP session and the media ressources. + and reply "200 OK". + + + If the SIP session is not found "481 Call/Transaction Does Not Exist" + is returned. + + + usage example + +... + if (is_method("BYE")){ + rms_media_stop(); + } +... + +
+ +
+ <varname>rms_play</varname> () + + Play a wav file, a resampler is automaticaly configured to resample + and convert stereo to mono if needed. + + The second parameter is the event route that will be called when the file was played. + + + usage example + +... + rms_play("file.wav", "event_route_name"); +... + +
+
+
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