mirror of https://github.com/sipwise/rtpengine.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3566 lines
114 KiB
3566 lines
114 KiB
#include "codec.h"
|
|
#include <glib.h>
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <sys/types.h>
|
|
#include "call.h"
|
|
#include "log.h"
|
|
#include "rtplib.h"
|
|
#include "codeclib.h"
|
|
#include "ssrc.h"
|
|
#include "rtcp.h"
|
|
#include "call_interfaces.h"
|
|
#include "dtmf.h"
|
|
#include "dtmflib.h"
|
|
#include "t38.h"
|
|
#include "media_player.h"
|
|
#include "timerthread.h"
|
|
#include "log_funcs.h"
|
|
|
|
|
|
|
|
|
|
static codec_handler_func handler_func_passthrough;
|
|
|
|
static struct rtp_payload_type *__rtp_payload_type_copy(const struct rtp_payload_type *pt);
|
|
static void __rtp_payload_type_dup(struct call *call, struct rtp_payload_type *pt);
|
|
static void __rtp_payload_type_add_name(GHashTable *, struct rtp_payload_type *pt);
|
|
|
|
|
|
static struct codec_handler codec_handler_stub = {
|
|
.source_pt.payload_type = -1,
|
|
.func = handler_func_passthrough,
|
|
.kernelize = 1,
|
|
};
|
|
|
|
|
|
|
|
static GList *__delete_x_codec(GList *link, GHashTable *codecs, GHashTable *codec_names, GQueue *codecs_prefs) {
|
|
struct rtp_payload_type *pt = link->data;
|
|
|
|
g_hash_table_remove(codecs, &pt->payload_type);
|
|
g_hash_table_remove(codec_names, &pt->encoding);
|
|
g_hash_table_remove(codec_names, &pt->encoding_with_params);
|
|
|
|
GList *next = link->next;
|
|
g_queue_delete_link(codecs_prefs, link);
|
|
payload_type_free(pt);
|
|
return next;
|
|
}
|
|
static GList *__delete_receiver_codec(struct call_media *receiver, GList *link) {
|
|
return __delete_x_codec(link, receiver->codecs_recv, receiver->codec_names_recv,
|
|
&receiver->codecs_prefs_recv);
|
|
}
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
|
|
|
|
#include <spandsp/telephony.h>
|
|
#include <spandsp/super_tone_rx.h>
|
|
#include <spandsp/logging.h>
|
|
#include <spandsp/dtmf.h>
|
|
#include "resample.h"
|
|
#include "dtmf_rx_fillin.h"
|
|
|
|
|
|
|
|
struct codec_ssrc_handler;
|
|
struct transcode_packet;
|
|
|
|
struct dtx_buffer {
|
|
struct timerthread_queue ttq;
|
|
mutex_t lock;
|
|
struct codec_ssrc_handler *csh;
|
|
int ptime; // ms per packet
|
|
int tspp; // timestamp increment per packet
|
|
struct call *call;
|
|
unsigned long ts;
|
|
time_t start;
|
|
};
|
|
struct dtx_entry {
|
|
struct timerthread_queue_entry ttq_entry;
|
|
struct transcode_packet *packet;
|
|
struct media_packet mp;
|
|
unsigned long ts;
|
|
void *ssrc_ptr; // opaque pointer, doesn't hold a reference
|
|
};
|
|
|
|
struct silence_event {
|
|
uint64_t start;
|
|
uint64_t end;
|
|
};
|
|
|
|
struct codec_ssrc_handler {
|
|
struct ssrc_entry h; // must be first
|
|
struct codec_handler *handler;
|
|
decoder_t *decoder;
|
|
encoder_t *encoder;
|
|
format_t encoder_format;
|
|
int bitrate;
|
|
int ptime;
|
|
int bytes_per_packet;
|
|
unsigned long first_ts; // for output TS scaling
|
|
unsigned long last_ts; // to detect input lag and handle lost packets
|
|
unsigned long ts_in; // for DTMF dupe detection
|
|
struct timeval first_send;
|
|
unsigned long first_send_ts;
|
|
unsigned long output_skew;
|
|
GString *sample_buffer;
|
|
struct dtx_buffer *dtx_buffer;
|
|
|
|
// DTMF DSP stuff
|
|
dtmf_rx_state_t *dtmf_dsp;
|
|
resample_t dtmf_resampler;
|
|
format_t dtmf_format;
|
|
uint64_t dtmf_ts, last_dtmf_event_ts;
|
|
GQueue dtmf_events;
|
|
struct dtmf_event dtmf_event;
|
|
|
|
// silence detection
|
|
GQueue silence_events;
|
|
|
|
uint64_t skip_pts;
|
|
|
|
int rtp_mark:1;
|
|
};
|
|
struct transcode_packet {
|
|
seq_packet_t p; // must be first
|
|
unsigned long ts;
|
|
str *payload;
|
|
struct codec_handler *handler;
|
|
int marker:1,
|
|
ignore_seq:1;
|
|
int (*func)(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *);
|
|
int (*dup_func)(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *);
|
|
struct rtp_header rtp;
|
|
};
|
|
struct codec_tracker {
|
|
GHashTable *clockrates; // 8000, 16000, etc, for each real audio codec that is present
|
|
GHashTable *touched; // 8000, 16000, etc, for each audio codec that was touched (added, removed, etc)
|
|
int all_touched;
|
|
GHashTable *supp_codecs; // telephone-event etc => hash table of clock rates
|
|
};
|
|
|
|
struct rtcp_timer_queue {
|
|
struct timerthread_queue ttq;
|
|
};
|
|
struct rtcp_timer {
|
|
struct timerthread_queue_entry ttq_entry;
|
|
struct call *call;
|
|
struct call_media *media;
|
|
};
|
|
|
|
|
|
|
|
static struct timerthread codec_timers_thread;
|
|
static struct rtcp_timer_queue *rtcp_timer_queue;
|
|
|
|
|
|
static codec_handler_func handler_func_passthrough_ssrc;
|
|
static codec_handler_func handler_func_transcode;
|
|
static codec_handler_func handler_func_playback;
|
|
static codec_handler_func handler_func_inject_dtmf;
|
|
static codec_handler_func handler_func_supplemental;
|
|
static codec_handler_func handler_func_dtmf;
|
|
static codec_handler_func handler_func_t38;
|
|
|
|
static struct ssrc_entry *__ssrc_handler_transcode_new(void *p);
|
|
static struct ssrc_entry *__ssrc_handler_new(void *p);
|
|
static void __free_ssrc_handler(void *);
|
|
|
|
static void __transcode_packet_free(struct transcode_packet *);
|
|
|
|
static int packet_decode(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *);
|
|
static int packet_encoded_rtp(encoder_t *enc, void *u1, void *u2);
|
|
static int packet_decoded_fifo(decoder_t *decoder, AVFrame *frame, void *u1, void *u2);
|
|
static int packet_decoded_direct(decoder_t *decoder, AVFrame *frame, void *u1, void *u2);
|
|
|
|
static void codec_touched(struct rtp_payload_type *pt, struct call_media *media);
|
|
|
|
|
|
static struct codec_handler codec_handler_stub_ssrc = {
|
|
.source_pt.payload_type = -1,
|
|
.func = handler_func_passthrough_ssrc,
|
|
.kernelize = 1,
|
|
};
|
|
|
|
|
|
|
|
static void __handler_shutdown(struct codec_handler *handler) {
|
|
free_ssrc_hash(&handler->ssrc_hash);
|
|
if (handler->ssrc_handler)
|
|
obj_put(&handler->ssrc_handler->h);
|
|
handler->ssrc_handler = NULL;
|
|
handler->kernelize = 0;
|
|
handler->transcoder = 0;
|
|
handler->dtmf_scaler = 0;
|
|
handler->output_handler = handler; // reset to default
|
|
handler->dtmf_payload_type = -1;
|
|
handler->cn_payload_type = -1;
|
|
handler->pcm_dtmf_detect = 0;
|
|
|
|
if (handler->stats_entry) {
|
|
g_atomic_int_add(&handler->stats_entry->num_transcoders, -1);
|
|
handler->stats_entry = NULL;
|
|
g_free(handler->stats_chain);
|
|
}
|
|
}
|
|
|
|
static void __codec_handler_free(void *pp) {
|
|
struct codec_handler *h = pp;
|
|
__handler_shutdown(h);
|
|
g_slice_free1(sizeof(*h), h);
|
|
}
|
|
void codec_handler_free(struct codec_handler **handler) {
|
|
if (!handler || !*handler)
|
|
return;
|
|
__codec_handler_free(*handler);
|
|
*handler = NULL;
|
|
}
|
|
|
|
static struct codec_handler *__handler_new(const struct rtp_payload_type *pt, struct call_media *media) {
|
|
struct codec_handler *handler = g_slice_alloc0(sizeof(*handler));
|
|
if (pt)
|
|
handler->source_pt = *pt;
|
|
handler->output_handler = handler; // default
|
|
handler->dtmf_payload_type = -1;
|
|
handler->cn_payload_type = -1;
|
|
handler->packet_encoded = packet_encoded_rtp;
|
|
handler->packet_decoded = packet_decoded_fifo;
|
|
handler->media = media;
|
|
return handler;
|
|
}
|
|
|
|
static void __make_passthrough(struct codec_handler *handler) {
|
|
__handler_shutdown(handler);
|
|
ilog(LOG_DEBUG, "Using passthrough handler for " STR_FORMAT,
|
|
STR_FMT(&handler->source_pt.encoding_with_params));
|
|
if (handler->source_pt.codec_def && handler->source_pt.codec_def->dtmf)
|
|
handler->func = handler_func_dtmf;
|
|
else if (handler->source_pt.codec_def && handler->source_pt.codec_def->supplemental)
|
|
handler->func = handler_func_supplemental;
|
|
else {
|
|
handler->func = handler_func_passthrough;
|
|
handler->kernelize = 1;
|
|
}
|
|
handler->dest_pt = handler->source_pt;
|
|
handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_new, handler);
|
|
}
|
|
static void __make_passthrough_ssrc(struct codec_handler *handler) {
|
|
__handler_shutdown(handler);
|
|
ilog(LOG_DEBUG, "Using passthrough handler with new SSRC for " STR_FORMAT,
|
|
STR_FMT(&handler->source_pt.encoding_with_params));
|
|
if (handler->source_pt.codec_def && handler->source_pt.codec_def->dtmf)
|
|
handler->func = handler_func_dtmf;
|
|
else if (handler->source_pt.codec_def && handler->source_pt.codec_def->supplemental)
|
|
handler->func = handler_func_supplemental;
|
|
else {
|
|
handler->func = handler_func_passthrough_ssrc;
|
|
handler->kernelize = 1;
|
|
}
|
|
handler->dest_pt = handler->source_pt;
|
|
handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_new, handler);
|
|
}
|
|
|
|
static void __make_transcoder(struct codec_handler *handler, struct rtp_payload_type *dest,
|
|
GHashTable *output_transcoders, int dtmf_payload_type, int pcm_dtmf_detect)
|
|
{
|
|
assert(handler->source_pt.codec_def != NULL);
|
|
assert(dest->codec_def != NULL);
|
|
|
|
// if we're just repacketising:
|
|
if (dtmf_payload_type == -1 && dest->codec_def && dest->codec_def->dtmf)
|
|
dtmf_payload_type = dest->payload_type;
|
|
|
|
// don't reset handler if it already matches what we want
|
|
if (!handler->transcoder)
|
|
goto reset;
|
|
if (rtp_payload_type_cmp(dest, &handler->dest_pt))
|
|
goto reset;
|
|
if (handler->func != handler_func_transcode)
|
|
goto reset;
|
|
|
|
ilog(LOG_DEBUG, "Leaving transcode context for " STR_FORMAT " -> " STR_FORMAT " intact",
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
STR_FMT(&dest->encoding_with_params));
|
|
|
|
goto check_output;
|
|
|
|
reset:
|
|
__handler_shutdown(handler);
|
|
|
|
handler->dest_pt = *dest;
|
|
handler->func = handler_func_transcode;
|
|
handler->transcoder = 1;
|
|
if (dtmf_payload_type != -1)
|
|
handler->dtmf_payload_type = dtmf_payload_type;
|
|
handler->pcm_dtmf_detect = pcm_dtmf_detect ? 1 : 0;
|
|
|
|
// is this DTMF to DTMF?
|
|
if (dtmf_payload_type != -1 && handler->source_pt.codec_def->dtmf) {
|
|
ilog(LOG_DEBUG, "Created DTMF transcode context for " STR_FORMAT " -> PT %i",
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
dtmf_payload_type);
|
|
handler->dtmf_scaler = 1;
|
|
}
|
|
else
|
|
ilog(LOG_DEBUG, "Created transcode context for " STR_FORMAT " -> " STR_FORMAT
|
|
" with DTMF output %i",
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
STR_FMT(&dest->encoding_with_params), dtmf_payload_type);
|
|
|
|
handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_transcode_new, handler);
|
|
|
|
// stats entry
|
|
handler->stats_chain = g_strdup_printf(STR_FORMAT " -> " STR_FORMAT,
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
STR_FMT(&dest->encoding_with_params));
|
|
|
|
mutex_lock(&rtpe_codec_stats_lock);
|
|
struct codec_stats *stats_entry =
|
|
g_hash_table_lookup(rtpe_codec_stats, handler->stats_chain);
|
|
if (!stats_entry) {
|
|
stats_entry = g_slice_alloc0(sizeof(*stats_entry));
|
|
stats_entry->chain = strdup(handler->stats_chain);
|
|
g_hash_table_insert(rtpe_codec_stats, stats_entry->chain, stats_entry);
|
|
stats_entry->chain_brief = g_strdup_printf(STR_FORMAT "_" STR_FORMAT,
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
STR_FMT(&dest->encoding_with_params));
|
|
}
|
|
handler->stats_entry = stats_entry;
|
|
mutex_unlock(&rtpe_codec_stats_lock);
|
|
|
|
g_atomic_int_inc(&stats_entry->num_transcoders);
|
|
|
|
check_output:;
|
|
// check if we have multiple decoders transcoding to the same output PT
|
|
struct codec_handler *output_handler = NULL;
|
|
if (output_transcoders)
|
|
output_handler = g_hash_table_lookup(output_transcoders,
|
|
GINT_TO_POINTER(dest->payload_type));
|
|
if (output_handler) {
|
|
ilog(LOG_DEBUG, "Using existing encoder context");
|
|
handler->output_handler = output_handler;
|
|
}
|
|
else {
|
|
if (output_transcoders)
|
|
g_hash_table_insert(output_transcoders, GINT_TO_POINTER(dest->payload_type), handler);
|
|
handler->output_handler = handler; // make sure we don't have a stale pointer
|
|
}
|
|
}
|
|
|
|
struct codec_handler *codec_handler_make_playback(const struct rtp_payload_type *src_pt,
|
|
const struct rtp_payload_type *dst_pt, unsigned long last_ts, struct call_media *media)
|
|
{
|
|
struct codec_handler *handler = __handler_new(src_pt, media);
|
|
handler->dest_pt = *dst_pt;
|
|
handler->func = handler_func_playback;
|
|
handler->ssrc_handler = (void *) __ssrc_handler_transcode_new(handler);
|
|
handler->ssrc_handler->first_ts = last_ts;
|
|
while (handler->ssrc_handler->first_ts == 0)
|
|
handler->ssrc_handler->first_ts = random();
|
|
handler->ssrc_handler->rtp_mark = 1;
|
|
|
|
ilog(LOG_DEBUG, "Created media playback context for " STR_FORMAT " -> " STR_FORMAT "",
|
|
STR_FMT(&src_pt->encoding_with_params),
|
|
STR_FMT(&dst_pt->encoding_with_params));
|
|
|
|
return handler;
|
|
}
|
|
|
|
void ensure_codec_def(struct rtp_payload_type *pt, struct call_media *media) {
|
|
if (pt->codec_def)
|
|
return;
|
|
|
|
pt->codec_def = codec_find(&pt->encoding, media->type_id);
|
|
if (!pt->codec_def)
|
|
return;
|
|
if (!pt->codec_def->support_encoding || !pt->codec_def->support_decoding)
|
|
pt->codec_def = NULL;
|
|
}
|
|
|
|
static GList *__delete_send_codec(struct call_media *sender, GList *link) {
|
|
return __delete_x_codec(link, sender->codecs_send, sender->codec_names_send,
|
|
&sender->codecs_prefs_send);
|
|
}
|
|
|
|
// only called from codec_handlers_update()
|
|
static void __make_passthrough_gsl(struct codec_handler *handler, GSList **handlers) {
|
|
__make_passthrough(handler);
|
|
*handlers = g_slist_prepend(*handlers, handler);
|
|
}
|
|
|
|
// only called from codec_handlers_update()
|
|
static void __dtmf_dsp_shutdown(struct call_media *sink, int payload_type) {
|
|
if (!sink->codec_handlers)
|
|
return;
|
|
|
|
for (GList *l = sink->codec_handlers_store.head; l; l = l->next) {
|
|
struct codec_handler *handler = l->data;
|
|
if (!handler->transcoder)
|
|
continue;
|
|
if (handler->dtmf_payload_type != payload_type)
|
|
continue;
|
|
if (handler->dtmf_scaler)
|
|
continue;
|
|
|
|
ilog(LOG_DEBUG, "Shutting down DTMF DSP for '" STR_FORMAT "' -> %i (not needed)",
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
payload_type);
|
|
handler->dtmf_payload_type = -1;
|
|
}
|
|
}
|
|
|
|
|
|
static void __track_supp_codec(GHashTable *supplemental_sinks, struct rtp_payload_type *pt) {
|
|
if (!pt->codec_def || !pt->codec_def->supplemental)
|
|
return;
|
|
|
|
GHashTable *supp_sinks = g_hash_table_lookup(supplemental_sinks, pt->codec_def->rtpname);
|
|
if (!supp_sinks)
|
|
return;
|
|
if (!g_hash_table_lookup(supp_sinks, GUINT_TO_POINTER(pt->clock_rate)))
|
|
g_hash_table_insert(supp_sinks, GUINT_TO_POINTER(pt->clock_rate), pt);
|
|
}
|
|
|
|
static struct rtp_payload_type *__check_dest_codecs(struct call_media *receiver, struct call_media *sink,
|
|
const struct sdp_ng_flags *flags, GHashTable *supplemental_sinks, int *sink_transcoding)
|
|
{
|
|
struct rtp_payload_type *pref_dest_codec = NULL;
|
|
struct rtp_payload_type *first_tc_codec = NULL;
|
|
|
|
for (GList *l = sink->codecs_prefs_send.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
ensure_codec_def(pt, sink);
|
|
if (!pt->codec_def) // not supported, next
|
|
continue;
|
|
|
|
// fix up ptime
|
|
if (!pt->ptime)
|
|
pt->ptime = pt->codec_def->default_ptime;
|
|
if (sink->ptime)
|
|
pt->ptime = sink->ptime;
|
|
|
|
if (!pref_dest_codec && !pt->codec_def->supplemental)
|
|
pref_dest_codec = pt;
|
|
|
|
|
|
// also check if this is a transcoding codec: if we can send a codec to the sink,
|
|
// but can't receive it on the receiver side, then it's transcoding. this is to check
|
|
// whether transcoding on the sink side is actually needed. if transcoding has been
|
|
// previously enabled on the sink, but no transcoding codecs are actually present,
|
|
// we can disable the transcoding engine.
|
|
struct rtp_payload_type *recv_pt = g_hash_table_lookup(receiver->codecs_send,
|
|
&pt->payload_type);
|
|
if (recv_pt && rtp_payload_type_cmp(pt, recv_pt))
|
|
recv_pt = NULL;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX old flag is %i", *sink_transcoding);
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX checking dest codec " STR_FORMAT " is %i",
|
|
//STR_FMT(&pt->encoding_with_params),
|
|
//pt->for_transcoding);
|
|
//if (recv_pt)
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX checking dest codec reverse " STR_FORMAT " is %i",
|
|
//STR_FMT(&recv_pt->encoding_with_params),
|
|
//recv_pt->for_transcoding);
|
|
if (MEDIA_ISSET(sink, TRANSCODE)) {
|
|
if (!recv_pt) {
|
|
// can the sink receive codec but the receiver can't send it?
|
|
*sink_transcoding |= 0x3;
|
|
}
|
|
}
|
|
if (pt->for_transcoding) {
|
|
// codec is explicitly marked for transcoding. enable transcoding engine
|
|
MEDIA_SET(receiver, TRANSCODE);
|
|
*sink_transcoding |= 0x3;
|
|
if (!first_tc_codec && !pt->codec_def->supplemental)
|
|
first_tc_codec = pt;
|
|
if (pt->codec_def->supplemental)
|
|
*sink_transcoding |= 0x4;
|
|
}
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX new flag is %i", *sink_transcoding);
|
|
|
|
__track_supp_codec(supplemental_sinks, pt);
|
|
}
|
|
|
|
if (first_tc_codec)
|
|
pref_dest_codec = first_tc_codec;
|
|
if (pref_dest_codec)
|
|
ilog(LOG_DEBUG, "Default sink codec is " STR_FORMAT,
|
|
STR_FMT(&pref_dest_codec->encoding_with_params));
|
|
|
|
return pref_dest_codec;
|
|
}
|
|
|
|
static void __check_send_codecs(struct call_media *receiver, struct call_media *sink,
|
|
const struct sdp_ng_flags *flags, GHashTable *supplemental_sinks, int *sink_transcoding)
|
|
{
|
|
if (!MEDIA_ISSET(sink, TRANSCODE))
|
|
return;
|
|
|
|
for (GList *l = sink->codecs_prefs_recv.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
struct rtp_payload_type *recv_pt = g_hash_table_lookup(receiver->codecs_send,
|
|
&pt->payload_type);
|
|
int tc_flag = 0;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX old flag is %i", *sink_transcoding);
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX checking send codec " STR_FORMAT " is %i",
|
|
//STR_FMT(&pt->encoding_with_params),
|
|
//pt->for_transcoding);
|
|
//if (recv_pt)
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX checking send codec reverse " STR_FORMAT " is %i",
|
|
//STR_FMT(&recv_pt->encoding_with_params),
|
|
//recv_pt->for_transcoding);
|
|
if (!recv_pt || rtp_payload_type_cmp(pt, recv_pt))
|
|
tc_flag |= 0x3;
|
|
if (flags && flags->inject_dtmf)
|
|
tc_flag |= 0x1;
|
|
if (pt->for_transcoding)
|
|
tc_flag |= 0x3;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX set flag is %i", *sink_transcoding);
|
|
if (tc_flag) {
|
|
// can the sink receive codec but the receiver can't send it?
|
|
*sink_transcoding |= tc_flag;
|
|
continue;
|
|
}
|
|
|
|
// even if the receiver can receive the same codec that the sink can
|
|
// send, we might still have it configured as a transcoder due to
|
|
// force accepted codec in the offer
|
|
struct codec_handler *ch_recv =
|
|
g_hash_table_lookup(sink->codec_handlers, GINT_TO_POINTER(recv_pt->payload_type));
|
|
if (!ch_recv)
|
|
continue;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX handler transcoder %i", ch_recv->transcoder);
|
|
if (ch_recv->transcoder)
|
|
*sink_transcoding |= 0x3;
|
|
}
|
|
}
|
|
|
|
static int __supp_payload_type(GHashTable *supplemental_sinks, struct rtp_payload_type *pref_dest_codec,
|
|
const char *codec)
|
|
{
|
|
GHashTable *supp_sinks = g_hash_table_lookup(supplemental_sinks, codec);
|
|
if (!supp_sinks)
|
|
return -1;
|
|
if (!g_hash_table_size(supp_sinks) || !pref_dest_codec)
|
|
return -1;
|
|
|
|
// find the codec entry with a matching clock rate
|
|
struct rtp_payload_type *pt = g_hash_table_lookup(supp_sinks,
|
|
GUINT_TO_POINTER(pref_dest_codec->clock_rate));
|
|
if (!pt)
|
|
return -1;
|
|
return pt->payload_type;
|
|
}
|
|
|
|
static int __dtmf_payload_type(GHashTable *supplemental_sinks, struct rtp_payload_type *pref_dest_codec) {
|
|
GHashTable *dtmf_sinks = g_hash_table_lookup(supplemental_sinks, "telephone-event");
|
|
if (!dtmf_sinks)
|
|
return -1;
|
|
if (!g_hash_table_size(dtmf_sinks) || !pref_dest_codec)
|
|
return -1;
|
|
|
|
int dtmf_payload_type = __supp_payload_type(supplemental_sinks, pref_dest_codec, "telephone-event");
|
|
|
|
if (dtmf_payload_type == -1)
|
|
ilog(LOG_INFO, "Not transcoding PCM DTMF tones to telephone-event packets as "
|
|
"no payload type with a matching clock rate for '" STR_FORMAT
|
|
"' was found", STR_FMT(&pref_dest_codec->encoding_with_params));
|
|
else
|
|
ilog(LOG_DEBUG, "Output DTMF payload type is %i", dtmf_payload_type);
|
|
|
|
return dtmf_payload_type;
|
|
}
|
|
|
|
static int __unused_pt_number(struct call_media *media, struct call_media *other_media,
|
|
struct rtp_payload_type *pt)
|
|
{
|
|
int num = pt ? pt->payload_type : -1;
|
|
struct rtp_payload_type *pt_match;
|
|
|
|
if (num < 0)
|
|
num = 96; // default first dynamic payload type number
|
|
while (1) {
|
|
if ((pt_match = g_hash_table_lookup(media->codecs_recv, &num)))
|
|
goto next;
|
|
if ((pt_match = g_hash_table_lookup(media->codecs_send, &num)))
|
|
goto next;
|
|
if (other_media) {
|
|
if ((pt_match = g_hash_table_lookup(other_media->codecs_recv, &num)))
|
|
goto next;
|
|
if ((pt_match = g_hash_table_lookup(other_media->codecs_send, &num)))
|
|
goto next;
|
|
}
|
|
// OK
|
|
break;
|
|
|
|
next:
|
|
// is this actually the same?
|
|
if (pt && !rtp_payload_type_cmp_nf(pt, pt_match))
|
|
break;
|
|
num++;
|
|
if (num < 96) // if an RFC type was taken already
|
|
num = 96;
|
|
else if (num >= 128)
|
|
return -1;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
static void __single_codec(struct call_media *media, const struct sdp_ng_flags *flags) {
|
|
if (!flags || flags->opmode != OP_ANSWER || !flags->single_codec)
|
|
return;
|
|
int have_codec = 0;
|
|
for (GList *l = media->codecs_prefs_recv.head; l;) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
ensure_codec_def(pt, media);
|
|
if (pt->codec_def && pt->codec_def->supplemental) {
|
|
// leave these alone
|
|
l = l->next;
|
|
continue;
|
|
}
|
|
if (!have_codec) {
|
|
have_codec = 1;
|
|
l = l->next;
|
|
continue;
|
|
}
|
|
ilog(LOG_DEBUG, "Removing codec '" STR_FORMAT "' due to 'single codec' flag",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
codec_touched(pt, media);
|
|
l = __delete_receiver_codec(media, l);
|
|
}
|
|
}
|
|
|
|
static int __check_receiver_codecs(struct call_media *receiver, struct call_media *sink) {
|
|
int ret = 0;
|
|
// if some codecs were explicitly marked for transcoding, then we accept only those.
|
|
// otherwise we accept all that we can.
|
|
for (GList *l = receiver->codecs_prefs_send.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
ensure_codec_def(pt, receiver);
|
|
if (!pt->codec_def)
|
|
continue;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX checking recv send " STR_FORMAT " %i %i", STR_FMT(&pt->encoding_with_params), pt->for_transcoding, pt->codec_def->supplemental);
|
|
struct rtp_payload_type *sink_pt = g_hash_table_lookup(sink->codecs_recv,
|
|
&pt->payload_type);
|
|
if (sink_pt && !rtp_payload_type_cmp(pt, sink_pt))
|
|
continue;
|
|
if (pt->for_transcoding) {
|
|
if (pt->codec_def->supplemental)
|
|
ret |= 0x2 | 0x4;
|
|
else
|
|
ret |= 0x1 | 0x2;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void __accept_transcode_codecs(struct call_media *receiver, struct call_media *sink,
|
|
const struct sdp_ng_flags *flags, int accept_only_tc)
|
|
{
|
|
// if the other side is transcoding, we need to accept codecs that were
|
|
// originally offered (recv->send) if we support them, even if the
|
|
// response (sink->send) doesn't include them
|
|
GList *insert_pos = NULL;
|
|
for (GList *l = receiver->codecs_prefs_send.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
ensure_codec_def(pt, receiver);
|
|
if (!pt->codec_def)
|
|
continue;
|
|
if (accept_only_tc && !pt->for_transcoding)
|
|
continue;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXX accept codec " STR_FORMAT " flag %i", STR_FMT(&pt->encoding_with_params), pt->for_transcoding);
|
|
struct rtp_payload_type *existing_pt
|
|
= g_hash_table_lookup(receiver->codecs_recv, &pt->payload_type);
|
|
if (existing_pt && !rtp_payload_type_cmp_nf(existing_pt, pt)) {
|
|
// already present.
|
|
// to keep the order intact, we seek the list for the position
|
|
// of this codec entry. all newly added codecs must come after
|
|
// this entry.
|
|
if (!insert_pos)
|
|
insert_pos = receiver->codecs_prefs_recv.head;
|
|
while (insert_pos) {
|
|
if (!insert_pos->next)
|
|
break; // end of list - we insert everything after
|
|
struct rtp_payload_type *test_pt = insert_pos->data;
|
|
if (test_pt->payload_type == pt->payload_type)
|
|
break;
|
|
insert_pos = insert_pos->next;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (existing_pt) {
|
|
// PT collision. We must renumber one of the entries. `pt` is taken
|
|
// from the send list, so the PT should remain the same. Renumber
|
|
// the existing entry.
|
|
int new_pt = __unused_pt_number(receiver, sink, existing_pt);
|
|
if (new_pt < 0) {
|
|
ilog(LOG_WARN, "Ran out of RTP payload type numbers while accepting '"
|
|
STR_FORMAT "' due to '" STR_FORMAT "'",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&existing_pt->encoding_with_params));
|
|
continue;
|
|
}
|
|
ilog(LOG_DEBUG, "Renumbering '" STR_FORMAT "' from PT %i to %i due to '" STR_FORMAT "'",
|
|
STR_FMT(&existing_pt->encoding_with_params),
|
|
existing_pt->payload_type,
|
|
new_pt,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
g_hash_table_steal(receiver->codecs_recv, &existing_pt->payload_type);
|
|
existing_pt->payload_type = new_pt;
|
|
g_hash_table_insert(receiver->codecs_recv, &existing_pt->payload_type, existing_pt);
|
|
}
|
|
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXXX offered codec %i", pt->for_transcoding);
|
|
ilog(LOG_DEBUG, "Accepting offered codec " STR_FORMAT " due to transcoding",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
MEDIA_SET(receiver, TRANSCODE);
|
|
|
|
|
|
// we need a new pt entry
|
|
pt = __rtp_payload_type_copy(pt);
|
|
pt->for_transcoding = 1;
|
|
codec_touched(pt, receiver);
|
|
// this somewhat duplicates __rtp_payload_type_add_recv
|
|
g_hash_table_insert(receiver->codecs_recv, &pt->payload_type, pt);
|
|
__rtp_payload_type_add_name(receiver->codec_names_recv, pt);
|
|
|
|
// keep supplemental codecs last
|
|
ensure_codec_def(pt, receiver);
|
|
if (!pt->codec_def || !pt->codec_def->supplemental) {
|
|
while (insert_pos) {
|
|
struct rtp_payload_type *ipt = insert_pos->data;
|
|
ensure_codec_def(ipt, receiver);
|
|
if (!ipt->codec_def || !ipt->codec_def->supplemental)
|
|
break;
|
|
insert_pos = insert_pos->prev;
|
|
}
|
|
}
|
|
else {
|
|
if (!insert_pos)
|
|
insert_pos = receiver->codecs_prefs_recv.tail;
|
|
}
|
|
|
|
if (!insert_pos) {
|
|
g_queue_push_head(&receiver->codecs_prefs_recv, pt);
|
|
insert_pos = receiver->codecs_prefs_recv.head;
|
|
}
|
|
else {
|
|
g_queue_insert_after(&receiver->codecs_prefs_recv, insert_pos, pt);
|
|
insert_pos = insert_pos->next;
|
|
}
|
|
}
|
|
|
|
__single_codec(receiver, flags);
|
|
}
|
|
|
|
static void __eliminate_rejected_codecs(struct call_media *receiver, struct call_media *sink,
|
|
const struct sdp_ng_flags *flags)
|
|
{
|
|
if (flags && flags->asymmetric_codecs)
|
|
return;
|
|
|
|
// in the other case (not transcoding), we can eliminate rejected codecs from our
|
|
// `send` list if the receiver cannot receive it.
|
|
for (GList *l = receiver->codecs_prefs_send.head; l;) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
if (g_hash_table_lookup(receiver->codec_names_recv, &pt->encoding)) {
|
|
l = l->next;
|
|
continue;
|
|
}
|
|
ilog(LOG_DEBUG, "Eliminating asymmetric outbound codec " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
l = __delete_send_codec(receiver, l);
|
|
}
|
|
}
|
|
|
|
// transfers ownership of payload type objects from a queue to a hash table.
|
|
// duplicates are removed.
|
|
static GHashTable *__payload_type_queue_hash(GQueue *prefs, GQueue *order) {
|
|
GHashTable *ret = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
|
|
(GDestroyNotify) payload_type_free);
|
|
g_queue_init(order);
|
|
for (GList *l = prefs->head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
if (g_hash_table_lookup(ret, GINT_TO_POINTER(pt->payload_type))) {
|
|
ilog(LOG_DEBUG, "Removing duplicate RTP payload type %i", pt->payload_type);
|
|
payload_type_free(pt);
|
|
continue;
|
|
}
|
|
g_hash_table_insert(ret, GINT_TO_POINTER(pt->payload_type), pt);
|
|
g_queue_push_tail(order, GINT_TO_POINTER(pt->payload_type));
|
|
}
|
|
|
|
// ownership has been transferred
|
|
g_queue_clear(prefs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __symmetric_codecs(struct call_media *receiver, struct call_media *sink,
|
|
int *sink_transcoding)
|
|
{
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXXXXX symm codec flags %i %i", MEDIA_ISSET(sink, TRANSCODE), *sink_transcoding);
|
|
if (!MEDIA_ISSET(sink, TRANSCODE))
|
|
return;
|
|
if (!*sink_transcoding)
|
|
return;
|
|
|
|
// sink still looks like it's transcoding. reconstruct our answer to the receiver
|
|
// (receiver->prefs_recv) based on the codecs accepted by the sink (sink->prefs_send).
|
|
|
|
GQueue prefs_recv_order, prefs_send_order;
|
|
GHashTable *prefs_recv = __payload_type_queue_hash(&receiver->codecs_prefs_recv, &prefs_recv_order);
|
|
GHashTable *prefs_send = __payload_type_queue_hash(&receiver->codecs_prefs_send, &prefs_send_order);
|
|
|
|
// ownership of the objects has been transferred. clear out old structures.
|
|
g_hash_table_remove_all(receiver->codecs_recv);
|
|
g_hash_table_remove_all(receiver->codec_names_recv);
|
|
g_hash_table_remove_all(receiver->codecs_send);
|
|
g_hash_table_remove_all(receiver->codec_names_send);
|
|
|
|
// reconstruct list based on other side's preference.
|
|
int transcoding = 0;
|
|
|
|
// keep track of our reconstruction order. there might be some codecs that have been force accepted
|
|
// that aren't present in sink->codecs_prefs_send. we must add them our output (receiver->send/recv)
|
|
// in order.
|
|
GList *prefix_pt_pos = prefs_send_order.head;
|
|
|
|
for (GList *l = sink->codecs_prefs_send.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXXXXXX symm codec check " STR_FORMAT, STR_FMT(&pt->encoding_with_params));
|
|
// do we have a matching output?
|
|
struct rtp_payload_type *out_pt = g_hash_table_lookup(prefs_recv,
|
|
GINT_TO_POINTER(pt->payload_type));
|
|
struct rtp_payload_type *send_pt;
|
|
if (!out_pt || !(send_pt = g_hash_table_lookup(prefs_send, GINT_TO_POINTER(pt->payload_type)))) {
|
|
// we must transcode after all.
|
|
ilog(LOG_DEBUG, "RTP payload type %i is not symmetric and must be transcoded",
|
|
pt->payload_type);
|
|
transcoding = 1;
|
|
continue;
|
|
}
|
|
|
|
// seek forward in our prefix list and check any PTs to see if they're force accepted
|
|
while (prefix_pt_pos) {
|
|
void *ptype = prefix_pt_pos->data;
|
|
struct rtp_payload_type *prefix_pt = g_hash_table_lookup(prefs_send, ptype);
|
|
prefix_pt_pos = prefix_pt_pos->next;
|
|
if (!prefix_pt)
|
|
continue; // bug?
|
|
if (prefix_pt == send_pt)
|
|
break; // caught up
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXXXXXX prefix codec check " STR_FORMAT " %i", STR_FMT(&prefix_pt->encoding_with_params), prefix_pt->for_transcoding);
|
|
if (!prefix_pt->for_transcoding)
|
|
continue; // not interesting
|
|
|
|
// add it to the list
|
|
ilog(LOG_DEBUG, "Adding force-accepted RTP payload type %i", prefix_pt->payload_type);
|
|
g_hash_table_steal(prefs_send, ptype);
|
|
__rtp_payload_type_add_send(receiver, prefix_pt);
|
|
// and our receive leg
|
|
struct rtp_payload_type *in_pt = g_hash_table_lookup(prefs_recv, ptype);
|
|
if (in_pt) {
|
|
g_hash_table_steal(prefs_recv, ptype);
|
|
__rtp_payload_type_add_recv(receiver, in_pt, 1);
|
|
}
|
|
transcoding = 1;
|
|
}
|
|
|
|
// add it to the list
|
|
ilog(LOG_DEBUG, "Adding symmetric RTP payload type %i", pt->payload_type);
|
|
g_hash_table_steal(prefs_recv, GINT_TO_POINTER(pt->payload_type));
|
|
__rtp_payload_type_add_recv(receiver, out_pt, 1);
|
|
// and our send leg
|
|
out_pt = g_hash_table_lookup(prefs_send, GINT_TO_POINTER(pt->payload_type));
|
|
if (out_pt) {
|
|
g_hash_table_steal(prefs_send, GINT_TO_POINTER(pt->payload_type));
|
|
__rtp_payload_type_add_send(receiver, out_pt);
|
|
}
|
|
}
|
|
|
|
if (!transcoding)
|
|
*sink_transcoding = 0;
|
|
else {
|
|
// append any leftover codecs
|
|
while (prefs_recv_order.length) {
|
|
void *ptype = g_queue_pop_head(&prefs_recv_order);
|
|
struct rtp_payload_type *out_pt = g_hash_table_lookup(prefs_recv, ptype);
|
|
if (!out_pt)
|
|
continue;
|
|
g_hash_table_steal(prefs_recv, ptype);
|
|
__rtp_payload_type_add_recv(receiver, out_pt, 1);
|
|
}
|
|
while (prefs_send_order.length) {
|
|
void *ptype = g_queue_pop_head(&prefs_send_order);
|
|
struct rtp_payload_type *out_pt = g_hash_table_lookup(prefs_send, ptype);
|
|
if (!out_pt)
|
|
continue;
|
|
g_hash_table_steal(prefs_send, ptype);
|
|
__rtp_payload_type_add_send(receiver, out_pt);
|
|
}
|
|
}
|
|
|
|
g_hash_table_destroy(prefs_recv);
|
|
g_queue_clear(&prefs_recv_order);
|
|
g_hash_table_destroy(prefs_send);
|
|
g_queue_clear(&prefs_send_order);
|
|
}
|
|
|
|
static void __check_dtmf_injector(const struct sdp_ng_flags *flags, struct call_media *receiver,
|
|
struct rtp_payload_type *pref_dest_codec, GHashTable *output_transcoders,
|
|
int dtmf_payload_type)
|
|
{
|
|
if (!flags || !flags->inject_dtmf)
|
|
return;
|
|
if (receiver->dtmf_injector) {
|
|
// is this still valid?
|
|
if (!rtp_payload_type_cmp(pref_dest_codec, &receiver->dtmf_injector->dest_pt))
|
|
return;
|
|
|
|
receiver->dtmf_injector = NULL;
|
|
}
|
|
|
|
// synthesise input rtp payload type
|
|
struct rtp_payload_type src_pt = {
|
|
.payload_type = -1,
|
|
.clock_rate = pref_dest_codec->clock_rate,
|
|
.channels = pref_dest_codec->channels,
|
|
};
|
|
str_init(&src_pt.encoding, "DTMF injector");
|
|
str_init(&src_pt.encoding_with_params, "DTMF injector");
|
|
const str tp_event = STR_CONST_INIT("telephone-event");
|
|
src_pt.codec_def = codec_find(&tp_event, MT_AUDIO);
|
|
if (!src_pt.codec_def) {
|
|
ilog(LOG_ERR, "RTP payload type 'telephone-event' is not defined");
|
|
return;
|
|
}
|
|
|
|
//receiver->dtmf_injector = codec_handler_make_playback(&src_pt, pref_dest_codec, 0);
|
|
//receiver->dtmf_injector->dtmf_payload_type = dtmf_payload_type;
|
|
receiver->dtmf_injector = __handler_new(&src_pt, receiver);
|
|
__make_transcoder(receiver->dtmf_injector, pref_dest_codec, output_transcoders, dtmf_payload_type, 0);
|
|
receiver->dtmf_injector->func = handler_func_inject_dtmf;
|
|
g_queue_push_tail(&receiver->codec_handlers_store, receiver->dtmf_injector);
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct codec_handler *__get_pt_handler(struct call_media *receiver, struct rtp_payload_type *pt) {
|
|
ensure_codec_def(pt, receiver);
|
|
struct codec_handler *handler;
|
|
handler = g_hash_table_lookup(receiver->codec_handlers, GINT_TO_POINTER(pt->payload_type));
|
|
if (handler) {
|
|
// make sure existing handler matches this PT
|
|
if (rtp_payload_type_cmp(pt, &handler->source_pt)) {
|
|
ilog(LOG_DEBUG, "Resetting codec handler for PT %u", pt->payload_type);
|
|
handler = NULL;
|
|
g_atomic_pointer_set(&receiver->codec_handler_cache, NULL);
|
|
g_hash_table_remove(receiver->codec_handlers, GINT_TO_POINTER(pt->payload_type));
|
|
}
|
|
}
|
|
if (!handler) {
|
|
ilog(LOG_DEBUG, "Creating codec handler for " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
handler = __handler_new(pt, receiver);
|
|
g_hash_table_insert(receiver->codec_handlers,
|
|
GINT_TO_POINTER(handler->source_pt.payload_type),
|
|
handler);
|
|
g_queue_push_tail(&receiver->codec_handlers_store, handler);
|
|
}
|
|
|
|
// figure out our ptime
|
|
if (!pt->ptime && pt->codec_def)
|
|
pt->ptime = pt->codec_def->default_ptime;
|
|
if (receiver->ptime)
|
|
pt->ptime = receiver->ptime;
|
|
|
|
return handler;
|
|
}
|
|
|
|
|
|
|
|
|
|
static void __check_t38_decoder(struct call_media *t38_media) {
|
|
if (t38_media->t38_handler)
|
|
return;
|
|
ilog(LOG_DEBUG, "Creating T.38 packet handler");
|
|
t38_media->t38_handler = __handler_new(NULL, t38_media);
|
|
t38_media->t38_handler->func = handler_func_t38;
|
|
}
|
|
|
|
static int packet_encoded_t38(encoder_t *enc, void *u1, void *u2) {
|
|
struct media_packet *mp = u2;
|
|
|
|
if (!mp->media)
|
|
return 0;
|
|
|
|
return t38_gateway_input_samples(mp->media->t38_gateway,
|
|
(int16_t *) enc->avpkt.data, enc->avpkt.size / 2);
|
|
}
|
|
|
|
static void __generator_stop(struct call_media *media) {
|
|
if (media->t38_gateway) {
|
|
t38_gateway_stop(media->t38_gateway);
|
|
t38_gateway_put(&media->t38_gateway);
|
|
}
|
|
}
|
|
|
|
static void __t38_options_from_flags(struct t38_options *t_opts, const struct sdp_ng_flags *flags) {
|
|
#define t38_opt(name) t_opts->name = flags->t38_ ## name
|
|
t38_opt(no_ecm);
|
|
t38_opt(no_v17);
|
|
t38_opt(no_v27ter);
|
|
t38_opt(no_v29);
|
|
t38_opt(no_v34);
|
|
t38_opt(no_iaf);
|
|
}
|
|
|
|
static void __check_t38_gateway(struct call_media *pcm_media, struct call_media *t38_media,
|
|
const struct stream_params *sp, const struct sdp_ng_flags *flags)
|
|
{
|
|
struct t38_options t_opts = {0,};
|
|
|
|
if (sp)
|
|
t_opts = sp->t38_options;
|
|
else {
|
|
// create our own options
|
|
if (flags->t38_fec)
|
|
t_opts.fec_span = 3;
|
|
t_opts.max_ec_entries = 3;
|
|
}
|
|
__t38_options_from_flags(&t_opts, flags);
|
|
|
|
MEDIA_SET(pcm_media, TRANSCODE);
|
|
MEDIA_SET(pcm_media, GENERATOR);
|
|
MEDIA_SET(t38_media, TRANSCODE);
|
|
MEDIA_SET(t38_media, GENERATOR);
|
|
|
|
if (t38_gateway_pair(t38_media, pcm_media, &t_opts))
|
|
return;
|
|
|
|
// need a packet handler on the T.38 side
|
|
__check_t38_decoder(t38_media);
|
|
|
|
|
|
// for each codec type supported by the pcm_media, we create a codec handler that
|
|
// links to the T.38 encoder
|
|
for (GList *l = pcm_media->codecs_prefs_recv.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
struct codec_handler *handler = __get_pt_handler(pcm_media, pt);
|
|
if (!pt->codec_def) {
|
|
// should not happen
|
|
ilog(LOG_WARN, "Unsupported codec " STR_FORMAT " for T.38 transcoding",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
continue;
|
|
}
|
|
|
|
ilog(LOG_DEBUG, "Creating T.38 encoder for " STR_FORMAT, STR_FMT(&pt->encoding_with_params));
|
|
|
|
__make_transcoder(handler, &pcm_media->t38_gateway->pcm_pt, NULL, -1, 0);
|
|
|
|
handler->packet_decoded = packet_decoded_direct;
|
|
handler->packet_encoded = packet_encoded_t38;
|
|
}
|
|
}
|
|
|
|
// call must be locked in W
|
|
static int codec_handler_udptl_update(struct call_media *receiver, struct call_media *sink,
|
|
const struct sdp_ng_flags *flags)
|
|
{
|
|
// anything to do?
|
|
if (proto_is(sink->protocol, PROTO_UDPTL))
|
|
return 0;
|
|
|
|
if (sink->type_id == MT_AUDIO && proto_is_rtp(sink->protocol) && receiver->type_id == MT_IMAGE) {
|
|
if (!str_cmp(&receiver->format_str, "t38")) {
|
|
__check_t38_gateway(sink, receiver, NULL, flags);
|
|
return 1;
|
|
}
|
|
}
|
|
ilog(LOG_WARN, "Unsupported non-RTP protocol: " STR_FORMAT "/" STR_FORMAT
|
|
" -> " STR_FORMAT "/" STR_FORMAT,
|
|
STR_FMT(&receiver->type), STR_FMT(&receiver->format_str),
|
|
STR_FMT(&sink->type), STR_FMT(&sink->format_str));
|
|
return 0;
|
|
}
|
|
|
|
// call must be locked in W
|
|
// for transcoding RTP types to non-RTP
|
|
static int codec_handler_non_rtp_update(struct call_media *receiver, struct call_media *sink,
|
|
const struct sdp_ng_flags *flags, const struct stream_params *sp)
|
|
{
|
|
if (proto_is(sink->protocol, PROTO_UDPTL) && !str_cmp(&sink->format_str, "t38")) {
|
|
__check_t38_gateway(receiver, sink, sp, flags);
|
|
return 1;
|
|
}
|
|
ilog(LOG_WARN, "Unsupported non-RTP protocol: " STR_FORMAT "/" STR_FORMAT
|
|
" -> " STR_FORMAT "/" STR_FORMAT,
|
|
STR_FMT(&receiver->type), STR_FMT(&receiver->format_str),
|
|
STR_FMT(&sink->type), STR_FMT(&sink->format_str));
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void __rtcp_timer_free(void *p) {
|
|
struct rtcp_timer *rt = p;
|
|
if (rt->call)
|
|
obj_put(rt->call);
|
|
g_slice_free1(sizeof(*rt), rt);
|
|
}
|
|
// master lock held in W
|
|
static void __codec_rtcp_timer_schedule(struct call_media *media) {
|
|
struct rtcp_timer *rt = g_slice_alloc0(sizeof(*rt));
|
|
rt->ttq_entry.when = media->rtcp_timer;
|
|
rt->call = obj_get(media->call);
|
|
rt->media = media;
|
|
|
|
timerthread_queue_push(&rtcp_timer_queue->ttq, &rt->ttq_entry);
|
|
}
|
|
// no lock held
|
|
static void __rtcp_timer_run(struct timerthread_queue *q, void *p) {
|
|
struct rtcp_timer *rt = p;
|
|
|
|
// check scheduling
|
|
rwlock_lock_w(&rt->call->master_lock);
|
|
struct call_media *media = rt->media;
|
|
struct timeval rtcp_timer = media->rtcp_timer;
|
|
|
|
log_info_call(rt->call);
|
|
|
|
if (!rtcp_timer.tv_sec || timeval_diff(&rtpe_now, &rtcp_timer) < 0 || !proto_is_rtp(media->protocol)) {
|
|
__rtcp_timer_free(rt);
|
|
rwlock_unlock_w(&rt->call->master_lock);
|
|
goto out;
|
|
}
|
|
timeval_add_usec(&rtcp_timer, 5000000 + (random() % 2000000));
|
|
media->rtcp_timer = rtcp_timer;
|
|
__codec_rtcp_timer_schedule(media);
|
|
|
|
// switch locks to be more graceful
|
|
rwlock_unlock_w(&rt->call->master_lock);
|
|
|
|
rwlock_lock_r(&rt->call->master_lock);
|
|
|
|
struct ssrc_ctx *ssrc_out = NULL;
|
|
if (media->streams.head) {
|
|
struct packet_stream *ps = media->streams.head->data;
|
|
mutex_lock(&ps->out_lock);
|
|
ssrc_out = ps->ssrc_out;
|
|
if (ssrc_out)
|
|
obj_hold(&ssrc_out->parent->h);
|
|
mutex_unlock(&ps->out_lock);
|
|
}
|
|
|
|
if (ssrc_out)
|
|
rtcp_send_report(media, ssrc_out);
|
|
|
|
rwlock_unlock_r(&rt->call->master_lock);
|
|
|
|
if (ssrc_out)
|
|
obj_put(&ssrc_out->parent->h);
|
|
|
|
__rtcp_timer_free(rt);
|
|
|
|
out:
|
|
log_info_clear();
|
|
}
|
|
// master lock held in W
|
|
static void __codec_rtcp_timer(struct call_media *receiver) {
|
|
if (receiver->rtcp_timer.tv_sec) // already scheduled
|
|
return;
|
|
|
|
receiver->rtcp_timer = rtpe_now;
|
|
timeval_add_usec(&receiver->rtcp_timer, 5000000 + (random() % 2000000));
|
|
__codec_rtcp_timer_schedule(receiver);
|
|
// XXX unify with media player into a generic RTCP player
|
|
}
|
|
|
|
|
|
// returns: 0 = supp codec not present; 1 = sink has codec but receiver does not, 2 = both have codec
|
|
int __supp_codec_match(struct call_media *receiver, struct call_media *sink, int pt,
|
|
struct rtp_payload_type **sink_pt, struct rtp_payload_type **recv_pt)
|
|
{
|
|
if (pt == -1)
|
|
return 0;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXX checking supp PT match %i", pt);
|
|
|
|
struct rtp_payload_type *sink_pt_stor = NULL;
|
|
struct rtp_payload_type *recv_pt_stor = NULL;
|
|
if (!sink_pt)
|
|
sink_pt = &sink_pt_stor;
|
|
if (!recv_pt)
|
|
recv_pt = &recv_pt_stor;
|
|
|
|
// find a matching output payload type
|
|
*sink_pt = g_hash_table_lookup(sink->codecs_send, &pt);
|
|
if (!*sink_pt)
|
|
return 0;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXX sink has supp PT %i", pt);
|
|
// XXX should go by codec name/params, not payload type number
|
|
*recv_pt = g_hash_table_lookup(receiver->codecs_recv, &pt);
|
|
if (!*recv_pt)
|
|
return 1;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXX recv has supp PT %i", pt);
|
|
if (rtp_payload_type_cmp(*sink_pt, *recv_pt))
|
|
return 1;
|
|
//ilog(LOG_DEBUG, "XXXXXXXXX recv has matching supp PT %i", pt);
|
|
return 2;
|
|
}
|
|
|
|
// call must be locked in W
|
|
void codec_handlers_update(struct call_media *receiver, struct call_media *sink,
|
|
const struct sdp_ng_flags *flags, const struct stream_params *sp)
|
|
{
|
|
MEDIA_CLEAR(receiver, GENERATOR);
|
|
MEDIA_CLEAR(sink, GENERATOR);
|
|
|
|
// non-RTP protocol?
|
|
if (proto_is(receiver->protocol, PROTO_UDPTL)) {
|
|
if (codec_handler_udptl_update(receiver, sink, flags))
|
|
return;
|
|
}
|
|
// everything else is unsupported: pass through
|
|
if (proto_is_not_rtp(receiver->protocol)) {
|
|
__generator_stop(receiver);
|
|
__generator_stop(sink);
|
|
return;
|
|
}
|
|
|
|
if (!receiver->codec_handlers)
|
|
receiver->codec_handlers = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
|
|
// should we transcode to a non-RTP protocol?
|
|
if (proto_is_not_rtp(sink->protocol)) {
|
|
if (codec_handler_non_rtp_update(receiver, sink, flags, sp))
|
|
return;
|
|
}
|
|
|
|
// we're doing some kind of media passthrough - shut down local generators
|
|
__generator_stop(receiver);
|
|
__generator_stop(sink);
|
|
|
|
MEDIA_CLEAR(receiver, TRANSCODE);
|
|
receiver->rtcp_handler = NULL;
|
|
GSList *passthrough_handlers = NULL;
|
|
|
|
// we go through the list of codecs that the receiver supports and compare it
|
|
// with the list of codecs supported by the sink. if the receiver supports
|
|
// a codec that the sink doesn't support, we must transcode.
|
|
//
|
|
// if we transcode, we transcode to the highest-preference supported codec
|
|
// that the sink specified. determine this first.
|
|
struct rtp_payload_type *pref_dest_codec = NULL;
|
|
|
|
// 0x1 = any transcoder present, 0x2 = non pseudo transcoder present,
|
|
// 0x4 = supplemental codec for transcoding
|
|
int sink_transcoding = 0;
|
|
|
|
// keep track of supplemental payload types. we hash them by clock rate
|
|
// in case there's several of them. the clock rates of the destination
|
|
// codec and the supplemental codec must match.
|
|
GHashTable *supplemental_sinks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
|
|
(GDestroyNotify) g_hash_table_destroy);
|
|
for (GList *l = codec_supplemental_codecs->head; l; l = l->next) {
|
|
codec_def_t *def = l->data;
|
|
g_hash_table_replace(supplemental_sinks, (void *) def->rtpname,
|
|
g_hash_table_new(g_direct_hash, g_direct_equal));
|
|
}
|
|
|
|
pref_dest_codec = __check_dest_codecs(receiver, sink, flags, supplemental_sinks, &sink_transcoding);
|
|
|
|
// similarly, if the sink can receive a codec that the receiver can't send, it's also transcoding
|
|
__check_send_codecs(receiver, sink, flags, supplemental_sinks, &sink_transcoding);
|
|
|
|
// 0x1 = accept only codecs marked for transcoding, 0x2 = some codecs marked for transcoding
|
|
// present, 0x4 = supplemental codec for transcoding
|
|
int receiver_transcoding = __check_receiver_codecs(receiver, sink);
|
|
|
|
if (flags && flags->opmode == OP_ANSWER && flags->symmetric_codecs)
|
|
__symmetric_codecs(receiver, sink, &sink_transcoding);
|
|
|
|
int dtmf_payload_type = __dtmf_payload_type(supplemental_sinks, pref_dest_codec);
|
|
int cn_payload_type = __supp_payload_type(supplemental_sinks, pref_dest_codec, "CN");
|
|
|
|
g_hash_table_destroy(supplemental_sinks);
|
|
supplemental_sinks = NULL;
|
|
|
|
struct rtp_payload_type *dtmf_pt = NULL;
|
|
struct rtp_payload_type *reverse_dtmf_pt = NULL;
|
|
int dtmf_pt_match = __supp_codec_match(receiver, sink, dtmf_payload_type, &dtmf_pt, &reverse_dtmf_pt);
|
|
int cn_pt_match = __supp_codec_match(receiver, sink, cn_payload_type, NULL, NULL);
|
|
|
|
// stop transcoding if we've determined that we don't need it
|
|
if (MEDIA_ISSET(sink, TRANSCODE) && !sink_transcoding && !(receiver_transcoding & 0x2)) {
|
|
ilog(LOG_DEBUG, "Disabling transcoding engine (not needed)");
|
|
MEDIA_CLEAR(sink, TRANSCODE);
|
|
}
|
|
|
|
if (MEDIA_ISSET(sink, TRANSCODE) && (sink_transcoding & 0x2))
|
|
__accept_transcode_codecs(receiver, sink, flags, (receiver_transcoding & 0x1));
|
|
else
|
|
__eliminate_rejected_codecs(receiver, sink, flags);
|
|
|
|
// if multiple input codecs transcode to the same output codec, we want to make sure
|
|
// that all the decoders output their media to the same encoder. we use the destination
|
|
// payload type to keep track of this.
|
|
GHashTable *output_transcoders = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
|
|
int transcode_supplemental = 0; // is one of our source codecs a supplemental one?
|
|
if ((sink_transcoding & 0x4))
|
|
transcode_supplemental = 1;
|
|
|
|
// do we need to detect PCM DTMF tones?
|
|
int pcm_dtmf_detect = 0;
|
|
if ((MEDIA_ISSET(sink, TRANSCODE) || (sink_transcoding & 0x2))
|
|
&& dtmf_payload_type != -1
|
|
&& dtmf_pt && (!reverse_dtmf_pt || reverse_dtmf_pt->for_transcoding ||
|
|
!g_hash_table_lookup(receiver->codecs_send, &dtmf_payload_type)))
|
|
pcm_dtmf_detect = 1;
|
|
|
|
|
|
for (GList *l = receiver->codecs_prefs_recv.head; l; ) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
|
|
if (MEDIA_ISSET(sink, TRANSCODE) && flags && flags->opmode == OP_ANSWER) {
|
|
// if the other side is transcoding, we may come across a receiver entry
|
|
// (recv->recv) that wasn't originally offered (recv->send). we must eliminate
|
|
// those, unless we added them ourselves for transcoding.
|
|
struct rtp_payload_type *recv_pt =
|
|
g_hash_table_lookup(receiver->codecs_send, &pt->payload_type);
|
|
if (!recv_pt && !pt->for_transcoding) {
|
|
ilog(LOG_DEBUG, "Eliminating transcoded codec " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
|
|
codec_touched(pt, receiver);
|
|
l = __delete_receiver_codec(receiver, l);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
struct codec_handler *handler = __get_pt_handler(receiver, pt);
|
|
|
|
// check our own support for this codec
|
|
if (!pt->codec_def) {
|
|
// not supported
|
|
__make_passthrough_gsl(handler, &passthrough_handlers);
|
|
goto next;
|
|
}
|
|
|
|
// if the sink's codec preferences are unknown (empty), or there are
|
|
// no supported codecs to transcode to, then we have nothing
|
|
// to do. most likely this is an initial offer without a received answer.
|
|
// we default to forwarding without transcoding.
|
|
if (!pref_dest_codec) {
|
|
ilog(LOG_DEBUG, "No known/supported sink codec for " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
__make_passthrough_gsl(handler, &passthrough_handlers);
|
|
goto next;
|
|
}
|
|
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXX pref dest codec " STR_FORMAT " is %i, CN match %i DTMF match %i "
|
|
//"sink TC %i/%i recv TC %i TC supp %i DTMF DSP %i",
|
|
//STR_FMT(&pref_dest_codec->encoding_with_params),
|
|
//pref_dest_codec->for_transcoding,
|
|
//cn_pt_match, dtmf_pt_match,
|
|
//MEDIA_ISSET(sink, TRANSCODE), sink_transcoding,
|
|
//receiver_transcoding,
|
|
//transcode_supplemental, pcm_dtmf_detect);
|
|
|
|
struct rtp_payload_type *dest_pt; // transcode to this
|
|
|
|
GQueue *dest_codecs = NULL;
|
|
if (pref_dest_codec->for_transcoding) {
|
|
// with force accepted codec, we still accept DTMF payloads if possible
|
|
if (pt->codec_def && pt->codec_def->supplemental)
|
|
dest_codecs = g_hash_table_lookup(sink->codec_names_send, &pt->encoding);
|
|
}
|
|
else {
|
|
// we ignore output codec matches if we must transcode supp codecs
|
|
if ((dtmf_pt_match == 1 || cn_pt_match == 1) && MEDIA_ISSET(sink, TRANSCODE))
|
|
;
|
|
else if ((receiver_transcoding & 0x4))
|
|
;
|
|
else if (pcm_dtmf_detect)
|
|
;
|
|
else
|
|
dest_codecs = g_hash_table_lookup(sink->codec_names_send, &pt->encoding);
|
|
}
|
|
if (dest_codecs) {
|
|
// the sink supports this codec - check offered formats
|
|
dest_pt = NULL;
|
|
for (GList *k = dest_codecs->head; k; k = k->next) {
|
|
unsigned int dest_ptype = GPOINTER_TO_UINT(k->data);
|
|
dest_pt = g_hash_table_lookup(sink->codecs_send, &dest_ptype);
|
|
if (!dest_pt)
|
|
continue;
|
|
if (dest_pt->clock_rate != pt->clock_rate ||
|
|
dest_pt->channels != pt->channels) {
|
|
dest_pt = NULL;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!dest_pt)
|
|
goto unsupported;
|
|
|
|
// in case of ptime mismatch, we transcode, but between the same codecs
|
|
if (dest_pt->ptime && pt->ptime
|
|
&& dest_pt->ptime != pt->ptime)
|
|
{
|
|
ilog(LOG_DEBUG, "Mismatched ptime between source and sink (%i <> %i), "
|
|
"enabling transcoding",
|
|
dest_pt->ptime, pt->ptime);
|
|
goto transcode;
|
|
}
|
|
|
|
if (flags && flags->inject_dtmf) {
|
|
// we have a matching output codec, but we were told that we might
|
|
// want to inject DTMF, so we must still go through our transcoding
|
|
// engine, despite input and output codecs being the same.
|
|
goto transcode;
|
|
}
|
|
|
|
// XXX needs more intelligent fmtp matching
|
|
if (rtp_payload_type_cmp_nf(pt, dest_pt))
|
|
goto transcode;
|
|
|
|
// do we need silence detection?
|
|
if (cn_pt_match == 2 && MEDIA_ISSET(sink, TRANSCODE))
|
|
goto transcode;
|
|
|
|
// XXX check format parameters as well
|
|
ilog(LOG_DEBUG, "Sink supports codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params));
|
|
__make_passthrough_gsl(handler, &passthrough_handlers);
|
|
if (pt->codec_def && pt->codec_def->dtmf)
|
|
__dtmf_dsp_shutdown(sink, pt->payload_type);
|
|
goto next;
|
|
}
|
|
|
|
unsupported:
|
|
// the sink does not support this codec -> transcode
|
|
ilog(LOG_DEBUG, "Sink does not support codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params));
|
|
dest_pt = pref_dest_codec;
|
|
if (pt->codec_def->supplemental)
|
|
transcode_supplemental = 1;
|
|
transcode:;
|
|
// look up the reverse side of this payload type, which is the decoder to our
|
|
// encoder. if any codec options such as bitrate were set during an offer,
|
|
// they're in the decoder // PT. copy them to the encoder PT.
|
|
struct rtp_payload_type *reverse_pt = g_hash_table_lookup(sink->codecs_recv,
|
|
&dest_pt->payload_type);
|
|
if (reverse_pt) {
|
|
if (!dest_pt->bitrate)
|
|
dest_pt->bitrate = reverse_pt->bitrate;
|
|
if (!dest_pt->codec_opts.len)
|
|
call_str_cpy(sink->call, &dest_pt->codec_opts, &reverse_pt->codec_opts);
|
|
}
|
|
MEDIA_SET(receiver, TRANSCODE);
|
|
__make_transcoder(handler, dest_pt, output_transcoders, dtmf_payload_type, pcm_dtmf_detect);
|
|
handler->cn_payload_type = cn_payload_type;
|
|
|
|
next:
|
|
l = l->next;
|
|
}
|
|
|
|
// if we've determined that we transcode, we must remove all unsupported codecs from
|
|
// the list, as we must expect to potentially receive media in that codec, which we
|
|
// then could not transcode.
|
|
if (MEDIA_ISSET(receiver, TRANSCODE)) {
|
|
ilog(LOG_INFO, "Enabling transcoding engine");
|
|
|
|
for (GList *l = receiver->codecs_prefs_recv.head; l; ) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
|
|
if (pt->codec_def) {
|
|
// supported
|
|
l = l->next;
|
|
continue;
|
|
}
|
|
|
|
ilog(LOG_DEBUG, "Stripping unsupported codec " STR_FORMAT " due to active transcoding",
|
|
STR_FMT(&pt->encoding));
|
|
codec_touched(pt, receiver);
|
|
l = __delete_receiver_codec(receiver, l);
|
|
}
|
|
|
|
// we have to translate RTCP packets
|
|
receiver->rtcp_handler = rtcp_transcode_handler;
|
|
|
|
__check_dtmf_injector(flags, receiver, pref_dest_codec, output_transcoders, dtmf_payload_type);
|
|
|
|
// at least some payload types will be transcoded, which will result in SSRC
|
|
// change. for payload types which we don't actually transcode, we still
|
|
// must substitute the SSRC
|
|
while (passthrough_handlers) {
|
|
struct codec_handler *handler = passthrough_handlers->data;
|
|
// if the sink does not support DTMF but we can receive it, we must transcode
|
|
// DTMF event packets to PCM. this requires all codecs to be transcoded to the
|
|
// sink's preferred destination codec.
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXXX tc supp %i DTMF PT %i DTMF PT match %i PCM detect %i",
|
|
//transcode_supplemental, dtmf_payload_type, dtmf_pt_match, pcm_dtmf_detect);
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXXX tc supp %i CN PT %i CN PT match %i",
|
|
//transcode_supplemental, cn_payload_type, cn_pt_match);
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXXX %p %p %p",
|
|
//pref_dest_codec, handler->source_pt.codec_def, pref_dest_codec->codec_def);
|
|
if (!transcode_supplemental && !pcm_dtmf_detect)
|
|
__make_passthrough_ssrc(handler);
|
|
else if (dtmf_pt_match == 2)
|
|
__make_passthrough_ssrc(handler);
|
|
else if (!pref_dest_codec
|
|
|| !handler->source_pt.codec_def || !pref_dest_codec->codec_def)
|
|
__make_passthrough_ssrc(handler);
|
|
else {
|
|
__make_transcoder(handler, pref_dest_codec, output_transcoders,
|
|
dtmf_payload_type, pcm_dtmf_detect);
|
|
handler->cn_payload_type = cn_payload_type;
|
|
}
|
|
passthrough_handlers = g_slist_delete_link(passthrough_handlers, passthrough_handlers);
|
|
|
|
}
|
|
}
|
|
while (passthrough_handlers) {
|
|
passthrough_handlers = g_slist_delete_link(passthrough_handlers, passthrough_handlers);
|
|
}
|
|
|
|
g_hash_table_destroy(output_transcoders);
|
|
|
|
if (MEDIA_ISSET(receiver, RTCP_GEN)) {
|
|
receiver->rtcp_handler = rtcp_sink_handler;
|
|
__codec_rtcp_timer(receiver);
|
|
}
|
|
if (MEDIA_ISSET(sink, RTCP_GEN)) {
|
|
sink->rtcp_handler = rtcp_sink_handler;
|
|
__codec_rtcp_timer(sink);
|
|
}
|
|
}
|
|
|
|
|
|
static struct codec_handler *codec_handler_get_rtp(struct call_media *m, int payload_type) {
|
|
struct codec_handler *h;
|
|
|
|
if (payload_type < 0)
|
|
return NULL;
|
|
|
|
h = g_atomic_pointer_get(&m->codec_handler_cache);
|
|
if (G_LIKELY(G_LIKELY(h) && G_LIKELY(h->source_pt.payload_type == payload_type)))
|
|
return h;
|
|
|
|
if (G_UNLIKELY(!m->codec_handlers))
|
|
return NULL;
|
|
h = g_hash_table_lookup(m->codec_handlers, GINT_TO_POINTER(payload_type));
|
|
if (!h)
|
|
return NULL;
|
|
|
|
g_atomic_pointer_set(&m->codec_handler_cache, h);
|
|
|
|
return h;
|
|
}
|
|
static struct codec_handler *codec_handler_get_udptl(struct call_media *m) {
|
|
if (m->t38_handler)
|
|
return m->t38_handler;
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
// call must be locked in R
|
|
struct codec_handler *codec_handler_get(struct call_media *m, int payload_type) {
|
|
#ifdef WITH_TRANSCODING
|
|
struct codec_handler *ret = NULL;
|
|
|
|
if (!m->protocol)
|
|
goto out;
|
|
|
|
if (m->protocol->rtp)
|
|
ret = codec_handler_get_rtp(m, payload_type);
|
|
else if (m->protocol->index == PROTO_UDPTL)
|
|
ret = codec_handler_get_udptl(m);
|
|
|
|
out:
|
|
if (ret)
|
|
return ret;
|
|
if (MEDIA_ISSET(m, TRANSCODE))
|
|
return &codec_handler_stub_ssrc;
|
|
#endif
|
|
return &codec_handler_stub;
|
|
}
|
|
|
|
void codec_handlers_free(struct call_media *m) {
|
|
if (m->codec_handlers)
|
|
g_hash_table_destroy(m->codec_handlers);
|
|
m->codec_handlers = NULL;
|
|
m->codec_handler_cache = NULL;
|
|
#ifdef WITH_TRANSCODING
|
|
g_queue_clear_full(&m->codec_handlers_store, __codec_handler_free);
|
|
m->dtmf_injector = NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
void codec_add_raw_packet(struct media_packet *mp) {
|
|
struct codec_packet *p = g_slice_alloc0(sizeof(*p));
|
|
p->s = mp->raw;
|
|
p->free_func = NULL;
|
|
if (mp->rtp && mp->ssrc_out) {
|
|
p->ssrc_out = ssrc_ctx_get(mp->ssrc_out);
|
|
p->rtp = mp->rtp;
|
|
}
|
|
g_queue_push_tail(&mp->packets_out, p);
|
|
}
|
|
static int handler_func_passthrough(struct codec_handler *h, struct media_packet *mp) {
|
|
if (mp->call->block_media || mp->media->monologue->block_media)
|
|
return 0;
|
|
|
|
codec_add_raw_packet(mp);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
static void __ssrc_lock_both(struct media_packet *mp) {
|
|
struct ssrc_ctx *ssrc_in = mp->ssrc_in;
|
|
struct ssrc_entry_call *ssrc_in_p = ssrc_in->parent;
|
|
struct ssrc_ctx *ssrc_out = mp->ssrc_out;
|
|
struct ssrc_entry_call *ssrc_out_p = ssrc_out->parent;
|
|
|
|
// we need a nested lock here - both input and output SSRC needs to be locked.
|
|
// we don't know the lock order, so try both, and keep trying until we succeed.
|
|
while (1) {
|
|
mutex_lock(&ssrc_in_p->h.lock);
|
|
if (ssrc_in_p == ssrc_out_p)
|
|
break;
|
|
if (!mutex_trylock(&ssrc_out_p->h.lock))
|
|
break;
|
|
mutex_unlock(&ssrc_in_p->h.lock);
|
|
|
|
mutex_lock(&ssrc_out_p->h.lock);
|
|
if (!mutex_trylock(&ssrc_in_p->h.lock))
|
|
break;
|
|
mutex_unlock(&ssrc_out_p->h.lock);
|
|
}
|
|
}
|
|
static void __ssrc_unlock_both(struct media_packet *mp) {
|
|
struct ssrc_ctx *ssrc_in = mp->ssrc_in;
|
|
struct ssrc_entry_call *ssrc_in_p = ssrc_in->parent;
|
|
struct ssrc_ctx *ssrc_out = mp->ssrc_out;
|
|
struct ssrc_entry_call *ssrc_out_p = ssrc_out->parent;
|
|
|
|
mutex_unlock(&ssrc_in_p->h.lock);
|
|
if (ssrc_in_p != ssrc_out_p)
|
|
mutex_unlock(&ssrc_out_p->h.lock);
|
|
}
|
|
|
|
static int __handler_func_sequencer(struct media_packet *mp, struct transcode_packet *packet)
|
|
{
|
|
struct codec_handler *h = packet->handler;
|
|
|
|
if (G_UNLIKELY(!h->ssrc_hash)) {
|
|
if (!packet->func || !packet->handler || !packet->handler->ssrc_hash) {
|
|
h->func(h, mp);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
struct ssrc_ctx *ssrc_in = mp->ssrc_in;
|
|
struct ssrc_entry_call *ssrc_in_p = ssrc_in->parent;
|
|
struct ssrc_ctx *ssrc_out = mp->ssrc_out;
|
|
struct ssrc_entry_call *ssrc_out_p = ssrc_out->parent;
|
|
|
|
struct codec_ssrc_handler *ch = get_ssrc(ssrc_in_p->h.ssrc, h->ssrc_hash);
|
|
if (G_UNLIKELY(!ch))
|
|
return 0;
|
|
|
|
atomic64_inc(&ssrc_in->packets);
|
|
atomic64_add(&ssrc_in->octets, mp->payload.len);
|
|
|
|
packet->p.seq = ntohs(mp->rtp->seq_num);
|
|
packet->payload = str_dup(&mp->payload);
|
|
uint32_t packet_ts = ntohl(mp->rtp->timestamp);
|
|
packet->ts = packet_ts;
|
|
packet->marker = (mp->rtp->m_pt & 0x80) ? 1 : 0;
|
|
|
|
// how should we retrieve packets from the sequencer?
|
|
void *(*seq_next_packet)(packet_sequencer_t *) = packet_sequencer_next_packet;
|
|
if (packet->ignore_seq)
|
|
seq_next_packet = packet_sequencer_force_next_packet;
|
|
|
|
__ssrc_lock_both(mp);
|
|
|
|
packet_sequencer_init(&ssrc_in_p->sequencer, (GDestroyNotify) __transcode_packet_free);
|
|
|
|
u_int16_t seq_ori = ssrc_in_p->sequencer.seq;
|
|
int seq_ret = packet_sequencer_insert(&ssrc_in_p->sequencer, &packet->p);
|
|
if (seq_ret < 0) {
|
|
// dupe
|
|
if (packet->dup_func)
|
|
packet->dup_func(ch, packet, mp);
|
|
else
|
|
ilog(LOG_DEBUG, "Ignoring duplicate RTP packet");
|
|
__transcode_packet_free(packet);
|
|
atomic64_inc(&ssrc_in->duplicates);
|
|
goto out;
|
|
}
|
|
|
|
// got a new packet, run decoder
|
|
|
|
while (1) {
|
|
int func_ret = 0;
|
|
|
|
packet = seq_next_packet(&ssrc_in_p->sequencer);
|
|
if (G_UNLIKELY(!packet)) {
|
|
if (!ch->encoder_format.clockrate || !ch->handler || !ch->handler->dest_pt.codec_def)
|
|
break;
|
|
|
|
uint32_t ts_diff = packet_ts - ch->last_ts;
|
|
unsigned long long ts_diff_us =
|
|
(unsigned long long) ts_diff * 1000000 / ch->encoder_format.clockrate
|
|
* ch->handler->dest_pt.codec_def->clockrate_mult;
|
|
if (ts_diff_us >= 60000) { // arbitrary value
|
|
packet = packet_sequencer_force_next_packet(&ssrc_in_p->sequencer);
|
|
if (!packet)
|
|
break;
|
|
ilog(LOG_DEBUG, "Timestamp difference too large (%llu ms) after lost packet, "
|
|
"forcing next packet", ts_diff_us / 1000);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
h = packet->handler;
|
|
obj_put(&ch->h);
|
|
ch = get_ssrc(ssrc_in_p->h.ssrc, h->ssrc_hash);
|
|
if (G_UNLIKELY(!ch))
|
|
goto next;
|
|
|
|
atomic64_set(&ssrc_in->packets_lost, ssrc_in_p->sequencer.lost_count);
|
|
atomic64_set(&ssrc_in->last_seq, ssrc_in_p->sequencer.ext_seq);
|
|
|
|
ilog(LOG_DEBUG, "Processing RTP packet: seq %u, TS %lu",
|
|
packet->p.seq, packet->ts);
|
|
|
|
if (seq_ret == 1) {
|
|
// seq reset - update output seq. we keep our output seq clean
|
|
ssrc_out_p->seq_diff -= packet->p.seq - seq_ori;
|
|
seq_ret = 0;
|
|
}
|
|
|
|
// we might be working with a different packet now
|
|
mp->rtp = &packet->rtp;
|
|
|
|
func_ret = packet->func(ch, packet, mp);
|
|
if (func_ret < 0)
|
|
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Decoder error while processing RTP packet");
|
|
next:
|
|
if (func_ret != 1)
|
|
__transcode_packet_free(packet);
|
|
}
|
|
|
|
out:
|
|
__ssrc_unlock_both(mp);
|
|
obj_put(&ch->h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __output_rtp(struct media_packet *mp, struct codec_ssrc_handler *ch,
|
|
struct codec_handler *handler, // normally == ch->handler except for DTMF
|
|
char *buf, // malloc'd, room for rtp_header + filled-in payload
|
|
unsigned int payload_len,
|
|
unsigned long payload_ts,
|
|
int marker, int seq, int seq_inc, int payload_type)
|
|
{
|
|
struct rtp_header *rh = (void *) buf;
|
|
struct ssrc_ctx *ssrc_out = mp->ssrc_out;
|
|
struct ssrc_entry_call *ssrc_out_p = ssrc_out->parent;
|
|
// reconstruct RTP header
|
|
unsigned long ts = payload_ts;
|
|
ZERO(*rh);
|
|
rh->v_p_x_cc = 0x80;
|
|
if (payload_type == -1)
|
|
payload_type = handler->dest_pt.payload_type;
|
|
rh->m_pt = payload_type | (marker ? 0x80 : 0);
|
|
if (seq != -1)
|
|
rh->seq_num = htons(seq);
|
|
else
|
|
rh->seq_num = htons(ntohs(mp->rtp->seq_num) + (ssrc_out_p->seq_diff += seq_inc));
|
|
rh->timestamp = htonl(ts);
|
|
rh->ssrc = htonl(ssrc_out_p->h.ssrc);
|
|
|
|
// add to output queue
|
|
struct codec_packet *p = g_slice_alloc0(sizeof(*p));
|
|
p->s.s = buf;
|
|
p->s.len = payload_len + sizeof(struct rtp_header);
|
|
payload_tracker_add(&ssrc_out->tracker, handler->dest_pt.payload_type);
|
|
p->free_func = free;
|
|
p->ttq_entry.source = handler;
|
|
p->rtp = rh;
|
|
p->ts = ts;
|
|
p->ssrc_out = ssrc_ctx_get(ssrc_out);
|
|
|
|
// this packet is dynamically allocated, so we're able to schedule it.
|
|
// determine scheduled time to send
|
|
if (ch->first_send.tv_sec && ch->encoder_format.clockrate) {
|
|
// scale first_send from first_send_ts to ts
|
|
p->ttq_entry.when = ch->first_send;
|
|
uint32_t ts_diff = (uint32_t) ts - (uint32_t) ch->first_send_ts; // allow for wrap-around
|
|
unsigned long long ts_diff_us =
|
|
(unsigned long long) ts_diff * 1000000 / ch->encoder_format.clockrate
|
|
* ch->handler->dest_pt.codec_def->clockrate_mult;
|
|
timeval_add_usec(&p->ttq_entry.when, ts_diff_us);
|
|
|
|
// how far in the future is this?
|
|
ts_diff_us = timeval_diff(&p->ttq_entry.when, &rtpe_now); // negative wrap-around to positive OK
|
|
|
|
if (ts_diff_us > 1000000) // more than one second, can't be right
|
|
ch->first_send.tv_sec = 0; // fix it up below
|
|
}
|
|
if (!ch->first_send.tv_sec) {
|
|
p->ttq_entry.when = ch->first_send = rtpe_now;
|
|
ch->first_send_ts = ts;
|
|
}
|
|
|
|
unsigned long long ts_diff_us
|
|
= timeval_diff(&p->ttq_entry.when, &rtpe_now);
|
|
|
|
ch->output_skew = ch->output_skew * 15 / 16 + ts_diff_us / 16;
|
|
if (ch->output_skew > 40000 && ts_diff_us > 10000) { // arbitrary value, 40 ms, 10 ms shift
|
|
ilog(LOG_DEBUG, "Steady clock skew of %lu.%01lu ms detected, shifting send timer back by 10 ms",
|
|
ch->output_skew / 1000,
|
|
(ch->output_skew % 1000) / 100);
|
|
timeval_add_usec(&p->ttq_entry.when, -10000);
|
|
ch->output_skew -= 10000;
|
|
ch->first_send_ts += ch->encoder_format.clockrate / 100;
|
|
}
|
|
|
|
ilog(LOG_DEBUG, "Scheduling to send RTP packet (seq %u TS %lu) in %llu.%01llu ms (at %lu.%06lu)",
|
|
ntohs(rh->seq_num),
|
|
ts,
|
|
ts_diff_us / 1000,
|
|
(ts_diff_us % 1000) / 100,
|
|
(long unsigned) p->ttq_entry.when.tv_sec,
|
|
(long unsigned) p->ttq_entry.when.tv_usec);
|
|
|
|
g_queue_push_tail(&mp->packets_out, p);
|
|
}
|
|
|
|
// returns new reference
|
|
static struct codec_ssrc_handler *__output_ssrc_handler(struct codec_ssrc_handler *ch, struct media_packet *mp) {
|
|
struct codec_handler *handler = ch->handler;
|
|
if (handler->output_handler == handler) {
|
|
obj_get(&ch->h);
|
|
return ch;
|
|
}
|
|
|
|
// our encoder is in a different codec handler
|
|
ilog(LOG_DEBUG, "Switching context from decoder to encoder");
|
|
handler = handler->output_handler;
|
|
struct codec_ssrc_handler *new_ch = get_ssrc(mp->ssrc_in->parent->h.ssrc, handler->ssrc_hash);
|
|
if (G_UNLIKELY(!new_ch)) {
|
|
ilog(LOG_ERR, "Switched from input to output codec context, but no codec handler present");
|
|
obj_get(&ch->h);
|
|
return ch;
|
|
}
|
|
|
|
return new_ch;
|
|
}
|
|
|
|
static void packet_dtmf_fwd(struct codec_ssrc_handler *ch, struct transcode_packet *packet,
|
|
struct media_packet *mp, int seq_inc)
|
|
{
|
|
int payload_type = -1; // take from handler's output config
|
|
|
|
if (ch->handler->dtmf_scaler) {
|
|
// this is actually a DTMF -> PCM handler
|
|
// grab our underlying PCM transcoder
|
|
struct codec_ssrc_handler *output_ch = __output_ssrc_handler(ch, mp);
|
|
if (G_UNLIKELY(!ch->encoder || !output_ch->encoder))
|
|
goto skip;
|
|
|
|
// init some vars
|
|
if (!ch->first_ts)
|
|
ch->first_ts = output_ch->first_ts;
|
|
|
|
// the correct output TS is the encoder's FIFO PTS at the start of the DTMF
|
|
// event. however, we must shift the FIFO PTS forward as the DTMF event goes on
|
|
// as the DTMF event replaces the audio samples. therefore we must remember
|
|
// the TS at the start of the event and the last seen event duration.
|
|
if (ch->dtmf_ts != packet->ts) {
|
|
// this is a new event
|
|
ch->dtmf_ts = packet->ts; // start TS
|
|
ch->last_dtmf_event_ts = 0; // last shifted FIFO PTS
|
|
}
|
|
|
|
unsigned long ts = output_ch->encoder->fifo_pts;
|
|
// roll back TS to start of event
|
|
ts -= ch->last_dtmf_event_ts;
|
|
// adjust to output RTP TS
|
|
unsigned long packet_ts = ts + output_ch->first_ts;
|
|
|
|
ilog(LOG_DEBUG, "Scaling DTMF packet timestamp and duration: TS %lu -> %lu "
|
|
"(%u -> %u)",
|
|
packet->ts, packet_ts,
|
|
ch->handler->source_pt.clock_rate, ch->handler->dest_pt.clock_rate);
|
|
packet->ts = packet_ts;
|
|
|
|
if (packet->payload->len >= sizeof(struct telephone_event_payload)) {
|
|
struct telephone_event_payload *dtmf = (void *) packet->payload->s;
|
|
unsigned int duration = av_rescale(ntohs(dtmf->duration),
|
|
ch->handler->dest_pt.clock_rate, ch->handler->source_pt.clock_rate);
|
|
dtmf->duration = htons(duration);
|
|
|
|
// shift forward our output RTP TS
|
|
output_ch->encoder->fifo_pts = ts + duration;
|
|
ch->last_dtmf_event_ts = duration;
|
|
}
|
|
payload_type = ch->handler->dtmf_payload_type;
|
|
obj_put(&output_ch->h);
|
|
}
|
|
|
|
skip:;
|
|
char *buf = malloc(packet->payload->len + sizeof(struct rtp_header) + RTP_BUFFER_TAIL_ROOM);
|
|
memcpy(buf + sizeof(struct rtp_header), packet->payload->s, packet->payload->len);
|
|
if (packet->ignore_seq) // inject original seq
|
|
__output_rtp(mp, ch, packet->handler ? : ch->handler, buf, packet->payload->len, packet->ts,
|
|
packet->marker, packet->p.seq, -1, payload_type);
|
|
else // use our own sequencing
|
|
__output_rtp(mp, ch, packet->handler ? : ch->handler, buf, packet->payload->len, packet->ts,
|
|
packet->marker, -1, seq_inc, payload_type);
|
|
}
|
|
static int packet_dtmf(struct codec_ssrc_handler *ch, struct transcode_packet *packet, struct media_packet *mp)
|
|
{
|
|
if (ch->ts_in != packet->ts) { // ignore already processed events
|
|
int ret = dtmf_event(mp, packet->payload, ch->encoder_format.clockrate);
|
|
if (G_UNLIKELY(ret == -1)) // error
|
|
return -1;
|
|
if (ret == 1) {
|
|
// END event
|
|
ch->ts_in = packet->ts;
|
|
}
|
|
}
|
|
|
|
if (!mp->call->block_dtmf && !mp->media->monologue->block_dtmf)
|
|
packet_dtmf_fwd(ch, packet, mp, 0);
|
|
return 0;
|
|
}
|
|
static int packet_dtmf_dup(struct codec_ssrc_handler *ch, struct transcode_packet *packet,
|
|
struct media_packet *mp)
|
|
{
|
|
if (!mp->call->block_dtmf && !mp->media->monologue->block_dtmf)
|
|
packet_dtmf_fwd(ch, packet, mp, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int __handler_func_supplemental(struct codec_handler *h, struct media_packet *mp,
|
|
int (*func)(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *),
|
|
int (*dup_func)(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *))
|
|
{
|
|
if (G_UNLIKELY(!mp->rtp))
|
|
return handler_func_passthrough(h, mp);
|
|
|
|
assert((mp->rtp->m_pt & 0x7f) == h->source_pt.payload_type);
|
|
|
|
// create new packet and insert it into sequencer queue
|
|
|
|
ilog(LOG_DEBUG, "Received %s RTP packet: SSRC %" PRIx32 ", PT %u, seq %u, TS %u, len %i",
|
|
h->source_pt.codec_def->rtpname,
|
|
ntohl(mp->rtp->ssrc), mp->rtp->m_pt, ntohs(mp->rtp->seq_num),
|
|
ntohl(mp->rtp->timestamp), mp->payload.len);
|
|
|
|
// determine the primary audio codec used by this SSRC, as the sequence numbers
|
|
// and timing info is shared with it. we'll need to use the same sequencer
|
|
|
|
struct codec_handler *sequencer_h = h; // handler that contains the appropriate sequencer
|
|
if (mp->ssrc_in) {
|
|
for (int i = 0; i < mp->ssrc_in->tracker.most_len; i++) {
|
|
int prim_pt = mp->ssrc_in->tracker.most[i];
|
|
if (prim_pt == 255)
|
|
continue;
|
|
|
|
sequencer_h = codec_handler_get(mp->media, prim_pt);
|
|
if (sequencer_h == h)
|
|
continue;
|
|
if (sequencer_h->source_pt.codec_def && sequencer_h->source_pt.codec_def->supplemental)
|
|
continue;
|
|
ilog(LOG_DEBUG, "Primary RTP payload type for handling %s is %i",
|
|
h->source_pt.codec_def->rtpname,
|
|
prim_pt);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// XXX ? h->output_handler = sequencer_h->output_handler; // XXX locking?
|
|
|
|
struct transcode_packet *packet = g_slice_alloc0(sizeof(*packet));
|
|
packet->func = func;
|
|
packet->dup_func = dup_func;
|
|
packet->handler = h;
|
|
packet->rtp = *mp->rtp;
|
|
|
|
if (sequencer_h->kernelize) {
|
|
// this sequencer doesn't actually keep track of RTP seq properly. instruct
|
|
// the sequencer not to wait for the next in-seq packet but always return
|
|
// them immediately
|
|
packet->ignore_seq = 1;
|
|
}
|
|
|
|
return __handler_func_sequencer(mp, packet);
|
|
}
|
|
static int handler_func_supplemental(struct codec_handler *h, struct media_packet *mp) {
|
|
return __handler_func_supplemental(h, mp, packet_decode, NULL);
|
|
}
|
|
static int handler_func_dtmf(struct codec_handler *h, struct media_packet *mp) {
|
|
return __handler_func_supplemental(h, mp, packet_dtmf, packet_dtmf_dup);
|
|
}
|
|
|
|
static int handler_func_t38(struct codec_handler *h, struct media_packet *mp) {
|
|
if (!mp->media)
|
|
return 0;
|
|
|
|
return t38_gateway_input_udptl(mp->media->t38_gateway, &mp->raw);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
void codec_packet_free(void *pp) {
|
|
struct codec_packet *p = pp;
|
|
if (p->free_func)
|
|
p->free_func(p->s.s);
|
|
ssrc_ctx_put(&p->ssrc_out);
|
|
g_slice_free1(sizeof(*p), p);
|
|
}
|
|
|
|
|
|
|
|
struct rtp_payload_type *codec_make_payload_type(const str *codec_str, struct call_media *media) {
|
|
str codec_fmt = *codec_str;
|
|
str codec, parms, chans, opts, extra_opts, fmt_params, codec_opts;
|
|
if (str_token_sep(&codec, &codec_fmt, '/'))
|
|
return NULL;
|
|
str_token_sep(&parms, &codec_fmt, '/');
|
|
str_token_sep(&chans, &codec_fmt, '/');
|
|
str_token_sep(&opts, &codec_fmt, '/');
|
|
str_token_sep(&extra_opts, &codec_fmt, '/');
|
|
str_token_sep(&fmt_params, &codec_fmt, '/');
|
|
str_token_sep(&codec_opts, &codec_fmt, '/');
|
|
|
|
int clockrate = str_to_i(&parms, 0);
|
|
int channels = str_to_i(&chans, 0);
|
|
int bitrate = str_to_i(&opts, 0);
|
|
int ptime = str_to_i(&extra_opts, 0);
|
|
|
|
if (clockrate && !channels)
|
|
channels = 1;
|
|
|
|
struct rtp_payload_type *ret = g_slice_alloc0(sizeof(*ret));
|
|
ret->payload_type = -1;
|
|
ret->encoding = codec;
|
|
ret->clock_rate = clockrate;
|
|
ret->channels = channels;
|
|
ret->bitrate = bitrate;
|
|
ret->ptime = ptime;
|
|
ret->format_parameters = fmt_params;
|
|
ret->codec_opts = codec_opts;
|
|
|
|
const codec_def_t *def = codec_find(&ret->encoding, 0);
|
|
ret->codec_def = def;
|
|
|
|
codec_init_payload_type(ret, media);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void codec_init_payload_type(struct rtp_payload_type *ret, struct call_media *media) {
|
|
#ifdef WITH_TRANSCODING
|
|
const codec_def_t *def = ret->codec_def;
|
|
|
|
if (def) {
|
|
if (!ret->clock_rate)
|
|
ret->clock_rate = def->default_clockrate;
|
|
if (!ret->channels)
|
|
ret->channels = def->default_channels;
|
|
if (!ret->ptime)
|
|
ret->ptime = def->default_ptime;
|
|
if ((!ret->format_parameters.s || !ret->format_parameters.s[0]) && def->default_fmtp)
|
|
str_init(&ret->format_parameters, (char *) def->default_fmtp);
|
|
|
|
if (def->init)
|
|
def->init(ret);
|
|
|
|
if (def->rfc_payload_type >= 0) {
|
|
const struct rtp_payload_type *rfc_pt = rtp_get_rfc_payload_type(def->rfc_payload_type);
|
|
// only use the RFC payload type if all parameters match
|
|
if (rfc_pt
|
|
&& (ret->clock_rate == 0 || ret->clock_rate == rfc_pt->clock_rate)
|
|
&& (ret->channels == 0 || ret->channels == rfc_pt->channels))
|
|
{
|
|
ret->payload_type = rfc_pt->payload_type;
|
|
if (!ret->clock_rate)
|
|
ret->clock_rate = rfc_pt->clock_rate;
|
|
if (!ret->channels)
|
|
ret->channels = rfc_pt->channels;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// init params strings
|
|
char full_encoding[64];
|
|
char params[32] = "";
|
|
|
|
if (ret->channels > 1) {
|
|
snprintf(full_encoding, sizeof(full_encoding), STR_FORMAT "/%u/%i", STR_FMT(&ret->encoding),
|
|
ret->clock_rate,
|
|
ret->channels);
|
|
snprintf(params, sizeof(params), "%i", ret->channels);
|
|
}
|
|
else
|
|
snprintf(full_encoding, sizeof(full_encoding), STR_FORMAT "/%u", STR_FMT(&ret->encoding),
|
|
ret->clock_rate);
|
|
|
|
str_init(&ret->encoding_with_params, full_encoding);
|
|
str_init(&ret->encoding_parameters, params);
|
|
|
|
if (media)
|
|
__rtp_payload_type_dup(media->call, ret);
|
|
}
|
|
|
|
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
|
|
|
|
static int handler_func_passthrough_ssrc(struct codec_handler *h, struct media_packet *mp) {
|
|
if (G_UNLIKELY(!mp->rtp))
|
|
return handler_func_passthrough(h, mp);
|
|
if (mp->call->block_media || mp->media->monologue->block_media)
|
|
return 0;
|
|
|
|
// substitute out SSRC etc
|
|
mp->rtp->ssrc = htonl(mp->ssrc_in->ssrc_map_out);
|
|
//mp->rtp->timestamp = htonl(ntohl(mp->rtp->timestamp));
|
|
mp->rtp->seq_num = htons(ntohs(mp->rtp->seq_num) + mp->ssrc_out->parent->seq_diff);
|
|
|
|
// keep track of other stats here?
|
|
|
|
codec_add_raw_packet(mp);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void __transcode_packet_free(struct transcode_packet *p) {
|
|
free(p->payload);
|
|
g_slice_free1(sizeof(*p), p);
|
|
}
|
|
|
|
static struct ssrc_entry *__ssrc_handler_new(void *p) {
|
|
// XXX combine with __ssrc_handler_transcode_new
|
|
struct codec_handler *h = p;
|
|
struct codec_ssrc_handler *ch = obj_alloc0("codec_ssrc_handler", sizeof(*ch), __free_ssrc_handler);
|
|
ch->handler = h;
|
|
return &ch->h;
|
|
}
|
|
|
|
static void __dtmf_dsp_callback(void *ptr, int code, int level, int delay) {
|
|
struct codec_ssrc_handler *ch = ptr;
|
|
uint64_t ts = ch->last_dtmf_event_ts + delay;
|
|
ch->last_dtmf_event_ts = ts;
|
|
ts = av_rescale(ts, ch->encoder_format.clockrate, ch->dtmf_format.clockrate);
|
|
codec_add_dtmf_event(ch, code, level, ts);
|
|
}
|
|
|
|
void codec_add_dtmf_event(struct codec_ssrc_handler *ch, int code, int level, uint64_t ts) {
|
|
struct dtmf_event *ev = g_slice_alloc(sizeof(*ev));
|
|
*ev = (struct dtmf_event) { .code = code, .volume = level, .ts = ts };
|
|
ilog(LOG_DEBUG, "DTMF event state change: code %i, volume %i, TS %lu",
|
|
ev->code, ev->volume, (unsigned long) ts);
|
|
g_queue_push_tail(&ch->dtmf_events, ev);
|
|
}
|
|
|
|
uint64_t codec_last_dtmf_event(struct codec_ssrc_handler *ch) {
|
|
struct dtmf_event *ev = g_queue_peek_tail(&ch->dtmf_events);
|
|
if (!ev)
|
|
return 0;
|
|
return ev->ts;
|
|
}
|
|
|
|
uint64_t codec_encoder_pts(struct codec_ssrc_handler *ch) {
|
|
return ch->encoder->fifo_pts;
|
|
}
|
|
|
|
void codec_decoder_skip_pts(struct codec_ssrc_handler *ch, uint64_t pts) {
|
|
ilog(LOG_DEBUG, "Skipping next %" PRIu64 " samples", pts);
|
|
ch->skip_pts += pts;
|
|
}
|
|
|
|
uint64_t codec_decoder_unskip_pts(struct codec_ssrc_handler *ch) {
|
|
uint64_t prev = ch->skip_pts;
|
|
ilog(LOG_DEBUG, "Un-skipping next %" PRIu64 " samples", prev);
|
|
ch->skip_pts = 0;
|
|
return prev;
|
|
}
|
|
|
|
static int codec_decoder_event(enum codec_event event, void *ptr, void *data) {
|
|
struct call_media *media = data;
|
|
if (!media)
|
|
return 0;
|
|
|
|
switch (event) {
|
|
case CE_AMR_CMR_RECV:
|
|
// ignore locking and races for this
|
|
media->u.amr.cmr.cmr_in = GPOINTER_TO_UINT(ptr);
|
|
media->u.amr.cmr.cmr_in_ts = rtpe_now;
|
|
break;
|
|
case CE_AMR_SEND_CMR:
|
|
// ignore locking and races for this
|
|
media->u.amr.cmr.cmr_out = GPOINTER_TO_UINT(ptr);
|
|
media->u.amr.cmr.cmr_out_ts = rtpe_now;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void __dtx_add_callback(struct dtx_buffer *dtxb, const struct timeval *base, unsigned int offset,
|
|
const struct media_packet *mp, unsigned long ts, int seq_add, void *ssrc_ptr)
|
|
{
|
|
struct dtx_entry *dtxe = g_slice_alloc0(sizeof(*dtxe));
|
|
dtxe->ttq_entry.when = *base;
|
|
timeval_add_usec(&dtxe->ttq_entry.when, offset);
|
|
dtxe->ts = ts;
|
|
media_packet_copy(&dtxe->mp, mp);
|
|
dtxe->mp.rtp->seq_num += htons(seq_add);
|
|
dtxe->ssrc_ptr = ssrc_ptr;
|
|
timerthread_queue_push(&dtxb->ttq, &dtxe->ttq_entry);
|
|
}
|
|
|
|
static void __dtx_entry_free(void *p) {
|
|
struct dtx_entry *dtxe = p;
|
|
if (dtxe->packet)
|
|
__transcode_packet_free(dtxe->packet);
|
|
media_packet_release(&dtxe->mp);
|
|
g_slice_free1(sizeof(*dtxe), dtxe);
|
|
}
|
|
static void __dtx_send_later(struct timerthread_queue *ttq, void *p) {
|
|
struct dtx_buffer *dtxb = (void *) ttq;
|
|
struct dtx_entry *dtxe = p;
|
|
struct transcode_packet *packet = dtxe->packet;
|
|
struct media_packet *mp = &dtxe->mp;
|
|
struct packet_stream *ps = mp->stream;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&dtxb->lock);
|
|
struct codec_ssrc_handler *ch = dtxb->csh ? obj_get(&dtxb->csh->h) : NULL;
|
|
struct call *call = dtxb->call ? obj_get(dtxb->call) : NULL;
|
|
mutex_unlock(&dtxb->lock);
|
|
|
|
if (!call)
|
|
goto out; // shut down
|
|
|
|
log_info_stream_fd(mp->sfd);
|
|
|
|
rwlock_lock_r(&call->master_lock);
|
|
__ssrc_lock_both(mp);
|
|
|
|
if (packet) {
|
|
ilog(LOG_DEBUG, "Decoding DTX-buffered RTP packet (TS %lu) now", packet->ts);
|
|
|
|
ret = decoder_input_data(ch->decoder, packet->payload, packet->ts,
|
|
ch->handler->packet_decoded, ch, &dtxe->mp);
|
|
mp->ssrc_out->parent->seq_diff--;
|
|
if (ret)
|
|
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Decoder error while processing buffered RTP packet");
|
|
}
|
|
else {
|
|
unsigned long dtxe_ts = dtxe->ts;
|
|
|
|
mutex_lock(&dtxb->lock);
|
|
unsigned int diff = rtpe_now.tv_sec - dtxb->start;
|
|
unsigned long dtxb_ts = dtxb->ts;
|
|
void *ssrc_ptr = dtxe->mp.stream->ssrc_in;
|
|
|
|
if (dtxe_ts == dtxb_ts
|
|
&& ssrc_ptr == dtxe->ssrc_ptr
|
|
&& (rtpe_config.max_dtx <= 0 || diff < rtpe_config.max_dtx))
|
|
{
|
|
ilog(LOG_DEBUG, "RTP media for TS %lu+ missing, triggering DTX",
|
|
dtxe_ts);
|
|
|
|
dtxb_ts += dtxb->tspp;
|
|
dtxe_ts = dtxb_ts;
|
|
dtxb->ts = dtxb_ts;
|
|
mutex_unlock(&dtxb->lock);
|
|
|
|
ret = decoder_lost_packet(ch->decoder, dtxe_ts,
|
|
ch->handler->packet_decoded, ch, &dtxe->mp);
|
|
if (ret)
|
|
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Decoder error handling DTX/lost packet");
|
|
|
|
__dtx_add_callback(dtxb, &dtxe->ttq_entry.when, dtxb->ptime * 1000, mp, dtxe_ts, 0, ssrc_ptr);
|
|
}
|
|
else
|
|
mutex_unlock(&dtxb->lock);
|
|
}
|
|
|
|
__ssrc_unlock_both(mp);
|
|
|
|
if (mp->packets_out.length && ret == 0) {
|
|
struct packet_stream *sink = ps->rtp_sink;
|
|
|
|
if (!sink)
|
|
media_socket_dequeue(mp, NULL); // just free
|
|
else {
|
|
if (ps->handler && media_packet_encrypt(ps->handler->out->rtp_crypt, sink, mp))
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error encrypting buffered RTP media");
|
|
|
|
mutex_lock(&sink->out_lock);
|
|
if (media_socket_dequeue(mp, sink))
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error sending buffered media to RTP sink");
|
|
mutex_unlock(&sink->out_lock);
|
|
}
|
|
}
|
|
|
|
rwlock_unlock_r(&call->master_lock);
|
|
obj_put(call);
|
|
obj_put(&ch->h);
|
|
|
|
out:
|
|
__dtx_entry_free(dtxe);
|
|
log_info_clear();
|
|
}
|
|
static void __dtx_shutdown(struct dtx_buffer *dtxb) {
|
|
if (dtxb->csh)
|
|
obj_put(&dtxb->csh->h);
|
|
dtxb->csh = NULL;
|
|
if (dtxb->call)
|
|
obj_put(dtxb->call);
|
|
dtxb->call = NULL;
|
|
}
|
|
static void __dtx_free(void *p) {
|
|
struct dtx_buffer *dtxb = p;
|
|
ilog(LOG_DEBUG, "__dtx_free");
|
|
__dtx_shutdown(dtxb);
|
|
mutex_destroy(&dtxb->lock);
|
|
}
|
|
static void __dtx_setup(struct codec_ssrc_handler *ch) {
|
|
if (!ch->handler->source_pt.codec_def->packet_lost || ch->dtx_buffer)
|
|
return;
|
|
|
|
if (!rtpe_config.dtx_delay)
|
|
return;
|
|
|
|
struct dtx_buffer *dtx =
|
|
ch->dtx_buffer = timerthread_queue_new("dtx_buffer", sizeof(*ch->dtx_buffer),
|
|
&codec_timers_thread, NULL, __dtx_send_later, __dtx_free, __dtx_entry_free);
|
|
dtx->csh = obj_get(&ch->h);
|
|
dtx->call = obj_get(ch->handler->media->call);
|
|
mutex_init(&dtx->lock);
|
|
dtx->ptime = ch->ptime;
|
|
if (!dtx->ptime)
|
|
dtx->ptime = 20; // XXX ?
|
|
dtx->tspp = dtx->ptime * ch->handler->source_pt.clock_rate / 1000;
|
|
}
|
|
void __ssrc_handler_stop(void *p) {
|
|
struct codec_ssrc_handler *ch = p;
|
|
if (ch->dtx_buffer) {
|
|
mutex_lock(&ch->dtx_buffer->lock);
|
|
__dtx_shutdown(ch->dtx_buffer);
|
|
mutex_unlock(&ch->dtx_buffer->lock);
|
|
|
|
obj_put(&ch->dtx_buffer->ttq.tt_obj);
|
|
ch->dtx_buffer = NULL;
|
|
}
|
|
}
|
|
void codec_handlers_stop(GQueue *q) {
|
|
for (GList *l = q->head; l; l = l->next) {
|
|
struct codec_handler *h = l->data;
|
|
ssrc_hash_foreach(h->ssrc_hash, __ssrc_handler_stop);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
static void silence_event_free(void *p) {
|
|
g_slice_free1(sizeof(struct silence_event), p);
|
|
}
|
|
|
|
#define __silence_detect_type(type) \
|
|
static void __silence_detect_ ## type(struct codec_ssrc_handler *ch, AVFrame *frame, type thres) { \
|
|
type *s = (void *) frame->data[0]; \
|
|
struct silence_event *last = g_queue_peek_tail(&ch->silence_events); \
|
|
\
|
|
if (last && last->end) /* last event finished? */ \
|
|
last = NULL; \
|
|
\
|
|
for (unsigned int i = 0; i < frame->nb_samples; i++) { \
|
|
/* ilog(LOG_DEBUG, "XXXXXXXXXXXX checking %u %i vs %i", i, (int) s[i], (int) thres); */ \
|
|
if (s[i] <= thres && s[1] >= -thres) { \
|
|
/* silence */ \
|
|
if (!last) { \
|
|
/* new event */ \
|
|
last = g_slice_alloc0(sizeof(*last)); \
|
|
last->start = frame->pts + i; \
|
|
g_queue_push_tail(&ch->silence_events, last); \
|
|
} \
|
|
} \
|
|
else { \
|
|
/* not silence */ \
|
|
if (last && !last->end) { \
|
|
/* close off event */ \
|
|
last->end = frame->pts + i; \
|
|
last = NULL; \
|
|
} \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
__silence_detect_type(double)
|
|
__silence_detect_type(float)
|
|
__silence_detect_type(int32_t)
|
|
__silence_detect_type(int16_t)
|
|
|
|
static void __silence_detect(struct codec_ssrc_handler *ch, AVFrame *frame) {
|
|
//ilog(LOG_DEBUG, "XXXXXXXXXXXXXXXXXXXX silence detect %i %i", rtpe_config.silence_detect_int, ch->handler->cn_payload_type);
|
|
if (!rtpe_config.silence_detect_int)
|
|
return;
|
|
if (ch->handler->cn_payload_type < 0)
|
|
return;
|
|
switch (frame->format) {
|
|
case AV_SAMPLE_FMT_DBL:
|
|
__silence_detect_double(ch, frame, rtpe_config.silence_detect_double);
|
|
break;
|
|
case AV_SAMPLE_FMT_FLT:
|
|
__silence_detect_float(ch, frame, rtpe_config.silence_detect_double);
|
|
break;
|
|
case AV_SAMPLE_FMT_S32:
|
|
__silence_detect_int32_t(ch, frame, rtpe_config.silence_detect_int);
|
|
break;
|
|
case AV_SAMPLE_FMT_S16:
|
|
__silence_detect_int16_t(ch, frame, rtpe_config.silence_detect_int >> 16);
|
|
break;
|
|
default:
|
|
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unsupported sample format %i for silence detection",
|
|
frame->format);
|
|
}
|
|
}
|
|
static int is_silence_event(str *inout, GQueue *events, uint64_t pts, uint64_t duration) {
|
|
uint64_t end = pts + duration;
|
|
|
|
while (events->length) {
|
|
struct silence_event *first = g_queue_peek_head(events);
|
|
if (first->start > pts) // future event
|
|
return 0;
|
|
if (!first->end) // ongoing event
|
|
goto silence;
|
|
if (first->end > end) // event finished with end in the future
|
|
goto silence;
|
|
// event has ended: remove it
|
|
g_queue_pop_head(events);
|
|
// does the event fill the entire span?
|
|
if (first->end == end) {
|
|
silence_event_free(first);
|
|
goto silence;
|
|
}
|
|
// keep going, there might be more
|
|
silence_event_free(first);
|
|
}
|
|
return 0;
|
|
|
|
silence:
|
|
// replace with CN payload
|
|
inout->len = rtpe_config.cn_payload.len;
|
|
memcpy(inout->s, rtpe_config.cn_payload.s, inout->len);
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct ssrc_entry *__ssrc_handler_transcode_new(void *p) {
|
|
struct codec_handler *h = p;
|
|
|
|
if (h->dtmf_scaler)
|
|
ilog(LOG_DEBUG, "Creating SSRC DTMF transcoder from %s/%u/%i to "
|
|
"PT %i",
|
|
h->source_pt.codec_def->rtpname, h->source_pt.clock_rate,
|
|
h->source_pt.channels,
|
|
h->dtmf_payload_type);
|
|
else
|
|
ilog(LOG_DEBUG, "Creating SSRC transcoder from %s/%u/%i to "
|
|
"%s/%u/%i",
|
|
h->source_pt.codec_def->rtpname, h->source_pt.clock_rate,
|
|
h->source_pt.channels,
|
|
h->dest_pt.codec_def->rtpname, h->dest_pt.clock_rate,
|
|
h->dest_pt.channels);
|
|
|
|
struct codec_ssrc_handler *ch = obj_alloc0("codec_ssrc_handler", sizeof(*ch), __free_ssrc_handler);
|
|
ch->handler = h;
|
|
ch->ptime = h->dest_pt.ptime;
|
|
ch->sample_buffer = g_string_new("");
|
|
ch->bitrate = h->dest_pt.bitrate ? : h->dest_pt.codec_def->default_bitrate;
|
|
|
|
format_t enc_format = {
|
|
.clockrate = h->dest_pt.clock_rate * h->dest_pt.codec_def->clockrate_mult,
|
|
.channels = h->dest_pt.channels,
|
|
.format = -1,
|
|
};
|
|
ch->encoder = encoder_new();
|
|
if (!ch->encoder)
|
|
goto err;
|
|
if (encoder_config_fmtp(ch->encoder, h->dest_pt.codec_def,
|
|
ch->bitrate,
|
|
ch->ptime,
|
|
&enc_format, &ch->encoder_format, &h->dest_pt.format_parameters,
|
|
&h->dest_pt.codec_opts))
|
|
goto err;
|
|
|
|
if (h->pcm_dtmf_detect) {
|
|
ilog(LOG_DEBUG, "Inserting DTMF DSP for output payload type %i", h->dtmf_payload_type);
|
|
ch->dtmf_format = (format_t) { .clockrate = 8000, .channels = 1, .format = AV_SAMPLE_FMT_S16 };
|
|
ch->dtmf_dsp = dtmf_rx_init(NULL, NULL, NULL);
|
|
if (!ch->dtmf_dsp)
|
|
ilog(LOG_ERR, "Failed to allocate DTMF RX context");
|
|
else
|
|
dtmf_rx_set_realtime_callback(ch->dtmf_dsp, __dtmf_dsp_callback, ch);
|
|
}
|
|
|
|
ch->decoder = decoder_new_fmtp(h->source_pt.codec_def, h->source_pt.clock_rate, h->source_pt.channels,
|
|
h->source_pt.ptime,
|
|
&ch->encoder_format, &h->source_pt.format_parameters, &h->source_pt.codec_opts);
|
|
if (!ch->decoder)
|
|
goto err;
|
|
|
|
ch->decoder->event_data = h->media;
|
|
ch->decoder->event_func = codec_decoder_event;
|
|
|
|
ch->bytes_per_packet = (ch->encoder->samples_per_packet ? : ch->encoder->samples_per_frame)
|
|
* h->dest_pt.codec_def->bits_per_sample / 8;
|
|
|
|
__dtx_setup(ch);
|
|
|
|
ilog(LOG_DEBUG, "Encoder created with clockrate %i, %i channels, using sample format %i "
|
|
"(ptime %i for %i samples per frame and %i samples (%i bytes) per packet, bitrate %i)",
|
|
ch->encoder_format.clockrate, ch->encoder_format.channels, ch->encoder_format.format,
|
|
ch->ptime, ch->encoder->samples_per_frame, ch->encoder->samples_per_packet,
|
|
ch->bytes_per_packet, ch->bitrate);
|
|
|
|
return &ch->h;
|
|
|
|
err:
|
|
obj_put(&ch->h);
|
|
return NULL;
|
|
}
|
|
static int __encoder_flush(encoder_t *enc, void *u1, void *u2) {
|
|
int *going = u1;
|
|
*going = 1;
|
|
return 0;
|
|
}
|
|
static void __free_ssrc_handler(void *chp) {
|
|
struct codec_ssrc_handler *ch = chp;
|
|
if (ch->decoder)
|
|
decoder_close(ch->decoder);
|
|
if (ch->encoder) {
|
|
// flush out queue to avoid ffmpeg warnings
|
|
int going;
|
|
do {
|
|
going = 0;
|
|
encoder_input_data(ch->encoder, NULL, __encoder_flush, &going, NULL);
|
|
} while (going);
|
|
encoder_free(ch->encoder);
|
|
}
|
|
if (ch->sample_buffer)
|
|
g_string_free(ch->sample_buffer, TRUE);
|
|
if (ch->dtmf_dsp)
|
|
dtmf_rx_free(ch->dtmf_dsp);
|
|
resample_shutdown(&ch->dtmf_resampler);
|
|
g_queue_clear_full(&ch->dtmf_events, dtmf_event_free);
|
|
g_queue_clear_full(&ch->silence_events, silence_event_free);
|
|
if (ch->dtx_buffer)
|
|
obj_put(&ch->dtx_buffer->ttq.tt_obj);
|
|
}
|
|
|
|
static int packet_encoded_rtp(encoder_t *enc, void *u1, void *u2) {
|
|
struct codec_ssrc_handler *ch = u1;
|
|
struct media_packet *mp = u2;
|
|
//unsigned int seq_off = (mp->iter_out > mp->iter_in) ? 1 : 0;
|
|
|
|
ilog(LOG_DEBUG, "RTP media successfully encoded: TS %llu, len %i",
|
|
(unsigned long long) enc->avpkt.pts, enc->avpkt.size);
|
|
|
|
// run this through our packetizer
|
|
AVPacket *in_pkt = &enc->avpkt;
|
|
|
|
while (1) {
|
|
// figure out how big of a buffer we need
|
|
unsigned int payload_len = MAX(MAX(enc->avpkt.size, ch->bytes_per_packet),
|
|
sizeof(struct telephone_event_payload));
|
|
unsigned int pkt_len = sizeof(struct rtp_header) + payload_len + RTP_BUFFER_TAIL_ROOM;
|
|
// prepare our buffers
|
|
char *buf = malloc(pkt_len);
|
|
char *payload = buf + sizeof(struct rtp_header);
|
|
// tell our packetizer how much we want
|
|
str inout;
|
|
str_init_len(&inout, payload, payload_len);
|
|
// and request a packet
|
|
if (in_pkt)
|
|
ilog(LOG_DEBUG, "Adding %i bytes to packetizer", in_pkt->size);
|
|
int ret = ch->handler->dest_pt.codec_def->packetizer(in_pkt,
|
|
ch->sample_buffer, &inout, enc);
|
|
|
|
if (G_UNLIKELY(ret == -1 || enc->avpkt.pts == AV_NOPTS_VALUE)) {
|
|
// nothing
|
|
free(buf);
|
|
break;
|
|
}
|
|
|
|
ilog(LOG_DEBUG, "Received packet of %i bytes from packetizer", inout.len);
|
|
|
|
// check special payloads
|
|
|
|
unsigned int repeats = 0;
|
|
int payload_type = -1;
|
|
|
|
int is_dtmf = dtmf_event_payload(&inout, (uint64_t *) &enc->avpkt.pts, enc->avpkt.duration,
|
|
&ch->dtmf_event, &ch->dtmf_events);
|
|
if (is_dtmf) {
|
|
payload_type = ch->handler->dtmf_payload_type;
|
|
if (is_dtmf == 1)
|
|
ch->rtp_mark = 1; // DTMF start event
|
|
else if (is_dtmf == 3)
|
|
repeats = 2; // DTMF end event
|
|
}
|
|
else {
|
|
if (is_silence_event(&inout, &ch->silence_events, enc->avpkt.pts, enc->avpkt.duration))
|
|
payload_type = ch->handler->cn_payload_type;
|
|
}
|
|
|
|
// ready to send
|
|
|
|
do {
|
|
char *send_buf = buf;
|
|
if (repeats > 0) {
|
|
// need to duplicate the payload as __output_rtp consumes it
|
|
send_buf = malloc(pkt_len);
|
|
memcpy(send_buf, buf, pkt_len);
|
|
}
|
|
__output_rtp(mp, ch, ch->handler, send_buf, inout.len, ch->first_ts
|
|
+ enc->avpkt.pts / enc->def->clockrate_mult,
|
|
ch->rtp_mark ? 1 : 0, -1, 0,
|
|
payload_type);
|
|
mp->ssrc_out->parent->seq_diff++;
|
|
//mp->iter_out++;
|
|
ch->rtp_mark = 0;
|
|
} while (repeats--);
|
|
|
|
if (ret == 0) {
|
|
// no more to go
|
|
break;
|
|
}
|
|
|
|
// loop around and get more
|
|
in_pkt = NULL;
|
|
//seq_off = 1; // next packet needs last seq + 1 XXX set unkernelize if used
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __dtmf_detect(struct codec_ssrc_handler *ch, AVFrame *frame) {
|
|
if (!ch->dtmf_dsp)
|
|
return;
|
|
if (ch->handler->dtmf_payload_type == -1 || !ch->handler->pcm_dtmf_detect) {
|
|
ch->dtmf_event.code = 0;
|
|
return;
|
|
}
|
|
|
|
AVFrame *dsp_frame = resample_frame(&ch->dtmf_resampler, frame, &ch->dtmf_format);
|
|
if (!dsp_frame) {
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Failed to resample audio for DTMF DSP");
|
|
return;
|
|
}
|
|
|
|
ilog(LOG_DEBUG, "DTMF detect, TS %lu -> %lu, %u -> %u samples",
|
|
(unsigned long) frame->pts,
|
|
(unsigned long) dsp_frame->pts,
|
|
frame->nb_samples,
|
|
dsp_frame->nb_samples);
|
|
|
|
if (dsp_frame->pts > ch->dtmf_ts)
|
|
dtmf_rx_fillin(ch->dtmf_dsp, dsp_frame->pts - ch->dtmf_ts);
|
|
else if (dsp_frame->pts < ch->dtmf_ts)
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "DTMF TS seems to run backwards (%lu < %lu)",
|
|
(unsigned long) dsp_frame->pts,
|
|
(unsigned long) ch->dtmf_ts);
|
|
|
|
int num_samples = dsp_frame->nb_samples;
|
|
int16_t *samples = (void *) dsp_frame->extended_data[0];
|
|
while (num_samples > 0) {
|
|
int ret = dtmf_rx(ch->dtmf_dsp, samples, num_samples);
|
|
if (ret < 0 || ret >= num_samples) {
|
|
ilog(LOG_ERR | LOG_FLAG_LIMIT, "DTMF DSP returned error %i", ret);
|
|
break;
|
|
}
|
|
samples += num_samples - ret;
|
|
num_samples = ret;
|
|
}
|
|
ch->dtmf_ts = dsp_frame->pts + dsp_frame->nb_samples;
|
|
av_frame_free(&dsp_frame);
|
|
}
|
|
|
|
static int packet_decoded_common(decoder_t *decoder, AVFrame *frame, void *u1, void *u2,
|
|
int (*input_func)(encoder_t *enc, AVFrame *frame,
|
|
int (*callback)(encoder_t *, void *u1, void *u2), void *u1, void *u2))
|
|
{
|
|
struct codec_ssrc_handler *ch = u1;
|
|
struct media_packet *mp = u2;
|
|
|
|
ilog(LOG_DEBUG, "RTP media successfully decoded: TS %llu, samples %u",
|
|
(unsigned long long) frame->pts, frame->nb_samples);
|
|
|
|
// switch from input codec context to output context if necessary
|
|
struct codec_ssrc_handler *new_ch = __output_ssrc_handler(ch, mp);
|
|
if (new_ch != ch) {
|
|
// copy some essential parameters
|
|
if (!new_ch->first_ts)
|
|
new_ch->first_ts = ch->first_ts;
|
|
|
|
ch = new_ch;
|
|
}
|
|
|
|
struct codec_handler *h = ch->handler;
|
|
if (h->stats_entry) {
|
|
int idx = rtpe_now.tv_sec & 1;
|
|
atomic64_add(&h->stats_entry->pcm_samples[idx], frame->nb_samples);
|
|
atomic64_add(&h->stats_entry->pcm_samples[2], frame->nb_samples);
|
|
}
|
|
|
|
if (ch->skip_pts) {
|
|
if (frame->nb_samples <= 0)
|
|
;
|
|
else if (frame->nb_samples < ch->skip_pts)
|
|
ch->skip_pts -= frame->nb_samples;
|
|
else
|
|
ch->skip_pts = 0;
|
|
ilog(LOG_DEBUG, "Discarding %i samples", frame->nb_samples);
|
|
goto discard;
|
|
}
|
|
|
|
if (G_UNLIKELY(!ch->encoder)) {
|
|
ilog(LOG_INFO | LOG_FLAG_LIMIT,
|
|
"Discarding decoded %i PCM samples due to lack of output encoder",
|
|
frame->nb_samples);
|
|
goto discard;
|
|
}
|
|
|
|
__dtmf_detect(ch, frame);
|
|
__silence_detect(ch, frame);
|
|
|
|
// locking deliberately ignored
|
|
if (mp->media_out)
|
|
ch->encoder->codec_options.amr.cmr = mp->media_out->u.amr.cmr;
|
|
|
|
input_func(ch->encoder, frame, h->packet_encoded, ch, mp);
|
|
|
|
discard:
|
|
av_frame_free(&frame);
|
|
obj_put(&new_ch->h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int packet_decoded_fifo(decoder_t *decoder, AVFrame *frame, void *u1, void *u2) {
|
|
return packet_decoded_common(decoder, frame, u1, u2, encoder_input_fifo);
|
|
}
|
|
static int packet_decoded_direct(decoder_t *decoder, AVFrame *frame, void *u1, void *u2) {
|
|
return packet_decoded_common(decoder, frame, u1, u2, encoder_input_data);
|
|
}
|
|
|
|
static int packet_decode(struct codec_ssrc_handler *ch, struct transcode_packet *packet, struct media_packet *mp)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!ch->first_ts)
|
|
ch->first_ts = packet->ts;
|
|
ch->last_ts = packet->ts;
|
|
|
|
if (ch->dtx_buffer && mp->sfd && mp->ssrc_in && mp->ssrc_out) {
|
|
ilog(LOG_DEBUG, "Adding packet to DTX buffer");
|
|
|
|
struct dtx_buffer *dtxb = ch->dtx_buffer;
|
|
unsigned long ts = packet->ts;
|
|
|
|
mutex_lock(&dtxb->lock);
|
|
if (ts != dtxb->ts)
|
|
dtxb->ts = ts;
|
|
dtxb->start = rtpe_now.tv_sec;
|
|
mutex_unlock(&dtxb->lock);
|
|
|
|
struct dtx_entry *dtxe = g_slice_alloc0(sizeof(*dtxe));
|
|
dtxe->ttq_entry.when = rtpe_now;
|
|
timeval_add_usec(&dtxe->ttq_entry.when, rtpe_config.dtx_delay * 1000);
|
|
dtxe->packet = packet;
|
|
media_packet_copy(&dtxe->mp, mp);
|
|
timerthread_queue_push(&dtxb->ttq, &dtxe->ttq_entry);
|
|
// packet now consumed
|
|
packet = NULL;
|
|
|
|
__dtx_add_callback(dtxb, &rtpe_now, (rtpe_config.dtx_delay + dtxb->ptime) * 1000, mp, ts, 1,
|
|
mp->stream->ssrc_in);
|
|
|
|
ret = 1;
|
|
}
|
|
else {
|
|
ilog(LOG_DEBUG, "Decoding RTP packet now");
|
|
ret = decoder_input_data(ch->decoder, packet->payload, packet->ts, ch->handler->packet_decoded,
|
|
ch, mp);
|
|
ret = ret ? -1 : 0;
|
|
mp->ssrc_out->parent->seq_diff--;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void codec_calc_jitter(struct media_packet *mp, unsigned int clockrate) {
|
|
if (!mp->ssrc_in)
|
|
return;
|
|
struct ssrc_entry_call *sec = mp->ssrc_in->parent;
|
|
|
|
// RFC 3550 A.8
|
|
uint32_t transit = (((timeval_us(&mp->tv) / 1000) * clockrate) / 1000)
|
|
- ntohl(mp->rtp->timestamp);
|
|
mutex_lock(&sec->h.lock);
|
|
int32_t d = 0;
|
|
if (sec->transit)
|
|
d = transit - sec->transit;
|
|
sec->transit = transit;
|
|
if (d < 0)
|
|
d = -d;
|
|
sec->jitter += d - ((sec->jitter + 8) >> 4);
|
|
mutex_unlock(&sec->h.lock);
|
|
}
|
|
|
|
|
|
static int handler_func_transcode(struct codec_handler *h, struct media_packet *mp) {
|
|
if (G_UNLIKELY(!mp->rtp))
|
|
return handler_func_passthrough(h, mp);
|
|
if (mp->call->block_media || mp->media->monologue->block_media)
|
|
return 0;
|
|
|
|
// create new packet and insert it into sequencer queue
|
|
|
|
ilog(LOG_DEBUG, "Received RTP packet: SSRC %" PRIx32 ", PT %u, seq %u, TS %u, len %i",
|
|
ntohl(mp->rtp->ssrc), mp->rtp->m_pt, ntohs(mp->rtp->seq_num),
|
|
ntohl(mp->rtp->timestamp), mp->payload.len);
|
|
|
|
codec_calc_jitter(mp, h->source_pt.clock_rate);
|
|
|
|
if (h->stats_entry) {
|
|
unsigned int idx = rtpe_now.tv_sec & 1;
|
|
int last_tv_sec = g_atomic_int_get(&h->stats_entry->last_tv_sec[idx]);
|
|
if (last_tv_sec != (int) rtpe_now.tv_sec) {
|
|
if (g_atomic_int_compare_and_exchange(&h->stats_entry->last_tv_sec[idx],
|
|
last_tv_sec, rtpe_now.tv_sec))
|
|
{
|
|
// new second - zero out stats. slight race condition here
|
|
atomic64_set(&h->stats_entry->packets_input[idx], 0);
|
|
atomic64_set(&h->stats_entry->bytes_input[idx], 0);
|
|
atomic64_set(&h->stats_entry->pcm_samples[idx], 0);
|
|
}
|
|
}
|
|
atomic64_inc(&h->stats_entry->packets_input[idx]);
|
|
atomic64_add(&h->stats_entry->bytes_input[idx], mp->payload.len);
|
|
atomic64_inc(&h->stats_entry->packets_input[2]);
|
|
atomic64_add(&h->stats_entry->bytes_input[2], mp->payload.len);
|
|
}
|
|
|
|
struct transcode_packet *packet = g_slice_alloc0(sizeof(*packet));
|
|
packet->func = packet_decode;
|
|
packet->rtp = *mp->rtp;
|
|
packet->handler = h;
|
|
|
|
if (h->dtmf_scaler) {
|
|
packet->func = packet_dtmf;
|
|
packet->dup_func = packet_dtmf_dup;
|
|
}
|
|
|
|
int ret = __handler_func_sequencer(mp, packet);
|
|
|
|
//ilog(LOG_DEBUG, "tc iters: in %u out %u", mp->iter_in, mp->iter_out);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int handler_func_playback(struct codec_handler *h, struct media_packet *mp) {
|
|
decoder_input_data(h->ssrc_handler->decoder, &mp->payload, mp->rtp->timestamp,
|
|
h->packet_decoded, h->ssrc_handler, mp);
|
|
return 0;
|
|
}
|
|
|
|
static int handler_func_inject_dtmf(struct codec_handler *h, struct media_packet *mp) {
|
|
struct codec_ssrc_handler *ch = get_ssrc(mp->ssrc_in->parent->h.ssrc, h->ssrc_hash);
|
|
decoder_input_data(ch->decoder, &mp->payload, mp->rtp->timestamp,
|
|
h->packet_decoded, ch, mp);
|
|
obj_put(&ch->h);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// special return value `(void *) 0x1` to signal type mismatch
|
|
static struct rtp_payload_type *codec_make_payload_type_sup(const str *codec_str, struct call_media *media) {
|
|
struct rtp_payload_type *ret = codec_make_payload_type(codec_str, media);
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
if (!ret->codec_def || (media->type_id && ret->codec_def->media_type != media->type_id)) {
|
|
payload_type_free(ret);
|
|
return (void *) 0x1;
|
|
}
|
|
// we must support both encoding and decoding
|
|
if (!ret->codec_def->support_decoding)
|
|
goto err;
|
|
if (!ret->codec_def->support_encoding)
|
|
goto err;
|
|
if (ret->codec_def->default_channels <= 0 || ret->codec_def->default_clockrate < 0)
|
|
goto err;
|
|
|
|
return ret;
|
|
|
|
|
|
err:
|
|
payload_type_free(ret);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
static struct rtp_payload_type *codec_add_payload_type(const str *codec, struct call_media *media,
|
|
struct call_media *other_media)
|
|
{
|
|
struct rtp_payload_type *pt = codec_make_payload_type_sup(codec, media);
|
|
if (!pt) {
|
|
ilog(LOG_WARN, "Codec '" STR_FORMAT "' requested for transcoding is not supported",
|
|
STR_FMT(codec));
|
|
return NULL;
|
|
}
|
|
if (pt == (void *) 0x1)
|
|
return NULL;
|
|
|
|
pt->payload_type = __unused_pt_number(media, other_media, pt);
|
|
if (pt->payload_type < 0) {
|
|
ilog(LOG_WARN, "Ran out of RTP payload type numbers while adding codec '"
|
|
STR_FORMAT "' for transcoding",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
payload_type_free(pt);
|
|
return NULL;
|
|
}
|
|
|
|
return pt;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
call_str_cpy(call, &pt->codec_opts, &pt->codec_opts);
|
|
call_str_cpy(call, &pt->rtcp_fb, &pt->rtcp_fb);
|
|
}
|
|
static struct rtp_payload_type *__rtp_payload_type_copy(const struct rtp_payload_type *pt) {
|
|
struct rtp_payload_type *pt_copy = g_slice_alloc(sizeof(*pt));
|
|
*pt_copy = *pt;
|
|
return pt_copy;
|
|
}
|
|
static void __rtp_payload_type_add_name(GHashTable *ht, struct rtp_payload_type *pt)
|
|
{
|
|
GQueue *q = g_hash_table_lookup_queue_new(ht, str_dup(&pt->encoding), free);
|
|
g_queue_push_tail(q, GUINT_TO_POINTER(pt->payload_type));
|
|
q = g_hash_table_lookup_queue_new(ht, str_dup(&pt->encoding_with_params), free);
|
|
g_queue_push_tail(q, GUINT_TO_POINTER(pt->payload_type));
|
|
}
|
|
#ifdef WITH_TRANSCODING
|
|
static void __insert_codec_tracker(struct call_media *media, GList *link) {
|
|
struct rtp_payload_type *pt = link->data;
|
|
struct codec_tracker *sct = media->codec_tracker;
|
|
|
|
ensure_codec_def(pt, media);
|
|
|
|
if (!pt->codec_def || !pt->codec_def->supplemental)
|
|
g_hash_table_replace(sct->clockrates, GUINT_TO_POINTER(pt->clock_rate),
|
|
GUINT_TO_POINTER(GPOINTER_TO_UINT(
|
|
g_hash_table_lookup(sct->clockrates,
|
|
GUINT_TO_POINTER(pt->clock_rate))) + 1));
|
|
else {
|
|
GHashTable *clockrates = g_hash_table_lookup(sct->supp_codecs, &pt->encoding);
|
|
if (!clockrates) {
|
|
clockrates = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
|
|
(GDestroyNotify) g_queue_free);
|
|
g_hash_table_replace(sct->supp_codecs, str_dup(&pt->encoding), clockrates);
|
|
}
|
|
GQueue *entries = g_hash_table_lookup_queue_new(clockrates, GUINT_TO_POINTER(pt->clock_rate),
|
|
NULL);
|
|
g_queue_push_tail(entries, link);
|
|
}
|
|
}
|
|
#endif
|
|
static void __queue_insert_supp(GQueue *q, struct rtp_payload_type *pt, int supp_check) {
|
|
// do we care at all?
|
|
if (!supp_check) {
|
|
g_queue_push_tail(q, pt);
|
|
return;
|
|
}
|
|
|
|
// all new supp codecs go last
|
|
if (pt->codec_def && pt->codec_def->supplemental) {
|
|
g_queue_push_tail(q, pt);
|
|
return;
|
|
}
|
|
|
|
// find the cut-off point between non-supp and supp codecs
|
|
GList *insert_pos = NULL; // last non-supp codec
|
|
for (GList *l = q->tail; l; l = l->prev) {
|
|
struct rtp_payload_type *ptt = l->data;
|
|
if (!ptt->codec_def || !ptt->codec_def->supplemental) {
|
|
insert_pos = l;
|
|
break;
|
|
}
|
|
}
|
|
// do we have any non-supp codecs?
|
|
if (!insert_pos)
|
|
g_queue_push_head(q, pt);
|
|
else
|
|
g_queue_insert_after(q, insert_pos, pt);
|
|
}
|
|
// consumes 'pt'
|
|
void __rtp_payload_type_add_recv(struct call_media *media, struct rtp_payload_type *pt, int supp_check) {
|
|
if (!pt)
|
|
return;
|
|
#ifdef WITH_TRANSCODING
|
|
ensure_codec_def(pt, media);
|
|
#endif
|
|
if (proto_is_not_rtp(media->protocol)) {
|
|
payload_type_free(pt);
|
|
return;
|
|
}
|
|
// update ptime in case it was overridden
|
|
if (media->ptime > 0)
|
|
pt->ptime = media->ptime;
|
|
g_hash_table_insert(media->codecs_recv, &pt->payload_type, pt);
|
|
__rtp_payload_type_add_name(media->codec_names_recv, pt);
|
|
__queue_insert_supp(&media->codecs_prefs_recv, pt, supp_check);
|
|
}
|
|
// consumes 'pt'
|
|
void __rtp_payload_type_add_send(struct call_media *other_media,
|
|
struct rtp_payload_type *pt)
|
|
{
|
|
if (!pt)
|
|
return;
|
|
if (proto_is_not_rtp(other_media->protocol)) {
|
|
payload_type_free(pt);
|
|
return;
|
|
}
|
|
// update ptime in case it was overridden
|
|
if (other_media->ptime > 0)
|
|
pt->ptime = other_media->ptime;
|
|
g_hash_table_insert(other_media->codecs_send, &pt->payload_type, pt);
|
|
__rtp_payload_type_add_name(other_media->codec_names_send, pt);
|
|
g_queue_push_tail(&other_media->codecs_prefs_send, pt);
|
|
}
|
|
// duplicates 'pt'
|
|
void __rtp_payload_type_add_send_dup(struct call_media *other_media,
|
|
struct rtp_payload_type *pt)
|
|
{
|
|
if (proto_is_not_rtp(other_media->protocol))
|
|
return;
|
|
pt = __rtp_payload_type_copy(pt);
|
|
__rtp_payload_type_add_send(other_media, pt);
|
|
}
|
|
// consumes 'pt'
|
|
static void __rtp_payload_type_add(struct call_media *media, struct call_media *other_media,
|
|
struct rtp_payload_type *pt)
|
|
{
|
|
__rtp_payload_type_add_send_dup(other_media, pt);
|
|
__rtp_payload_type_add_recv(media, pt, 0);
|
|
}
|
|
|
|
static void __payload_queue_free(void *qq) {
|
|
GQueue *q = qq;
|
|
g_queue_free_full(q, (GDestroyNotify) payload_type_free);
|
|
}
|
|
static int __revert_codec_strip(GHashTable *stripped, GHashTable *masked, const str *codec,
|
|
struct call_media *media, struct call_media *other_media)
|
|
{
|
|
int ret = 0;
|
|
|
|
GQueue *q = g_hash_table_lookup(stripped, codec);
|
|
if (q) {
|
|
ilog(LOG_DEBUG, "Restoring codec '" STR_FORMAT "' from stripped codecs (%u payload types)",
|
|
STR_FMT(codec), q->length);
|
|
while (q->length) {
|
|
struct rtp_payload_type *pt = g_queue_pop_head(q);
|
|
__rtp_payload_type_add(media, other_media, pt);
|
|
}
|
|
g_hash_table_remove(stripped, codec);
|
|
ret = 1;
|
|
}
|
|
|
|
q = g_hash_table_lookup(masked, codec);
|
|
if (q) {
|
|
ilog(LOG_DEBUG, "Restoring codec '" STR_FORMAT "' from masked codecs (%u payload types)",
|
|
STR_FMT(codec), q->length);
|
|
while (q->length) {
|
|
struct rtp_payload_type *pt = g_queue_pop_head(q);
|
|
__rtp_payload_type_add_recv(media, pt, 1);
|
|
}
|
|
g_hash_table_remove(masked, codec);
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
static int __codec_options_set1(struct call *call, struct rtp_payload_type *pt, const str *enc,
|
|
GHashTable *codec_set)
|
|
{
|
|
str *pt_str = g_hash_table_lookup(codec_set, enc);
|
|
if (!pt_str)
|
|
return 0;
|
|
struct rtp_payload_type *pt_parsed = codec_make_payload_type(pt_str, NULL);
|
|
if (!pt_parsed)
|
|
return 0;
|
|
// match parameters
|
|
if (pt->clock_rate != pt_parsed->clock_rate || pt->channels != pt_parsed->channels) {
|
|
payload_type_free(pt_parsed);
|
|
return 0;
|
|
}
|
|
// match - apply options
|
|
if (!pt->bitrate)
|
|
pt->bitrate = pt_parsed->bitrate;
|
|
if (!pt->codec_opts.len && pt_parsed->codec_opts.len)
|
|
call_str_cpy(call, &pt->codec_opts, &pt_parsed->codec_opts);
|
|
payload_type_free(pt_parsed);
|
|
return 1;
|
|
}
|
|
static void __codec_options_set(struct call *call, struct rtp_payload_type *pt, GHashTable *codec_set) {
|
|
if (!codec_set)
|
|
return;
|
|
if (__codec_options_set1(call, pt, &pt->encoding_with_params, codec_set))
|
|
return;
|
|
if (__codec_options_set1(call, pt, &pt->encoding, codec_set))
|
|
return;
|
|
}
|
|
#ifdef WITH_TRANSCODING
|
|
static void codec_tracker_destroy(struct codec_tracker **sct) {
|
|
if (!*sct)
|
|
return;
|
|
g_hash_table_destroy((*sct)->clockrates);
|
|
g_hash_table_destroy((*sct)->touched);
|
|
g_hash_table_destroy((*sct)->supp_codecs);
|
|
g_slice_free1(sizeof(**sct), *sct);
|
|
*sct = NULL;
|
|
}
|
|
void codec_tracker_init(struct call_media *m) {
|
|
codec_tracker_destroy(&m->codec_tracker);
|
|
m->codec_tracker = g_slice_alloc0(sizeof(*m->codec_tracker));
|
|
m->codec_tracker->clockrates = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
m->codec_tracker->touched = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
m->codec_tracker->supp_codecs = g_hash_table_new_full(str_case_hash, str_case_equal, free,
|
|
(GDestroyNotify) g_hash_table_destroy);
|
|
}
|
|
static void codec_touched(struct rtp_payload_type *pt, struct call_media *media) {
|
|
if (!media->codec_tracker)
|
|
return;
|
|
ensure_codec_def(pt, media);
|
|
if (pt->codec_def && pt->codec_def->supplemental) {
|
|
media->codec_tracker->all_touched = 1;
|
|
return;
|
|
}
|
|
g_hash_table_replace(media->codec_tracker->touched, GUINT_TO_POINTER(pt->clock_rate), (void *) 0x1);
|
|
}
|
|
static int ptr_cmp(const void *a, const void *b) {
|
|
if (a < b)
|
|
return -1;
|
|
if (a > b)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
void codec_tracker_finish(struct call_media *media) {
|
|
struct codec_tracker *sct = media->codec_tracker;
|
|
if (!sct)
|
|
return;
|
|
|
|
// build our tables
|
|
for (GList *l = media->codecs_prefs_recv.head; l; l = l->next)
|
|
__insert_codec_tracker(media, l);
|
|
|
|
// get all supported audio clock rates
|
|
GList *clockrates = g_hash_table_get_keys(sct->clockrates);
|
|
// and to ensure consistent results
|
|
clockrates = g_list_sort(clockrates, ptr_cmp);
|
|
|
|
// for each supplemental codec supported ...
|
|
GList *supp_codecs = g_hash_table_get_keys(sct->supp_codecs);
|
|
|
|
for (GList *l = supp_codecs; l; l = l->next) {
|
|
// ... compare the list of clock rates against the clock rates supported by the audio codecs
|
|
str *supp_codec = l->data;
|
|
GHashTable *supp_clockrates = g_hash_table_lookup(sct->supp_codecs, supp_codec);
|
|
|
|
// iterate audio clock rates and check against supp clockrates
|
|
for (GList *k = clockrates; k; k = k->next) {
|
|
unsigned int clockrate = GPOINTER_TO_UINT(k->data);
|
|
|
|
// has it been removed?
|
|
if (!g_hash_table_lookup(sct->clockrates, GUINT_TO_POINTER(clockrate)))
|
|
continue;
|
|
|
|
// is this already supported?
|
|
if (g_hash_table_lookup(supp_clockrates, GUINT_TO_POINTER(clockrate))) {
|
|
// good, remember this
|
|
g_hash_table_remove(supp_clockrates, GUINT_TO_POINTER(clockrate));
|
|
continue;
|
|
}
|
|
|
|
// ignore if we haven't touched anything with that clock rate
|
|
if (!sct->all_touched && !g_hash_table_lookup(sct->touched, GUINT_TO_POINTER(clockrate)))
|
|
continue;
|
|
|
|
ilog(LOG_DEBUG, "Adding supplemental codec " STR_FORMAT " for clock rate %u", STR_FMT(supp_codec), clockrate);
|
|
|
|
char *pt_s = g_strdup_printf(STR_FORMAT "/%u", STR_FMT(supp_codec), clockrate);
|
|
str pt_str;
|
|
str_init(&pt_str, pt_s);
|
|
|
|
struct rtp_payload_type *pt = codec_add_payload_type(&pt_str, media, NULL);
|
|
if (!pt)
|
|
continue;
|
|
pt->for_transcoding = 1;
|
|
|
|
__rtp_payload_type_add_recv(media, pt, 1);
|
|
|
|
g_free(pt_s);
|
|
}
|
|
|
|
// finally check which clock rates are left over and remove those
|
|
GList *to_remove = g_hash_table_get_keys(supp_clockrates);
|
|
for (GList *k = to_remove; k; k = k->next) {
|
|
unsigned int clockrate = GPOINTER_TO_UINT(k->data);
|
|
|
|
// ignore if we haven't touched anything with that clock rate
|
|
if (!sct->all_touched && !g_hash_table_lookup(sct->touched, GUINT_TO_POINTER(clockrate)))
|
|
continue;
|
|
|
|
GQueue *entries = g_hash_table_lookup(supp_clockrates, GUINT_TO_POINTER(clockrate));
|
|
for (GList *j = entries->head; j; j = j->next) {
|
|
GList *link = j->data;
|
|
struct rtp_payload_type *pt = link->data;
|
|
|
|
ilog(LOG_DEBUG, "Eliminating supplemental codec " STR_FORMAT " with stray clock rate %u",
|
|
STR_FMT(&pt->encoding), clockrate);
|
|
|
|
__delete_receiver_codec(media, link);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_list_free(supp_codecs);
|
|
g_list_free(clockrates);
|
|
codec_tracker_destroy(&media->codec_tracker);
|
|
}
|
|
#endif
|
|
int __codec_ht_except(int all_flag, GHashTable *yes_ht, GHashTable *no_ht, struct rtp_payload_type *pt) {
|
|
int do_this = 0;
|
|
if (all_flag)
|
|
do_this = 1;
|
|
if (yes_ht) {
|
|
if (g_hash_table_lookup(yes_ht, &pt->encoding))
|
|
do_this = 1;
|
|
else if (g_hash_table_lookup(yes_ht, &pt->encoding_with_params))
|
|
do_this = 1;
|
|
}
|
|
if (no_ht && all_flag) {
|
|
if (g_hash_table_lookup(no_ht, &pt->encoding))
|
|
do_this = 0;
|
|
else if (g_hash_table_lookup(no_ht, &pt->encoding_with_params))
|
|
do_this = 0;
|
|
}
|
|
return do_this;
|
|
}
|
|
void __ht_merge(GHashTable **dst, GHashTable *src) {
|
|
if (!src)
|
|
return;
|
|
if (!*dst)
|
|
*dst = g_hash_table_new_full(str_case_hash, str_case_equal, free, NULL);
|
|
GHashTableIter iter;
|
|
g_hash_table_iter_init(&iter, src);
|
|
void *key;
|
|
while (g_hash_table_iter_next(&iter, &key, NULL)) {
|
|
str *dup = str_dup(key);
|
|
g_hash_table_replace(*dst, dup, dup);
|
|
}
|
|
}
|
|
void codec_rtp_payload_types(struct call_media *media, struct call_media *other_media,
|
|
GQueue *types, struct sdp_ng_flags *flags)
|
|
{
|
|
if (!flags)
|
|
return;
|
|
|
|
// '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");
|
|
static const str str_full = STR_CONST_INIT("full");
|
|
GHashTable *stripped = g_hash_table_new_full(str_case_hash, str_case_equal, free, __payload_queue_free);
|
|
GHashTable *masked = g_hash_table_new_full(str_case_hash, str_case_equal, free, __payload_queue_free);
|
|
int strip_all = 0, mask_all = 0, consume_all = 0, accept_all = 0;
|
|
|
|
// start fresh
|
|
if (!proto_is_rtp(other_media->protocol) && proto_is_rtp(media->protocol) && flags->opmode == OP_OFFER) {
|
|
// leave them alone if incoming proto is not RTP but outgoing is,
|
|
// as this is needed for T.38 decoding during a re-invite.
|
|
// this special case is only needed in an offer as in the answer
|
|
// we can go by media->codecs_prefs_send.
|
|
;
|
|
}
|
|
else {
|
|
// receiving part for 'media'
|
|
g_queue_clear_full(&media->codecs_prefs_recv, (GDestroyNotify) payload_type_free);
|
|
g_hash_table_remove_all(media->codecs_recv);
|
|
g_hash_table_remove_all(media->codec_names_recv);
|
|
}
|
|
// and sending part for 'other_media'
|
|
g_queue_clear_full(&other_media->codecs_prefs_send, (GDestroyNotify) payload_type_free);
|
|
g_hash_table_remove_all(other_media->codecs_send);
|
|
g_hash_table_remove_all(other_media->codec_names_send);
|
|
|
|
if (flags->codec_strip && g_hash_table_lookup(flags->codec_strip, &str_all))
|
|
strip_all = 1;
|
|
else if (flags->codec_strip && g_hash_table_lookup(flags->codec_strip, &str_full))
|
|
strip_all = 2;
|
|
if (flags->codec_mask && g_hash_table_lookup(flags->codec_mask, &str_all))
|
|
mask_all = 1;
|
|
else if (flags->codec_mask && g_hash_table_lookup(flags->codec_mask, &str_full))
|
|
mask_all = 2;
|
|
if (flags->codec_consume && g_hash_table_lookup(flags->codec_consume, &str_all))
|
|
consume_all = 1;
|
|
else if (flags->codec_consume && g_hash_table_lookup(flags->codec_consume, &str_full))
|
|
consume_all = 2;
|
|
if (flags->codec_accept && g_hash_table_lookup(flags->codec_accept, &str_all))
|
|
accept_all = 1;
|
|
|
|
__ht_merge(&flags->codec_except, flags->codec_consume);
|
|
__ht_merge(&flags->codec_except, flags->codec_accept);
|
|
__ht_merge(&flags->codec_except, flags->codec_strip);
|
|
__ht_merge(&flags->codec_except, flags->codec_mask);
|
|
|
|
/* 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 (__codec_ht_except(strip_all, flags->codec_strip, flags->codec_except, pt)) {
|
|
ilog(LOG_DEBUG, "Stripping codec '" STR_FORMAT "'",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
#ifdef WITH_TRANSCODING
|
|
codec_touched(pt, media);
|
|
#endif
|
|
GQueue *q = g_hash_table_lookup_queue_new(stripped, str_dup(&pt->encoding), free);
|
|
g_queue_push_tail(q, __rtp_payload_type_copy(pt));
|
|
q = g_hash_table_lookup_queue_new(stripped, str_dup(&pt->encoding_with_params), free);
|
|
g_queue_push_tail(q, pt);
|
|
continue;
|
|
}
|
|
|
|
__codec_options_set(call, pt, flags->codec_set);
|
|
|
|
// codec masking
|
|
if (__codec_ht_except(mask_all, flags->codec_mask, flags->codec_except, pt)) {
|
|
ilog(LOG_DEBUG, "Masking codec '" STR_FORMAT "'",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
#ifdef WITH_TRANSCODING
|
|
codec_touched(pt, media);
|
|
#endif
|
|
// special case for handling of the legacy always-transcode flag (= accept-all)
|
|
// in combination with codec-mask
|
|
if (accept_all)
|
|
pt->for_transcoding = 1;
|
|
|
|
GQueue *q = g_hash_table_lookup_queue_new(masked, str_dup(&pt->encoding), free);
|
|
g_queue_push_tail(q, __rtp_payload_type_copy(pt));
|
|
q = g_hash_table_lookup_queue_new(masked, str_dup(&pt->encoding_with_params), free);
|
|
g_queue_push_tail(q, __rtp_payload_type_copy(pt));
|
|
__rtp_payload_type_add_send(other_media, pt);
|
|
}
|
|
else if (__codec_ht_except(consume_all, flags->codec_consume, flags->codec_except, pt)) {
|
|
ilog(LOG_DEBUG, "Consuming codec '" STR_FORMAT "'",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
#ifdef WITH_TRANSCODING
|
|
codec_touched(pt, media);
|
|
#endif
|
|
pt->for_transcoding = 1;
|
|
GQueue *q = g_hash_table_lookup_queue_new(masked, str_dup(&pt->encoding), free);
|
|
g_queue_push_tail(q, __rtp_payload_type_copy(pt));
|
|
q = g_hash_table_lookup_queue_new(masked, str_dup(&pt->encoding_with_params), free);
|
|
g_queue_push_tail(q, __rtp_payload_type_copy(pt));
|
|
__rtp_payload_type_add_send(other_media, pt);
|
|
}
|
|
else if (__codec_ht_except(accept_all, flags->codec_accept, NULL, pt)) {
|
|
ilog(LOG_DEBUG, "Accepting codec '" STR_FORMAT "'",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
#ifdef WITH_TRANSCODING
|
|
codec_touched(pt, media);
|
|
#endif
|
|
pt->for_transcoding = 1;
|
|
__rtp_payload_type_add(media, other_media, pt);
|
|
}
|
|
else
|
|
__rtp_payload_type_add(media, other_media, pt);
|
|
}
|
|
|
|
// now restore codecs that have been removed, but should be offered
|
|
for (GList *l = flags->codec_offer.head; l; l = l->next) {
|
|
str *codec = l->data;
|
|
__revert_codec_strip(stripped, masked, codec, media, other_media);
|
|
}
|
|
|
|
if (!flags->asymmetric_codecs) {
|
|
// eliminate rejected codecs from the reverse direction. a rejected codec is missing
|
|
// from the `send` list. also remove it from the `receive` list.
|
|
for (GList *l = other_media->codecs_prefs_recv.head; l;) {
|
|
pt = l->data;
|
|
if (g_hash_table_lookup(other_media->codec_names_send, &pt->encoding)) {
|
|
l = l->next;
|
|
continue;
|
|
}
|
|
ilog(LOG_DEBUG, "Eliminating asymmetric inbound codec " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
l = __delete_receiver_codec(other_media, l);
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
__single_codec(media, flags);
|
|
#endif
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
// add transcode codecs
|
|
for (GList *l = flags->codec_transcode.head; l; l = l->next) {
|
|
str *codec = l->data;
|
|
// if we wish to 'transcode' to a codec that was offered originally
|
|
// and removed by a strip=all option,
|
|
// simply restore it from the original list and handle it the same way
|
|
// as 'offer'
|
|
if ((strip_all == 1 || mask_all == 1)
|
|
&& __revert_codec_strip(stripped, masked, codec, media, other_media))
|
|
continue;
|
|
// also check if maybe the codec was never stripped
|
|
if (g_hash_table_lookup(media->codec_names_recv, 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, other_media);
|
|
if (!pt)
|
|
continue;
|
|
pt->for_transcoding = 1;
|
|
codec_touched(pt, media);
|
|
|
|
ilog(LOG_DEBUG, "Codec '" STR_FORMAT "' added for transcoding with payload type %u",
|
|
STR_FMT(&pt->encoding_with_params), pt->payload_type);
|
|
__rtp_payload_type_add_recv(media, pt, 1);
|
|
}
|
|
|
|
if (media->type_id == MT_AUDIO && other_media->type_id == MT_IMAGE) {
|
|
if (media->codecs_prefs_recv.length == 0) {
|
|
// find some codecs to put into our outgoing SDP body
|
|
|
|
if (media->codecs_prefs_send.length && media->t38_gateway
|
|
&& flags->opmode == OP_ANSWER)
|
|
{
|
|
// audio -> T.38 transcoder, answer:
|
|
// we answer with the codec that we're sending audio with, taken from
|
|
// our PCM player
|
|
if (media->t38_gateway && media->t38_gateway->pcm_player
|
|
&& media->t38_gateway->pcm_player->handler)
|
|
__rtp_payload_type_add_recv(media,
|
|
__rtp_payload_type_copy(&media->t38_gateway->pcm_player->handler->dest_pt), 1);
|
|
}
|
|
else if (flags->opmode == OP_OFFER) {
|
|
// T.38 -> audio transcoder, initial offer, and no codecs have been given.
|
|
// Default to PCMA and PCMU
|
|
// XXX can we improve the codec lookup/synthesis?
|
|
static const str PCMU_str = STR_CONST_INIT("PCMU");
|
|
static const str PCMA_str = STR_CONST_INIT("PCMA");
|
|
pt = codec_add_payload_type(&PCMU_str, media, NULL);
|
|
assert(pt != NULL);
|
|
__rtp_payload_type_add_recv(media, pt, 1);
|
|
pt = codec_add_payload_type(&PCMA_str, media, NULL);
|
|
assert(pt != NULL);
|
|
__rtp_payload_type_add_recv(media, pt, 1);
|
|
|
|
ilog(LOG_DEBUG, "Using default codecs PCMU and PCMA for T.38 gateway");
|
|
}
|
|
}
|
|
else if (flags->opmode == OP_OFFER) {
|
|
// re-invite - we remember some codecs from before, or perhaps they
|
|
// were added manually through the transcoding options. make sure
|
|
// they're all supported by us
|
|
|
|
for (GList *l = media->codecs_prefs_recv.head; l;) {
|
|
pt = l->data;
|
|
ensure_codec_def(pt, media);
|
|
if (pt->codec_def) {
|
|
l = l->next;
|
|
continue;
|
|
}
|
|
ilog(LOG_DEBUG, "Eliminating unsupported codec " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
codec_touched(pt, media);
|
|
l = __delete_receiver_codec(media, l);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
g_hash_table_destroy(stripped);
|
|
g_hash_table_destroy(masked);
|
|
}
|
|
|
|
void codecs_init(void) {
|
|
#ifdef WITH_TRANSCODING
|
|
timerthread_init(&codec_timers_thread, timerthread_queue_run);
|
|
rtcp_timer_queue = timerthread_queue_new("rtcp_timer_queue", sizeof(*rtcp_timer_queue),
|
|
&codec_timers_thread, NULL, __rtcp_timer_run, NULL, __rtcp_timer_free);
|
|
#endif
|
|
}
|
|
void codecs_cleanup(void) {
|
|
#ifdef WITH_TRANSCODING
|
|
timerthread_free(&codec_timers_thread);
|
|
#endif
|
|
}
|
|
void codec_timers_loop(void *p) {
|
|
#ifdef WITH_TRANSCODING
|
|
//ilog(LOG_DEBUG, "codec_timers_loop");
|
|
timerthread_run(&codec_timers_thread);
|
|
#endif
|
|
}
|