diff --git a/README.md b/README.md index 0f2af5ec2..5511b001f 100644 --- a/README.md +++ b/README.md @@ -433,6 +433,7 @@ The following codecs are supported by *rtpengine*: * iLBC * Opus * AMR (narrowband and wideband) +* EVS (if supplied -- see below) Codec support is dependent on support provided by the `ffmpeg` codec libraries, which may vary from version to version. Use the `--codecs` command line option to have *rtpengine* print a list of codecs @@ -557,6 +558,33 @@ outgoing bitrate without being requested to by the peer via a CMR. To enable thi longer than this interval ago, *rtpengine* will increase the bitrate by one step if possible. Afterwards, the interval starts over. +EVS +--- + +Enhanced Voice Services (EVS) is a patent-encumbered codec for which (at the +time of writing) no implementation exists which can be freely used and +distributed. As such, support for EVS is only available if an implementation is +supplied separately. Currently the only implementation supported is the +ETSI/3GPP reference implementation (either floating-point or fixed-point). Any +licensing issues that might result from such usage are the responsibility of +the user of this software. + +The EVS codec implementation can be provided as a shared object library (*.so*) +which is loaded in during runtime (at startup). The supported implementations +can be seen as subdirectories within the `evs/` directory. Currently supported +are version 17.0.0 of the ETSI/3GPP reference implementation, *126.442* for the +fixed-point implementation and *126.443* for the floating-point implementation. +(The floating-point implementation seems to be significantly faster, but is not +bit-precise.) + +To supply the codec implementation as a shared object during runtime, extract +the reference implementation's *.zip* file and apply the provided `patch` +([from here](https://github.com/sipwise/rtpengine/tree/master/evs)) that is +appropriate for the chosen implementation. Run the build using `make` +(suggested build flags are `RELEASE=1 make`) and it should produce a file +`lib3gpp-evs.so`. Point *rtpengine* to this file using the `evs-lib-path=` +option to enable support for EVS. + Call recording ============== diff --git a/daemon/Makefile b/daemon/Makefile index 5bda5f223..3e95eed58 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -41,7 +41,7 @@ endif #CFLAGS+= -DSRTCP_KEY_DERIVATION_RFC_COMPLIANCE #CFLAGS+= -DSTRICT_SDES_KEY_LIFETIME -LDLIBS= -lm +LDLIBS= -lm -ldl LDLIBS+= $(shell pkg-config --libs glib-2.0) LDLIBS+= $(shell pkg-config --libs gthread-2.0) LDLIBS+= $(shell pkg-config --libs zlib) diff --git a/daemon/codec.c b/daemon/codec.c index 04b4df876..250740134 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -2343,6 +2343,12 @@ static int codec_decoder_event(enum codec_event event, void *ptr, void *data) { // ignore locking and races for this media->encoder_callback.amr.cmr_out = GPOINTER_TO_UINT(ptr); media->encoder_callback.amr.cmr_out_ts = rtpe_now; + break; + case CE_EVS_CMR_RECV: + // ignore locking and races for this + media->encoder_callback.evs.cmr_in = GPOINTER_TO_UINT(ptr); + media->encoder_callback.evs.cmr_in_ts = rtpe_now; + break; default: break; } diff --git a/daemon/rtpengine.pod b/daemon/rtpengine.pod index c30d53468..e8e9b0ff5 100644 --- a/daemon/rtpengine.pod +++ b/daemon/rtpengine.pod @@ -356,6 +356,11 @@ launched. Set the stack size of each thread to the value given in kB. Defaults to 2048 kB. Can be set to -1 to leave the default provided by the OS unchanged. +=item B<--evs-lib-path=>I + +Points to the shared object file (B<.so>) containing the reference +implementation for the EVS codec. See the F for more details. + =item B<--sip-source> The original B as well as older version of B by default diff --git a/evs/etsi-ts-126.442-3gpp-ts-26.442-v17.0.0/patch b/evs/etsi-ts-126.442-3gpp-ts-26.442-v17.0.0/patch new file mode 100644 index 000000000..496b0ea7f --- /dev/null +++ b/evs/etsi-ts-126.442-3gpp-ts-26.442-v17.0.0/patch @@ -0,0 +1,131 @@ +diff --git a/Makefile b/Makefile +--- a/Makefile ++++ b/Makefile +@@ -3,6 +3,7 @@ + # Paths + SRC_ENC = lib_enc lib_com basic_op basic_math + SRC_DEC = lib_dec lib_com basic_op basic_math ++SRC_LIB = lib_dec lib_enc lib_com basic_op basic_math + BUILD = build + + SRC_DIRS = $(sort -u $(SRC_ENC) $(SRC_DEC)) +@@ -10,6 +11,7 @@ SRC_DIRS = $(sort -u $(SRC_ENC) $(SRC_DEC)) + # Name of CLI binaries + CLI_ENC = EVS_cod + CLI_DEC = EVS_dec ++LIB_SO = lib3gpp-evs.so + + # Default tool settings + CC = gcc +@@ -49,13 +51,17 @@ CFLAGS += $(foreach DIR,$(SRC_DIRS),-I$(DIR)) + # Source file search paths + VPATH = $(SRC_DIRS) + ++CFLAGS += -fPIC ++ + ############################################################################### + + SRCS_ENC = $(foreach DIR,$(SRC_ENC),$(patsubst $(DIR)/%,%,$(wildcard $(DIR)/*.c))) + SRCS_DEC = $(foreach DIR,$(SRC_DEC),$(patsubst $(DIR)/%,%,$(wildcard $(DIR)/*.c))) ++SRCS_LIB = $(filter-out encoder.c decoder.c voip_client.c,$(foreach DIR,$(SRC_LIB),$(patsubst $(DIR)/%,%,$(wildcard $(DIR)/*.c)))) + + OBJS_ENC = $(addprefix $(BUILD)/,$(SRCS_ENC:.c=.o)) + OBJS_DEC = $(addprefix $(BUILD)/,$(SRCS_DEC:.c=.o)) ++OBJS_LIB = $(addprefix $(BUILD)/,$(SRCS_LIB:.c=.o)) + + DEPS = $(addprefix $(BUILD)/,$(SRCS_ENC:.c=.P) $(SRCS_DEC:.c=.P)) + +@@ -63,7 +69,7 @@ DEPS = $(addprefix $(BUILD)/,$(SRCS_ENC:.c=.P) $(SRCS_DEC:.c=.P)) + + .PHONY: all clean clean_all + +-all: $(CLI_ENC) $(CLI_DEC) ++all: $(CLI_ENC) $(CLI_DEC) $(LIB_SO) + + $(BUILD): + $(QUIET)mkdir -p $(BUILD) +@@ -74,6 +80,9 @@ $(CLI_ENC): $(OBJS_ENC) + $(CLI_DEC): $(OBJS_DEC) + $(QUIET_LINK)$(CC) $(LDFLAGS) $(OBJS_DEC) -lm -o $(CLI_DEC) + ++$(LIB_SO): $(OBJS_LIB) ++ $(QUIET_LINK)$(CC) $(LDFLAGS) $(OBJS_LIB) -lm -shared -o $(LIB_SO) ++ + clean: + $(QUIET)$(RM) $(OBJS_ENC) $(OBJS_DEC) $(DEPS) + $(QUIET)$(RM) $(DEPS:.P=.d) +diff --git a/lib_com/prot_fx.h b/lib_com/prot_fx.h +--- a/lib_com/prot_fx.h ++++ b/lib_com/prot_fx.h +@@ -622,6 +622,11 @@ void SetModeIndex( + void init_encoder_fx( + Encoder_State_fx *st_fx /* i/o: Encoder static variables structure */ + ); ++unsigned long encoder_size(void); ++unsigned long encoder_ind_list_size(void); ++void encoder_set_opts(Encoder_State_fx *st_fx, unsigned long Fs, void *ind_list); ++void encoder_set_brate(Encoder_State_fx *st, unsigned long brate, unsigned int bwidth, ++ unsigned int mode, unsigned int amr); + + void destroy_encoder_fx( Encoder_State_fx *st_fx ); + +@@ -654,6 +659,8 @@ Word16 gp_clip_fx( + void init_decoder_fx( + Decoder_State_fx *st_fx /* o: Decoder static variables structure */ + ); ++unsigned long decoder_size(void); ++void decoder_set_Fs(Decoder_State_fx *st_fx, unsigned long Fs); + + void destroy_decoder( + Decoder_State_fx *st_fx /* o: Decoder static variables structure */ +diff --git a/lib_dec/init_dec_fx.c b/lib_dec/init_dec_fx.c +--- a/lib_dec/init_dec_fx.c ++++ b/lib_dec/init_dec_fx.c +@@ -965,3 +965,12 @@ void destroy_decoder( + + return; + } ++ ++ ++ ++unsigned long decoder_size(void) { ++ return sizeof(Decoder_State_fx); ++} ++void decoder_set_Fs(Decoder_State_fx *st_fx, unsigned long Fs) { ++ st_fx->output_Fs_fx = Fs; ++} +diff --git a/lib_enc/init_enc_fx.c b/lib_enc/init_enc_fx.c +--- a/lib_enc/init_enc_fx.c ++++ b/lib_enc/init_enc_fx.c +@@ -1006,3 +1006,31 @@ void destroy_encoder_fx( + + return; + } ++ ++ ++ ++unsigned long encoder_size(void) { ++ return sizeof(Encoder_State_fx); ++} ++void encoder_set_opts(Encoder_State_fx *st_fx, unsigned long Fs, void *ind_list) { ++ st_fx->input_Fs_fx = Fs; ++ st_fx->ind_list_fx = ind_list; ++ st_fx->rf_fec_indicator = 1; ++ st_fx->last_codec_mode = st_fx->codec_mode; ++} ++void encoder_set_brate(Encoder_State_fx *st, unsigned long brate, unsigned int bwidth, ++ unsigned int mode, unsigned int amr) { ++ if (brate == 5900) { ++ st->Opt_SC_VBR_fx = 1; ++ brate = 7200; ++ } ++ else ++ st->Opt_SC_VBR_fx = 0; ++ st->total_brate_fx = brate; ++ st->codec_mode = mode; ++ st->Opt_AMR_WB_fx = amr; ++ st->max_bwidth_fx = bwidth; ++} ++unsigned long encoder_ind_list_size(void) { ++ return sizeof(Indice_fx) * MAX_NUM_INDICES; ++} diff --git a/evs/etsi-ts-126.443-3gpp-ts-26.443-v17.0.0/patch b/evs/etsi-ts-126.443-3gpp-ts-26.443-v17.0.0/patch new file mode 100644 index 000000000..5b2436856 --- /dev/null +++ b/evs/etsi-ts-126.443-3gpp-ts-26.443-v17.0.0/patch @@ -0,0 +1,131 @@ +diff --git a/Makefile b/Makefile +--- a/Makefile ++++ b/Makefile +@@ -3,6 +3,7 @@ + # Paths + SRC_ENC = lib_enc lib_com + SRC_DEC = lib_dec lib_com ++SRC_LIB = lib_dec lib_enc lib_com + BUILD = build + + SRC_DIRS = $(sort -u $(SRC_ENC) $(SRC_DEC)) +@@ -10,6 +11,7 @@ SRC_DIRS = $(sort -u $(SRC_ENC) $(SRC_DEC)) + # Name of CLI binaries + CLI_ENC = EVS_cod + CLI_DEC = EVS_dec ++LIB_SO = lib3gpp-evs.so + + # Default tool settings + CC = gcc +@@ -62,13 +64,17 @@ CFLAGS += $(foreach DIR,$(SRC_DIRS),-I$(DIR)) + # Source file search paths + VPATH = $(SRC_DIRS) + ++CFLAGS += -fPIC ++ + ############################################################################### + + SRCS_ENC = $(foreach DIR,$(SRC_ENC),$(patsubst $(DIR)/%,%,$(wildcard $(DIR)/*.c))) + SRCS_DEC = $(foreach DIR,$(SRC_DEC),$(patsubst $(DIR)/%,%,$(wildcard $(DIR)/*.c))) ++SRCS_LIB = $(filter-out encoder.c decoder.c voip_client.c,$(foreach DIR,$(SRC_LIB),$(patsubst $(DIR)/%,%,$(wildcard $(DIR)/*.c)))) + + OBJS_ENC = $(addprefix $(BUILD)/,$(SRCS_ENC:.c=.o)) + OBJS_DEC = $(addprefix $(BUILD)/,$(SRCS_DEC:.c=.o)) ++OBJS_LIB = $(addprefix $(BUILD)/,$(SRCS_LIB:.c=.o)) + + DEPS = $(addprefix $(BUILD)/,$(SRCS_ENC:.c=.P) $(SRCS_DEC:.c=.P)) + +@@ -76,7 +82,7 @@ DEPS = $(addprefix $(BUILD)/,$(SRCS_ENC:.c=.P) $(SRCS_DEC:.c=.P)) + + .PHONY: all clean clean_all + +-all: $(CLI_ENC) $(CLI_DEC) ++all: $(CLI_ENC) $(CLI_DEC) $(LIB_SO) + + $(BUILD): + $(QUIET)mkdir -p $(BUILD) +@@ -87,6 +93,9 @@ $(CLI_ENC): $(OBJS_ENC) + $(CLI_DEC): $(OBJS_DEC) + $(QUIET_LINK)$(CC) $(LDFLAGS) $(OBJS_DEC) -lm -o $(CLI_DEC) + ++$(LIB_SO): $(OBJS_LIB) ++ $(QUIET_LINK)$(CC) $(LDFLAGS) $(OBJS_LIB) -lm -shared -o $(LIB_SO) ++ + clean: + $(QUIET)$(RM) $(OBJS_ENC) $(OBJS_DEC) $(DEPS) + $(QUIET)$(RM) $(DEPS:.P=.d) +diff --git a/lib_com/prot.h b/lib_com/prot.h +--- a/lib_com/prot.h ++++ b/lib_com/prot.h +@@ -624,6 +624,11 @@ short lsp_convert_poly( + const short L_frame, /* i : flag for up or down conversion */ + const short Opt_AMRWB /* i : flag for the AMR-WB IO mode */ + ); ++unsigned long encoder_size(void); ++unsigned long encoder_ind_list_size(void); ++void encoder_set_opts(Encoder_State *st, unsigned long Fs, void *ind_list); ++void encoder_set_brate(Encoder_State *st, unsigned long brate, unsigned int bwidth, ++ unsigned int mode, unsigned int amr); + + short findpulse( /* o : pulse position */ + const short L_frame, /* i : length of the frame */ +@@ -651,6 +656,8 @@ void preemph( + const short L, /* i : vector size */ + float *mem /* i/o: memory (x[-1]) */ + ); ++unsigned long decoder_size(void); ++void decoder_set_Fs(Decoder_State *st, unsigned long Fs); + + void cb_shape( + const short preemphFlag, /* i : flag for pre-emphasis */ +diff --git a/lib_dec/init_dec.c b/lib_dec/init_dec.c +--- a/lib_dec/init_dec.c ++++ b/lib_dec/init_dec.c +@@ -653,3 +653,12 @@ void destroy_decoder( + + return; + } ++ ++ ++ ++unsigned long decoder_size(void) { ++ return sizeof(Decoder_State); ++} ++void decoder_set_Fs(Decoder_State *st, unsigned long Fs) { ++ st->output_Fs = Fs; ++} +diff --git a/lib_enc/init_enc.c b/lib_enc/init_enc.c +--- a/lib_enc/init_enc.c ++++ b/lib_enc/init_enc.c +@@ -729,3 +729,31 @@ void destroy_encoder( + + return; + } ++ ++ ++ ++unsigned long encoder_size(void) { ++ return sizeof(Encoder_State); ++} ++void encoder_set_opts(Encoder_State *st, unsigned long Fs, void *ind_list) { ++ st->input_Fs = Fs; ++ st->ind_list = ind_list; ++ st->rf_fec_indicator = 1; ++ st->last_codec_mode = st->codec_mode; ++} ++void encoder_set_brate(Encoder_State *st, unsigned long brate, unsigned int bwidth, ++ unsigned int mode, unsigned int amr) { ++ if (brate == 5900) { ++ st->Opt_SC_VBR = 1; ++ brate = 7200; ++ } ++ else ++ st->Opt_SC_VBR = 0; ++ st->total_brate = brate; ++ st->codec_mode = mode; ++ st->Opt_AMR_WB = amr; ++ st->max_bwidth = bwidth; ++} ++unsigned long encoder_ind_list_size(void) { ++ return sizeof(Indice) * MAX_NUM_INDICES; ++} diff --git a/lib/auxlib.c b/lib/auxlib.c index 880f444ab..083bb8227 100644 --- a/lib/auxlib.c +++ b/lib/auxlib.c @@ -162,6 +162,7 @@ void config_load(int *argc, char ***argv, GOptionEntry *app_entries, const char { "pidfile", 'p', 0, G_OPTION_ARG_FILENAME, &rtpe_common_config_ptr->pidfile, "Write PID to file", "FILE" }, { "foreground", 'f', 0, G_OPTION_ARG_NONE, &rtpe_common_config_ptr->foreground, "Don't fork to background", NULL }, { "thread-stack", 0,0, G_OPTION_ARG_INT, &rtpe_common_config_ptr->thread_stack, "Thread stack size in kB", "INT" }, + { "evs-lib-path", 0,0, G_OPTION_ARG_FILENAME, &rtpe_common_config_ptr->evs_lib_path, "Location of .so for 3GPP EVS codec", "FILE" }, { NULL, } }; #undef ll diff --git a/lib/auxlib.h b/lib/auxlib.h index 65f397cb9..568e1ef8b 100644 --- a/lib/auxlib.h +++ b/lib/auxlib.h @@ -30,6 +30,7 @@ struct rtpengine_common_config { int foreground; int thread_stack; int max_log_line_length; + char *evs_lib_path; }; extern struct rtpengine_common_config *rtpe_common_config_ptr; diff --git a/lib/codeclib.c b/lib/codeclib.c index e95c73765..99323c62f 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef HAVE_BCG729 #include #include @@ -68,6 +69,7 @@ static int format_cmp_ignore(const struct rtp_payload_type *, const struct rtp_p static int generic_silence_dtx(decoder_t *, GQueue *, int); static int amr_dtx(decoder_t *, GQueue *, int); +static int evs_dtx(decoder_t *, GQueue *, int); static int generic_cn_dtx_init(decoder_t *); static void generic_cn_dtx_cleanup(decoder_t *); @@ -76,6 +78,44 @@ static int generic_cn_dtx(decoder_t *, GQueue *, int); +static void *evs_lib_handle; +static unsigned int evs_decoder_size; +static unsigned int evs_encoder_size; +static unsigned int evs_encoder_ind_list_size; +static void (*evs_init_decoder)(void *); +static void (*evs_init_encoder)(void *); +static void (*evs_destroy_decoder)(void *); +static void (*evs_destroy_encoder)(void *); +static void (*evs_set_encoder_opts)(void *, unsigned long, void *); +static void (*evs_set_encoder_brate)(void *, unsigned long br, unsigned int bwidth, + unsigned int mode, unsigned int amr); +static void (*evs_set_decoder_Fs)(void *, unsigned long); +static void (*evs_enc_in)(void *, const uint16_t *s, const uint16_t n); +static void (*evs_amr_enc_in)(void *, const uint16_t *s, const uint16_t n); +static void (*evs_enc_out)(void *, unsigned char *buf, uint16_t *len); +static void (*evs_dec_in)(void *, char *in, uint16_t len, uint16_t amr_mode, uint16_t core_mode, + uint16_t q_bit, uint16_t partial_frame, uint16_t next_type); +static void (*evs_dec_out)(void *, void *, int frame_mode); // frame_mode=1: missing +static void (*evs_amr_dec_out)(void *, void *); +static void (*evs_syn_output)(float *in, const uint16_t len, uint16_t *out); +static void (*evs_reset_enc_ind)(void *); + +static void evs_def_init(codec_def_t *); +static const char *evs_decoder_init(decoder_t *, const str *); +static int evs_decoder_input(decoder_t *dec, const str *data, GQueue *out); +static void evs_decoder_close(decoder_t *); +static const char *evs_encoder_init(encoder_t *enc, const str *); +static int evs_encoder_input(encoder_t *enc, AVFrame **frame); +static void evs_encoder_close(encoder_t *); +static format_parse_f evs_format_parse; +static format_cmp_f evs_format_cmp; +static format_print_f evs_format_print; +static format_answer_f evs_format_answer; + + + + + static const codec_type_t codec_type_avcodec = { .def_init = avc_def_init, .decoder_init = avc_decoder_init, @@ -104,6 +144,16 @@ static const codec_type_t codec_type_amr = { .encoder_got_packet = amr_encoder_got_packet, .encoder_close = avc_encoder_close, }; +static const codec_type_t codec_type_evs = { + .def_init = evs_def_init, + .decoder_init = evs_decoder_init, + .decoder_input = evs_decoder_input, + .decoder_close = evs_decoder_close, + .encoder_init = evs_encoder_init, + .encoder_input = evs_encoder_input, +// .encoder_got_packet = amr_encoder_got_packet, + .encoder_close = evs_encoder_close, +}; static const codec_type_t codec_type_dtmf = { .decoder_init = dtmf_decoder_init, .decoder_input = dtmf_decoder_input, @@ -129,6 +179,10 @@ static const dtx_method_t dtx_method_amr = { .method_id = DTX_NATIVE, .do_dtx = amr_dtx, }; +static const dtx_method_t dtx_method_evs = { + .method_id = DTX_NATIVE, + .do_dtx = evs_dtx, +}; #ifdef HAVE_BCG729 static packetizer_f packetizer_g729; // aggregate some frames into packets @@ -366,6 +420,29 @@ static codec_def_t __codec_defs[] = { [DTX_CN] = &dtx_method_cn, }, }, + { + .rtpname = "EVS", + .avcodec_id = -1, + .clockrate_mult = 3, + .default_clockrate = 16000, + .default_channels = 1, + .default_ptime = 20, + .default_bitrate = 16400, + .default_fmtp = "dtx=0;dtx-recv=0", + .format_parse = evs_format_parse, + .format_cmp = evs_format_cmp, + .format_print = evs_format_print, + .format_answer = evs_format_answer, + .packetizer = packetizer_passthrough, + .bits_per_sample = 1, + .media_type = MT_AUDIO, + .codec_type = &codec_type_evs, + .dtx_methods = { + [DTX_NATIVE] = &dtx_method_evs, + [DTX_SILENCE] = &dtx_method_silence, + [DTX_CN] = &dtx_method_cn, + }, + }, { .rtpname = "vorbis", .avcodec_id = AV_CODEC_ID_VORBIS, @@ -1038,6 +1115,8 @@ void codeclib_free(void) { g_hash_table_destroy(codecs_ht); g_hash_table_destroy(codecs_ht_by_av); avformat_network_deinit(); + if (evs_lib_handle) + dlclose(evs_lib_handle); } void codeclib_init(int print) { @@ -2802,3 +2881,1145 @@ void frame_fill_dtmf_samples(enum AVSampleFormat fmt, void *samples, unsigned in break; } } + + + + + +// lamely parse out decimal numbers without using floating point +static unsigned int str_to_i_k(str *s) { + str intg; + str frac = *s; + if (!str_token(&intg, &frac, '.')) { + unsigned int ret = str_to_i(s, 0) * 1000; + if (frac.len > 1) // at most one decimal digit + frac.len = 1; + return ret + str_to_i(&frac, 0) * 100; + } + return str_to_i(s, 0) * 1000; +} + +static const char *evs_bw_strings[__EVS_BW_MAX] = { "nb", "wb", "swb", "fb" }; + +static void evs_parse_bw(enum evs_bw *minp, enum evs_bw *maxp, const str *token) { + if (!str_cmp(token, "nb")) + *maxp = EVS_BW_NB; + else if (!str_cmp(token, "wb")) + *maxp = EVS_BW_WB; + else if (!str_cmp(token, "swb")) + *maxp = EVS_BW_SWB; + else if (!str_cmp(token, "fb")) + *maxp = EVS_BW_FB; + else if (!str_cmp(token, "nb-wb")) { + *minp = EVS_BW_NB; + *maxp = EVS_BW_WB; + } + else if (!str_cmp(token, "nb-swb")) { + *minp = EVS_BW_NB; + *maxp = EVS_BW_SWB; + } + else if (!str_cmp(token, "nb-fb")) { + *minp = EVS_BW_NB; + *maxp = EVS_BW_FB; + } + // the ones below are not mentioned in the spec - lower bound ignored + else if (!str_cmp(token, "wb-swb")) { + *minp = EVS_BW_WB; + *maxp = EVS_BW_SWB; + } + else if (!str_cmp(token, "wb-fb")) { + *minp = EVS_BW_WB; + *maxp = EVS_BW_FB; + } + else if (!str_cmp(token, "swb-fb")) { + *minp = EVS_BW_SWB; + *maxp = EVS_BW_FB; + } + else + ilog(LOG_WARN, "EVS: bandwidth selection '" STR_FORMAT "' not understood", + STR_FMT(token)); +} +static void evs_parse_br(unsigned int *minp, unsigned int *maxp, str *token) { + str min; + str max = *token; + if (!str_token(&min, &max, '-')) { + *minp = str_to_i_k(&min); + *maxp = str_to_i_k(&max); + } + else + *minp = *maxp = str_to_i_k(token); + if (*minp > *maxp) { + ilog(LOG_WARN, "EVS: min bitrate %u is larger than max bitrate %u", + *minp, *maxp); + *maxp = *minp; + } +} +// lamely print fractional number +static void evs_print_frac_num(GString *s, unsigned int num) { + unsigned int frac = (num / 100 % 10); + unsigned int intg = num / 1000; + if (frac) + g_string_append_printf(s, "%u.%u", intg, frac); + else + g_string_append_printf(s, "%u", intg); +} +static void evs_format_print_br(GString *s, const char *k, unsigned int min, unsigned int max) { + if (!max) + return; + + g_string_append(s, k); + g_string_append_c(s, '='); + + if (min != max) { + evs_print_frac_num(s, min); + g_string_append_c(s, '-'); + } + evs_print_frac_num(s, max); + g_string_append(s, "; "); +} +static void evs_format_print_bw(GString *s, const char *k, enum evs_bw min, enum evs_bw max) { + if (max == EVS_BW_UNSPEC) + return; + + g_string_append(s, k); + g_string_append_c(s, '='); + + if (min != EVS_BW_UNSPEC) { + g_string_append(s, evs_bw_strings[min]); + g_string_append_c(s, '-'); + } + g_string_append(s, evs_bw_strings[max]); + g_string_append(s, "; "); +} +static bool evs_format_print(GString *s, const struct rtp_payload_type *p) { + if (!p->format.fmtp_parsed) + return false; + + __auto_type f = &p->format.parsed.evs; + gsize orig_len = s->len; + + if (f->hf_only) + g_string_append(s, "hf-only=1; "); + if (f->no_dtx) + g_string_append(s, "dtx=0; "); + if (f->no_dtx_recv) + g_string_append(s, "dtx-recv=0; "); + if (f->cmr) + g_string_append_printf(s, "cmr=%i; ", f->cmr); + + if (f->amr_io) { + // AMR + g_string_append(s, "evs-mode-switch=1; "); + + if (f->mode_set) { + g_string_append(s, "mode-set="); + for (unsigned int i = 0; i < 8; i++) { + if ((f->mode_set & (1 << i))) + g_string_append_printf(s, "%u,", i); + } + g_string_truncate(s, s->len - 1); // remove trailing "," + g_string_append(s, "; "); + } + + if (f->mode_change_neighbor) + g_string_append(s, "mode-change-neighbor=1; "); + if (f->mode_change_period) + g_string_append_printf(s, "mode-change-period=%i; ", f->mode_change_period); + } + else { + // EVS + evs_format_print_br(s, "br", f->min_br, f->max_br); + evs_format_print_br(s, "br-send", f->min_br_send, f->max_br_send); + evs_format_print_br(s, "br-recv", f->min_br_recv, f->max_br_recv); + + evs_format_print_bw(s, "bw", f->min_bw, f->max_bw); + evs_format_print_bw(s, "bw-send", f->min_bw_send, f->max_bw_send); + evs_format_print_bw(s, "bw-recv", f->min_bw_recv, f->max_bw_recv); + } + + if (orig_len != s->len) + g_string_truncate(s, s->len - 2); // remove trailing "; " if anything was printed + + return true; +} +static void evs_parse_format_cb(str *key, str *token, void *data) { + union codec_format_options *opts = data; + __auto_type o = &opts->evs; + + if (!str_cmp(key, "hf-only")) { + if (token->len == 1 && token->s[0] == '1') + o->hf_only = 1; + } + else if (!str_cmp(key, "evs-mode-switch")) { + if (token->len == 1 && token->s[0] == '1') + o->amr_io = 1; + } + else if (!str_cmp(key, "dtx")) { + if (token->len == 1 && token->s[0] == '0') + o->no_dtx = 1; + } + else if (!str_cmp(key, "dtx-recv")) { + if (token->len == 1 && token->s[0] == '0') + o->no_dtx_recv = 1; + } + else if (!str_cmp(key, "cmr")) { + if (token->len == 1 && token->s[0] == '1') + o->cmr = 1; + else if (token->len == 2 && token->s[0] == '-' && token->s[1] == '1') + o->cmr = -1; + } + else if (!str_cmp(key, "br")) + evs_parse_br(&o->min_br, &o->max_br, token); + else if (!str_cmp(key, "br-send")) + evs_parse_br(&o->min_br_send, &o->max_br_send, token); + else if (!str_cmp(key, "br-recv")) + evs_parse_br(&o->min_br_recv, &o->max_br_recv, token); + else if (!str_cmp(key, "bw")) + evs_parse_bw(&o->min_bw, &o->max_bw, token); + else if (!str_cmp(key, "bw-send")) + evs_parse_bw(&o->min_bw_send, &o->max_bw_send, token); + else if (!str_cmp(key, "bw-recv")) + evs_parse_bw(&o->min_bw_recv, &o->max_bw_recv, token); + else if (!str_cmp(key, "mode-set")) { + str mode; + while (str_token_sep(&mode, token, ',') == 0) { + int m = str_to_i(&mode, -1); + if (m < 0 || m > 8) + continue; + o->mode_set |= (1 << m); + } + } + else if (!str_cmp(key, "mode-change-period")) + o->mode_change_period = str_to_i(token, 0); + else if (!str_cmp(key, "mode-change-neighbor")) { + if (token->len == 1 && token->s[0] == '1') + o->mode_change_neighbor = 1; + } +} +static int evs_format_parse(struct rtp_codec_format *f, const str *fmtp) { + // initialise + f->parsed.evs.max_bw = EVS_BW_UNSPEC; + f->parsed.evs.min_bw = EVS_BW_UNSPEC; + f->parsed.evs.max_bw_send = EVS_BW_UNSPEC; + f->parsed.evs.min_bw_send = EVS_BW_UNSPEC; + f->parsed.evs.max_bw_recv = EVS_BW_UNSPEC; + f->parsed.evs.min_bw_recv = EVS_BW_UNSPEC; + + codeclib_key_value_parse(fmtp, true, evs_parse_format_cb, f); + return 0; +} +static void evs_format_answer(struct rtp_payload_type *p) { + if (!p->format.fmtp_parsed) + return; + + __auto_type f = &p->format.parsed.evs; + + // swap send/recv + + __auto_type t1 = f->max_br_recv; + f->max_br_recv = f->max_br_send; + f->max_br_send = t1; + + t1 = f->min_br_recv; + f->min_br_recv = f->min_br_send; + f->min_br_send = t1; + + __auto_type t2 = f->max_bw_recv; + f->max_bw_recv = f->max_bw_send; + f->max_bw_send = t2; + + t2 = f->min_bw_recv; + f->min_bw_recv = f->min_bw_send; + f->min_bw_send = t2; +} +static int evs_format_cmp(const struct rtp_payload_type *A, const struct rtp_payload_type *B) { + // params must have been parsed successfully + if (!A->format.fmtp_parsed || !B->format.fmtp_parsed) + return -1; + + __auto_type a = &A->format.parsed.evs; + __auto_type b = &B->format.parsed.evs; + + // reject what is incompatible + if (a->amr_io != b->amr_io) + return -1; + if (a->hf_only != b->hf_only) + return -1; + + // determine whether we are compatible + int compat = 0; + +#define FEATURE_CMP(field, compat_op, undefined_val) \ + if (a->field != undefined_val && b->field != undefined_val) { \ + if (a->field == b->field) \ + ; \ + else if (a->field compat_op b->field) \ + compat++; \ + else \ + return -1; \ + } \ + else if (a->field == undefined_val && b->field != undefined_val) /* `a` is broader than `b` */ \ + compat++; \ + else if (a->field != undefined_val && b->field == undefined_val) \ + return -1; + + if (!a->amr_io) { + // EVS + FEATURE_CMP(max_br, >, 0) + FEATURE_CMP(min_br, <, 0) + FEATURE_CMP(max_br_recv, >, 0) + FEATURE_CMP(min_br_recv, <, 0) + FEATURE_CMP(max_br_send, >, 0) + FEATURE_CMP(min_br_send, <, 0) + + FEATURE_CMP(max_bw, >, EVS_BW_UNSPEC) + FEATURE_CMP(min_bw, <, EVS_BW_UNSPEC) + FEATURE_CMP(max_bw_recv, >, EVS_BW_UNSPEC) + FEATURE_CMP(min_bw_recv, <, EVS_BW_UNSPEC) + FEATURE_CMP(max_bw_send, >, EVS_BW_UNSPEC) + FEATURE_CMP(min_bw_send, <, EVS_BW_UNSPEC) + } + else { + // AMR + int match = amr_mode_set_cmp(a->mode_set, b->mode_set); + if (match == 1) + compat++; + else if (match == -1) + return -1; + } + +#undef FEATURE_CMP + + return (compat == 0) ? 0 : 1; +} + + + +static const char *evs_decoder_init(decoder_t *dec, const str *extra_opts) { + dec->u.evs = g_slice_alloc0(evs_decoder_size); + if (dec->in_format.clockrate != 48000) + ilog(LOG_WARN, "EVS: invalid decoder clock rate (%i) requested", + dec->in_format.clockrate / dec->def->clockrate_mult); + if (dec->in_format.channels != 1) + ilog(LOG_WARN, "EVS: %i-channel EVS is not supported", + dec->in_format.channels); + dec->in_format.clockrate = 48000; + evs_set_decoder_Fs(dec->u.evs, dec->in_format.clockrate); + evs_init_decoder(dec->u.evs); + return NULL; +} +static void evs_decoder_close(decoder_t *dec) { + evs_destroy_decoder(dec->u.evs); + g_slice_free1(evs_decoder_size, dec->u.evs); +} + + + +// upper 16 bits: 0 = EVS, 1 = AMR +// lower 8 bits: mode num +// 0x000000AA = mode num +// 0x00AAAA00 = actual number of bits +// 0xAA000000 = 0=EVS, 1=AMR +// -1 == invalid +static int32_t evs_mode_from_bytes(int bytes) { + switch (bytes) { + // EVS + case 7: // 2.8 + return 0 | (56 << 8); + case 18: // 7.2 + return 1 | (144 << 8); + case 20: // 8.0 + return 2 | (160 << 8); + case 24: // 9.6 + return 3 | (192 << 8); + case 33: // 13.2 + return 4 | (264 << 8); + case 41: // 16.4 + return 5 | (328 << 8); + case 61: // 24.4 + return 6 | (488 << 8); + case 80: // 32.0 + return 7 | (640 << 8); + case 120: // 48.8 + return 8 | (960 << 8); + case 160: // 64.0 + return 9 | (1280 << 8); + case 240: // 96.0 + return 10 | (1920 << 8); + case 320: // 128.0 + return 11 | (2560 << 8); + case 6: // sid + return 12 | (48 << 8); + // AMR + case 17: // (16.5) 6.60 kbit/s // 0 + return 0 | 0x01000000 | (132 << 8); + case 23: // (22.125) 8.85 kbit/s // 1 + return 1 | 0x01000000 | (177 << 8); + case 32: // (31.625) 12.65 kbit/s // 2 + return 2 | 0x01000000 | (253 << 8); + case 36: // (35.625) 14.25 kbit/s // 3 + return 3 | 0x01000000 | (285 << 8); + case 40: // (39.625) 15.85 kbit/s // 4 + return 4 | 0x01000000 | (317 << 8); + case 46: // (45.625) 18.25 kbit/s // 5 + return 5 | 0x01000000 | (365 << 8); + case 50: // (49.625) 19.85 kbit/s // 6 + return 6 | 0x01000000 | (397 << 8); + case 58: // (57.625) 23.05 kbit/s // 7 + return 7 | 0x01000000 | (461 << 8); + case 60: // (59.625) 23.85 kbit/s // 8 + return 8 | 0x01000000 | (477 << 8); + case 5: // sid + return 9 | 0x01000000 | (40 << 8); + } + return -1; +} +static int32_t evs_mode_from_bitrate(int bitrate) { + int bytes_per_frame = ((bitrate / 50) + 7) / 8; + if (bytes_per_frame >= 7) + return evs_mode_from_bytes(bytes_per_frame); + return -1; +} + +static int evs_bitrate_mode(int bitrate) { + switch (bitrate) { + // EVS + case 2800: + case 5900: + case 7200: + case 8000: + case 13200: + case 32000: + case 64000: + // AMR + case 6600: + case 8850: + case 12650: + case 14250: + case 15850: + case 18250: + case 19850: + case 23050: + case 23850: + return 1; + // EVS + case 9600: + case 16400: + case 24400: + case 48000: + case 96000: + case 128000: + return 2; + } + return 0; +} + +static const int evs_mode_bits[2][16] = { + // EVS + { + 56, // 0 + 144, // 1 + 160, // 2 + 192, // 3 + 264, // 4 + 328, // 5 + 488, // 6 + 640, // 7 + 960, // 8 + 1280, // 9 + 1920, // 10 + 2560, // 11 + 48, // 12 + 0, // 13 invalid + 0, // 14 invalid + 0, // 15 invalid + }, + // AMR + { + 132, // 6.60 kbit/s // 0 + 177, // 8.85 kbit/s // 1 + 253, // 12.65 kbit/s // 2 + 285, // 14.25 kbit/s // 3 + 317, // 15.85 kbit/s // 4 + 365, // 18.25 kbit/s // 5 + 397, // 19.85 kbit/s // 6 + 461, // 23.05 kbit/s // 7 + 477, // 23.85 kbit/s // 8 + 40, // comfort noise // 9 + 0, // invalid // 10 + 0, // invalid // 11 + 0, // invalid // 12 + 0, // invalid // 13 + 0, // invalid // 14 + 0, // invalid // 15 + }, +}; +static const int evs_mode_bitrates[2][16] = { + // EVS + { + 5900, // 0 (VBR) + 7200, // 1 + 8000, // 2 + 9600, // 3 + 13200, // 4 + 16400, // 5 + 24400, // 6 + 32000, // 7 + 48800, // 8 + 64000, // 9 + 96000, // 10 + 128000, // 11 + 0, // 12 SID + 0, // 13 invalid + 0, // 14 invalid + 0, // 15 invalid + }, + // AMR + { + 6600, // 0 + 8850, // 1 + 12650, // 2 + 14250, // 3 + 15850, // 4 + 18250, // 5 + 19850, // 6 + 23050, // 7 + 23850, // 8 + 0, // comfort noise // 9 + 0, // invalid // 10 + 0, // invalid // 11 + 0, // invalid // 12 + 0, // invalid // 13 + 0, // invalid // 14 + 0, // invalid // 15 + }, +}; +static const bool evs_modes_allowed_by_bw[__EVS_BW_MAX][12] = { + // NB + { true, true, true, true, true, true, true, false, false, false, false, false, }, + // WB + { true, true, true, true, true, true, true, true, true, true, true, true, }, + // SWB + { false, false, false, true, true, true, true, true, true, true, true, true, }, + // FB + { false, false, false, false, false, true, true, true, true, true, true, true, }, +}; + +static int evs_match_bitrate(int orig_br, unsigned int amr) { + // is it already a valid bitrate? + int32_t mode = evs_mode_from_bitrate(orig_br); + if (mode >= 0) { + int bits = (mode >> 8) & 0xffff; + if (mode > 0 && (mode >> 24) == amr && bits * 50 == orig_br) + return orig_br; + } + + // find closest match + int max_mode = amr ? 8 : 11; + int test_mode = max_mode / 2; + int mode_off = (max_mode + 1) / 2; + bool last = false; + while (1) { + int new_br = evs_mode_bitrates[amr][test_mode]; + int new_off = (mode_off + 1) / 2; + if (new_br > orig_br) { + if (test_mode == 0 || last) + return new_br; + test_mode -= new_off; + } + else { // new_br < orig_br + if (test_mode == max_mode) + return new_br; + test_mode += new_off; + } + if (mode_off == 1) + last = true; + mode_off = new_off; + } +} + + + +static const char *evs_encoder_init(encoder_t *enc, const str *extra_opts) { + enc->u.evs.ctx = g_slice_alloc0(evs_encoder_size); + enc->u.evs.ind_list = g_slice_alloc(evs_encoder_ind_list_size); + if (enc->requested_format.clockrate != 48000) + ilog(LOG_WARN, "EVS: invalid encoder clock rate (%i) requested", + enc->requested_format.clockrate / enc->def->clockrate_mult); + if (enc->requested_format.channels != 1) + ilog(LOG_WARN, "EVS: %i-channel EVS is not supported", + enc->requested_format.channels); + enc->actual_format.clockrate = 48000; + enc->actual_format.channels = enc->requested_format.channels; + enc->actual_format.format = AV_SAMPLE_FMT_S16; + enc->samples_per_frame = enc->actual_format.clockrate * 20 / 1000; + + __auto_type o = &enc->format_options.evs; + + // determine max BW + if (o->max_bw_send != EVS_BW_UNSPEC) + enc->codec_options.evs.max_bw = o->max_bw_send; + else if (o->max_bw != EVS_BW_UNSPEC) + enc->codec_options.evs.max_bw = o->max_bw; + else + enc->codec_options.evs.max_bw = EVS_BW_WB; + assert(enc->codec_options.evs.max_bw >= 0 && enc->codec_options.evs.max_bw < __EVS_BW_MAX); + + evs_set_encoder_opts(enc->u.evs.ctx, enc->actual_format.clockrate, enc->u.evs.ind_list); + + // limit bitrate to given range + if (!o->amr_io) { + // EVS + if (o->max_br && enc->bitrate > o->max_br) + enc->bitrate = o->max_br; + if (o->min_br && enc->bitrate < o->max_br) + enc->bitrate = o->min_br; + + // verify bitrate + int bitrate = evs_match_bitrate(enc->bitrate, 0); + if (bitrate != enc->bitrate) { + ilog(LOG_INFO, "EVS: Using bitrate %i instead of %i", bitrate, enc->bitrate); + enc->bitrate = bitrate; + } + + // limit max bitrate to one supported by the selected BW + int32_t mode = evs_mode_from_bitrate(enc->bitrate); + if (mode == -1) + ilog(LOG_WARN, "EVS: ended up with unknown bitrate %i", enc->bitrate); + else { + mode &= 0xff; + while (!evs_modes_allowed_by_bw[enc->codec_options.evs.max_bw][mode]) { + // modes 5 and 6 are allowed by all BWs + if (mode > 6) // must be too high + mode--; + else // must be too low + mode++; + } + int bitrate = evs_mode_bitrates[0][mode]; + ilog(LOG_INFO, "EVS: using bitrate %i instead of %i as restricted by BW %i", + bitrate, enc->bitrate, enc->codec_options.evs.max_bw); + enc->bitrate = bitrate; + } + } + else { + // AMR + int32_t mode = evs_mode_from_bitrate(enc->bitrate); + if (mode != -1) { + if (mode >> 24 != 1) + mode = -1; // EVS bitrate + else if (o->mode_set) { + if ((o->mode_set & (1 << (mode & 0xff))) == 0) + mode = -1; // not part of the mode-set + } + } + if (mode == -1) { + // find closest match bitrate + int bitrate = evs_match_bitrate(enc->bitrate, 1); + mode = evs_mode_from_bitrate(bitrate); + if (mode == -1 || (mode >> 24 != 1)) + ilog(LOG_WARN, "EVS: ended up with unknown bitrate %i", bitrate); + else { + mode &= 0xff; + // restrict by mode-set if there is one + if (o->mode_set) { + if ((o->mode_set & (1 << (mode & 0xff))) == 0) { + // pick next higher mode if possible, otherwise go lower: + // clear lower unwanted modes from mode-set + unsigned int mode_set = o->mode_set & (0xfe << mode); + if (mode_set) { + // got a higher mode: which one? + mode = __builtin_ffs(mode_set) - 1; + } + else { + // no higher mode, get next lower one + mode = sizeof(int) * 8 - __builtin_clz(o->mode_set) - 1; + } + } + } + bitrate = evs_mode_bitrates[1][mode]; + ilog(LOG_INFO, "EVS: using bitrate %i instead of %i as restricted by mode-set", + bitrate, enc->bitrate); + enc->bitrate = bitrate; + } + } + } + + evs_set_encoder_brate(enc->u.evs.ctx, enc->bitrate, enc->codec_options.evs.max_bw, + evs_bitrate_mode(enc->bitrate), o->amr_io); + evs_init_encoder(enc->u.evs.ctx); + + return NULL; +} +static void evs_encoder_close(encoder_t *enc) { + evs_destroy_encoder(enc->u.evs.ctx); + g_slice_free1(evs_encoder_size, enc->u.evs.ctx); + g_slice_free1(evs_encoder_ind_list_size, enc->u.evs.ind_list); +} + + + + +static void evs_handle_cmr(encoder_t *enc) { + if ((enc->callback.evs.cmr_in & 0x80) == 0) + return; + if (!memcmp(&enc->callback.evs.cmr_in_ts, + &enc->u.evs.cmr_in_ts, sizeof(struct timeval))) + return; + + enc->u.evs.cmr_in_ts = enc->callback.evs.cmr_in_ts; // XXX should use a queue or something instead + + __auto_type f = &enc->format_options.evs; + __auto_type o = &enc->codec_options.evs; + unsigned char type = (enc->callback.evs.cmr_in >> 4) & 0x7; + unsigned char req = enc->callback.evs.cmr_in & 0xf; + int bitrate; + + if (type == 1) { + // AMR + if (!f->amr_io) + goto err; + if (req > 8) + goto err; + bitrate = evs_mode_bitrates[1][req]; + } + else if (type <= 4) { + // EVS modes + if (f->amr_io) + goto err; + if (req > 11) + goto err; + int bw = type; + if (bw >= 2) + bw--; // 0..3 + // ignore min BW + if (o->max_bw != EVS_BW_UNSPEC && o->max_bw < bw) + goto err; + if (!evs_modes_allowed_by_bw[bw][req]) + goto err; + bitrate = evs_mode_bitrates[0][req]; + } + else + goto err; + + enc->bitrate = bitrate; + evs_set_encoder_brate(enc->u.evs.ctx, bitrate, o->max_bw, + evs_bitrate_mode(bitrate), f->amr_io); + + return; + +err: + if (f->amr_io) + ilog(LOG_WARN | LOG_FLAG_LIMIT, "EVS: received invalid CMR (type %u, " + "request %u) in AMR mode", type, req); + else + ilog(LOG_WARN | LOG_FLAG_LIMIT, "EVS: received invalid CMR (type %u, " + "request %u) with BW <%i", type, req, o->max_bw); +} + +static int evs_encoder_input(encoder_t *enc, AVFrame **frame) { + if (!*frame) + return 0; + + if ((*frame)->nb_samples != enc->actual_format.clockrate * 20 / 1000) { + ilog(LOG_ERR | LOG_FLAG_LIMIT, "EVS: input %u samples instead of %i", (*frame)->nb_samples, + enc->actual_format.clockrate * 20 / 1000); + return -1; + } + + evs_handle_cmr(enc); + + if (!enc->format_options.evs.amr_io) + evs_enc_in(enc->u.evs.ctx, (void *) (*frame)->extended_data[0], (*frame)->nb_samples); + else + evs_amr_enc_in(enc->u.evs.ctx, (void *) (*frame)->extended_data[0], (*frame)->nb_samples); + + // max output: 320 bytes, plus some overhead + av_new_packet(enc->avpkt, 340); + + unsigned char *out = enc->avpkt->data; + unsigned char *cmr = NULL; + + if (!enc->format_options.evs.amr_io) { + // EVS + if (enc->format_options.evs.cmr == 1) { + cmr = out; + *cmr = 0xff; // no CMR + out++; + } + } + else { + // AMR IO + if (!enc->format_options.evs.hf_only) { + // compact + cmr = out; + *cmr = 0xe0; // no CMR + out++; // to be shuffled below + } + else { + // header-full + if (enc->format_options.evs.cmr == 1) { + cmr = out; + *cmr = 0xff; // no CMR + out++; + } + } + } + + // TOC byte + unsigned char *toc = NULL; + if (enc->format_options.evs.hf_only) { + // header-full always has TOC + toc = out; + out++; + } + else { + // compact + if (cmr && !enc->format_options.evs.amr_io) { + // EVS with CMR is also header-full with TOC + toc = out; + out++; + } + } + + uint16_t bits = 0; + evs_enc_out(enc->u.evs.ctx, out, &bits); + uint16_t bytes = (bits + 7) / 8; + int32_t mode = evs_mode_from_bytes(bytes); + if (mode < 0) { + ilog(LOG_ERR | LOG_FLAG_LIMIT, "EVS: invalid encoding received from codec " + "(%i bits per frame)", bits); + av_packet_unref(enc->avpkt); + return -1; + } + evs_reset_enc_ind(enc->u.evs.ctx); + + if (toc) { + *toc = (mode & 0xff); + if (enc->format_options.evs.amr_io) + *toc |= 0x30; + } + + if (enc->format_options.evs.amr_io && !enc->format_options.evs.hf_only) { + // how many output bytes (frame minus CMR bits) total? + bytes = (bits - 5 + 7) / 8; + // bit-shuffle payload + unsigned char first = out[0]; + *cmr |= (first >> 2) & 0x1f; + // XXX accelerate with larger word sizes + for (int i = 0; i < bytes; i++) { + out[i] <<= 6; + out[i] |= out[i+1] >> 2; + } + // restore first bit, clear out tail end padding bits + unsigned int first_bit_shift = (bits + 2) % 8; + out[bytes-1] &= (0xff << (8 - first_bit_shift)); // clear leftovers + out[bytes-1] |= ((first & 0x80) >> first_bit_shift); // last/first bit + } + + bytes += (out - enc->avpkt->data); + assert(bytes <= enc->avpkt->size); + enc->avpkt->size = bytes; + enc->avpkt->pts = (*frame)->pts; + enc->avpkt->duration = (*frame)->nb_samples; + + return 0; +} + + +// 3GPP TS 26.445 A.2.1.2.1 -> A.2.2.1.1 +static const char evs_amr_io_compact_cmr[8] = { + 0x90 | 0, // 6.6 + 0x90 | 1, // 8.85 + 0x90 | 2, // 12.65 + 0x90 | 4, // 15.85 + 0x90 | 5, // 18.25 + 0x90 | 7, // 23.05 + 0x90 | 8, // 23.85 + 0xff // no req +}; + + +static int evs_decoder_input(decoder_t *dec, const str *data, GQueue *out) { + str input = *data; + uint64_t pts = dec->pts; + const char *err = NULL; + + if (input.len == 0) + return 0; + + unsigned int n_samples = dec->in_format.clockrate * 20 / 1000; + + str frame_data = STR_NULL; + const unsigned char *toc = NULL, *toc_end = NULL; + unsigned char cmr = 0xff; + // check for single frame in compact format + int32_t mode = evs_mode_from_bytes(input.len); + int is_amr, bits, q_bit; + if ((mode & 0xff0000ff) == 0) { + // special case, clause A.2.1.3 + if ((input.s[0] & 0x80)) { + // AMR in HF format with CMR + mode = -1; + } + } + if (mode != -1) { + // single compact frame: consume all + frame_data = input; + input.len = 0; + + // extract mode information + bits = (mode >> 8) & 0xffff; + is_amr = mode >> 24; + q_bit = 1; + mode = mode & 0xff; + + if (is_amr) { + // save and clear CMR + unsigned char *shifter = (unsigned char *) frame_data.s; // use unsigned + cmr = shifter[0] & 0xe0; + shifter[0] &= 0x1f; + + // convert CMR to full byte format + cmr >>= 5; // now guaranteed to be 0..7 + cmr = evs_amr_io_compact_cmr[cmr]; + + // bit shift payload + // XXX use larger word sizes + for (size_t i = 0; i < frame_data.len; i++) { + shifter[i] <<= 2; + shifter[i] |= shifter[i+1] >> 6; + } + // restore first bit + size_t first_bit_octet = bits / 8; + size_t first_bit_bit = bits % 8; + shifter[0] |= (shifter[first_bit_octet] << first_bit_bit) & 0x80; + } + } + else { + // header-full + toc = (unsigned char *) input.s; + str_shift(&input, 1); + // is this TOC or CMR? + if ((*toc & 0x80)) { + cmr = *toc; + toc = (unsigned char *) input.s; + err = "short packet (no TOC after CMR)"; + if (str_shift(&input, 1)) + goto err; + err = "invalid TOC byte"; + if ((*toc & 0x80)) + goto err; + } + // skip over all TOC entries + unsigned char toc_ent = *toc; + while ((toc_ent & 0x40)) { + toc_ent = *((unsigned char *) input.s); + err = "short packet (no repeating TOC)"; + if (str_shift(&input, 1)) + goto err; + } + // `toc` is now the first TOC entry and `input` points to the first speech frame + toc_end = (void *) input.s; + } + + while (1) { + // process frame if we have one; we don't have one if + // this is the first iteration and this is not a compact frame + if (mode != -1) { + AVFrame *frame = av_frame_alloc(); + frame->nb_samples = n_samples; + frame->format = AV_SAMPLE_FMT_S16; + frame->sample_rate = dec->in_format.clockrate; // 48000 + DEF_CH_LAYOUT(&frame->CH_LAYOUT, dec->in_format.channels); + frame->pts = pts; + if (av_frame_get_buffer(frame, 0) < 0) + abort(); + + evs_dec_in(dec->u.evs, frame_data.s, bits, is_amr, mode, q_bit, 0, 0); + + if (evs_syn_output) { + // temp float buffer + float tmp[n_samples * 3]; + if (!is_amr) + evs_dec_out(dec->u.evs, tmp, 0); + else + evs_amr_dec_out(dec->u.evs, tmp); + evs_syn_output(tmp, n_samples, (void *) frame->extended_data[0]); + // XXX ^ use something SIMD accelerated? ffmpeg? + } + else { + if (!is_amr) + evs_dec_out(dec->u.evs, frame->extended_data[0], 0); + else + evs_amr_dec_out(dec->u.evs, frame->extended_data[0]); + } + + pts += n_samples; + g_queue_push_tail(out, frame); + } + + // anything left? we break here in compact mode + if (!input.len) + break; + + // if we're here, we're in HF mode: look at the next TOC and extract speech frame + if (toc >= toc_end) // leftover data/padding at the end + break; + mode = *toc & 0xf; + is_amr = (*toc >> 5) & 0x1; + if (is_amr) + q_bit = (*toc >> 4) & 0x1; + else + q_bit = 1; + bits = evs_mode_bits[is_amr][mode]; // guaranteed to be 0..1 and 0..15 + + // consume and shift + toc++; + int bytes = (bits + 7) / 8; + frame_data.s = input.s; + frame_data.len = bytes; + err = "speech frame truncated"; + if (str_shift(&input, bytes)) + goto err; + } + + if (cmr != 0xff) + decoder_event(dec, CE_EVS_CMR_RECV, GUINT_TO_POINTER(cmr)); + + return 0; + +err: + if (err) + ilog(LOG_WARN | LOG_FLAG_LIMIT, "Error unpacking EVS packet: %s", err); + return -1; +} + + +static void evs_load_so(const char *path) { + if (!path) + return; + + evs_lib_handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (!evs_lib_handle) + goto err; + + static unsigned int (*get_evs_decoder_size)(void); + static unsigned int (*get_evs_encoder_size)(void); + static unsigned int (*get_evs_encoder_ind_list_size)(void); + + // flp codec? + evs_init_decoder = dlsym(evs_lib_handle, "init_decoder"); + if (!evs_init_decoder) { + // fx codec? + evs_init_decoder = dlsym(evs_lib_handle, "init_decoder_fx"); + if (!evs_init_decoder) + goto err; + evs_init_encoder = dlsym(evs_lib_handle, "init_encoder_fx"); + if (!evs_init_encoder) + goto err; + evs_destroy_encoder = dlsym(evs_lib_handle, "destroy_encoder_fx"); + if (!evs_destroy_encoder) + goto err; + evs_enc_in = dlsym(evs_lib_handle, "evs_enc_fx"); + if (!evs_enc_in) + goto err; + evs_amr_enc_in = dlsym(evs_lib_handle, "amr_wb_enc_fx"); + if (!evs_amr_enc_in) + goto err; + evs_reset_enc_ind = dlsym(evs_lib_handle, "reset_indices_enc_fx"); + if (!evs_reset_enc_ind) + goto err; + evs_dec_in = dlsym(evs_lib_handle, "read_indices_from_djb_fx"); + if (!evs_dec_in) + goto err; + evs_dec_out = dlsym(evs_lib_handle, "evs_dec_fx"); + if (!evs_dec_out) + goto err; + evs_amr_dec_out = dlsym(evs_lib_handle, "amr_wb_dec_fx"); + if (!evs_amr_dec_out) + goto err; + } + else { + // flp codec + evs_init_encoder = dlsym(evs_lib_handle, "init_encoder"); + if (!evs_init_encoder) + goto err; + evs_destroy_encoder = dlsym(evs_lib_handle, "destroy_encoder"); + if (!evs_destroy_encoder) + goto err; + evs_enc_in = dlsym(evs_lib_handle, "evs_enc"); + if (!evs_enc_in) + goto err; + evs_amr_enc_in = dlsym(evs_lib_handle, "amr_wb_enc"); + if (!evs_amr_enc_in) + goto err; + evs_reset_enc_ind = dlsym(evs_lib_handle, "reset_indices_enc"); + if (!evs_reset_enc_ind) + goto err; + evs_dec_in = dlsym(evs_lib_handle, "read_indices_from_djb"); + if (!evs_dec_in) + goto err; + evs_dec_out = dlsym(evs_lib_handle, "evs_dec"); + if (!evs_dec_out) + goto err; + evs_syn_output = dlsym(evs_lib_handle, "syn_output"); + if (!evs_syn_output) + goto err; + evs_amr_dec_out = dlsym(evs_lib_handle, "amr_wb_dec"); + if (!evs_amr_dec_out) + goto err; + } + + // common + get_evs_decoder_size = dlsym(evs_lib_handle, "decoder_size"); + if (!get_evs_decoder_size) + goto err; + get_evs_encoder_size = dlsym(evs_lib_handle, "encoder_size"); + if (!get_evs_encoder_size) + goto err; + get_evs_encoder_ind_list_size = dlsym(evs_lib_handle, "encoder_ind_list_size"); + if (!get_evs_encoder_ind_list_size) + goto err; + evs_destroy_decoder = dlsym(evs_lib_handle, "destroy_decoder"); + if (!evs_destroy_decoder) + goto err; + evs_enc_out = dlsym(evs_lib_handle, "indices_to_serial"); + if (!evs_enc_out) + goto err; + evs_set_encoder_opts = dlsym(evs_lib_handle, "encoder_set_opts"); + if (!evs_set_encoder_opts) + goto err; + evs_set_encoder_brate = dlsym(evs_lib_handle, "encoder_set_brate"); + if (!evs_set_encoder_brate) + goto err; + evs_set_decoder_Fs = dlsym(evs_lib_handle, "decoder_set_Fs"); + if (!evs_set_decoder_Fs) + goto err; + + // all ok + + evs_decoder_size = get_evs_decoder_size(); + evs_encoder_size = get_evs_encoder_size(); + evs_encoder_ind_list_size = get_evs_encoder_ind_list_size(); + + return; + +err: + ilog(LOG_ERR, "Failed to open EVS codec .so '%s': %s", path, dlerror()); + if (evs_lib_handle) + dlclose(evs_lib_handle); + evs_lib_handle = NULL; +} + +static void evs_def_init(codec_def_t *def) { + evs_load_so(rtpe_common_config_ptr->evs_lib_path); + + if (evs_lib_handle) { + def->support_decoding = 1; + def->support_encoding = 1; + } +} + +static int evs_dtx(decoder_t *dec, GQueue *out, int ptime) { + return 0; +} diff --git a/lib/codeclib.h b/lib/codeclib.h index d06ba5883..a3c364f80 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -112,8 +112,12 @@ struct encoder_callback_s { struct timeval cmr_out_ts; unsigned int cmr_out; } amr; -}; + struct { + struct timeval cmr_in_ts; + unsigned int cmr_in; + } evs; +}; union codec_options_u { struct { const unsigned int *bits_per_frame; @@ -122,6 +126,10 @@ union codec_options_u { int mode_change_interval; int cmr_interval; } amr; + + struct { + enum evs_bw max_bw; + } evs; }; enum dtx_method { @@ -190,6 +198,7 @@ struct resample_s { enum codec_event { CE_AMR_CMR_RECV, CE_AMR_SEND_CMR, + CE_EVS_CMR_RECV, }; struct dtx_method_s { @@ -240,6 +249,7 @@ struct decoder_s { unsigned int event; unsigned long duration; } dtmf; + void *evs; } u; unsigned long rtp_ts; @@ -276,6 +286,11 @@ struct encoder_s { #ifdef HAVE_BCG729 bcg729EncoderChannelContextStruct *bcg729; #endif + struct { + void *ctx; + void *ind_list; + struct timeval cmr_in_ts; + } evs; } u; AVPacket *avpkt; AVAudioFifo *fifo; diff --git a/lib/rtplib.h b/lib/rtplib.h index 0b76e785c..582c1b686 100644 --- a/lib/rtplib.h +++ b/lib/rtplib.h @@ -21,6 +21,17 @@ struct rtp_header { } __attribute__ ((packed)); +enum evs_bw { + EVS_BW_NB = 0, + EVS_BW_WB = 1, + EVS_BW_SWB = 2, + EVS_BW_FB = 3, + + __EVS_BW_MAX, + + EVS_BW_UNSPEC = -1, +}; + union codec_format_options { struct { int interleaving; @@ -35,6 +46,27 @@ union codec_format_options { struct { int mode; } ilbc; + + struct { + // EVS options + unsigned int min_br, max_br; + unsigned int min_br_send, max_br_send; + unsigned int min_br_recv, max_br_recv; + enum evs_bw min_bw, max_bw; + enum evs_bw min_bw_send, max_bw_send; + enum evs_bw min_bw_recv, max_bw_recv; + // AMR options + unsigned int mode_set; // bitfield + int mode_change_period; + // bit field options + unsigned int hf_only:1; + unsigned int amr_io:1; + unsigned int no_dtx:1; + unsigned int no_dtx_recv:1; + int cmr:2; // -1, 0, 1 + // AMR bit options + unsigned int mode_change_neighbor:1; + } evs; }; struct rtp_codec_format { diff --git a/recording-daemon/Makefile b/recording-daemon/Makefile index 8882f3fd6..74175c175 100644 --- a/recording-daemon/Makefile +++ b/recording-daemon/Makefile @@ -14,7 +14,7 @@ CFLAGS+= $(shell pkg-config --cflags libavfilter) CFLAGS+= $(shell mysql_config --cflags) CFLAGS+= $(shell pkg-config --cflags openssl) -LDLIBS= -lm +LDLIBS= -lm -ldl LDLIBS+= $(shell pkg-config --libs glib-2.0) LDLIBS+= $(shell pkg-config --libs gthread-2.0) LDLIBS+= $(shell pkg-config --libs libavcodec) diff --git a/recording-daemon/rtpengine-recording.pod b/recording-daemon/rtpengine-recording.pod index 6dbe73291..b4e4d1a1e 100644 --- a/recording-daemon/rtpengine-recording.pod +++ b/recording-daemon/rtpengine-recording.pod @@ -127,6 +127,11 @@ known. Set the stack size of each thread to the value given in kB. Defaults to 2048 kB. Can be set to -1 to leave the default provided by the OS unchanged. +=item B<--evs-lib-path=>I + +Points to the shared object file (B<.so>) containing the reference +implementation for the EVS codec. See the F for more details. + =item B<--output-storage=>B|B|B Where to store media files. By default, media files are written directly to the diff --git a/t/Makefile b/t/Makefile index 4189a2164..25ed2e041 100644 --- a/t/Makefile +++ b/t/Makefile @@ -35,7 +35,7 @@ else CFLAGS+= -DWITHOUT_CODECLIB endif -LDLIBS= -lm +LDLIBS= -lm -ldl LDLIBS+= $(shell pkg-config --libs glib-2.0) LDLIBS+= $(shell pkg-config --libs gthread-2.0) LDLIBS+= $(shell pkg-config --libs libcrypto) @@ -88,7 +88,8 @@ include ../lib/common.Makefile .PHONY: all-tests unit-tests daemon-tests daemon-tests \ daemon-tests-main daemon-tests-jb daemon-tests-dtx daemon-tests-dtx-cn daemon-tests-pubsub \ - daemon-tests-intfs daemon-tests-stats daemon-tests-delay-buffer daemon-tests-delay-timing + daemon-tests-intfs daemon-tests-stats daemon-tests-delay-buffer daemon-tests-delay-timing \ + daemon-tests-evs TESTS= test-bitstr aes-crypt aead-aes-crypt test-const_str_hash.strhash ifeq ($(with_transcoding),yes) @@ -123,6 +124,7 @@ unit-tests: $(TESTS) fi daemon-tests: daemon-tests-main daemon-tests-jb daemon-tests-pubsub daemon-tests-websocket \ + daemon-tests-evs \ daemon-tests-intfs daemon-tests-stats # daemon-tests-delay-buffer daemon-tests-delay-timing daemon-test-deps: tests-preload.so @@ -208,6 +210,14 @@ daemon-tests-delay-timing: daemon-test-deps test "$$(ls fake-$@-sockets)" = "" rmdir fake-$@-sockets +daemon-tests-evs: daemon-test-deps + rm -rf fake-$@-sockets + mkdir fake-$@-sockets + LD_PRELOAD=../t/tests-preload.so RTPE_BIN=../daemon/rtpengine TEST_SOCKET_PATH=./fake-$@-sockets \ + perl -I../perl auto-daemon-tests-evs.pl + test "$$(ls fake-$@-sockets)" = "" + rmdir fake-$@-sockets + test-bitstr: test-bitstr.o spandsp_send_fax_pcm: spandsp_send_fax_pcm.o diff --git a/t/auto-daemon-tests-evs.pl b/t/auto-daemon-tests-evs.pl new file mode 100755 index 000000000..10b4ea001 --- /dev/null +++ b/t/auto-daemon-tests-evs.pl @@ -0,0 +1,1578 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use NGCP::Rtpengine::Test; +use NGCP::Rtpclient::SRTP; +use NGCP::Rtpengine::AutoTest; +use Test::More; +use IPC::Open3; + + +$ENV{RTPENGINE_EXTENDED_TESTS} or exit(); +$ENV{RTPENGINE_3GPP_EVS_LIB} or exit(); + + +autotest_start(qw(--config-file=none -t -1 -i 203.0.113.1 -i 2001:db8:4321::1 + -n 2223 -f -L 7 -E), "--evs-lib-path=$ENV{RTPENGINE_3GPP_EVS_LIB}") + or die; + + +my ($sock_a, $sock_b, $port_a, $port_b, $ssrc, $ssrc_b, $resp, $srtp_ctx_a, $srtp_ctx_b, @ret1, @ret2); + + + + +new_call; + +offer('EVS codec accept basic', { codec => { accept => ['EVS'] } }, < { accept => ['EVS'] } }, < { accept => ['EVS/16000/1///evs-mode-switch=1;hf-only=1'] } }, < { accept => ['EVS/16000/1///hf-only=1'] } }, < { accept => ['EVS/16000/1///hf-only=1'] } }, < { accept => ['EVS/16000/1///hf-only=1'] } }, < { accept => ['EVS/16000/1///hf-only=1;br=8-32'] } }, < { accept => ['EVS/16000/1///hf-only=1;br=6-50'] } }, < { accept => ['EVS/16000/1///evs-mode-switch=1;mode-set=3,4,5,6'] } }, < { transcode => ['PCMA'] } }, < { transcode => ['PCMA'] } }, < { transcode => ['EVS'] } }, < { transcode => ['PCMA', 'PCMU'] } }, < { transcode => ['EVS/16000/1///hf-only=1'] } }, < { transcode => ['EVS/16000/1///evs-mode-switch=1'] } }, < { transcode => ['PCMA','PCMU'] } }, < { transcode => ['EVS/16000/1///evs-mode-switch=1;hf-only=1'] } }, < { transcode => ['EVS/16000/1/96000//hf-only=1'] } }, < { transcode => ['EVS/16000/1///hf-only=1;br=96'] } }, < { transcode => ['EVS/16000/1///hf-only=1;br=24.4-96'] } }, < mode 6 +($port_a) = offer('EVS forward bw=nb', { codec => { transcode => ['EVS/16000/1/48800//hf-only=1;bw=nb'] } }, < { transcode => ['EVS/16000/1///cmr=1'] } }, < { transcode => ['EVS/16000/1///evs-mode-switch=1;mode-set=2'] } }, < { transcode => ['EVS/16000/1///evs-mode-switch=1;mode-set=7'] } }, < { transcode => ['EVS'] } }, < { transcode => ['EVS/16000/1///hf-only=1;br=5.9'] } }, <