From c1d29a41a79c1a18795abb23defdaccd4c7e9937 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Mon, 13 Apr 2020 10:34:45 -0400 Subject: [PATCH] TT#79601 support media/DTMF playback to all parties Change-Id: I534ba39c800e0b075c1502808a56b887baf3e323 --- README.md | 8 ++- daemon/call_interfaces.c | 124 +++++++++++++++++++++++--------------- utils/rtpengine-ng-client | 3 +- 3 files changed, 84 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 13d40b8c8..ac17c67f7 100644 --- a/README.md +++ b/README.md @@ -1571,7 +1571,8 @@ and stopped independently of each other. Only available if compiled with transcoding support. The message must contain the key `call-id` and one of the participant selection keys described under the `block DTMF` message (such as `from-tag`, -`address`, or `label`). +`address`, or `label`). Alternatively, the `all` flag can be set to play the media to all involved +call parties. Starts playback of a provided media file to the selected call participant. The format of the media file can be anything that is supported by *ffmpeg*, for example a `.wav` or `.mp3` file. It will automatically @@ -1611,8 +1612,9 @@ The same participant selection keys as for the `play media` message can and must ------------------- Instructs *rtpengine* to inject a DTMF tone or event into a running audio stream. A call participant must -be selected in the same way as described under the `block DTMF` message above. The selected call participant -is the one generating the DTMF event, not the one receiving it. +be selected in the same way as described under the `play media` message above (including the possibility +of using the `all` flag). The selected call participant is the one generating the DTMF event, not the +one receiving it. The dictionary key `code` must be present in the message, indicating the DTMF event to be generated. It can be either an integer with values 0-15, or a string containing a single character diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 7a0395b8e..ccd165731 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -1603,6 +1603,9 @@ static const char *media_block_match(struct call **call, struct call_monologue * return "Unknown call-id"; // directional? + if (flags->all) // explicitly non-directional, so skip the rest + return NULL; + if (flags->label.s) { *monologue = g_hash_table_lookup((*call)->labels, &flags->label); if (!*monologue) @@ -1848,14 +1851,23 @@ out: #ifdef WITH_TRANSCODING -static const char *play_media_select_party(struct call **call, struct call_monologue **monologue, +static const char *play_media_select_party(struct call **call, GQueue *monologues, bencode_item_t *input) { - const char *err = media_block_match(call, monologue, NULL, input); + struct call_monologue *monologue; + struct sdp_ng_flags flags; + + g_queue_init(monologues); + + const char *err = media_block_match(call, &monologue, &flags, input); if (err) return err; - if (!*monologue) + if (flags.all) + g_queue_append(monologues, &(*call)->monologues); + else if (!monologue) return "No participant party specified"; + else + g_queue_push_tail(monologues, monologue); return NULL; } #endif @@ -1865,42 +1877,47 @@ const char *call_play_media_ng(bencode_item_t *input, bencode_item_t *output) { #ifdef WITH_TRANSCODING str str; struct call *call; - struct call_monologue *monologue; + GQueue monologues; const char *err = NULL; long long db_id; - err = play_media_select_party(&call, &monologue, input); + err = play_media_select_party(&call, &monologues, input); if (err) goto out; - if (!monologue->player) - monologue->player = media_player_new(monologue); + for (GList *l = monologues.head; l; l = l->next) { + struct call_monologue *monologue = l->data; - err = "No media file specified"; - if (bencode_dictionary_get_str(input, "file", &str)) { - err = "Failed to start media playback from file"; - if (media_player_play_file(monologue->player, &str)) - goto out; - } - else if (bencode_dictionary_get_str(input, "blob", &str)) { - err = "Failed to start media playback from blob"; - 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)) + if (!monologue->player) + monologue->player = media_player_new(monologue); + + err = "No media file specified"; + if (bencode_dictionary_get_str(input, "file", &str)) { + err = "Failed to start media playback from file"; + if (media_player_play_file(monologue->player, &str)) + goto out; + } + else if (bencode_dictionary_get_str(input, "blob", &str)) { + err = "Failed to start media playback from blob"; + 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 goto out; - } - else - goto out; - if (monologue->player->duration) - bencode_dictionary_add_integer(output, "duration", monologue->player->duration); + if (l == monologues.head && monologue->player->duration) + bencode_dictionary_add_integer(output, "duration", monologue->player->duration); + } err = NULL; out: + g_queue_clear(&monologues); if (call) { rwlock_unlock_w(&call->master_lock); obj_put(call); @@ -1915,21 +1932,27 @@ out: const char *call_stop_media_ng(bencode_item_t *input, bencode_item_t *output) { #ifdef WITH_TRANSCODING struct call *call; - struct call_monologue *monologue; + GQueue monologues; const char *err = NULL; - err = play_media_select_party(&call, &monologue, input); + err = play_media_select_party(&call, &monologues, input); if (err) goto out; - if (!monologue->player) - return "Not currently playing media"; + for (GList *l = monologues.head; l; l = l->next) { + struct call_monologue *monologue = l->data; - media_player_stop(monologue->player); + err = "Not currently playing media"; + if (!monologue->player) + goto out; + + media_player_stop(monologue->player); + } err = NULL; out: + g_queue_clear(&monologues); if (call) { rwlock_unlock_w(&call->master_lock); obj_put(call); @@ -1944,11 +1967,11 @@ out: const char *call_play_dtmf_ng(bencode_item_t *input, bencode_item_t *output) { #ifdef WITH_TRANSCODING struct call *call; - struct call_monologue *monologue; + GQueue monologues; str str; const char *err = NULL; - err = play_media_select_party(&call, &monologue, input); + err = play_media_select_party(&call, &monologues, input); if (err) goto out; @@ -1998,25 +2021,32 @@ const char *call_play_dtmf_ng(bencode_item_t *input, bencode_item_t *output) { if (volume > 0) volume *= -1; - // find a usable output media - struct call_media *media; - for (GList *l = monologue->medias.head; l; l = l->next) { - media = l->data; - if (media->type_id != MT_AUDIO) - continue; - if (!media->dtmf_injector) - continue; - goto found; - } + for (GList *l = monologues.head; l; l = l->next) { + struct call_monologue *monologue = l->data; - err = "Monologue has no media capable of DTMF injection"; - // XXX fall back to generating a secondary stream - goto out; + // find a usable output media + struct call_media *media; + for (GList *l = monologue->medias.head; l; l = l->next) { + media = l->data; + if (media->type_id != MT_AUDIO) + continue; + if (!media->dtmf_injector) + continue; + goto found; + } + + err = "Monologue has no media capable of DTMF injection"; + // XXX fall back to generating a secondary stream + goto out; found:; - err = dtmf_inject(media, code, volume, duration, pause); + err = dtmf_inject(media, code, volume, duration, pause); + if (err) + break; + } out: + g_queue_clear(&monologues); if (call) { rwlock_unlock_w(&call->master_lock); obj_put(call); diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client index 6afe6d711..acd680a60 100755 --- a/utils/rtpengine-ng-client +++ b/utils/rtpengine-ng-client @@ -72,13 +72,14 @@ GetOptions( 'blob-file=s' => \$options{'blob-file'}, 'db-id=i' => \$options{'db-id'}, 'T38=s@' => \$options{'T.38'}, + 'code=s' => \$options{'code'}, ) 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,db-id')) { +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,code')) { defined($options{$x}) and $packet{$x} = \$options{$x}; } for my $x (split(/,/, 'TOS,delete-delay')) {