diff --git a/daemon/cli.c b/daemon/cli.c index 0c99ebdf5..4907fe37d 100644 --- a/daemon/cli.c +++ b/daemon/cli.c @@ -111,6 +111,8 @@ static void cli_incoming_tag_detdtmf(str *instr, struct cli_writer *cw, const cl static void cli_incoming_media_reload_file(str *instr, struct cli_writer *cw, const cli_handler_t *); static void cli_incoming_media_reload_files(str *instr, struct cli_writer *cw, const cli_handler_t *); +static void cli_incoming_media_reload_db(str *instr, struct cli_writer *cw, const cli_handler_t *); +static void cli_incoming_media_reload_dbs(str *instr, struct cli_writer *cw, const cli_handler_t *); #endif static const cli_handler_t cli_set_handlers[] = { @@ -186,6 +188,8 @@ static const cli_handler_t cli_params_handlers[] = { static const cli_handler_t cli_media_reload_handlers[] = { { "file", cli_incoming_media_reload_file, NULL }, { "files", cli_incoming_media_reload_files, NULL }, + { "db", cli_incoming_media_reload_db, NULL }, + { "dbs", cli_incoming_media_reload_dbs, NULL }, { NULL, }, }; static const cli_handler_t cli_media_handlers[] = { @@ -1792,4 +1796,27 @@ static void cli_incoming_media_reload_files(str *instr, struct cli_writer *cw, c unsigned int num = media_player_reload_files(); cw->cw_printf(cw, "%u media files reloaded\n", num); } + +static void cli_incoming_media_reload_db(str *instr, struct cli_writer *cw, const cli_handler_t *handler) { + if (instr->len == 0) { + cw->cw_printf(cw, "More parameters required.\n"); + return ; + } + + unsigned long long id = str_to_ui(instr, 0); + if (id == 0 || id == ULLONG_MAX) + cw->cw_printf(cw, "Invalid ID '" STR_FORMAT "'\n", STR_FMT(instr)); + else { + bool ok = media_player_reload_db_media(id); + if (ok) + cw->cw_printf(cw, "Success\n"); + else + cw->cw_printf(cw, "Failed to reload '" STR_FORMAT "'\n", STR_FMT(instr)); + } +} + +static void cli_incoming_media_reload_dbs(str *instr, struct cli_writer *cw, const cli_handler_t *handler) { + unsigned int num = media_player_reload_db_medias(); + cw->cw_printf(cw, "%u media entries reloaded\n", num); +} #endif diff --git a/daemon/main.c b/daemon/main.c index 8ebe86a66..1473045ea 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -675,6 +675,7 @@ static void options(int *argc, char ***argv, GHashTable *templates) { { "kernel-player-media",0,0,G_OPTION_ARG_INT, &rtpe_config.kernel_player_media,"Max number of kernel media files","INT"}, { "preload-media-files",0,0,G_OPTION_ARG_FILENAME_ARRAY,&rtpe_config.preload_media_files,"Preload media file(s) for playback into memory","FILE"}, { "media-files-reload",0,0,G_OPTION_ARG_INT, &rtpe_config.media_refresh,"Refresh/reload preloaded media files at a certain interval","SECONDS"}, + { "preload-db-media",0,0,G_OPTION_ARG_STRING_ARRAY,&rtpe_config.preload_db_media,"Preload media from database for playback into memory","INT"}, { "audio-buffer-length",0,0, G_OPTION_ARG_INT,&rtpe_config.audio_buffer_length,"Length in milliseconds of audio buffer","INT"}, { "audio-buffer-delay",0,0, G_OPTION_ARG_INT,&rtpe_config.audio_buffer_delay,"Initial delay in milliseconds for buffered audio","INT"}, { "audio-player",0,0, G_OPTION_ARG_STRING, &use_audio_player, "When to enable the internal audio player","on-demand|play-media|transcoding|always"}, @@ -1484,6 +1485,9 @@ static void create_everything(void) { if (!media_player_preload_files(rtpe_config.preload_media_files)) die("Failed to preload media files"); + + if (!media_player_preload_db(rtpe_config.preload_db_media)) + die("Failed to preload media from database"); } diff --git a/daemon/media_player.c b/daemon/media_player.c index ca7e00cc4..2b7b245eb 100644 --- a/daemon/media_player.c +++ b/daemon/media_player.c @@ -88,6 +88,7 @@ struct media_player_media_file { str blob; union { str_list *str_link; + GList *gen_link; }; time_t ts; }; @@ -103,6 +104,14 @@ static rwlock_t media_player_media_files_names_lock = RWLOCK_STATIC_INIT; static str_q media_player_media_files_names = TYPED_GQUEUE_INIT; // lock order: media_player_media_files_names_lock first, media_player_media_files_lock second +TYPED_GHASHTABLE(media_player_db_media_ht, void, struct media_player_media_file, g_direct_hash, g_direct_equal, + NULL, __obj_put); +static mutex_t media_player_db_media_lock = MUTEX_STATIC_INIT; +static media_player_db_media_ht media_player_db_media; +static rwlock_t media_player_db_media_ids_lock = RWLOCK_STATIC_INIT; +static GQueue media_player_db_media_ids = G_QUEUE_INIT; +// lock order: media_player_db_media_ids_lock first, media_player_db_media_lock second + static bool media_player_read_packet(struct media_player *mp); static mp_cached_code __media_player_add_blob_id(struct media_player *mp, media_player_opts_t opts, @@ -1218,6 +1227,16 @@ static struct media_player_media_file *media_player_media_file_read_str(const st return media_player_media_file_read_c(file_s); } +static const char *media_player_get_db_id(str *out, unsigned long long id, str (*dup_fn)(const char *, size_t)); + +static struct media_player_media_file *media_player_db_id_read(unsigned long long id) { + str blob; + const char *err = media_player_get_db_id(&blob, id, str_dup_len); + if (err || blob.len == 0) + return NULL; + return media_player_media_file_new(blob); +} + static struct media_player_media_file *media_player_media_files_get_only(const str *fn) { struct media_player_media_file *fo; @@ -1235,6 +1254,24 @@ static struct media_player_media_file *media_player_media_files_get_only(const s return fo; } +// lock must be held, reference will be taken over +static struct media_player_media_file *media_player_db_id_get_only(unsigned long long id) { + struct media_player_media_file *fo; + + { + LOCK(&media_player_db_media_lock); + if (!t_hash_table_is_set(media_player_db_media)) + return NULL; + fo = t_hash_table_lookup(media_player_db_media, GUINT_TO_POINTER(id)); + if (!fo) + return NULL; + + obj_hold(fo); + } + + return fo; +} + // locks must be held, reference will be taken over static void media_player_media_files_insert(const str *fn, struct media_player_media_file *fo) { if (!t_hash_table_is_set(media_player_media_files)) @@ -1258,9 +1295,20 @@ static mp_cached_code media_player_set_media_file(struct media_player *mp, // switch to blob playing opts.file = STR_NULL; opts.blob = fo->blob; + // db_id remains set if it was, so that the cache lookup can succeed return __media_player_add_blob_id(mp, opts, dst_pt); } +// locks must be held, reference will be taken over +static void media_player_db_id_insert(unsigned long long id, struct media_player_media_file *fo) { + if (!t_hash_table_is_set(media_player_db_media)) + media_player_db_media = media_player_db_media_ht_new(); + t_hash_table_insert(media_player_db_media, GUINT_TO_POINTER(id), fo); + g_queue_push_tail(&media_player_db_media_ids, GUINT_TO_POINTER(id)); + fo->gen_link = media_player_db_media_ids.tail; + +} + static struct media_player_media_file *media_player_media_files_get_create(const str *fn) { __auto_type fo = media_player_media_files_get_only(fn); if (fo) @@ -1285,6 +1333,9 @@ static struct media_player_media_file *media_player_media_files_get_create(const static struct media_player_media_file *(*media_player_media_files_get)(const str *fn) = media_player_media_files_get_only; +static struct media_player_media_file *(*media_player_db_id_get)(unsigned long long) + = media_player_db_id_get_only; + static void __media_player_set_opts(struct media_player *mp, media_player_opts_t opts) { mp->opts = opts; @@ -1749,6 +1800,13 @@ static mp_cached_code __media_player_add_db(struct media_player *mp, { const char *err; + // check if we have it in memory + __auto_type fo = media_player_db_id_get(opts.db_id); + if (fo) { + ilog(LOG_DEBUG, "Using cached DB media for playback"); + return media_player_set_media_file(mp, opts, dst_pt, fo); + } + err = media_player_get_db_id(&opts.blob, opts.db_id, call_str_cpy_len); if (err) return MPC_ERR; @@ -1922,6 +1980,10 @@ void media_player_free(void) { if (t_hash_table_is_set(media_player_media_files)) t_hash_table_destroy(media_player_media_files); t_queue_clear_full(&media_player_media_files_names, str_free); + + if (t_hash_table_is_set(media_player_db_media)) + t_hash_table_destroy(media_player_db_media); + g_queue_clear(&media_player_db_media_ids); #endif timerthread_free(&send_timer_thread); } @@ -1970,6 +2032,40 @@ bool media_player_preload_files(char **files) { return true; } +bool media_player_preload_db(char **ids) { +#ifdef WITH_TRANSCODING + if (!ids || !ids[0]) + return true; + + for (char **idp = ids; *idp; idp++) { + char *id_s = *idp; + + char *endp = NULL; + unsigned long long id = strtoull(id_s, &endp, 0); + if (id == 0 || id == ULLONG_MAX || (endp && *endp != '\0')) { + ilog(LOG_CRIT, "Invalid DB ID string number: '%s'", id_s); + return false; + } + + ilog(LOG_DEBUG, "Reading media ID %llu from DB for caching", id); + + if (t_hash_table_is_set(media_player_db_media) + && t_hash_table_lookup(media_player_db_media, GUINT_TO_POINTER(id))) + { + ilog(LOG_CRIT, "Duplicate entry for caching media ID %llu", id); + return false; + } + + __auto_type fo = media_player_db_id_read(id); + if (!fo) + return false; + media_player_db_id_insert(id, fo); + } +#endif + + return true; +} + bool media_player_reload_file(str *name) { bool ret = false; @@ -2027,6 +2123,53 @@ unsigned int media_player_reload_files(void) { return ret; } +bool media_player_reload_db_media(unsigned long long id) { + bool ret = false; + +#ifdef WITH_TRANSCODING + __auto_type fo = media_player_db_id_get(id); + if (!fo) + return false; + + // read fresh copy + __auto_type fonew = media_player_db_id_read(id); + if (fonew) { + // got a new entry. swap it out against the old one + LOCK(&media_player_db_media_lock); + if (t_hash_table_is_set(media_player_db_media) + && t_hash_table_lookup(media_player_db_media, GUINT_TO_POINTER(id)) == fo) + { + t_hash_table_insert(media_player_db_media, GUINT_TO_POINTER(id), fonew); // releases `fo` reference + ilog(LOG_DEBUG, "Reloaded cached media DB entry %llu", id); + ret = true; + } + else // somebody beat us to it + obj_put(fonew); + } + + obj_put(fo); +#endif + + return ret; +} + +unsigned int media_player_reload_db_medias(void) { + unsigned int ret = 0; + +#ifdef WITH_TRANSCODING + RWLOCK_R(&media_player_db_media_ids_lock); + + for (__auto_type l = media_player_db_media_ids.head; l; l = l->next) { + unsigned long long id = GPOINTER_TO_UINT(l->data); + if (media_player_reload_db_media(id)) + ret++; + } + +#endif + + return ret; +} + enum thread_looper_action media_player_refresh_timer(void) { if (rtpe_config.media_refresh <= 0) return TLA_BREAK; diff --git a/docs/rtpengine.md b/docs/rtpengine.md index e8fd1c2d4..2cd9b81da 100644 --- a/docs/rtpengine.md +++ b/docs/rtpengine.md @@ -1172,6 +1172,12 @@ call to inject-DTMF won't be sent to __\-\-dtmf-log-dest=__ or __\-\-listen-tcp- since then, it will be re-read and will replace the previous cached contents. +- __\-\-preload-db-media=__*INT* + + Similar to the __preload-media-files__ option, but preloads media from + database instead of reading them from files. Each entry must be an integer + corresponding to an index from the database. + - __\-\-audio-buffer-length=__*INT* Set the buffer length used by the audio player (see below) in milliseconds. The diff --git a/etc/rtpengine.conf b/etc/rtpengine.conf index 4bddd8806..1540ffd22 100644 --- a/etc/rtpengine.conf +++ b/etc/rtpengine.conf @@ -167,6 +167,7 @@ recording-method = proc # preload-media-files = /var/media/file1.wav ; /var/media/file2.wav ; /var/media/file3.wav ; on-demand # media-files-reload = 60 +# preload-db-media = 1; 2; 3; 4 # signalling templates (see key `templates` above) [templates] diff --git a/include/main.h b/include/main.h index b3ff8b47f..3bbac7156 100644 --- a/include/main.h +++ b/include/main.h @@ -184,6 +184,7 @@ enum endpoint_learning { X(http_ifs) \ X(https_ifs) \ X(preload_media_files) \ + X(preload_db_media) \ // these are not automatically included in rtpe_config due to different types #define RTPE_CONFIG_ENUM_PARAMS \ diff --git a/include/media_player.h b/include/media_player.h index 339e682df..d688196df 100644 --- a/include/media_player.h +++ b/include/media_player.h @@ -152,7 +152,10 @@ void media_player_launch(void); bool media_player_preload_files(char **); bool media_player_reload_file(str *name); unsigned int media_player_reload_files(void); +bool media_player_reload_db_media(unsigned long long); +unsigned int media_player_reload_db_medias(void); enum thread_looper_action media_player_refresh_timer(void); +bool media_player_preload_db(char **); struct send_timer *send_timer_new(struct packet_stream *); void send_timer_push(struct send_timer *, struct codec_packet *); diff --git a/utils/rtpengine-ctl b/utils/rtpengine-ctl index bcb238af1..0c0bfb58d 100755 --- a/utils/rtpengine-ctl +++ b/utils/rtpengine-ctl @@ -167,6 +167,8 @@ sub showusage { print " reload