MT#61630 Introduce MoH functionality

Introduced Music on Hold functionality:

- available only for the offer/answer model,
  no other scenarios (publish, subscriber etc.)
  are covered with it
- it gets advertised always at the beginning of
  the call (original offer/answer exchange)
- can be added for both sides: offerer/answerer
- the one who advertises its MoH capabilities
  with its SDP offer or answer, can later trigger
  MoH using sendonly SDP and unhold remote
  party using sendrecv SDP
- MoH covers only audio type of media sessions
- there is no specific selection of media sections
  to be held, thus, if one audio media puts the
  call on hold, the whole call is held
- list of parameters to be given when advertising
  MoH capabilities: a sound source (file, blob or db source);
  sendonly/sendrecv hold; zero-connection hold;
  At least an audio file source must be given
- MoH cannot be mixed with the play media functionality,
  the last one triggered will override previous one
- MoH must be unheld to stop the media being sent
  towards a recipient, otherwise only a termination
  of monologues will stop this packets stream

Change-Id: Iefd83ced79c14dadad936348a1d529007d6e7b3b
pull/1897/head
Donat Zenichev 12 months ago
parent de7d14bc0c
commit 9be0c02eca

@ -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 */

@ -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? */

@ -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)
{

@ -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;
};

@ -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;
};

@ -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);

@ -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' } }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 33041 RTP/AVP 8
c=IN IP4 198.51.100.1
a=sendrecv
----------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio PORT RTP/AVP 8
c=IN IP4 203.0.113.1
a=rtpmap:8 PCMA/8000
a=sendrecv
a=rtcp:PORT
SDP
# declare that answerer is capable of moh (fake db-id)
answer('Music on hold - sendrecv', { ICE => 'remove', moh => { 'db-id' => '123', connection => 'zero', mode => 'sendrecv' } }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 33042 RTP/AVP 8
c=IN IP4 198.51.100.3
a=sendrecv
--------------------------------------
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio PORT RTP/AVP 8
c=IN IP4 203.0.113.1
a=rtpmap:8 PCMA/8000
a=sendrecv
a=rtcp:PORT
SDP
# offerer puts on hold
offer('Music on hold - MoH set by offerer', { ICE => 'remove', DTLS => 'off' }, <<SDP);
v=0
o=- 1545997028 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 33041 RTP/AVP 8
c=IN IP4 198.51.100.1
a=sendonly
----------------------------------
v=0
o=- 1545997028 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio PORT RTP/AVP 8
c=IN IP4 203.0.113.1
a=rtpmap:8 PCMA/8000
a=sendonly
a=rtcp:PORT
SDP
# test received packets on the recepient side
(undef, $seq, $ts, $ssrc) = rcv($sock_b, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1));
rcv($sock_b, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2));
rcv($sock_b, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3));
rcv($sock_b, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4));
rcv($sock_b, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5));
answer('Music on hold - MoH set by offerer', { ICE => 'remove' }, <<SDP);
v=0
o=- 1545997028 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 33042 RTP/AVP 8
c=IN IP4 198.51.100.3
a=recvonly
--------------------------------------
v=0
o=- 1545997028 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio PORT RTP/AVP 8
c=IN IP4 203.0.113.1
a=rtpmap:8 PCMA/8000
a=recvonly
a=rtcp:PORT
SDP
# offerer puts off hold
offer('Music on hold - MoH put off by offerer', { ICE => 'remove', DTLS => 'off' }, <<SDP);
v=0
o=- 1545997029 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 33041 RTP/AVP 8
c=IN IP4 198.51.100.1
a=sendrecv
----------------------------------
v=0
o=- 1545997029 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio PORT RTP/AVP 8
c=IN IP4 203.0.113.1
a=rtpmap:8 PCMA/8000
a=sendrecv
a=rtcp:PORT
SDP
#rcv_no($sock_b);
answer('Music on hold - MoH put off by offerer', { ICE => 'remove' }, <<SDP);
v=0
o=- 1545997029 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 33042 RTP/AVP 8
c=IN IP4 198.51.100.3
a=sendrecv
--------------------------------------
v=0
o=- 1545997029 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio PORT RTP/AVP 8
c=IN IP4 203.0.113.1
a=rtpmap:8 PCMA/8000
a=sendrecv
a=rtcp:PORT
SDP
# SDP version tests

Loading…
Cancel
Save