diff --git a/daemon/audio_player.c b/daemon/audio_player.c index ba7ca9daa..99d3bad48 100644 --- a/daemon/audio_player.c +++ b/daemon/audio_player.c @@ -51,7 +51,7 @@ static bool audio_player_run(struct media_player *mp) { // call locked in W bool audio_player_setup(struct call_media *m, const rtp_payload_type *dst_pt, - unsigned int size_ms, unsigned int delay_ms) + unsigned int size_ms, unsigned int delay_ms, str_case_value_ht codec_set) { if (!dst_pt) return false; @@ -123,7 +123,7 @@ bool audio_player_setup(struct call_media *m, const rtp_payload_type *dst_pt, ap->ptime_us = ptime_us; ap->ptime = ptime_smp; - if (media_player_setup(mp, &src_pt, dst_pt)) + if (media_player_setup(mp, &src_pt, dst_pt, codec_set)) goto error; bufsize_ms = MAX(bufsize_ms, ptime_ms * 2); // make sure the buf size is at least 2 frames diff --git a/daemon/call.c b/daemon/call.c index 2969baa26..4c33a2805 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -2600,7 +2600,7 @@ static void __update_init_subscribers(struct call_media *media, struct stream_pa ice_update(media->ice_agent, sp, opmode == OP_OFFER); /* sp == NULL: update in case rtcp-mux changed */ recording_setup_media(media); - t38_gateway_start(media->t38_gateway); + t38_gateway_start(media->t38_gateway, flags ? flags->codec_set : str_case_value_ht_null()); audio_player_start(media); if (mqtt_publish_scope() == MPS_MEDIA) @@ -2710,6 +2710,7 @@ static void __call_monologue_init_from_flags(struct call_monologue *ml, struct c .repeat = flags->repeat_times, .start_pos = flags->start_pos, .block_egress = !!flags->block_egress, + .codec_set = flags->codec_set, ); if (flags->file.len) ret = media_player_init_file(ml->rec_player, &flags->file, opts); diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index d9f4c8ca0..9caa23588 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -1313,7 +1313,9 @@ void call_ng_codec_flags(ng_parser_ctx_t *ctx, str *key, parser_arg value, helpe return; } #ifdef WITH_TRANSCODING - if (out->opmode == OP_OFFER || out->opmode == OP_REQUEST || out->opmode == OP_PUBLISH) { + if (out->opmode == OP_OFFER || out->opmode == OP_REQUEST || out->opmode == OP_PUBLISH + || out->opmode == OP_PLAY_MEDIA) + { switch (__csh_lookup(key)) { case CSH_LOOKUP("accept"): call_ng_flags_str_list(ctx, value, call_ng_flags_esc_str_list, &out->codec_accept); @@ -3486,6 +3488,7 @@ const char *call_play_media_ng(ng_parser_ctx_t *ctx) { .repeat = flags.repeat_times, .start_pos = flags.start_pos, .block_egress = !!flags.block_egress, + .codec_set = flags.codec_set, ); if (flags.file.len) { diff --git a/daemon/codec.c b/daemon/codec.c index b8334c0d8..e1c548283 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -58,6 +58,7 @@ static rtp_payload_type *codec_store_find_compatible(struct codec_store *cs, const rtp_payload_type *pt); static void __rtp_payload_type_add_name(codec_names_ht, rtp_payload_type *pt); static void codec_calc_lost(struct ssrc_ctx *ssrc, uint16_t seq); +static void __codec_options_set(call_t *call, rtp_payload_type *pt, str_case_value_ht codec_set); static struct codec_handler codec_handler_stub = { @@ -560,10 +561,11 @@ static void __make_audio_player_decoder(struct codec_handler *handler, rtp_paylo // used for generic playback (audio_player, t38_gateway) struct codec_handler *codec_handler_make_playback(const rtp_payload_type *src_pt, const rtp_payload_type *dst_pt, unsigned long last_ts, struct call_media *media, - uint32_t ssrc) + uint32_t ssrc, str_case_value_ht codec_set) { struct codec_handler *handler = __handler_new(src_pt, media, NULL); rtp_payload_type_copy(&handler->dest_pt, dst_pt); + __codec_options_set(media ? media->call : NULL, &handler->dest_pt, codec_set); handler->handler_func = handler_func_playback; handler->ssrc_handler = (void *) __ssrc_handler_transcode_new(handler); if (!handler->ssrc_handler) { @@ -588,9 +590,9 @@ struct codec_handler *codec_handler_make_playback(const rtp_payload_type *src_pt // used for "play media" player struct codec_handler *codec_handler_make_media_player(const rtp_payload_type *src_pt, const rtp_payload_type *dst_pt, unsigned long last_ts, struct call_media *media, - uint32_t ssrc) + uint32_t ssrc, str_case_value_ht codec_set) { - struct codec_handler *h = codec_handler_make_playback(src_pt, dst_pt, last_ts, media, ssrc); + struct codec_handler *h = codec_handler_make_playback(src_pt, dst_pt, last_ts, media, ssrc, codec_set); if (!h) return NULL; if (audio_player_is_active(media)) { @@ -603,10 +605,12 @@ struct codec_handler *codec_handler_make_media_player(const rtp_payload_type *sr } return h; } -struct codec_handler *codec_handler_make_dummy(const rtp_payload_type *dst_pt, struct call_media *media) +struct codec_handler *codec_handler_make_dummy(const rtp_payload_type *dst_pt, struct call_media *media, + str_case_value_ht codec_set) { struct codec_handler *handler = __handler_new(NULL, media, NULL); rtp_payload_type_copy(&handler->dest_pt, dst_pt); + __codec_options_set(media->call, &handler->dest_pt, codec_set); return handler; } @@ -1523,7 +1527,8 @@ next: } audio_player_setup(sink, pref_dest_codec, rtpe_config.audio_buffer_length, - rtpe_config.audio_buffer_delay); + rtpe_config.audio_buffer_delay, + a.flags ? a.flags->codec_set : str_case_value_ht_null()); if (a.flags && (a.flags->early_media || a.flags->opmode == OP_ANSWER)) audio_player_activate(sink); } diff --git a/daemon/media_player.c b/daemon/media_player.c index 6d437eda1..346fad074 100644 --- a/daemon/media_player.c +++ b/daemon/media_player.c @@ -572,7 +572,7 @@ static void media_player_kernel_player_start(struct media_player *mp) { media_player_kernel_player_start_now(mp); } -static void media_player_cached_reader_start(struct media_player *mp) { +static void media_player_cached_reader_start(struct media_player *mp, str_case_value_ht codec_set) { struct media_player_cache_entry *entry = mp->cache_entry; const rtp_payload_type *dst_pt = &entry->coder.handler->dest_pt; @@ -583,7 +583,7 @@ static void media_player_cached_reader_start(struct media_player *mp) { // create dummy codec handler and start timer - mp->coder.handler = codec_handler_make_dummy(&entry->coder.handler->dest_pt, mp->media); + mp->coder.handler = codec_handler_make_dummy(&entry->coder.handler->dest_pt, mp->media, codec_set); mp->run_func = media_player_read_decoded_packet; mp->next_run = rtpe_now; @@ -611,7 +611,7 @@ static void cache_packet_free(struct media_player_cache_packet *p) { // returns: true = entry exists, decoding handled separately, use entry for playback // false = no entry exists, OR entry is a new one, proceed to open decoder, then call _play_start static bool media_player_cache_get_entry(struct media_player *mp, - const rtp_payload_type *dst_pt) + const rtp_payload_type *dst_pt, str_case_value_ht codec_set) { if (!rtpe_config.player_cache) return false; @@ -630,7 +630,7 @@ static bool media_player_cache_get_entry(struct media_player *mp, bool ret = true; // entry exists, use cached data if (entry) { - media_player_cached_reader_start(mp); + media_player_cached_reader_start(mp, codec_set); goto out; } @@ -789,7 +789,9 @@ static int media_player_packet_cache(encoder_t *enc, void *u1, void *u2) { // do have a cache entry, initialise it, set up the thread, take over decoding, and then proceed as a // media player consuming the data from the decoder thread. // returns: false = continue normally decode in-thread, true = take data from other thread -static bool media_player_cache_entry_init(struct media_player *mp, const rtp_payload_type *dst_pt) { +static bool media_player_cache_entry_init(struct media_player *mp, const rtp_payload_type *dst_pt, + str_case_value_ht codec_set) +{ struct media_player_cache_entry *entry = mp->cache_entry; if (!entry) return false; @@ -804,7 +806,7 @@ static bool media_player_cache_entry_init(struct media_player *mp, const rtp_pay // use low priority (10 nice) thread_create_detach_prio(media_player_cache_entry_decoder_thread, entry, NULL, 10, "mp decoder"); - media_player_cached_reader_start(mp); + media_player_cached_reader_start(mp, codec_set); return true; } @@ -870,7 +872,7 @@ static int media_player_setup_common(struct media_player *mp, const rtp_payload_ // used for generic playback (audio_player, t38_gateway) int media_player_setup(struct media_player *mp, const rtp_payload_type *src_pt, - const rtp_payload_type *dst_pt) + const rtp_payload_type *dst_pt, str_case_value_ht codec_set) { int ret = media_player_setup_common(mp, src_pt, &dst_pt); if (ret) @@ -878,7 +880,7 @@ int media_player_setup(struct media_player *mp, const rtp_payload_type *src_pt, if (!mp->coder.handler) mp->coder.handler = codec_handler_make_playback(src_pt, dst_pt, mp->sync_ts, mp->media, - mp->ssrc_out->parent->h.ssrc); + mp->ssrc_out->parent->h.ssrc, codec_set); if (!mp->coder.handler) return -1; @@ -886,7 +888,7 @@ int media_player_setup(struct media_player *mp, const rtp_payload_type *src_pt, } // used for "play media" player static int __media_player_setup_internal(struct media_player *mp, const rtp_payload_type *src_pt, - const rtp_payload_type *dst_pt) + const rtp_payload_type *dst_pt, str_case_value_ht codec_set) { int ret = media_player_setup_common(mp, src_pt, &dst_pt); if (ret) @@ -894,14 +896,16 @@ static int __media_player_setup_internal(struct media_player *mp, const rtp_payl if (!mp->coder.handler) mp->coder.handler = codec_handler_make_media_player(src_pt, dst_pt, mp->sync_ts, mp->media, - mp->ssrc_out->parent->h.ssrc); + mp->ssrc_out->parent->h.ssrc, codec_set); if (!mp->coder.handler) return -1; return 0; } -static int __ensure_codec_handler(struct media_player *mp, const rtp_payload_type *dst_pt) { +static int __ensure_codec_handler(struct media_player *mp, const rtp_payload_type *dst_pt, + str_case_value_ht codec_set) +{ if (mp->coder.handler) return 0; @@ -917,7 +921,7 @@ static int __ensure_codec_handler(struct media_player *mp, const rtp_payload_typ src_pt.clock_rate = mp->coder.avstream->CODECPAR->sample_rate; codec_init_payload_type(&src_pt, MT_AUDIO); - if (__media_player_setup_internal(mp, &src_pt, dst_pt)) + if (__media_player_setup_internal(mp, &src_pt, dst_pt, codec_set)) return -1; mp->coder.duration = mp->coder.avstream->duration * 1000 * mp->coder.avstream->time_base.num @@ -1064,7 +1068,9 @@ static const rtp_payload_type *media_player_play_init(struct media_player *mp) { // call->master_lock held in W -static bool media_player_play_start(struct media_player *mp, const rtp_payload_type *dst_pt) { +static bool media_player_play_start(struct media_player *mp, const rtp_payload_type *dst_pt, + str_case_value_ht codec_set) +{ // needed to have usable duration for some formats. ignore errors. if (!mp->coder.fmtctx->streams || !mp->coder.fmtctx->streams[0]) avformat_find_stream_info(mp->coder.fmtctx, NULL); @@ -1075,13 +1081,13 @@ static bool media_player_play_start(struct media_player *mp, const rtp_payload_t return false; } - if (__ensure_codec_handler(mp, dst_pt)) + if (__ensure_codec_handler(mp, dst_pt, codec_set)) return false; if (mp->opts.block_egress) MEDIA_SET(mp->media, BLOCK_EGRESS); - if (media_player_cache_entry_init(mp, dst_pt)) + if (media_player_cache_entry_init(mp, dst_pt, codec_set)) return true; mp->next_run = rtpe_now; @@ -1114,7 +1120,7 @@ static mp_cached_code __media_player_init_file(struct media_player *mp, const st mp->opts = opts; - if (media_player_cache_get_entry(mp, dst_pt)) + if (media_player_cache_get_entry(mp, dst_pt, opts.codec_set)) return MPC_CACHED; char file_s[PATH_MAX]; @@ -1145,7 +1151,7 @@ bool media_player_play_file(struct media_player *mp, const str *file, media_play if (ret == MPC_ERR) return false; - return media_player_play_start(mp, dst_pt); + return media_player_play_start(mp, dst_pt, opts.codec_set); #else return false; #endif @@ -1219,14 +1225,14 @@ static mp_cached_code __media_player_init_blob_id(struct media_player *mp, const mp->cache_index.type = MP_DB; mp->cache_index.db_id = db_id; - if (media_player_cache_get_entry(mp, dst_pt)) + if (media_player_cache_get_entry(mp, dst_pt, opts.codec_set)) return MPC_CACHED; } else { mp->cache_index.type = MP_BLOB; mp->cache_index.file = str_dup_str(blob); - if (media_player_cache_get_entry(mp, dst_pt)) + if (media_player_cache_get_entry(mp, dst_pt, opts.codec_set)) return MPC_CACHED; } @@ -1282,7 +1288,7 @@ bool media_player_play_blob(struct media_player *mp, const str *blob, media_play if (ret == MPC_ERR) return false; - return media_player_play_start(mp, dst_pt); + return media_player_play_start(mp, dst_pt, opts.codec_set); } // call->master_lock held in W @@ -1389,7 +1395,7 @@ bool media_player_play_db(struct media_player *mp, long long id, media_player_op if (ret == MPC_ERR) return false; - return media_player_play_start(mp, dst_pt); + return media_player_play_start(mp, dst_pt, opts.codec_set); } // call->master_lock held in W @@ -1513,7 +1519,7 @@ bool media_player_start(struct media_player *mp) { if (!dst_pt) return false; - return media_player_play_start(mp, dst_pt); + return media_player_play_start(mp, dst_pt, str_case_value_ht_null()); #else return false; #endif diff --git a/daemon/t38.c b/daemon/t38.c index 8e285655d..d0d397886 100644 --- a/daemon/t38.c +++ b/daemon/t38.c @@ -457,13 +457,13 @@ err: // call is locked in W -void t38_gateway_start(struct t38_gateway *tg) { +void t38_gateway_start(struct t38_gateway *tg, str_case_value_ht codec_set) { if (!tg) return; // set up our player first media_player_set_media(tg->pcm_player, tg->pcm_media); - if (media_player_setup(tg->pcm_player, &tg->pcm_pt, NULL)) + if (media_player_setup(tg->pcm_player, &tg->pcm_pt, NULL, codec_set)) return; // now start our player if we can or should diff --git a/docs/ng_control_protocol.md b/docs/ng_control_protocol.md index 9ec9ee68a..8a3e439cd 100644 --- a/docs/ng_control_protocol.md +++ b/docs/ng_control_protocol.md @@ -1974,6 +1974,8 @@ Starts playback of a provided media file to the selected call participant. The f can be anything that is supported by *ffmpeg*, for example a `.wav` or `.mp3` file. It will automatically be resampled and transcoded to the appropriate sampling rate and codec. The selected participant's first listed (preferred) codec that is supported will be chosen for this purpose. +Encoder parameters such as bit rate can be set via the `codec-set` list +described above. Media files can be provided through one of these keys: diff --git a/include/audio_player.h b/include/audio_player.h index 582f15571..eecde3b28 100644 --- a/include/audio_player.h +++ b/include/audio_player.h @@ -21,7 +21,7 @@ struct audio_player; struct call_media; bool audio_player_setup(struct call_media *, const rtp_payload_type *, - unsigned int size_ms, unsigned int delay_ms); + unsigned int size_ms, unsigned int delay_ms, str_case_value_ht codec_set); void audio_player_activate(struct call_media *); void audio_player_free(struct call_media *); diff --git a/include/codec.h b/include/codec.h index b5db0f779..3208f4294 100644 --- a/include/codec.h +++ b/include/codec.h @@ -105,10 +105,13 @@ struct codec_handler *codec_handler_get(struct call_media *, int payload_type, s struct sink_handler *); void codec_handlers_free(struct call_media *); struct codec_handler *codec_handler_make_playback(const rtp_payload_type *src_pt, - const rtp_payload_type *dst_pt, unsigned long ts, struct call_media *, uint32_t ssrc); + const rtp_payload_type *dst_pt, unsigned long ts, struct call_media *, uint32_t ssrc, + str_case_value_ht codec_set); struct codec_handler *codec_handler_make_media_player(const rtp_payload_type *src_pt, - const rtp_payload_type *dst_pt, unsigned long ts, struct call_media *, uint32_t ssrc); -struct codec_handler *codec_handler_make_dummy(const rtp_payload_type *dst_pt, struct call_media *media); + const rtp_payload_type *dst_pt, unsigned long ts, struct call_media *, uint32_t ssrc, + str_case_value_ht codec_set); +struct codec_handler *codec_handler_make_dummy(const rtp_payload_type *dst_pt, struct call_media *media, + str_case_value_ht codec_set); void codec_calc_jitter(struct ssrc_ctx *, unsigned long ts, unsigned int clockrate, const struct timeval *); void codec_update_all_handlers(struct call_monologue *ml); void codec_update_all_source_handlers(struct call_monologue *ml, const sdp_ng_flags *flags); diff --git a/include/media_player.h b/include/media_player.h index 88f971864..c72548ec4 100644 --- a/include/media_player.h +++ b/include/media_player.h @@ -20,6 +20,7 @@ struct media_player; typedef struct { long long start_pos; int repeat; + str_case_value_ht codec_set; unsigned int block_egress:1; } media_player_opts_t; @@ -126,7 +127,7 @@ long long media_player_stop(struct media_player *); bool media_player_is_active(struct call_monologue *); int media_player_setup(struct media_player *mp, const rtp_payload_type *src_pt, - const rtp_payload_type *dst_pt); + const rtp_payload_type *dst_pt, str_case_value_ht codec_set); void media_player_set_media(struct media_player *mp, struct call_media *media); bool media_player_pt_match(const struct media_player *mp, const rtp_payload_type *src_pt, const rtp_payload_type *dst_pt); diff --git a/include/t38.h b/include/t38.h index cc7a3d4bd..f12d26261 100644 --- a/include/t38.h +++ b/include/t38.h @@ -69,7 +69,7 @@ struct t38_gateway { void t38_init(void); int t38_gateway_pair(struct call_media *t38_media, struct call_media *pcm_media, const struct t38_options *); -void t38_gateway_start(struct t38_gateway *); +void t38_gateway_start(struct t38_gateway *, str_case_value_ht codec_set); int t38_gateway_input_samples(struct t38_gateway *, int16_t amp[], int len); int t38_gateway_input_udptl(struct t38_gateway *, const str *); void t38_gateway_stop(struct t38_gateway *); @@ -88,7 +88,7 @@ INLINE void t38_gateway_put(struct t38_gateway **tp) { // stubs INLINE void t38_init(void) { } -INLINE void t38_gateway_start(struct t38_gateway *tg) { } +INLINE void t38_gateway_start(struct t38_gateway *tg, str_case_value_ht codec_set) { } INLINE void t38_gateway_stop(struct t38_gateway *tg) { } INLINE void t38_gateway_put(struct t38_gateway **tp) { }