diff --git a/daemon/.gitignore b/daemon/.gitignore index 2d7db3f16..5efb5c54b 100644 --- a/daemon/.gitignore +++ b/daemon/.gitignore @@ -7,3 +7,4 @@ core.* auxlib.c loglib.c rtplib.c +codeclib.c diff --git a/daemon/Makefile b/daemon/Makefile index f81d1a2a0..bf11749fa 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -57,7 +57,7 @@ SRCS= main.c kernel.c poller.c aux.c control_tcp.c streambuf.c call.c control_u crypto.c rtp.c call_interfaces.c dtls.c log.c cli.c graphite.c ice.c socket.c \ media_socket.c homer.c recording.c statistics.c cdr.c ssrc.c iptables.c tcp_listener.c \ codec.c -LIBSRCS= loglib.c auxlib.c rtplib.c +LIBSRCS= loglib.c auxlib.c rtplib.c codeclib.c OBJS= $(SRCS:.c=.o) $(LIBSRCS:.c=.o) diff --git a/daemon/call.c b/daemon/call.c index 486b16606..cf71f11c7 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -1450,92 +1450,6 @@ static void __dtls_logic(const struct sdp_ng_flags *flags, MEDIA_SET(other_media, DTLS); } -static void __rtp_payload_type_add(struct call_media *media, struct call_media *other_media, - struct rtp_payload_type *pt) -{ - struct call *call = media->call; - - if (g_hash_table_lookup(media->codecs, &pt->payload_type)) { - // collision/duplicate - ignore - __payload_type_free(pt); - return; - } - /* we must duplicate the contents */ - call_str_cpy(call, &pt->encoding_with_params, &pt->encoding_with_params); - call_str_cpy(call, &pt->encoding, &pt->encoding); - call_str_cpy(call, &pt->encoding_parameters, &pt->encoding_parameters); - call_str_cpy(call, &pt->format_parameters, &pt->format_parameters); - g_hash_table_replace(media->codecs, &pt->payload_type, pt); - - GQueue *q = g_hash_table_lookup_queue_new(media->codec_names, &pt->encoding); - g_queue_push_tail(q, GUINT_TO_POINTER(pt->payload_type)); - - g_queue_push_tail(&media->codecs_prefs_recv, pt); - - // for the other side, we need a new 'pt' struct - struct rtp_payload_type *pt_copy = g_slice_alloc(sizeof(*pt)); - *pt_copy = *pt; // contents are allocated from the 'call' - g_queue_push_tail(&other_media->codecs_prefs_send, pt_copy); - - // make sure we have at least an empty queue here to indicate support for this code. - // don't add anything to the queue as we don't know the reverse RTP payload type. - g_hash_table_lookup_queue_new(other_media->codec_names, &pt->encoding); -} - -static void __payload_queue_free(void *qq) { - GQueue *q = qq; - g_queue_free_full(q, __payload_type_free); -} -static void __rtp_payload_types(struct call_media *media, struct call_media *other_media, - GQueue *types, GHashTable *strip, - const GQueue *offer) -{ - // 'media' = receiver of this offer/answer; 'other_media' = sender of this offer/answer - struct rtp_payload_type *pt; - static const str str_all = STR_CONST_INIT("all"); - GHashTable *removed = g_hash_table_new_full(str_hash, str_equal, NULL, __payload_queue_free); - int remove_all = 0; - - // start fresh - g_queue_clear(&media->codecs_prefs_recv); - g_queue_clear_full(&other_media->codecs_prefs_send, __payload_type_free); - g_hash_table_remove_all(media->codecs); - g_hash_table_remove_all(media->codec_names); - - if (strip && g_hash_table_lookup(strip, &str_all)) - remove_all = 1; - - /* we steal the entire list to avoid duplicate allocs */ - while ((pt = g_queue_pop_head(types))) { - // codec stripping - if (strip) { - if (remove_all || g_hash_table_lookup(strip, &pt->encoding)) { - GQueue *q = g_hash_table_lookup_queue_new(removed, &pt->encoding); - g_queue_push_tail(q, pt); - continue; - } - } - __rtp_payload_type_add(media, other_media, pt); - } - - if (offer) { - // now restore codecs that have been removed, but should be offered - for (GList *l = offer->head; l; l = l->next) { - str *codec = l->data; - GQueue *q = g_hash_table_lookup(removed, codec); - if (!q) - continue; - g_hash_table_steal(removed, codec); - for (GList *l = q->head; l; l = l->next) { - pt = l->data; - __rtp_payload_type_add(media, other_media, pt); - } - g_queue_free(q); - } - } - - g_hash_table_destroy(removed); -} static void __ice_start(struct call_media *media) { if (MEDIA_ISSET(media, PASSTHRU)) { @@ -1658,8 +1572,8 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams, } // codec and RTP payload types handling - __rtp_payload_types(media, other_media, &sp->rtp_payload_types, - flags->codec_strip, &flags->codec_offer); + codec_rtp_payload_types(media, other_media, &sp->rtp_payload_types, + flags->codec_strip, &flags->codec_offer, &flags->codec_transcode); codec_handlers_update(media, other_media); /* send and recv are from our POV */ diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 8a5109f02..ef3789de0 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -618,6 +618,11 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { return; if (call_ng_flags_prefix(out, s, "codec-offer-", call_ng_flags_codec_list, &out->codec_offer)) return; + if (call_ng_flags_prefix(out, s, "transcode-", call_ng_flags_codec_list, &out->codec_transcode)) + return; + if (call_ng_flags_prefix(out, s, "codec-transcode-", call_ng_flags_codec_list, + &out->codec_transcode)) + return; ilog(LOG_WARN, "Unknown flag encountered: '" STR_FORMAT "'", STR_FMT(s)); @@ -693,11 +698,13 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu /* XXX module still needs to support these */ call_ng_flags_list(out, dict, "strip", call_ng_flags_codec_ht, out->codec_strip); call_ng_flags_list(out, dict, "offer", call_ng_flags_codec_list, &out->codec_offer); + call_ng_flags_list(out, dict, "transcode", call_ng_flags_codec_list, &out->codec_transcode); } } static void call_ng_free_flags(struct sdp_ng_flags *flags) { g_hash_table_destroy(flags->codec_strip); g_queue_clear_full(&flags->codec_offer, str_slice_free); + g_queue_clear_full(&flags->codec_transcode, str_slice_free); } static const char *call_offer_answer_ng(bencode_item_t *input, diff --git a/daemon/call_interfaces.h b/daemon/call_interfaces.h index f154f9913..5991f91ad 100644 --- a/daemon/call_interfaces.h +++ b/daemon/call_interfaces.h @@ -33,6 +33,7 @@ struct sdp_ng_flags { str metadata; GHashTable *codec_strip; GQueue codec_offer; + GQueue codec_transcode; int asymmetric:1, no_redis_update:1, unidirectional:1, diff --git a/daemon/codec.c b/daemon/codec.c index 9c2ebc7f1..3c5639c2d 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -3,6 +3,7 @@ #include "call.h" #include "log.h" #include "rtplib.h" +#include "codeclib.h" @@ -128,3 +129,193 @@ void codec_packet_free(void *pp) { p->free_func(p->s.s); g_slice_free1(sizeof(*p), p); } + + + +static struct rtp_payload_type *codec_make_payload_type(const str *codec) { + const codec_def_t *dec = codec_find(codec); + if (!dec) + return NULL; + const struct rtp_payload_type *rfc_pt = rtp_get_rfc_codec(codec); + if (!rfc_pt) + return NULL; // XXX amend for other codecs + + struct rtp_payload_type *ret = g_slice_alloc(sizeof(*ret)); + *ret = *rfc_pt; + ret->codec_def = dec; + + return ret; +} + + +static struct rtp_payload_type *codec_add_payload_type(const str *codec, struct call_media *media) { + struct rtp_payload_type *pt = codec_make_payload_type(codec); + if (!pt) { + ilog(LOG_WARN, "Codec '" STR_FORMAT "' requested for transcoding is not supported", + STR_FMT(codec)); + return NULL; + } + // find an unused payload type number + if (pt->payload_type < 0) + pt->payload_type = 96; // default first dynamic payload type number + while (1) { + if (!g_hash_table_lookup(media->codecs, &pt->payload_type)) + break; // OK + pt->payload_type++; + if (pt->payload_type < 96) // if an RFC type was taken already + pt->payload_type = 96; + else if (pt->payload_type >= 128) { + ilog(LOG_WARN, "Ran out of RTP payload type numbers while adding codec '" + STR_FORMAT "' for transcoding", + STR_FMT(codec)); + __payload_type_free(pt); + return NULL; + } + } + return pt; +} + + + + + + + + + +static void __rtp_payload_type_dup(struct call *call, struct rtp_payload_type *pt) { + /* we must duplicate the contents */ + call_str_cpy(call, &pt->encoding_with_params, &pt->encoding_with_params); + call_str_cpy(call, &pt->encoding, &pt->encoding); + call_str_cpy(call, &pt->encoding_parameters, &pt->encoding_parameters); + call_str_cpy(call, &pt->format_parameters, &pt->format_parameters); +} +// consumes 'pt' +static struct rtp_payload_type *__rtp_payload_type_add_recv(struct call_media *media, + struct rtp_payload_type *pt) +{ + struct rtp_payload_type *existing_pt; + if ((existing_pt = g_hash_table_lookup(media->codecs, &pt->payload_type))) { + // collision/duplicate - ignore + __payload_type_free(pt); + return existing_pt; + } + g_hash_table_replace(media->codecs, &pt->payload_type, pt); + + GQueue *q = g_hash_table_lookup_queue_new(media->codec_names, &pt->encoding); + g_queue_push_tail(q, GUINT_TO_POINTER(pt->payload_type)); + + g_queue_push_tail(&media->codecs_prefs_recv, pt); + + return pt; +} +// duplicates 'pt' +static void __rtp_payload_type_add_send(struct call_media *other_media, struct rtp_payload_type *pt) { + // for the other side, we need a new 'pt' struct + struct rtp_payload_type *pt_copy = g_slice_alloc(sizeof(*pt)); + *pt_copy = *pt; + g_queue_push_tail(&other_media->codecs_prefs_send, pt_copy); + + // make sure we have at least an empty queue here to indicate support for this code. + // don't add anything to the queue as we don't know the reverse RTP payload type. + g_hash_table_lookup_queue_new(other_media->codec_names, &pt->encoding); +} +// consumes 'pt' +static void __rtp_payload_type_add(struct call_media *media, struct call_media *other_media, + struct rtp_payload_type *pt) +{ + // if this payload type is already present in the 'codec' table, the _recv + // function frees its argument and returns the existing entry instead. + // otherwise it returns its argument. + pt = __rtp_payload_type_add_recv(media, pt); + __rtp_payload_type_add_send(other_media, pt); +} + +static void __payload_queue_free(void *qq) { + GQueue *q = qq; + g_queue_free_full(q, __payload_type_free); +} +static int __revert_codec_strip(GHashTable *removed, const str *codec, + struct call_media *media, struct call_media *other_media) { + GQueue *q = g_hash_table_lookup(removed, codec); + if (!q) + return 0; + ilog(LOG_DEBUG, "Restoring codec '" STR_FORMAT "' from stripped codecs (%u payload types)", + STR_FMT(codec), q->length); + g_hash_table_steal(removed, codec); + for (GList *l = q->head; l; l = l->next) { + struct rtp_payload_type *pt = l->data; + __rtp_payload_type_add(media, other_media, pt); + } + g_queue_free(q); + return 1; +} +void codec_rtp_payload_types(struct call_media *media, struct call_media *other_media, + GQueue *types, GHashTable *strip, + const GQueue *offer, const GQueue *transcode) +{ + // 'media' = receiver of this offer/answer; 'other_media' = sender of this offer/answer + struct call *call = media->call; + struct rtp_payload_type *pt; + static const str str_all = STR_CONST_INIT("all"); + GHashTable *removed = g_hash_table_new_full(str_hash, str_equal, NULL, __payload_queue_free); + int remove_all = 0; + + // start fresh + g_queue_clear(&media->codecs_prefs_recv); + g_queue_clear_full(&other_media->codecs_prefs_send, __payload_type_free); + g_hash_table_remove_all(media->codecs); + g_hash_table_remove_all(media->codec_names); + + if (strip && g_hash_table_lookup(strip, &str_all)) + remove_all = 1; + + /* we steal the entire list to avoid duplicate allocs */ + while ((pt = g_queue_pop_head(types))) { + __rtp_payload_type_dup(call, pt); // this takes care of string allocation + + // codec stripping + if (strip) { + if (remove_all || g_hash_table_lookup(strip, &pt->encoding)) { + ilog(LOG_DEBUG, "Stripping codec '" STR_FORMAT "'", STR_FMT(&pt->encoding)); + GQueue *q = g_hash_table_lookup_queue_new(removed, &pt->encoding); + g_queue_push_tail(q, pt); + continue; + } + } + __rtp_payload_type_add(media, other_media, pt); + } + + // now restore codecs that have been removed, but should be offered + for (GList *l = offer ? offer->head : NULL; l; l = l->next) { + str *codec = l->data; + __revert_codec_strip(removed, codec, media, other_media); + } + + // add transcode codecs + for (GList *l = transcode ? transcode->head : NULL; l; l = l->next) { + str *codec = l->data; + // if we wish to 'transcode' to a codec that was offered originally, + // simply restore it from the original list and handle it the same way + // as 'offer' + if (__revert_codec_strip(removed, codec, media, other_media)) + continue; + // also check if maybe the codec was never stripped + if (g_hash_table_lookup(media->codec_names, codec)) { + ilog(LOG_DEBUG, "Codec '" STR_FORMAT "' requested for transcoding is already present", + STR_FMT(codec)); + continue; + } + + // create new payload type + pt = codec_add_payload_type(codec, media); + if (!pt) + continue; + + ilog(LOG_DEBUG, "Codec '" STR_FORMAT "' added for transcoding with payload type %u", + STR_FMT(codec), pt->payload_type); + __rtp_payload_type_add_recv(media, pt); + } + + g_hash_table_destroy(removed); +} diff --git a/daemon/codec.h b/daemon/codec.h index 83beeb66c..caa3e1e54 100644 --- a/daemon/codec.h +++ b/daemon/codec.h @@ -32,6 +32,10 @@ void codec_handlers_free(struct call_media *); void codec_packet_free(void *); +void codec_rtp_payload_types(struct call_media *media, struct call_media *other_media, + GQueue *types, GHashTable *strip, + const GQueue *offer, const GQueue *transcode); + diff --git a/lib/codeclib.c b/lib/codeclib.c new file mode 100644 index 000000000..1b6d2c8a7 --- /dev/null +++ b/lib/codeclib.c @@ -0,0 +1,53 @@ +#include "codeclib.h" +#include +#include +#include "str.h" + + + + +#define CODEC_DEF_MULT_NAME(ref, id, mult, name) { \ + .rtpname = #ref, \ + .avcodec_id = AV_CODEC_ID_ ## id, \ + .clockrate_mult = mult, \ + .avcodec_name = #name, \ +} +#define CODEC_DEF_MULT(ref, id, mult) CODEC_DEF_MULT_NAME(ref, id, mult, NULL) +#define CODEC_DEF_NAME(ref, id, name) CODEC_DEF_MULT_NAME(ref, id, 1, name) +#define CODEC_DEF(ref, id) CODEC_DEF_MULT(ref, id, 1) + +static const struct codec_def_s codecs[] = { + CODEC_DEF(PCMA, PCM_ALAW), + CODEC_DEF(PCMU, PCM_MULAW), + CODEC_DEF(G723, G723_1), + CODEC_DEF_MULT(G722, ADPCM_G722, 2), + CODEC_DEF(QCELP, QCELP), + CODEC_DEF(G729, G729), + CODEC_DEF(speex, SPEEX), + CODEC_DEF(GSM, GSM), + CODEC_DEF(iLBC, ILBC), + CODEC_DEF_NAME(opus, OPUS, libopus), + CODEC_DEF_NAME(vorbis, VORBIS, libvorbis), + CODEC_DEF(ac3, AC3), + CODEC_DEF(eac3, EAC3), + CODEC_DEF(ATRAC3, ATRAC3), + CODEC_DEF(ATRAC-X, ATRAC3P), +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) + CODEC_DEF(EVRC, EVRC), + CODEC_DEF(EVRC0, EVRC), + CODEC_DEF(EVRC1, EVRC), +#endif + CODEC_DEF(AMR, AMR_NB), + CODEC_DEF(AMR-WB, AMR_WB), +}; + + + +// XXX use hashtable for quicker lookup +const codec_def_t *codec_find(const str *name) { + for (int i = 0; i < G_N_ELEMENTS(codecs); i++) { + if (!str_cmp(name, codecs[i].rtpname)) + return &codecs[i]; + } + return NULL; +} diff --git a/lib/codeclib.h b/lib/codeclib.h new file mode 100644 index 000000000..d4a7611f0 --- /dev/null +++ b/lib/codeclib.h @@ -0,0 +1,21 @@ +#ifndef __CODECLIB_H__ +#define __CODECLIB_H__ + + +#include "str.h" + + + +struct codec_def_s { + const char *rtpname; + int clockrate_mult; + int avcodec_id; + const char *avcodec_name; +}; +typedef struct codec_def_s codec_def_t; + + +const codec_def_t *codec_find(const str *name); + + +#endif diff --git a/lib/rtplib.c b/lib/rtplib.c index 95c7292d4..de05e0ca5 100644 --- a/lib/rtplib.c +++ b/lib/rtplib.c @@ -2,6 +2,7 @@ #include #include "str.h" #include "log.h" +#include "codeclib.h" @@ -125,3 +126,15 @@ const struct rtp_payload_type *rtp_get_rfc_payload_type(unsigned int type) { return NULL; return rtp_pt; } + +// XXX use hash table +const struct rtp_payload_type *rtp_get_rfc_codec(const str *codec) { + for (int i = 0; i < num_rfc_rtp_payload_types; i++) { + if (!rfc_rtp_payload_types[i].encoding.s) + continue; + if (str_cmp_str(codec, &rfc_rtp_payload_types[i].encoding)) + continue; + return &rfc_rtp_payload_types[i]; + } + return NULL; +} diff --git a/lib/rtplib.h b/lib/rtplib.h index 6e525b90e..8535c4a40 100644 --- a/lib/rtplib.h +++ b/lib/rtplib.h @@ -3,6 +3,7 @@ #include #include "str.h" +#include "codeclib.h" struct rtp_header { @@ -16,12 +17,14 @@ struct rtp_header { struct rtp_payload_type { - unsigned int payload_type; + int payload_type; str encoding_with_params; str encoding; unsigned int clock_rate; str encoding_parameters; str format_parameters; + + const codec_def_t *codec_def; }; @@ -32,6 +35,7 @@ extern const int num_rfc_rtp_payload_types; int rtp_payload(struct rtp_header **out, str *p, const str *s); int rtp_padding(struct rtp_header *header, str *payload); const struct rtp_payload_type *rtp_get_rfc_payload_type(unsigned int type); +const struct rtp_payload_type *rtp_get_rfc_codec(const str *codec); #endif diff --git a/recording-daemon/.gitignore b/recording-daemon/.gitignore index 860c1f6a8..959c66119 100644 --- a/recording-daemon/.gitignore +++ b/recording-daemon/.gitignore @@ -7,3 +7,4 @@ rtpengine-recording auxlib.c loglib.c rtplib.c +codeclib.c diff --git a/recording-daemon/Makefile b/recording-daemon/Makefile index 9d0e350d9..27e8e9fa3 100644 --- a/recording-daemon/Makefile +++ b/recording-daemon/Makefile @@ -26,7 +26,7 @@ include ../lib/lib.Makefile SRCS= epoll.c garbage.c inotify.c main.c metafile.c stream.c recaux.c packet.c \ decoder.c output.c mix.c resample.c db.c log.c forward.c -LIBSRCS= loglib.c auxlib.c rtplib.c +LIBSRCS= loglib.c auxlib.c rtplib.c codeclib.c OBJS= $(SRCS:.c=.o) $(LIBSRCS:.c=.o) diff --git a/recording-daemon/decoder.c b/recording-daemon/decoder.c index c8bb14497..9a409999c 100644 --- a/recording-daemon/decoder.c +++ b/recording-daemon/decoder.c @@ -15,6 +15,7 @@ #include "output.h" #include "mix.h" #include "resample.h" +#include "codeclib.h" struct decoder_s { @@ -33,67 +34,12 @@ struct decoder_s { }; -struct decoder_def_s { - const char *rtpname; - int clockrate_mult; - int avcodec_id; - const char *avcodec_name; -}; - - -#define DECODER_DEF_MULT_NAME(ref, id, mult, name) { \ - .rtpname = #ref, \ - .avcodec_id = AV_CODEC_ID_ ## id, \ - .clockrate_mult = mult, \ - .avcodec_name = #name, \ -} -#define DECODER_DEF_MULT(ref, id, mult) DECODER_DEF_MULT_NAME(ref, id, mult, NULL) -#define DECODER_DEF_NAME(ref, id, name) DECODER_DEF_MULT_NAME(ref, id, 1, name) -#define DECODER_DEF(ref, id) DECODER_DEF_MULT(ref, id, 1) - -static const struct decoder_def_s decoders[] = { - DECODER_DEF(PCMA, PCM_ALAW), - DECODER_DEF(PCMU, PCM_MULAW), - DECODER_DEF(G723, G723_1), - DECODER_DEF_MULT(G722, ADPCM_G722, 2), - DECODER_DEF(QCELP, QCELP), - DECODER_DEF(G729, G729), - DECODER_DEF(speex, SPEEX), - DECODER_DEF(GSM, GSM), - DECODER_DEF(iLBC, ILBC), - DECODER_DEF_NAME(opus, OPUS, libopus), - DECODER_DEF_NAME(vorbis, VORBIS, libvorbis), - DECODER_DEF(ac3, AC3), - DECODER_DEF(eac3, EAC3), - DECODER_DEF(ATRAC3, ATRAC3), - DECODER_DEF(ATRAC-X, ATRAC3P), -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0) - DECODER_DEF(EVRC, EVRC), - DECODER_DEF(EVRC0, EVRC), - DECODER_DEF(EVRC1, EVRC), -#endif - DECODER_DEF(AMR, AMR_NB), - DECODER_DEF(AMR-WB, AMR_WB), -}; -typedef struct decoder_def_s decoder_def_t; - - int resample_audio; - -static const decoder_def_t *decoder_find(const str *name) { - for (int i = 0; i < G_N_ELEMENTS(decoders); i++) { - if (!str_cmp(name, decoders[i].rtpname)) - return &decoders[i]; - } - return NULL; -} - - decoder_t *decoder_new(const char *payload_str) { const char *err = NULL; @@ -115,7 +61,7 @@ decoder_t *decoder_new(const char *payload_str) { channels = 1; } - const decoder_def_t *def = decoder_find(&name); + const codec_def_t *def = codec_find(&name); if (!def) { ilog(LOG_WARN, "No decoder for payload %s", payload_str); return NULL; diff --git a/recording-daemon/output.c b/recording-daemon/output.c index 389cffebd..20ec6f477 100644 --- a/recording-daemon/output.c +++ b/recording-daemon/output.c @@ -201,7 +201,7 @@ int output_config(output_t *output, const format_t *requested_format, format_t * avcodec_parameters_from_context(output->avst->codecpar, output->avcctx); #endif - char full_fn[PATH_MAX]; + char full_fn[PATH_MAX*2]; char suff[16] = ""; for (int i = 1; i < 20; i++) { snprintf(full_fn, sizeof(full_fn), "%s%s.%s", output->full_filename, suff, output->file_format); diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client index 927ae2582..c19992839 100755 --- a/utils/rtpengine-ng-client +++ b/utils/rtpengine-ng-client @@ -47,6 +47,7 @@ GetOptions( 'media-address=s' => \$options{'media address'}, 'codec-strip=s@' => \$options{'codec-strip'}, 'codec-offer=s@' => \$options{'codec-offer'}, + 'codec-transcode=s@' => \$options{'codec-transcode'}, 'flags=s@' => \$options{'flags'}, ) or die; @@ -74,11 +75,10 @@ if (defined($options{direction})) { $options{direction} =~ /(.*),(.*)/ or die; $packet{direction} = [$1,$2]; } -if ($options{'codec-strip'} && @{$options{'codec-strip'}}) { - $packet{codec}{strip} = $options{'codec-strip'}; -} -if ($options{'codec-offer'} && @{$options{'codec-offer'}}) { - $packet{codec}{offer} = $options{'codec-offer'}; +for my $x (qw(strip offer transcode)) { + if ($options{'codec-'.$x} && @{$options{'codec-'.$x}}) { + $packet{codec}{$x} = $options{'codec-'.$x}; + } } if ($options{'flags'} && @{$options{'flags'}}) { push(@{$packet{flags}}, @{$options{'flags'}});