diff --git a/README.md b/README.md index c21d40f66..b59a3931f 100644 --- a/README.md +++ b/README.md @@ -993,6 +993,9 @@ Optionally included keys are: e.g. `opus/48000/2/32000`. In this case, all format parameters (clock rate, channels) must also be specified. + Additional options that can be appended to the codec string with additional slashes + are ptime and the `fmtp` string, for example `iLBC/8000/1///mode=30`. + As a special case, if the `strip=all` option has been used and the `transcode` option is used on a codec that was originally present in the offer, then *rtpengine* will treat this codec the same as if it had been used with the `offer` diff --git a/daemon/codec.c b/daemon/codec.c index 1704cb5d7..78e03e4ab 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -1159,13 +1159,14 @@ void codec_packet_free(void *pp) { struct rtp_payload_type *codec_make_payload_type(const str *codec_str, struct call_media *media) { str codec_fmt = *codec_str; - str codec, parms, chans, opts, extra_opts; + str codec, parms, chans, opts, extra_opts, fmt_params; if (str_token_sep(&codec, &codec_fmt, '/')) return NULL; str_token_sep(&parms, &codec_fmt, '/'); str_token_sep(&chans, &codec_fmt, '/'); str_token_sep(&opts, &codec_fmt, '/'); str_token_sep(&extra_opts, &codec_fmt, '/'); + str_token_sep(&fmt_params, &codec_fmt, '/'); int clockrate = str_to_i(&parms, 0); int channels = str_to_i(&chans, 0); @@ -1182,7 +1183,7 @@ struct rtp_payload_type *codec_make_payload_type(const str *codec_str, struct ca ret->channels = channels; ret->bitrate = bitrate; ret->ptime = ptime; - ret->format_parameters = STR_EMPTY; + ret->format_parameters = fmt_params; const codec_def_t *def = codec_find(&ret->encoding, 0); ret->codec_def = def; @@ -1370,6 +1371,7 @@ static struct ssrc_entry *__ssrc_handler_transcode_new(void *p) { } ch->decoder = decoder_new_fmtp(h->source_pt.codec_def, h->source_pt.clock_rate, h->source_pt.channels, + h->source_pt.ptime, &ch->encoder_format, &h->source_pt.format_parameters); if (!ch->decoder) goto err; diff --git a/daemon/recording.c b/daemon/recording.c index 69be74082..a3fe945f1 100644 --- a/daemon/recording.c +++ b/daemon/recording.c @@ -783,6 +783,8 @@ static void setup_media_proc(struct call_media *media) { if (!recording) return; + append_meta_chunk_null(recording, "MEDIA %u PTIME %i", media->unique_id, media->ptime); + GList *pltypes = g_hash_table_get_values(media->codecs_recv); for (GList *l = pltypes; l; l = l->next) { diff --git a/lib/codeclib.c b/lib/codeclib.c index 2e27cff10..b73af45b8 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -42,6 +42,9 @@ static packetizer_f packetizer_amr; static format_init_f opus_init; static set_enc_options_f opus_set_enc_options; +static set_enc_options_f ilbc_set_enc_options; +static set_dec_options_f ilbc_set_dec_options; + static set_enc_options_f amr_set_enc_options; static set_dec_options_f amr_set_dec_options; @@ -54,6 +57,7 @@ static int avc_encoder_input(encoder_t *enc, AVFrame **frame); static void avc_encoder_close(encoder_t *enc); static int amr_decoder_input(decoder_t *dec, const str *data, GQueue *out); +static int ilbc_decoder_input(decoder_t *dec, const str *data, GQueue *out); static const char *dtmf_decoder_init(decoder_t *, const str *); static int dtmf_decoder_input(decoder_t *dec, const str *data, GQueue *out); @@ -70,6 +74,15 @@ static const codec_type_t codec_type_avcodec = { .encoder_input = avc_encoder_input, .encoder_close = avc_encoder_close, }; +static const codec_type_t codec_type_ilbc = { + .def_init = avc_def_init, + .decoder_init = avc_decoder_init, + .decoder_input = ilbc_decoder_input, + .decoder_close = avc_decoder_close, + .encoder_init = avc_encoder_init, + .encoder_input = avc_encoder_input, + .encoder_close = avc_encoder_close, +}; static const codec_type_t codec_type_amr = { .def_init = avc_def_init, .decoder_init = avc_decoder_init, @@ -242,11 +255,14 @@ static codec_def_t __codec_defs[] = { .avcodec_id = AV_CODEC_ID_ILBC, .default_clockrate = 8000, .default_channels = 1, - .default_ptime = 20, + .default_ptime = 30, + .default_fmtp = "mode=30", //.default_bitrate = 15200, .packetizer = packetizer_passthrough, .media_type = MT_AUDIO, - .codec_type = &codec_type_avcodec, + .codec_type = &codec_type_ilbc, + .set_enc_options = ilbc_set_enc_options, + .set_dec_options = ilbc_set_dec_options, }, { .rtpname = "opus", @@ -471,11 +487,11 @@ static const char *avc_decoder_init(decoder_t *dec, const str *fmtp) { -decoder_t *decoder_new_fmt(const codec_def_t *def, int clockrate, int channels, const format_t *resample_fmt) { - return decoder_new_fmtp(def, clockrate, channels, resample_fmt, NULL); +decoder_t *decoder_new_fmt(const codec_def_t *def, int clockrate, int channels, int ptime, const format_t *resample_fmt) { + return decoder_new_fmtp(def, clockrate, channels, ptime, resample_fmt, NULL); } -decoder_t *decoder_new_fmtp(const codec_def_t *def, int clockrate, int channels, const format_t *resample_fmt, +decoder_t *decoder_new_fmtp(const codec_def_t *def, int clockrate, int channels, int ptime, const format_t *resample_fmt, const str *fmtp) { const char *err; @@ -497,6 +513,10 @@ decoder_t *decoder_new_fmtp(const codec_def_t *def, int clockrate, int channels, ret->out_format = ret->in_format; if (resample_fmt) ret->out_format = *resample_fmt; + if (ptime > 0) + ret->ptime = ptime; + else + ret->ptime = def->default_ptime; err = def->codec_type->decoder_init(ret, fmtp); if (err) @@ -1358,6 +1378,101 @@ static void opus_set_enc_options(encoder_t *enc, const str *fmtp) { // XXX additional opus options } +static int ilbc_mode(int ptime, const str *fmtp, const char *direction) { + int mode = 0; + + if (fmtp) { + if (!str_cmp(fmtp, "mode=20")) { + mode = 20; + ilog(LOG_DEBUG, "Setting iLBC %s mode to 20 ms based on fmtp", direction); + } + else if (!str_cmp(fmtp, "mode=30")) { + mode = 30; + ilog(LOG_DEBUG, "Setting iLBC %s mode to 30 ms based on fmtp", direction); + } + } + + if (!mode) { + switch (ptime) { + case 20: + case 40: + case 60: + case 80: + case 100: + case 120: + mode = 20; + ilog(LOG_DEBUG, "Setting iLBC %s mode to 20 ms based on ptime %i", + direction, ptime); + break; + case 30: + case 90: + mode = 30; + ilog(LOG_DEBUG, "Setting iLBC %s mode to 30 ms based on ptime %i", + direction, ptime); + break; + } + } + + if (!mode) { + mode = 20; + ilog(LOG_WARNING, "No iLBC %s mode specified, setting to 20 ms", direction); + } + + return mode; +} + +static void ilbc_set_enc_options(encoder_t *enc, const str *fmtp) { + int ret; + int mode = ilbc_mode(enc->ptime, fmtp, "encoder"); + + if ((ret = av_opt_set_int(enc->u.avc.avcctx, "mode", mode, + AV_OPT_SEARCH_CHILDREN))) + ilog(LOG_WARN, "Failed to set iLBC mode option to %i: %s", + mode, av_error(ret)); +} + +static void ilbc_set_dec_options(decoder_t *dec, const str *fmtp) { + int mode = ilbc_mode(dec->ptime, fmtp, "decoder"); + if (mode == 20) + dec->u.avc.avcctx->block_align = 38; + else if (mode == 30) + dec->u.avc.avcctx->block_align = 50; + else + ilog(LOG_WARN, "Unsupported iLBC mode %i", mode); +} + +static int ilbc_decoder_input(decoder_t *dec, const str *data, GQueue *out) { + int mode = 0, block_align = 0; + static const str mode_20 = STR_CONST_INIT("mode=20"); + static const str mode_30 = STR_CONST_INIT("mode=30"); + const str *fmtp; + + if (data->len % 50 == 0) { + mode = 30; + block_align = 50; + fmtp = &mode_30; + } + else if (data->len % 38 == 0) { + mode = 20; + block_align = 38; + fmtp = &mode_20; + } + else + ilog(LOG_WARNING | LOG_FLAG_LIMIT, "iLBC received %i bytes packet, does not match " + "one of the block sizes", (int) data->len); + + if (block_align && dec->u.avc.avcctx->block_align != block_align) { + ilog(LOG_INFO | LOG_FLAG_LIMIT, "iLBC decoder set to %i bytes blocks, but received packet " + "of %i bytes, therefore resetting decoder and switching to %i bytes " + "block mode (%i ms mode)", + (int) dec->u.avc.avcctx->block_align, (int) data->len, block_align, mode); + avc_decoder_close(dec); + avc_decoder_init(dec, fmtp); + } + + return avc_decoder_input(dec, data, out); +} + diff --git a/lib/codeclib.h b/lib/codeclib.h index 91bc700cc..5227e4c2d 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -150,6 +150,7 @@ struct decoder_s { unsigned long rtp_ts; uint64_t pts; + int ptime; }; struct encoder_s { @@ -200,8 +201,8 @@ const codec_def_t *codec_find_by_av(enum AVCodecID); enum media_type codec_get_type(const str *type); -decoder_t *decoder_new_fmt(const codec_def_t *def, int clockrate, int channels, const format_t *resample_fmt); -decoder_t *decoder_new_fmtp(const codec_def_t *def, int clockrate, int channels, const format_t *resample_fmt, +decoder_t *decoder_new_fmt(const codec_def_t *def, int clockrate, int channels, int ptime, const format_t *resample_fmt); +decoder_t *decoder_new_fmtp(const codec_def_t *def, int clockrate, int channels, int ptime, const format_t *resample_fmt, const str *fmtp); void decoder_close(decoder_t *dec); int decoder_input_data(decoder_t *dec, const str *data, unsigned long ts, diff --git a/recording-daemon/decoder.c b/recording-daemon/decoder.c index 0c04ec0c0..42d4ebcbc 100644 --- a/recording-daemon/decoder.c +++ b/recording-daemon/decoder.c @@ -24,7 +24,7 @@ int resample_audio; -decode_t *decoder_new(const char *payload_str, output_t *outp) { +decode_t *decoder_new(const char *payload_str, int ptime, output_t *outp) { str name; char *slash = strchr(payload_str, '/'); if (!slash) { @@ -79,7 +79,7 @@ decode_t *decoder_new(const char *payload_str, output_t *outp) { outp->encoder->requested_format.format = out_format.format; } - decoder_t *dec = decoder_new_fmt(def, rtp_clockrate, channels, &out_format); + decoder_t *dec = decoder_new_fmt(def, rtp_clockrate, channels, ptime, &out_format); if (!dec) return NULL; decode_t *deco = g_slice_alloc0(sizeof(decode_t)); diff --git a/recording-daemon/decoder.h b/recording-daemon/decoder.h index 955d509ee..302ece1fe 100644 --- a/recording-daemon/decoder.h +++ b/recording-daemon/decoder.h @@ -8,7 +8,7 @@ extern int resample_audio; -decode_t *decoder_new(const char *payload_str, output_t *); +decode_t *decoder_new(const char *payload_str, int ptime, output_t *); int decoder_input(decode_t *, const str *, unsigned long ts, ssrc_t *); void decoder_free(decode_t *); diff --git a/recording-daemon/metafile.c b/recording-daemon/metafile.c index 9b98885ff..989f4d9a6 100644 --- a/recording-daemon/metafile.c +++ b/recording-daemon/metafile.c @@ -103,6 +103,12 @@ static void meta_rtp_payload_type(metafile_t *mf, unsigned long mnum, unsigned i { dbg("payload type in media %lu num %u is %s", mnum, payload_num, payload_type); + int ptime = 0; + + mnum--; + if (mnum < G_N_ELEMENTS(mf->media_ptimes)) + ptime = mf->media_ptimes[mnum]; + if (payload_num >= 128) { ilog(LOG_ERR, "Payload type number %u is invalid", payload_num); return; @@ -111,11 +117,22 @@ static void meta_rtp_payload_type(metafile_t *mf, unsigned long mnum, unsigned i pthread_mutex_lock(&mf->payloads_lock); mf->payload_types[payload_num] = g_string_chunk_insert(mf->gsc, payload_type); + mf->payload_ptimes[payload_num] = ptime; pthread_mutex_unlock(&mf->payloads_lock); } } +// mf is locked +static void meta_ptime(metafile_t *mf, unsigned long mnum, int ptime) +{ + mnum--; + if (mnum >= G_N_ELEMENTS(mf->media_ptimes)) + return; + mf->media_ptimes[mnum] = ptime; +} + + // mf is locked static void meta_metadata(metafile_t *mf, char *content) { mf->metadata = g_string_chunk_insert(mf->gsc, content); @@ -130,6 +147,7 @@ static void meta_metadata(metafile_t *mf, char *content) { static void meta_section(metafile_t *mf, char *section, char *content, unsigned long len) { unsigned long lu; unsigned int u; + int i; if (!strcmp(section, "CALL-ID")) mf->call_id = g_string_chunk_insert(mf->gsc, content); @@ -143,6 +161,8 @@ static void meta_section(metafile_t *mf, char *section, char *content, unsigned meta_stream_details(mf, lu, content); else if (sscanf_match(section, "MEDIA %lu PAYLOAD TYPE %u", &lu, &u) == 2) meta_rtp_payload_type(mf, lu, u, content); + else if (sscanf_match(section, "MEDIA %lu PTIME %i", &lu, &i) == 2) + meta_ptime(mf, lu, i); else if (sscanf_match(section, "TAG %lu", &lu) == 1) tag_name(mf, lu, content); else if (sscanf_match(section, "LABEL %lu", &lu) == 1) diff --git a/recording-daemon/packet.c b/recording-daemon/packet.c index d423eb81d..27826bec7 100644 --- a/recording-daemon/packet.c +++ b/recording-daemon/packet.c @@ -245,6 +245,7 @@ static void packet_decode(ssrc_t *ssrc, packet_t *packet) { metafile_t *mf = ssrc->metafile; pthread_mutex_lock(&mf->payloads_lock); char *payload_str = mf->payload_types[payload_type]; + int ptime = mf->payload_ptimes[payload_type]; pthread_mutex_unlock(&mf->payloads_lock); if (!payload_str) { @@ -264,7 +265,7 @@ static void packet_decode(ssrc_t *ssrc, packet_t *packet) { outp = mf->mix_out; else if (ssrc->output) outp = ssrc->output; - ssrc->decoders[payload_type] = decoder_new(payload_str, outp); + ssrc->decoders[payload_type] = decoder_new(payload_str, ptime, outp); pthread_mutex_unlock(&mf->mix_lock); if (!ssrc->decoders[payload_type]) { ilog(LOG_WARN, "Cannot decode RTP payload type %u (%s)", diff --git a/recording-daemon/types.h b/recording-daemon/types.h index 5217ba7c2..d43214818 100644 --- a/recording-daemon/types.h +++ b/recording-daemon/types.h @@ -130,6 +130,8 @@ struct metafile_s { pthread_mutex_t payloads_lock; char *payload_types[128]; + int payload_ptimes[128]; + int media_ptimes[4]; int recording_on:1; int forwarding_on:1; diff --git a/t/amr-decode-test.c b/t/amr-decode-test.c index 9ad373d59..9e5319641 100644 --- a/t/amr-decode-test.c +++ b/t/amr-decode-test.c @@ -51,7 +51,7 @@ static void do_test_amr_xx(const char *file, int line, str_init(&fmtp_str, fmtp_s); fmtp = &fmtp_str; } - decoder_t *d = decoder_new_fmtp(def, clockrate, 1, &fmt, fmtp); + decoder_t *d = decoder_new_fmtp(def, clockrate, 1, 0, &fmt, fmtp); assert(d); const str data = { data_s, data_len }; int ret = decoder_input_data(d, &data, 1, frame_cb, &expect_s, &expect_len); diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 7320f6843..25832b2ed 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -196,6 +196,142 @@ my ($sock_a, $sock_b, $port_a, $port_b, $ssrc, $resp, $srtp_ctx_a, $srtp_ctx_b); +if (0) { + +# github issue 854 + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 7326)], [qw(198.51.100.3 7328)]); + +($port_a) = offer('gh854 inbound 30 ms', + { ICE => 'remove', replace => ['origin'], codec => { transcode => ['PCMA'] } }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], codec => { transcode => ['PCMA'] } }, < 'remove', replace => ['origin'] }, <{media_id} COMPONENT $stream->{component} ". "FLAGS 0"); if ($ARGV[2]) { + if ($ARGV[3]) { + put_meta("MEDIA $stream->{media_id} PTIME $ARGV[3]", ''); + } put_meta("MEDIA $stream->{media_id} PAYLOAD TYPE $ARGV[1]", $ARGV[2]); } my @ret = msg_ret(7, '', 'I I I I',