diff --git a/daemon/.ycm_extra_conf.py b/daemon/.ycm_extra_conf.py index 8d2343d0a..5412d7120 100644 --- a/daemon/.ycm_extra_conf.py +++ b/daemon/.ycm_extra_conf.py @@ -29,6 +29,7 @@ flags = [ '-I/usr/include/glib-2.0', '-I/usr/lib/x86_64-linux-gnu/glib-2.0/include', '-I/usr/include/json-glib-1.0', + '-I/usr/include/mysql', '-pthread', '-I../kernel-module/', '-I../lib/', diff --git a/daemon/Makefile b/daemon/Makefile index ba51810ac..86b47ecb1 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -76,13 +76,14 @@ CFLAGS+= $(shell pkg-config --cflags libavutil) CFLAGS+= $(shell pkg-config --cflags libswresample) CFLAGS+= $(shell pkg-config --cflags libavfilter) CFLAGS+= -DWITH_TRANSCODING -else -CFLAGS+= -DWITHOUT_CODECLIB -endif ifeq ($(have_bcg729),yes) CFLAGS+= -DHAVE_BCG729 CFLAGS+= $(bcg729_inc) endif +CFLAGS+= $(shell mysql_config --cflags) +else +CFLAGS+= -DWITHOUT_CODECLIB +endif CFLAGS+= -DRE_PLUGIN_DIR="\"/usr/lib/rtpengine\"" @@ -114,10 +115,11 @@ LDLIBS+= $(shell pkg-config --libs libavformat) LDLIBS+= $(shell pkg-config --libs libavutil) LDLIBS+= $(shell pkg-config --libs libswresample) LDLIBS+= $(shell pkg-config --libs libavfilter) -endif ifeq ($(have_bcg729),yes) LDLIBS+= $(bcg729_lib) endif +LDLIBS+= $(shell mysql_config --libs) +endif SRCS= main.c kernel.c poller.c aux.c control_tcp.c call.c control_udp.c redis.c \ bencode.c cookie_cache.c udp_listener.c control_ng.strhash.c sdp.strhash.c stun.c rtcp.c \ diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 42bf24984..6fdeecdb8 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -1734,10 +1734,11 @@ out: const char *call_play_media_ng(bencode_item_t *input, bencode_item_t *output) { #ifdef WITH_TRANSCODING - str callid, fromtag, file; + str callid, fromtag, str; struct call *call; struct call_monologue *monologue; const char *err = NULL; + long long db_id; if (!bencode_dictionary_get_str(input, "call-id", &callid)) return "No call-id in message"; @@ -1759,14 +1760,19 @@ const char *call_play_media_ng(bencode_item_t *input, bencode_item_t *output) { monologue->player = media_player_new(monologue); err = "No media file specified"; - if (bencode_dictionary_get_str(input, "file", &file)) { + if (bencode_dictionary_get_str(input, "file", &str)) { err = "Failed to start media playback from file"; - if (media_player_play_file(monologue->player, &file)) + if (media_player_play_file(monologue->player, &str)) goto out; } - else if (bencode_dictionary_get_str(input, "blob", &file)) { + else if (bencode_dictionary_get_str(input, "blob", &str)) { err = "Failed to start media playback from blob"; - if (media_player_play_blob(monologue->player, &file)) + if (media_player_play_blob(monologue->player, &str)) + goto out; + } + else if ((db_id = bencode_dictionary_get_int_str(input, "db-id", 0)) > 0) { + err = "Failed to start media playback from database"; + if (media_player_play_db(monologue->player, db_id)) goto out; } else diff --git a/daemon/main.c b/daemon/main.c index 664963ac0..01fa2e535 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -366,6 +366,11 @@ static void options(int *argc, char ***argv) { { "idle-scheduling",0, 0,G_OPTION_ARG_STRING, &rtpe_config.idle_scheduling,"Idle thread scheduling policy", "default|none|fifo|rr|other|batch|idle" }, { "idle-priority",0, 0, G_OPTION_ARG_INT, &rtpe_config.idle_priority,"Idle thread scheduling priority", "INT" }, { "log-srtp-keys",'F', 0, G_OPTION_ARG_NONE, &rtpe_config.log_keys, "Log SRTP keys to error log", NULL }, + { "mysql-host", 0, 0, G_OPTION_ARG_STRING, &rtpe_config.mysql_host,"MySQL host for stored media files","HOST|IP" }, + { "mysql-port", 0, 0, G_OPTION_ARG_INT, &rtpe_config.mysql_port,"MySQL port" ,"INT" }, + { "mysql-user", 0, 0, G_OPTION_ARG_STRING, &rtpe_config.mysql_user,"MySQL connection credentials", "USERNAME" }, + { "mysql-pass", 0, 0, G_OPTION_ARG_STRING, &rtpe_config.mysql_pass,"MySQL connection credentials", "PASSWORD" }, + { "mysql-query",0, 0, G_OPTION_ARG_STRING, &rtpe_config.mysql_query,"MySQL select query", "STRING" }, { NULL, } }; @@ -518,6 +523,22 @@ static void options(int *argc, char ***argv) { rtpe_config.cpu_limit = max_cpu * 100; rtpe_config.load_limit = max_load * 100; + + if (rtpe_config.mysql_query) { + // require exactly one %llu placeholder and allow no other % placeholders + if (!strstr(rtpe_config.mysql_query, "%llu")) + die("No '%%llu' present in --mysql-query='%s'", rtpe_config.mysql_query); + const char *front = rtpe_config.mysql_query; + unsigned int count = 0; + const char *match; + while ((match = strchr(front, '%'))) { + front = match + 1; + count++; + } + if (count != 1) + die("Too many '%%' placeholders (%u) present in --mysql-query='%s'", + count, rtpe_config.mysql_query); + } } void fill_initial_rtpe_cfg(struct rtpengine_config* ini_rtpe_cfg) { diff --git a/daemon/media_player.c b/daemon/media_player.c index ad0f30cce..cea2d41ee 100644 --- a/daemon/media_player.c +++ b/daemon/media_player.c @@ -1,5 +1,9 @@ #include "media_player.h" #include +#ifdef WITH_TRANSCODING +#include +#include +#endif #include "obj.h" #include "log.h" #include "timerthread.h" @@ -10,6 +14,7 @@ #include "media_socket.h" #include "ssrc.h" #include "log_funcs.h" +#include "main.h" @@ -19,6 +24,7 @@ #ifdef WITH_TRANSCODING static struct timerthread media_player_thread; +static MYSQL __thread *mysql_conn; #endif static struct timerthread send_timer_thread; @@ -404,6 +410,7 @@ static int64_t __mp_avio_seek(void *opaque, int64_t offset, int whence) { #endif + // call->master_lock held in W int media_player_play_blob(struct media_player *mp, const str *blob) { #ifdef WITH_TRANSCODING @@ -453,6 +460,93 @@ err: #ifdef WITH_TRANSCODING +static int __connect_db(void) { + if (mysql_conn) { + mysql_close(mysql_conn); + mysql_conn = NULL; + } + mysql_conn = mysql_init(NULL); + if (!mysql_conn) + return -1; + if (!mysql_real_connect(mysql_conn, rtpe_config.mysql_host, rtpe_config.mysql_user, rtpe_config.mysql_pass, NULL, rtpe_config.mysql_port, + NULL, CLIENT_IGNORE_SIGPIPE)) + goto err; + + return 0; + +err: + ilog(LOG_ERR, "Couldn't connect to database: %s", mysql_error(mysql_conn)); + mysql_close(mysql_conn); + mysql_conn = NULL; + return -1; +} + + +// call->master_lock held in W +int media_player_play_db(struct media_player *mp, long long id) { + const char *err; + AUTO_CLEANUP_BUF(query); + + err = "missing configuration"; + if (!rtpe_config.mysql_host || !rtpe_config.mysql_query) + goto err; + + int len = asprintf(&query, rtpe_config.mysql_query, (unsigned long long) id); + err = "query print error"; + if (len <= 0) + goto err; + + for (int retries = 0; retries < 5; retries++) { + if (!mysql_conn || retries != 0) { + err = "failed to connect to database"; + if (__connect_db()) + goto err; + } + + int ret = mysql_real_query(mysql_conn, query, len); + if (ret == 0) + goto success; + + ret = mysql_errno(mysql_conn); + if (ret == CR_SERVER_GONE_ERROR || ret == CR_SERVER_LOST) + continue; + + ilog(LOG_ERR, "Failed to query from database: %s", mysql_error(mysql_conn)); + } + err = "exceeded max number of database retries"; + goto err; + +success:; + + MYSQL_RES *res = mysql_store_result(mysql_conn); + err = "failed to get result from database"; + if (!res) + goto err; + MYSQL_ROW row = mysql_fetch_row(res); + unsigned long *lengths = mysql_fetch_lengths(res); + err = "empty result from database"; + if (!row || !lengths || !row[0] || !lengths[0]) { + mysql_free_result(res); + goto err; + } + + str blob; + str_init_len(&blob, row[0], lengths[0]); + int ret = media_player_play_blob(mp, &blob); + + mysql_free_result(res); + + return ret; + +err: + if (query) + ilog(LOG_ERR, "Failed to start media playback from database (used query '%s'): %s", query, err); + else + ilog(LOG_ERR, "Failed to start media playback from database: %s", err); + return -1; +} + + static void media_player_run(void *ptr) { struct media_player *mp = ptr; struct call *call = mp->call; diff --git a/include/main.h b/include/main.h index 86f759fb7..23ca13fc4 100644 --- a/include/main.h +++ b/include/main.h @@ -78,6 +78,11 @@ struct rtpengine_config { char *idle_scheduling; int idle_priority; int log_keys; + char *mysql_host; + int mysql_port; + char *mysql_user; + char *mysql_pass; + char *mysql_query; }; diff --git a/include/media_player.h b/include/media_player.h index 862f0d9f3..d08bceaf2 100644 --- a/include/media_player.h +++ b/include/media_player.h @@ -69,6 +69,7 @@ struct send_timer { struct media_player *media_player_new(struct call_monologue *); int media_player_play_file(struct media_player *, const str *); int media_player_play_blob(struct media_player *, const str *); +int media_player_play_db(struct media_player *, long long); void media_player_stop(struct media_player *); void media_player_init(void); diff --git a/t/Makefile b/t/Makefile index a853bf0c0..3b98df0e5 100644 --- a/t/Makefile +++ b/t/Makefile @@ -26,6 +26,7 @@ CFLAGS+= $(shell pkg-config xmlrpc_util --cflags 2> /dev/null) ifeq ($(with_amr_tests),yes) CFLAGS+= -DWITH_AMR_TESTS endif +CFLAGS+= $(shell mysql_config --cflags) else CFLAGS+= -DWITHOUT_CODECLIB endif @@ -50,6 +51,7 @@ LDLIBS+= $(shell pkg-config xmlrpc_client --libs 2> /dev/null || xmlrpc-c-config LDLIBS+= $(shell pkg-config xmlrpc --libs 2> /dev/null) LDLIBS+= $(shell pkg-config xmlrpc_util --libs 2> /dev/null) LDLIBS+= -lhiredis +LDLIBS+= $(shell mysql_config --libs) endif SRCS= bitstr-test.c aes-crypt.c payload-tracker-test.c const_str_hash-test.strhash.c diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client index 2065346e3..2fbf54ed7 100755 --- a/utils/rtpengine-ng-client +++ b/utils/rtpengine-ng-client @@ -69,13 +69,14 @@ GetOptions( 'file=s' => \$options{'file'}, 'blob=s' => \$options{'blob'}, 'blob-file=s' => \$options{'blob-file'}, + 'db-id=i' => \$options{'db-id'}, ) or die; my $cmd = shift(@ARGV) or die; my %packet = (command => $cmd); -for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family,DTLS,via-branch,media address,ptime,xmlrpc-callback,metadata,address,file')) { +for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family,DTLS,via-branch,media address,ptime,xmlrpc-callback,metadata,address,file,db-id')) { defined($options{$x}) and $packet{$x} = \$options{$x}; } for my $x (split(/,/, 'TOS,delete-delay')) {