diff --git a/daemon/call.c b/daemon/call.c index c38967623..a99c54d28 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -2677,6 +2677,19 @@ static void __call_monologue_init_from_flags(struct call_monologue *ml, struct c ml->all_attributes = flags->all_attributes; t_queue_init(&flags->all_attributes); + /* set moh flags for future processing */ + if (flags->moh_sendrecv) + ML_SET(ml, MOH_SENDRECV); + if (flags->moh_zero_connection) + ML_SET(ml, MOH_ZEROCONN); + if (flags->moh_blob.len) + ml->moh_blob = call_str_cpy(&flags->moh_blob); + if (flags->moh_file.len) + ml->moh_file = call_str_cpy(&flags->moh_file); + if (flags->moh_db_id > 0) + /* only set when defined by flags, must be kept then for future offer/answer exchanges */ + ml->moh_db_id = flags->moh_db_id; + /* consume sdp session parts */ { /* for cases with origin replacements, keep the very first used origin */ diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 6cba6abe5..6ea9d9513 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -836,6 +836,36 @@ static void call_ng_flags_rtcp_mux(str *s, unsigned int idx, helper_arg arg) { STR_FMT(s)); } } + +static void call_ng_flags_moh(const ng_parser_t *parser, str *key, parser_arg value, helper_arg arg) { + sdp_ng_flags *out = arg.flags; + switch (__csh_lookup(key)) { + case CSH_LOOKUP("db-id"): + out->moh_db_id = parser->get_int_str(value, out->moh_db_id); + break; + case CSH_LOOKUP("blob"): + parser->get_str(value, &out->moh_blob); + break; + case CSH_LOOKUP("file"): + parser->get_str(value, &out->moh_file); + break; + case CSH_LOOKUP("connection"): + str connection = STR_NULL; + parser->get_str(value, &connection); + if (!str_cmp(&connection, "zero")) + out->moh_zero_connection = 1; + break; + case CSH_LOOKUP("mode"): + str mode = STR_NULL; + parser->get_str(value, &mode); + if (!str_cmp(&mode, "sendrecv")) + out->moh_sendrecv = 1; + break; + default: + ilog(LOG_WARN, "Unknown 'moh' flag encountered: '" STR_FORMAT "'", + STR_FMT(key)); + } +} static void call_ng_flags_replace(str *s, unsigned int idx, helper_arg arg) { sdp_ng_flags *out = arg.flags; str_hyphenate(s); @@ -1790,6 +1820,11 @@ void call_ng_main_flags(const ng_parser_t *parser, str *key, parser_arg value, h case CSH_LOOKUP("metadata"): out->metadata = s; break; + case CSH_LOOKUP("moh"): + case CSH_LOOKUP("MoH"): + case CSH_LOOKUP("MOH"): + parser->dict_iter(parser, value, call_ng_flags_moh, out); + break; case CSH_LOOKUP("OSRTP"): case CSH_LOOKUP("osrtp"): call_ng_flags_str_list(parser, value, ng_osrtp_option, out); @@ -2346,6 +2381,36 @@ static const char *call_offer_answer_ng(ng_command_ctx_t *ctx, const char* addr, meta_write_sdp_before(recording, &sdp, from_ml, flags.opmode); +#ifdef WITH_TRANSCODING + /* TODO: move this whole MoH handling into a separate function */ + + /* check if sender's monologue has any audio medias putting the call + * into the sendonly state, if so, check if it wants this call + * to be provided with moh playbacks */ + if (call_ml_wants_moh(from_ml, flags.opmode)) + { + /* TODO: should be fine tuned? */ + media_player_opts_t opts = MPO( + .repeat = 999, /* TODO: maybe there is a better way to loop it */ + .start_pos = 0, + .block_egress = 1, + .codec_set = flags.codec_set, + .file = from_ml->moh_file, + .blob = from_ml->moh_blob, + .db_id = from_ml->moh_db_id, + ); + /* whom to play the moh audio */ + errstr = call_play_media_for_ml(to_ml, opts, NULL); + if (errstr) + goto out; + to_ml->player->moh = true; /* mark player as used for MoH */ + } else if (call_ml_stops_moh(from_ml, to_ml, flags.opmode)) + { + /* whom to stop the moh audio */ + call_stop_media_for_ml(to_ml); + } +#endif + /* if all fine, prepare an outer sdp and save it */ if ((ret = sdp_create(&sdp_out, to_ml, &flags)) == 0) { /* TODO: should we save sdp_out? */ diff --git a/daemon/media_player.c b/daemon/media_player.c index c26c7f5cc..f96a682d6 100644 --- a/daemon/media_player.c +++ b/daemon/media_player.c @@ -1188,6 +1188,28 @@ static bool media_player_add_file(struct media_player *mp, media_player_opts_t o return ret == 0; } +bool call_ml_wants_moh(struct call_monologue *ml, enum ng_opmode opmode) +{ + if (opmode == OP_OFFER && call_ml_sendonly(ml) && + (ml->moh_db_id > 0 || ml->moh_file.len || ml->moh_blob.len)) + { + return true; + } + return false; +} + +bool call_ml_stops_moh(struct call_monologue *from_ml, struct call_monologue *to_ml, + enum ng_opmode opmode) +{ +#ifdef WITH_TRANSCODING + if (opmode == OP_OFFER && !call_ml_sendonly(from_ml) && (to_ml->player && to_ml->player->moh)) + { + return true; + } +#endif + return false; +} + const char * call_play_media_for_ml(struct call_monologue *ml, media_player_opts_t opts, sdp_ng_flags *flags) { diff --git a/include/call.h b/include/call.h index 9831830cf..47fc6002c 100644 --- a/include/call.h +++ b/include/call.h @@ -223,6 +223,8 @@ enum { #define ML_FLAG_BLOCK_SHORT 0x00400000 #define ML_FLAG_BLOCK_MEDIA 0x00800000 #define ML_FLAG_SILENCE_MEDIA 0x01000000 +#define ML_FLAG_MOH_SENDRECV 0x02000000 +#define ML_FLAG_MOH_ZEROCONN 0x04000000 /* call_t */ #define CALL_FLAG_IPV4_OFFER 0x00010000 @@ -628,6 +630,10 @@ struct call_monologue { sdp_attr_q all_attributes; sdp_attr_print_f *sdp_attr_print; + long long moh_db_id; + str moh_blob; + str moh_file; + atomic64 ml_flags; }; diff --git a/include/call_interfaces.h b/include/call_interfaces.h index 51b359881..c6246bd53 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -135,8 +135,11 @@ struct sdp_ng_flags { int repeat_times; int delete_delay; str file; + str moh_file; str blob; + str moh_blob; long long db_id; + long long moh_db_id; long long duration; long long pause; long long start_pos; @@ -258,7 +261,10 @@ RTPE_NG_FLAGS_STR_CASE_HT_PARAMS new_branch:1, provisional:1, /* to_tag is used especially by delete handling */ - to_tag_flag:1; + to_tag_flag:1, + moh_zero_connection:1, + /* by default sendonly */ + moh_sendrecv:1; }; diff --git a/include/media_player.h b/include/media_player.h index d5b2a76a6..34eb03504 100644 --- a/include/media_player.h +++ b/include/media_player.h @@ -7,6 +7,7 @@ #include "timerthread.h" #include "str.h" #include "types.h" +#include "control_ng.h" struct call_media; struct call_monologue; @@ -83,6 +84,7 @@ struct media_player { unsigned long sync_ts; struct timeval sync_ts_tv; long long last_frame_ts; + bool moh; }; INLINE void media_player_put(struct media_player **mp) { @@ -135,6 +137,9 @@ void media_player_add_packet(struct media_player *mp, char *buf, size_t len, const char * call_play_media_for_ml(struct call_monologue *ml, media_player_opts_t opts, sdp_ng_flags *flags); long long call_stop_media_for_ml(struct call_monologue *ml); +bool call_ml_wants_moh(struct call_monologue *ml, enum ng_opmode opmode); +bool call_ml_stops_moh(struct call_monologue *from_ml, struct call_monologue *to_ml, + enum ng_opmode opmode); void media_player_init(void); void media_player_free(void); diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index d9daca4cd..651b3dcca 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -36,7 +36,7 @@ my ($sock_a, $sock_b, $sock_c, $sock_d, $port_a, $port_b, $ssrc, $ssrc_b, $resp, $sock_ax, $sock_bx, $port_ax, $port_bx, $sock_cx, $sock_dx, $port_c, $port_d, $port_cx, $port_dx, $srtp_ctx_a, $srtp_ctx_b, $srtp_ctx_a_rev, $srtp_ctx_b_rev, $ufrag_a, $ufrag_b, - @ret1, @ret2, @ret3, @ret4, $srtp_key_a, $srtp_key_b, $ts, $seq, $has_recv); + @ret1, @ret2, @ret3, @ret4, $srtp_key_a, $srtp_key_b, $ts, $seq, $has_recv, $tmp_blob); @@ -26221,7 +26221,142 @@ rcv($sock_b, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); $resp = rtpe_req('statistics', 'statistics'); +# test MoH +($sock_a, $sock_b) = new_call([qw(198.51.100.1 33041)], [qw(198.51.100.3 33042)]); + +# declare that offerer is capable of moh +offer('Music on hold - sendrecv', { ICE => 'remove', DTLS => 'off', moh => { blob => $wav_file, connection => 'zero', mode => 'sendrecv' } }, < 'remove', moh => { 'db-id' => '123', connection => 'zero', mode => 'sendrecv' } }, < 'remove', DTLS => 'off' }, < 'remove' }, < 'remove', DTLS => 'off' }, < 'remove' }, <