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.
5532 lines
169 KiB
5532 lines
169 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"
|
|
#include "mqtt.h"
|
|
#include "audio_player.h"
|
|
#ifdef WITH_TRANSCODING
|
|
#include "fix_frame_channel_layout.h"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
struct codec_timer {
|
|
struct timerthread_obj tt_obj;
|
|
struct timeval next;
|
|
void (*timer_func)(struct codec_timer *);
|
|
};
|
|
struct mqtt_timer {
|
|
struct codec_timer ct;
|
|
struct mqtt_timer **self;
|
|
struct call *call;
|
|
struct call_media *media;
|
|
};
|
|
struct timer_callback {
|
|
struct codec_timer ct;
|
|
void (*timer_callback_func)(struct call *, void *);
|
|
struct call *call;
|
|
void *ptr;
|
|
};
|
|
|
|
typedef void (*raw_input_func_t)(struct media_packet *mp, unsigned int);
|
|
|
|
static void __buffer_delay_raw(struct delay_buffer *dbuf, struct codec_handler *handler,
|
|
raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate);
|
|
|
|
|
|
static codec_handler_func handler_func_passthrough;
|
|
static struct timerthread codec_timers_thread;
|
|
|
|
static void rtp_payload_type_copy(struct rtp_payload_type *dst, const struct rtp_payload_type *src);
|
|
static void codec_store_add_raw_order(struct codec_store *cs, struct rtp_payload_type *pt);
|
|
static struct rtp_payload_type *codec_store_find_compatible(struct codec_store *cs,
|
|
const struct rtp_payload_type *pt);
|
|
static void __rtp_payload_type_add_name(GHashTable *, struct rtp_payload_type *pt);
|
|
static void codec_calc_lost(struct ssrc_ctx *ssrc, uint16_t seq);
|
|
|
|
|
|
static struct codec_handler codec_handler_stub = {
|
|
.source_pt.payload_type = -1,
|
|
.dest_pt.payload_type = -1,
|
|
.handler_func = handler_func_passthrough,
|
|
.kernelize = 1,
|
|
.passthrough = 1,
|
|
};
|
|
|
|
|
|
|
|
static void __ht_queue_del(GHashTable *ht, const void *key, const void *val) {
|
|
GQueue *q = g_hash_table_lookup(ht, key);
|
|
if (!q)
|
|
return;
|
|
g_queue_remove_all(q, val);
|
|
}
|
|
|
|
static GList *__codec_store_delete_link(GList *link, struct codec_store *cs) {
|
|
struct rtp_payload_type *pt = link->data;
|
|
|
|
g_hash_table_remove(cs->codecs, GINT_TO_POINTER(pt->payload_type));
|
|
__ht_queue_del(cs->codec_names, &pt->encoding, GINT_TO_POINTER(pt->payload_type));
|
|
__ht_queue_del(cs->codec_names, &pt->encoding_with_params, GINT_TO_POINTER(pt->payload_type));
|
|
__ht_queue_del(cs->codec_names, &pt->encoding_with_full_params, GINT_TO_POINTER(pt->payload_type));
|
|
|
|
GList *next = link->next;
|
|
if (cs->supp_link == link)
|
|
cs->supp_link = next;
|
|
g_queue_delete_link(&cs->codec_prefs, link);
|
|
payload_type_free(pt);
|
|
return next;
|
|
}
|
|
#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 codec_timer ct;
|
|
mutex_t lock;
|
|
struct codec_ssrc_handler *csh;
|
|
int ptime; // ms per packet
|
|
int tspp; // timestamp increment per packet
|
|
unsigned int clockrate;
|
|
struct call *call;
|
|
GQueue packets;
|
|
struct media_packet last_mp;
|
|
unsigned long head_ts;
|
|
uint32_t ssrc;
|
|
time_t start;
|
|
};
|
|
struct dtx_packet {
|
|
struct transcode_packet *packet;
|
|
struct media_packet mp;
|
|
struct codec_ssrc_handler *decoder_handler; // holds reference
|
|
struct codec_ssrc_handler *input_handler; // holds reference
|
|
int (*dtx_func)(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet, struct media_packet *mp);
|
|
};
|
|
|
|
typedef int (*encoder_input_func_t)(encoder_t *enc, AVFrame *frame,
|
|
int (*callback)(encoder_t *, void *u1, void *u2), void *u1, void *u2);
|
|
typedef int (*packet_input_func_t)(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet,
|
|
unsigned long ts_delay,
|
|
int payload_type,
|
|
struct media_packet *mp);
|
|
|
|
struct delay_buffer {
|
|
struct codec_timer ct;
|
|
struct call *call;
|
|
struct codec_handler *handler;
|
|
mutex_t lock;
|
|
unsigned int delay;
|
|
GQueue frames; // in reverse order: newest packet first, oldest last
|
|
};
|
|
struct delay_frame {
|
|
AVFrame *frame;
|
|
struct media_packet mp;
|
|
struct transcode_packet *packet;
|
|
unsigned long ts_delay;
|
|
int payload_type;
|
|
unsigned int clockrate;
|
|
uint32_t ts;
|
|
encoder_input_func_t encoder_func;
|
|
raw_input_func_t raw_func;
|
|
packet_input_func_t packet_func;
|
|
struct codec_handler *handler;
|
|
struct codec_ssrc_handler *ch;
|
|
struct codec_ssrc_handler *input_ch;
|
|
int seq_adj;
|
|
};
|
|
|
|
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;
|
|
codec_chain_t *chain;
|
|
format_t encoder_format;
|
|
int bitrate;
|
|
int ptime;
|
|
int bytes_per_packet;
|
|
struct codec_scheduler csch;
|
|
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; // for replacing PCM with DTMF event
|
|
struct dtmf_event dtmf_state; // state tracker for DTMF actions
|
|
|
|
// silence detection
|
|
GQueue silence_events;
|
|
|
|
// DTMF audio suppression
|
|
unsigned long dtmf_start_ts;
|
|
// DTMF send delay
|
|
unsigned long dtmf_first_duration;
|
|
|
|
uint64_t skip_pts;
|
|
|
|
unsigned int rtp_mark:1;
|
|
};
|
|
struct transcode_packet {
|
|
seq_packet_t p; // must be first
|
|
unsigned long ts;
|
|
str *payload;
|
|
struct codec_handler *handler;
|
|
unsigned int marker:1,
|
|
bypass_seq:1;
|
|
int (*packet_func)(struct codec_ssrc_handler *, struct codec_ssrc_handler *, struct transcode_packet *,
|
|
struct media_packet *);
|
|
int (*dup_func)(struct codec_ssrc_handler *, struct codec_ssrc_handler *, struct transcode_packet *,
|
|
struct media_packet *);
|
|
struct rtp_header rtp;
|
|
};
|
|
struct codec_tracker {
|
|
GHashTable *touched; // 8000, 16000, etc, for each audio codec that was touched (added, removed, etc)
|
|
int all_touched;
|
|
};
|
|
|
|
|
|
struct rtcp_timer {
|
|
struct codec_timer ct;
|
|
struct call *call;
|
|
struct call_media *media;
|
|
};
|
|
|
|
|
|
|
|
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_dtmf;
|
|
static codec_handler_func handler_func_t38;
|
|
|
|
static struct ssrc_entry *__ssrc_handler_transcode_new(void *p);
|
|
static struct ssrc_entry *__ssrc_handler_decode_new(void *p);
|
|
static struct ssrc_entry *__ssrc_handler_new(void *p);
|
|
static void __ssrc_handler_stop(void *p, void *dummy);
|
|
static void __free_ssrc_handler(void *);
|
|
|
|
static void __transcode_packet_free(struct transcode_packet *);
|
|
|
|
static int packet_decode(struct codec_ssrc_handler *, 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 int packet_decoded_audio_player(decoder_t *decoder, AVFrame *frame, void *u1, void *u2);
|
|
|
|
static void codec_touched(struct codec_store *cs, struct rtp_payload_type *pt);
|
|
|
|
static int __buffer_dtx(struct dtx_buffer *dtxb, struct codec_ssrc_handler *ch,
|
|
struct codec_ssrc_handler *input_handler,
|
|
struct transcode_packet *packet, struct media_packet *mp,
|
|
int (*dtx_func)(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet,
|
|
struct media_packet *mp));
|
|
static void __dtx_shutdown(struct dtx_buffer *dtxb);
|
|
static struct codec_handler *__input_handler(struct codec_handler *h, struct media_packet *mp);
|
|
|
|
static void __delay_frame_process(struct delay_buffer *, struct delay_frame *dframe);
|
|
static void __dtx_restart(struct codec_handler *h);
|
|
static void __delay_buffer_setup(struct delay_buffer **dbufp,
|
|
struct codec_handler *h, struct call *call, unsigned int delay);
|
|
static void __delay_buffer_shutdown(struct delay_buffer *dbuf, bool);
|
|
static void delay_buffer_stop(struct delay_buffer **pcmbp);
|
|
static int __buffer_delay_packet(struct delay_buffer *dbuf,
|
|
struct codec_ssrc_handler *ch,
|
|
struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet,
|
|
unsigned long ts_delay,
|
|
int payload_type,
|
|
packet_input_func_t packet_func, struct media_packet *mp, unsigned int clockrate);
|
|
static void __buffer_delay_seq(struct delay_buffer *dbuf, struct media_packet *mp, int seq_adj);
|
|
|
|
|
|
static struct codec_handler codec_handler_stub_ssrc = {
|
|
.source_pt.payload_type = -1,
|
|
.dest_pt.payload_type = -1,
|
|
.handler_func = handler_func_passthrough_ssrc,
|
|
.kernelize = 1,
|
|
.passthrough = 1,
|
|
};
|
|
|
|
|
|
|
|
static void __handler_shutdown(struct codec_handler *handler) {
|
|
ssrc_hash_foreach(handler->ssrc_hash, __ssrc_handler_stop, (void *) true);
|
|
free_ssrc_hash(&handler->ssrc_hash);
|
|
if (handler->delay_buffer) {
|
|
__delay_buffer_shutdown(handler->delay_buffer, true);
|
|
delay_buffer_stop(&handler->delay_buffer);
|
|
}
|
|
|
|
if (handler->ssrc_handler)
|
|
obj_put(&handler->ssrc_handler->h);
|
|
handler->ssrc_handler = NULL;
|
|
handler->kernelize = 0;
|
|
handler->transcoder = 0;
|
|
handler->output_handler = handler; // reset to default
|
|
handler->packet_decoded = packet_decoded_fifo;
|
|
handler->dtmf_payload_type = -1;
|
|
handler->real_dtmf_payload_type = -1;
|
|
handler->cn_payload_type = -1;
|
|
handler->pcm_dtmf_detect = 0;
|
|
handler->passthrough = 0;
|
|
handler->payload_len = 0;
|
|
|
|
codec_handler_free(&handler->dtmf_injector);
|
|
|
|
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);
|
|
payload_type_clear(&h->source_pt);
|
|
payload_type_clear(&h->dest_pt);
|
|
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 call_media *sink)
|
|
{
|
|
struct codec_handler *handler = g_slice_alloc0(sizeof(*handler));
|
|
handler->source_pt.payload_type = -1;
|
|
if (pt)
|
|
rtp_payload_type_copy(&handler->source_pt, pt);
|
|
handler->dest_pt.payload_type = -1;
|
|
handler->output_handler = handler; // default
|
|
handler->dtmf_payload_type = -1;
|
|
handler->real_dtmf_payload_type = -1;
|
|
handler->cn_payload_type = -1;
|
|
handler->packet_encoded = packet_encoded_rtp;
|
|
handler->packet_decoded = packet_decoded_fifo;
|
|
handler->media = media;
|
|
handler->sink = sink;
|
|
return handler;
|
|
}
|
|
|
|
static void __make_passthrough(struct codec_handler *handler, int dtmf_pt, int cn_pt) {
|
|
__handler_shutdown(handler);
|
|
ilogs(codec, LOG_DEBUG, "Using passthrough handler for " STR_FORMAT " (%i) with DTMF %i, CN %i",
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
handler->source_pt.payload_type,
|
|
dtmf_pt, cn_pt);
|
|
if (handler->source_pt.codec_def && handler->source_pt.codec_def->dtmf)
|
|
handler->handler_func = handler_func_dtmf;
|
|
else {
|
|
handler->handler_func = handler_func_passthrough;
|
|
handler->kernelize = 1;
|
|
}
|
|
rtp_payload_type_copy(&handler->dest_pt, &handler->source_pt);
|
|
handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_new, handler);
|
|
handler->dtmf_payload_type = dtmf_pt;
|
|
handler->cn_payload_type = cn_pt;
|
|
handler->passthrough = 1;
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
if (handler->media->buffer_delay) {
|
|
__delay_buffer_setup(&handler->delay_buffer, handler, handler->media->call,
|
|
handler->media->buffer_delay);
|
|
handler->kernelize = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// converts existing passthrough handler to SSRC passthrough
|
|
static void __convert_passthrough_ssrc(struct codec_handler *handler) {
|
|
ilogs(codec, LOG_DEBUG, "Using passthrough handler with new SSRC for " STR_FORMAT,
|
|
STR_FMT(&handler->source_pt.encoding_with_params));
|
|
|
|
if (handler->handler_func == handler_func_passthrough)
|
|
handler->handler_func = handler_func_passthrough_ssrc;
|
|
|
|
}
|
|
|
|
static void __reset_sequencer(void *p, void *dummy) {
|
|
struct ssrc_entry_call *s = p;
|
|
if (s->sequencers)
|
|
g_hash_table_destroy(s->sequencers);
|
|
s->sequencers = NULL;
|
|
}
|
|
static void __make_transcoder_full(struct codec_handler *handler, struct rtp_payload_type *dest,
|
|
GHashTable *output_transcoders, int dtmf_payload_type, bool pcm_dtmf_detect,
|
|
int cn_payload_type, int (*packet_decoded)(decoder_t *, AVFrame *, void *, void *),
|
|
struct ssrc_entry *(*ssrc_handler_new_func)(void *p))
|
|
{
|
|
assert(handler->source_pt.codec_def != NULL);
|
|
|
|
assert(dest->codec_def != NULL);
|
|
|
|
// don't reset handler if it already matches what we want
|
|
if (!handler->transcoder)
|
|
goto reset;
|
|
if (!rtp_payload_type_eq_exact(dest, &handler->dest_pt))
|
|
goto reset;
|
|
if (handler->handler_func != handler_func_transcode)
|
|
goto reset;
|
|
if (handler->packet_decoded != packet_decoded)
|
|
goto reset;
|
|
if (handler->cn_payload_type != cn_payload_type)
|
|
goto reset;
|
|
if (handler->dtmf_payload_type != dtmf_payload_type)
|
|
goto reset;
|
|
if ((pcm_dtmf_detect ? 1 : 0) != handler->pcm_dtmf_detect)
|
|
goto reset;
|
|
|
|
ilogs(codec, LOG_DEBUG, "Leaving transcode context for " STR_FORMAT " (%i) -> " STR_FORMAT " (%i) intact",
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
handler->source_pt.payload_type,
|
|
STR_FMT(&dest->encoding_with_params),
|
|
dest->payload_type);
|
|
|
|
goto no_handler_reset;
|
|
|
|
reset:
|
|
__handler_shutdown(handler);
|
|
|
|
rtp_payload_type_copy(&handler->dest_pt, dest);
|
|
if (dest->codec_def->format_answer)
|
|
dest->codec_def->format_answer(&handler->dest_pt, &handler->source_pt);
|
|
handler->handler_func = handler_func_transcode;
|
|
handler->packet_decoded = packet_decoded;
|
|
handler->transcoder = 1;
|
|
handler->dtmf_payload_type = dtmf_payload_type;
|
|
handler->cn_payload_type = cn_payload_type;
|
|
handler->pcm_dtmf_detect = pcm_dtmf_detect ? 1 : 0;
|
|
|
|
// DTMF transcoder/scaler?
|
|
if (handler->source_pt.codec_def && handler->source_pt.codec_def->dtmf)
|
|
handler->handler_func = handler_func_dtmf;
|
|
|
|
ilogs(codec, LOG_DEBUG, "Created transcode context for " STR_FORMAT " (%i) -> " STR_FORMAT
|
|
" (%i) with DTMF output %i and CN output %i",
|
|
STR_FMT(&handler->source_pt.encoding_with_params),
|
|
handler->source_pt.payload_type,
|
|
STR_FMT(&dest->encoding_with_params),
|
|
dest->payload_type,
|
|
dtmf_payload_type, cn_payload_type);
|
|
|
|
handler->ssrc_hash = create_ssrc_hash_full(ssrc_handler_new_func, 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);
|
|
|
|
ssrc_hash_foreach(handler->media->monologue->ssrc_hash, __reset_sequencer, NULL);
|
|
|
|
no_handler_reset:
|
|
__delay_buffer_setup(&handler->delay_buffer, handler, handler->media->call, handler->media->buffer_delay);
|
|
__dtx_restart(handler);
|
|
// 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) {
|
|
ilogs(codec, 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
|
|
}
|
|
}
|
|
static void __make_transcoder(struct codec_handler *handler, struct rtp_payload_type *dest,
|
|
GHashTable *output_transcoders, int dtmf_payload_type, bool pcm_dtmf_detect,
|
|
int cn_payload_type)
|
|
{
|
|
__make_transcoder_full(handler, dest, output_transcoders, dtmf_payload_type, pcm_dtmf_detect,
|
|
cn_payload_type, packet_decoded_fifo, __ssrc_handler_transcode_new);
|
|
}
|
|
static void __make_audio_player_decoder(struct codec_handler *handler, struct rtp_payload_type *dest,
|
|
bool pcm_dtmf_detect)
|
|
{
|
|
__make_transcoder_full(handler, dest, NULL, -1, pcm_dtmf_detect, -1, packet_decoded_audio_player,
|
|
__ssrc_handler_decode_new);
|
|
}
|
|
|
|
// used for generic playback (audio_player, t38_gateway)
|
|
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,
|
|
uint32_t ssrc)
|
|
{
|
|
struct codec_handler *handler = __handler_new(src_pt, media, NULL);
|
|
rtp_payload_type_copy(&handler->dest_pt, dst_pt);
|
|
handler->handler_func = handler_func_playback;
|
|
handler->ssrc_handler = (void *) __ssrc_handler_transcode_new(handler);
|
|
if (!handler->ssrc_handler) {
|
|
codec_handler_free(&handler);
|
|
return NULL;
|
|
}
|
|
handler->ssrc_handler->csch.first_ts = last_ts;
|
|
handler->ssrc_handler->h.ssrc = ssrc;
|
|
while (handler->ssrc_handler->csch.first_ts == 0)
|
|
handler->ssrc_handler->csch.first_ts = ssl_random();
|
|
handler->ssrc_handler->rtp_mark = 1;
|
|
|
|
ilogs(codec, 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;
|
|
}
|
|
// used for "play media" player
|
|
struct codec_handler *codec_handler_make_media_player(const struct rtp_payload_type *src_pt,
|
|
const struct rtp_payload_type *dst_pt, unsigned long last_ts, struct call_media *media,
|
|
uint32_t ssrc)
|
|
{
|
|
struct codec_handler *h = codec_handler_make_playback(src_pt, dst_pt, last_ts, media, ssrc);
|
|
if (!h)
|
|
return NULL;
|
|
if (audio_player_is_active(media)) {
|
|
h->packet_decoded = packet_decoded_audio_player;
|
|
if (!audio_player_pt_match(media, dst_pt))
|
|
ilogs(codec, LOG_WARN, "Codec mismatch between audio player and media player (wanted: "
|
|
STR_FORMAT ")", STR_FMT(&dst_pt->encoding_with_params));
|
|
}
|
|
return h;
|
|
}
|
|
struct codec_handler *codec_handler_make_dummy(const struct rtp_payload_type *dst_pt, struct call_media *media)
|
|
{
|
|
struct codec_handler *handler = __handler_new(NULL, media, NULL);
|
|
rtp_payload_type_copy(&handler->dest_pt, dst_pt);
|
|
return handler;
|
|
}
|
|
|
|
|
|
// does not init/parse a=fmtp
|
|
static void ensure_codec_def_type(struct rtp_payload_type *pt, enum media_type type) {
|
|
if (pt->codec_def)
|
|
return;
|
|
|
|
pt->codec_def = codec_find(&pt->encoding, type);
|
|
if (!pt->codec_def)
|
|
return;
|
|
if (!pt->codec_def->support_encoding || !pt->codec_def->support_decoding)
|
|
pt->codec_def = NULL;
|
|
}
|
|
// does init/parse a=fmtp
|
|
void ensure_codec_def(struct rtp_payload_type *pt, struct call_media *media) {
|
|
if (!media)
|
|
return;
|
|
ensure_codec_def_type(pt, media->type_id);
|
|
if (pt->codec_def)
|
|
codec_parse_fmtp(pt->codec_def, &pt->format, &pt->format_parameters, NULL);
|
|
}
|
|
|
|
// only called from codec_handlers_update()
|
|
static void __make_passthrough_gsl(struct codec_handler *handler, GSList **handlers,
|
|
struct rtp_payload_type *dtmf_pt, struct rtp_payload_type *cn_pt,
|
|
bool use_ssrc_passthrough)
|
|
{
|
|
__make_passthrough(handler, dtmf_pt ? dtmf_pt->payload_type : -1,
|
|
cn_pt ? cn_pt->payload_type : -1);
|
|
if (use_ssrc_passthrough)
|
|
__convert_passthrough_ssrc(handler);
|
|
*handlers = g_slist_prepend(*handlers, handler);
|
|
}
|
|
|
|
|
|
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 void __check_codec_list(GHashTable **supplemental_sinks, struct rtp_payload_type **pref_dest_codec,
|
|
struct call_media *sink, GQueue *sink_list)
|
|
{
|
|
// first initialise and populate the list of supp sinks
|
|
GHashTable *ss = *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(ss, (void *) def->rtpname,
|
|
g_hash_table_new(g_direct_hash, g_direct_equal));
|
|
}
|
|
|
|
struct rtp_payload_type *pdc = NULL;
|
|
struct rtp_payload_type *first_tc_codec = NULL;
|
|
|
|
for (GList *l = sink->codecs.codec_prefs.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 <= 0)
|
|
pt->ptime = pt->codec_def->default_ptime;
|
|
if (sink->ptime > 0)
|
|
pt->ptime = sink->ptime;
|
|
|
|
if (!pdc && !pt->codec_def->supplemental)
|
|
pdc = pt;
|
|
if (pt->accepted) {
|
|
// codec is explicitly marked as accepted
|
|
if (!first_tc_codec && !pt->codec_def->supplemental)
|
|
first_tc_codec = pt;
|
|
}
|
|
|
|
__track_supp_codec(ss, pt);
|
|
}
|
|
|
|
if (first_tc_codec)
|
|
pdc = first_tc_codec;
|
|
if (pdc && pref_dest_codec) {
|
|
*pref_dest_codec = pdc;
|
|
ilogs(codec, LOG_DEBUG, "Default sink codec is " STR_FORMAT " (%i)",
|
|
STR_FMT(&(*pref_dest_codec)->encoding_with_params),
|
|
(*pref_dest_codec)->payload_type);
|
|
}
|
|
}
|
|
|
|
static struct rtp_payload_type *__supp_payload_type(GHashTable *supplemental_sinks, int clockrate,
|
|
const char *codec)
|
|
{
|
|
GHashTable *supp_sinks = g_hash_table_lookup(supplemental_sinks, codec);
|
|
if (!supp_sinks)
|
|
return NULL;
|
|
if (!g_hash_table_size(supp_sinks))
|
|
return NULL;
|
|
|
|
// find the codec entry with a matching clock rate
|
|
struct rtp_payload_type *pt = g_hash_table_lookup(supp_sinks,
|
|
GUINT_TO_POINTER(clockrate));
|
|
return pt;
|
|
}
|
|
|
|
static int __unused_pt_number(struct call_media *media, struct call_media *other_media,
|
|
struct codec_store *extra_cs,
|
|
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.codecs, GINT_TO_POINTER(num))))
|
|
goto next;
|
|
if (other_media) {
|
|
if ((pt_match = g_hash_table_lookup(other_media->codecs.codecs,
|
|
GINT_TO_POINTER(num))))
|
|
goto next;
|
|
}
|
|
if (extra_cs) {
|
|
if ((pt_match = g_hash_table_lookup(extra_cs->codecs,
|
|
GINT_TO_POINTER(num))))
|
|
goto next;
|
|
}
|
|
// OK
|
|
break;
|
|
|
|
next:
|
|
// is this actually the same?
|
|
if (pt && rtp_payload_type_eq_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 __check_dtmf_injector(struct call_media *receiver, struct call_media *sink,
|
|
struct codec_handler *parent,
|
|
GHashTable *output_transcoders)
|
|
{
|
|
if (!ML_ISSET(sink->monologue, INJECT_DTMF))
|
|
return;
|
|
if (parent->dtmf_payload_type != -1)
|
|
return;
|
|
if (parent->dtmf_injector)
|
|
return;
|
|
if (parent->source_pt.codec_def->supplemental)
|
|
return;
|
|
|
|
// synthesise input rtp payload type
|
|
struct rtp_payload_type src_pt = {
|
|
.payload_type = -1,
|
|
.clock_rate = parent->source_pt.clock_rate,
|
|
.channels = parent->source_pt.channels,
|
|
};
|
|
str_init(&src_pt.encoding, "DTMF injector");
|
|
str_init(&src_pt.encoding_with_params, "DTMF injector");
|
|
str_init(&src_pt.encoding_with_full_params, "DTMF injector");
|
|
static const str tp_event = STR_CONST_INIT("telephone-event");
|
|
src_pt.codec_def = codec_find(&tp_event, MT_AUDIO);
|
|
if (!src_pt.codec_def) {
|
|
ilogs(codec, LOG_ERR, "RTP payload type 'telephone-event' is not defined");
|
|
return;
|
|
}
|
|
|
|
parent->dtmf_injector = __handler_new(&src_pt, receiver, sink);
|
|
__make_transcoder(parent->dtmf_injector, &parent->dest_pt, output_transcoders, -1, 0, -1);
|
|
parent->dtmf_injector->handler_func = handler_func_inject_dtmf;
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct codec_handler *__get_pt_handler(struct call_media *receiver, struct rtp_payload_type *pt,
|
|
struct call_media *sink)
|
|
{
|
|
ensure_codec_def(pt, receiver);
|
|
struct codec_handler *handler;
|
|
handler = codec_handler_lookup(receiver->codec_handlers, pt->payload_type, sink);
|
|
if (handler) {
|
|
// make sure existing handler matches this PT
|
|
if (!rtp_payload_type_eq_exact(pt, &handler->source_pt)) {
|
|
ilogs(codec, LOG_DEBUG, "Resetting codec handler for PT %i", pt->payload_type);
|
|
g_hash_table_remove(receiver->codec_handlers, handler);
|
|
__handler_shutdown(handler);
|
|
handler = NULL;
|
|
g_atomic_pointer_set(&receiver->codec_handler_cache, NULL);
|
|
}
|
|
}
|
|
if (!handler) {
|
|
ilogs(codec, LOG_DEBUG, "Creating codec handler for " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
handler = __handler_new(pt, receiver, sink);
|
|
g_hash_table_insert(receiver->codec_handlers, handler, handler);
|
|
g_queue_push_tail(&receiver->codec_handlers_store, handler);
|
|
}
|
|
|
|
// figure out our ptime
|
|
if (pt->ptime <= 0 && pt->codec_def)
|
|
pt->ptime = pt->codec_def->default_ptime;
|
|
if (receiver->ptime > 0)
|
|
pt->ptime = receiver->ptime;
|
|
|
|
return handler;
|
|
}
|
|
|
|
|
|
|
|
|
|
static void __check_t38_decoder(struct call_media *t38_media) {
|
|
if (t38_media->t38_handler)
|
|
return;
|
|
ilogs(codec, LOG_DEBUG, "Creating T.38 packet handler");
|
|
t38_media->t38_handler = __handler_new(NULL, t38_media, NULL);
|
|
t38_media->t38_handler->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 __generator_stop_all(struct call_media *media) {
|
|
__generator_stop(media);
|
|
audio_player_stop(media);
|
|
}
|
|
|
|
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 ? flags->t38_ ## name : 0
|
|
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 && 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, GENERATOR);
|
|
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.codec_prefs.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
struct codec_handler *handler = __get_pt_handler(pcm_media, pt, t38_media);
|
|
if (!pt->codec_def) {
|
|
// should not happen
|
|
ilogs(codec, LOG_WARN, "Unsupported codec " STR_FORMAT " for T.38 transcoding",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
continue;
|
|
}
|
|
|
|
ilogs(codec, 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, false, -1);
|
|
|
|
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;
|
|
}
|
|
}
|
|
ilogs(codec, 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;
|
|
}
|
|
ilogs(codec, 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);
|
|
}
|
|
static void __rtcp_timer_run(struct codec_timer *);
|
|
// master lock held in W
|
|
static void __codec_rtcp_timer_schedule(struct call_media *media) {
|
|
struct rtcp_timer *rt = media->rtcp_timer;
|
|
if (!rt) {
|
|
media->rtcp_timer = rt = obj_alloc0("rtcp_timer", sizeof(*rt), __rtcp_timer_free);
|
|
rt->ct.tt_obj.tt = &codec_timers_thread;
|
|
rt->call = obj_get(media->call);
|
|
rt->media = media;
|
|
rt->ct.next = rtpe_now;
|
|
rt->ct.timer_func = __rtcp_timer_run;
|
|
}
|
|
|
|
timeval_add_usec(&rt->ct.next, 5000000 + (ssl_random() % 2000000));
|
|
timerthread_obj_schedule_abs(&rt->ct.tt_obj, &rt->ct.next);
|
|
}
|
|
// no lock held
|
|
static void __rtcp_timer_run(struct codec_timer *ct) {
|
|
struct rtcp_timer *rt = (void *) ct;
|
|
|
|
// check scheduling
|
|
rwlock_lock_w(&rt->call->master_lock);
|
|
struct call_media *media = rt->media;
|
|
|
|
log_info_media(media);
|
|
|
|
if (media->rtcp_timer != rt || !proto_is_rtp(media->protocol) || !MEDIA_ISSET(media, RTCP_GEN)) {
|
|
if (media->rtcp_timer == rt)
|
|
rtcp_timer_stop(&media->rtcp_timer);
|
|
rwlock_unlock_w(&rt->call->master_lock);
|
|
goto out;
|
|
}
|
|
timeval_add_usec(&ct->next, 5000000 + (ssl_random() % 2000000));
|
|
__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);
|
|
|
|
// copy out references to SSRCs for lock-free handling
|
|
struct ssrc_ctx *ssrc_out[RTPE_NUM_SSRC_TRACKING] = {NULL,};
|
|
if (media->streams.head) {
|
|
struct packet_stream *ps = media->streams.head->data;
|
|
mutex_lock(&ps->out_lock);
|
|
for (unsigned int u = 0; u < RTPE_NUM_SSRC_TRACKING; u++) {
|
|
if (!ps->ssrc_out[u]) // end of list
|
|
break;
|
|
ssrc_out[u] = ps->ssrc_out[u];
|
|
ssrc_ctx_hold(ssrc_out[u]);
|
|
}
|
|
mutex_unlock(&ps->out_lock);
|
|
}
|
|
|
|
for (unsigned int u = 0; u < RTPE_NUM_SSRC_TRACKING; u++) {
|
|
if (!ssrc_out[u]) // end of list
|
|
break;
|
|
// coverity[use : FALSE]
|
|
rtcp_send_report(media, ssrc_out[u]);
|
|
}
|
|
|
|
rwlock_unlock_r(&rt->call->master_lock);
|
|
|
|
for (unsigned int u = 0; u < RTPE_NUM_SSRC_TRACKING; u++) {
|
|
if (!ssrc_out[u]) // end of list
|
|
break;
|
|
ssrc_ctx_put(&ssrc_out[u]);
|
|
}
|
|
|
|
out:
|
|
log_info_pop();
|
|
}
|
|
// master lock held in W
|
|
static void __codec_rtcp_timer(struct call_media *receiver) {
|
|
if (receiver->rtcp_timer) // already scheduled
|
|
return;
|
|
__codec_rtcp_timer_schedule(receiver);
|
|
// XXX unify with media player into a generic RTCP player
|
|
}
|
|
|
|
static unsigned int __codec_handler_hash(const void *p) {
|
|
const struct codec_handler *h = p;
|
|
return h->source_pt.payload_type ^ GPOINTER_TO_UINT(h->sink);
|
|
}
|
|
static int __codec_handler_eq(const void *a, const void *b) {
|
|
const struct codec_handler *h = a, *j = b;
|
|
return h->source_pt.payload_type == j->source_pt.payload_type
|
|
&& h->sink == j->sink;
|
|
}
|
|
|
|
/**
|
|
* receiver - media / sink - other_media
|
|
* call must be locked in W
|
|
*/
|
|
void __codec_handlers_update(struct call_media *receiver, struct call_media *sink,
|
|
struct chu_args a)
|
|
{
|
|
struct call_monologue *monologue = receiver->monologue;
|
|
struct call_monologue *other_monologue = sink->monologue;
|
|
|
|
if (!monologue || !other_monologue)
|
|
return;
|
|
|
|
/* required for updating the transcoding attrs of subscriber */
|
|
struct media_subscription * ms = call_get_media_subscription(receiver->media_subscribers_ht, sink);
|
|
|
|
ilogs(codec, LOG_DEBUG, "Setting up codec handlers for " STR_FORMAT_M " #%u -> " STR_FORMAT_M " #%u",
|
|
STR_FMT_M(&monologue->tag), receiver->index,
|
|
STR_FMT_M(&other_monologue->tag), sink->index);
|
|
|
|
if (a.reset_transcoding && ms)
|
|
ms->attrs.transcoding = 0;
|
|
|
|
MEDIA_CLEAR(receiver, GENERATOR);
|
|
MEDIA_CLEAR(sink, GENERATOR);
|
|
|
|
if (!receiver->codec_handlers)
|
|
receiver->codec_handlers = g_hash_table_new(__codec_handler_hash, __codec_handler_eq);
|
|
if (!sink->codec_handlers)
|
|
sink->codec_handlers = g_hash_table_new(__codec_handler_hash, __codec_handler_eq);
|
|
|
|
// non-RTP protocol?
|
|
if (proto_is(receiver->protocol, PROTO_UDPTL)) {
|
|
if (codec_handler_udptl_update(receiver, sink, a.flags)) {
|
|
if (a.reset_transcoding && ms)
|
|
ms->attrs.transcoding = 1;
|
|
return;
|
|
}
|
|
}
|
|
// everything else is unsupported: pass through
|
|
if (proto_is_not_rtp(receiver->protocol)) {
|
|
__generator_stop_all(receiver);
|
|
__generator_stop_all(sink);
|
|
codec_handlers_stop(&receiver->codec_handlers_store);
|
|
return;
|
|
}
|
|
|
|
// should we transcode to a non-RTP protocol?
|
|
if (proto_is_not_rtp(sink->protocol)) {
|
|
if (codec_handler_non_rtp_update(receiver, sink, a.flags, a.sp)) {
|
|
if (a.reset_transcoding && ms)
|
|
ms->attrs.transcoding = 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// we're doing some kind of media passthrough - shut down local generators
|
|
__generator_stop(receiver);
|
|
__generator_stop(sink);
|
|
codec_handlers_stop(&receiver->codec_handlers_store);
|
|
|
|
bool is_transcoding = false;
|
|
receiver->rtcp_handler = NULL;
|
|
receiver->dtmf_count = 0;
|
|
GSList *passthrough_handlers = NULL;
|
|
|
|
// default choice of audio player usage is based on whether it was in use previously,
|
|
// overridden by signalling flags, overridden by global option
|
|
bool use_audio_player = !!MEDIA_ISSET(sink, AUDIO_PLAYER);
|
|
bool implicit_audio_player = false;
|
|
|
|
if (a.flags && a.flags->audio_player == AP_FORCE)
|
|
use_audio_player = true;
|
|
else if (a.flags && a.flags->audio_player == AP_OFF)
|
|
use_audio_player = false;
|
|
else if (rtpe_config.use_audio_player == UAP_ALWAYS)
|
|
use_audio_player = true;
|
|
else if (rtpe_config.use_audio_player == UAP_PLAY_MEDIA) {
|
|
// check for implicitly enabled player
|
|
if ((a.flags && a.flags->opmode == OP_PLAY_MEDIA) || (media_player_is_active(other_monologue))) {
|
|
use_audio_player = true;
|
|
implicit_audio_player = true;
|
|
}
|
|
}
|
|
|
|
// first gather info about what we can send
|
|
AUTO_CLEANUP_NULL(GHashTable *supplemental_sinks, __g_hash_table_destroy);
|
|
struct rtp_payload_type *pref_dest_codec = NULL;
|
|
__check_codec_list(&supplemental_sinks, &pref_dest_codec, sink, &sink->codecs.codec_prefs);
|
|
|
|
// then do the same with what we can receive
|
|
AUTO_CLEANUP_NULL(GHashTable *supplemental_recvs, __g_hash_table_destroy);
|
|
__check_codec_list(&supplemental_recvs, NULL, receiver, &receiver->codecs.codec_prefs);
|
|
|
|
// 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.
|
|
AUTO_CLEANUP(GHashTable *output_transcoders, __g_hash_table_destroy)
|
|
= g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
|
|
enum block_dtmf_mode dtmf_block_mode = dtmf_get_block_mode(NULL, monologue);
|
|
bool do_pcm_dtmf_blocking = is_pcm_dtmf_block_mode(dtmf_block_mode);
|
|
bool do_dtmf_blocking = is_dtmf_replace_mode(dtmf_block_mode);
|
|
|
|
if (monologue->dtmf_delay) // received DTMF must be replaced by silence initially, therefore:
|
|
do_pcm_dtmf_blocking = true;
|
|
|
|
bool do_dtmf_detect = false;
|
|
if (monologue->dtmf_trigger.len)
|
|
do_dtmf_detect = true;
|
|
|
|
if (a.flags && a.flags->inject_dtmf)
|
|
ML_SET(other_monologue, INJECT_DTMF);
|
|
|
|
bool use_ssrc_passthrough = MEDIA_ISSET(receiver, ECHO) || ML_ISSET(other_monologue, INJECT_DTMF);
|
|
|
|
// do we have to force everything through the transcoding engine even if codecs match?
|
|
bool force_transcoding = do_pcm_dtmf_blocking || do_dtmf_blocking || use_audio_player;
|
|
|
|
for (GList *l = receiver->codecs.codec_prefs.head; l; ) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
struct rtp_payload_type *sink_pt = NULL;
|
|
|
|
ilogs(codec, LOG_DEBUG, "Checking receiver codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_full_params), pt->payload_type);
|
|
|
|
struct codec_handler *handler = __get_pt_handler(receiver, pt, sink);
|
|
|
|
// check our own support for this codec
|
|
if (!pt->codec_def) {
|
|
// not supported
|
|
ilogs(codec, LOG_DEBUG, "No codec support for " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
__make_passthrough_gsl(handler, &passthrough_handlers, NULL, NULL, use_ssrc_passthrough);
|
|
goto next;
|
|
}
|
|
|
|
// fill matching supp codecs
|
|
struct rtp_payload_type *recv_dtmf_pt = __supp_payload_type(supplemental_recvs, pt->clock_rate,
|
|
"telephone-event");
|
|
struct rtp_payload_type *recv_cn_pt = __supp_payload_type(supplemental_recvs, pt->clock_rate,
|
|
"CN");
|
|
bool pcm_dtmf_detect = false;
|
|
|
|
// find the matching sink codec
|
|
|
|
if (!sink_pt) {
|
|
// can we send the same codec that we want to receive?
|
|
sink_pt = g_hash_table_lookup(sink->codecs.codecs,
|
|
GINT_TO_POINTER(pt->payload_type));
|
|
// is it actually the same?
|
|
if (sink_pt && !rtp_payload_type_eq_compat(pt, sink_pt))
|
|
sink_pt = NULL;
|
|
}
|
|
|
|
if (!sink_pt) {
|
|
// no matching/identical output codec. maybe we have the same output codec,
|
|
// but with a different payload type or a different format?
|
|
sink_pt = codec_store_find_compatible(&sink->codecs, pt);
|
|
}
|
|
|
|
if (sink_pt && !pt->codec_def->supplemental) {
|
|
// we have a matching output codec. do we actually want to use it, or
|
|
// do we want to transcode to something else?
|
|
// ignore the preference here - for now, all `for_transcoding` codecs
|
|
// take preference
|
|
if (pref_dest_codec && pref_dest_codec->for_transcoding)
|
|
sink_pt = pref_dest_codec;
|
|
}
|
|
|
|
// ignore DTMF sink if we're blocking DTMF in PCM replacement mode
|
|
if (do_pcm_dtmf_blocking && sink_pt && sink_pt->codec_def && sink_pt->codec_def->dtmf)
|
|
sink_pt = NULL;
|
|
|
|
// still no output? pick the preferred sink codec
|
|
if (!sink_pt)
|
|
sink_pt = pref_dest_codec;
|
|
|
|
if (!sink_pt) {
|
|
ilogs(codec, LOG_DEBUG, "No suitable output codec for " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params));
|
|
__make_passthrough_gsl(handler, &passthrough_handlers, recv_dtmf_pt, recv_cn_pt,
|
|
use_ssrc_passthrough);
|
|
goto next;
|
|
}
|
|
|
|
// sink_pt has been determined here now.
|
|
|
|
ilogs(codec, LOG_DEBUG, "Sink codec for " STR_FORMAT " is " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&sink_pt->encoding_with_full_params), sink_pt->payload_type);
|
|
|
|
sink_pt_fixed:;
|
|
// we have found a usable output codec. gather matching output supp codecs
|
|
struct rtp_payload_type *sink_dtmf_pt = __supp_payload_type(supplemental_sinks,
|
|
sink_pt->clock_rate, "telephone-event");
|
|
struct rtp_payload_type *sink_cn_pt = __supp_payload_type(supplemental_sinks,
|
|
sink_pt->clock_rate, "CN");
|
|
struct rtp_payload_type *real_sink_dtmf_pt = NULL; // for DTMF delay
|
|
|
|
// XXX synthesise missing supp codecs according to codec tracker XXX needed?
|
|
|
|
if (!a.flags) {
|
|
// second pass going through the offerer codecs during an answer:
|
|
// if an answer rejected a supplemental codec that isn't marked for transcoding,
|
|
// reject it on the sink side as well
|
|
if (sink_dtmf_pt && !recv_dtmf_pt && !sink_dtmf_pt->for_transcoding)
|
|
sink_dtmf_pt = NULL;
|
|
if (sink_cn_pt && !recv_cn_pt && !sink_cn_pt->for_transcoding)
|
|
sink_cn_pt = NULL;
|
|
}
|
|
|
|
// do we need DTMF detection?
|
|
if (!pt->codec_def->supplemental && !recv_dtmf_pt && sink_dtmf_pt
|
|
&& sink_dtmf_pt->for_transcoding)
|
|
pcm_dtmf_detect = true;
|
|
|
|
if (ML_ISSET(monologue, DETECT_DTMF))
|
|
pcm_dtmf_detect = true;
|
|
|
|
// special mode for DTMF blocking
|
|
if (do_pcm_dtmf_blocking) {
|
|
real_sink_dtmf_pt = sink_dtmf_pt; // remember for DTMF delay
|
|
sink_dtmf_pt = NULL; // always transcode DTMF to PCM
|
|
|
|
// enable DSP if we expect DTMF to be carried as PCM
|
|
if (!recv_dtmf_pt)
|
|
pcm_dtmf_detect = true;
|
|
}
|
|
else if (do_dtmf_blocking && !pcm_dtmf_detect) {
|
|
// we only need the DSP if there's no DTMF payload present, as otherwise
|
|
// we expect DTMF event packets
|
|
if (!recv_dtmf_pt)
|
|
pcm_dtmf_detect = true;
|
|
}
|
|
|
|
// same logic if we need to detect DTMF
|
|
if (do_dtmf_detect && !pcm_dtmf_detect) {
|
|
if (!recv_dtmf_pt)
|
|
pcm_dtmf_detect = true;
|
|
}
|
|
|
|
if (pcm_dtmf_detect) {
|
|
if (sink_dtmf_pt)
|
|
ilogs(codec, LOG_DEBUG, "Enabling PCM DTMF detection from " STR_FORMAT
|
|
" to " STR_FORMAT
|
|
"/" STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&sink_pt->encoding_with_params),
|
|
STR_FMT(&sink_dtmf_pt->encoding_with_params));
|
|
else
|
|
ilogs(codec, LOG_DEBUG, "Enabling PCM DTMF detection from " STR_FORMAT
|
|
" to " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&sink_pt->encoding_with_params));
|
|
}
|
|
|
|
// we can now decide whether we can do passthrough, or transcode
|
|
|
|
// different codecs? this will only be true for non-supplemental codecs
|
|
if (!a.allow_asymmetric && pt->payload_type != sink_pt->payload_type)
|
|
goto transcode;
|
|
if (!rtp_payload_type_fmt_eq_nf(pt, sink_pt))
|
|
goto transcode;
|
|
|
|
// supplemental codecs are always matched up. we want them as passthrough if
|
|
// possible. skip checks that are only applicable for real codecs
|
|
if (!pt->codec_def->supplemental) {
|
|
// different ptime?
|
|
if (sink_pt->ptime && pt->ptime && sink_pt->ptime != pt->ptime) {
|
|
if (MEDIA_ISSET(sink, PTIME_OVERRIDE) || MEDIA_ISSET(receiver, PTIME_OVERRIDE)) {
|
|
ilogs(codec, LOG_DEBUG, "Mismatched ptime between source and sink (%i <> %i), "
|
|
"enabling transcoding",
|
|
sink_pt->ptime, pt->ptime);
|
|
goto transcode;
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Mismatched ptime between source and sink (%i <> %i), "
|
|
"but no override requested",
|
|
sink_pt->ptime, pt->ptime);
|
|
}
|
|
|
|
if (force_transcoding)
|
|
goto transcode;
|
|
|
|
// compare supplemental codecs
|
|
// DTMF
|
|
if (pcm_dtmf_detect)
|
|
goto transcode;
|
|
if (recv_dtmf_pt && (recv_dtmf_pt->for_transcoding || do_pcm_dtmf_blocking) && !sink_dtmf_pt) {
|
|
ilogs(codec, LOG_DEBUG, "Transcoding DTMF events to PCM from " STR_FORMAT
|
|
" to " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&sink_pt->encoding_with_params));
|
|
goto transcode;
|
|
}
|
|
// CN
|
|
if (!recv_cn_pt && sink_cn_pt && sink_cn_pt->for_transcoding) {
|
|
ilogs(codec, LOG_DEBUG, "Enabling CN silence detection from " STR_FORMAT
|
|
" to " STR_FORMAT
|
|
"/" STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&sink_pt->encoding_with_params),
|
|
STR_FMT(&sink_cn_pt->encoding_with_params));
|
|
goto transcode;
|
|
}
|
|
if (recv_cn_pt && recv_cn_pt->for_transcoding && !sink_cn_pt) {
|
|
ilogs(codec, LOG_DEBUG, "Transcoding CN packets to PCM from " STR_FORMAT
|
|
" to " STR_FORMAT,
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&sink_pt->encoding_with_params));
|
|
goto transcode;
|
|
}
|
|
}
|
|
|
|
// force transcoding if we want DTMF injection and there's no DTMF PT
|
|
if (!sink_dtmf_pt && ML_ISSET(other_monologue, INJECT_DTMF))
|
|
goto transcode;
|
|
|
|
// everything matches - we can do passthrough
|
|
ilogs(codec, LOG_DEBUG, "Sink supports codec " STR_FORMAT " (%i) for passthrough (to %i)",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type,
|
|
sink_pt->payload_type);
|
|
__make_passthrough_gsl(handler, &passthrough_handlers, sink_dtmf_pt, sink_cn_pt,
|
|
use_ssrc_passthrough);
|
|
goto next;
|
|
|
|
transcode:
|
|
// enable audio player if not explicitly disabled
|
|
if (rtpe_config.use_audio_player == UAP_TRANSCODING && (!a.flags || a.flags->audio_player != AP_OFF))
|
|
use_audio_player = true;
|
|
else if (a.flags && a.flags->audio_player == AP_TRANSCODING)
|
|
use_audio_player = true;
|
|
|
|
if (use_audio_player) {
|
|
// when using the audio player, everything must decode to the same
|
|
// format that is appropriate for the audio player
|
|
if (sink_pt != pref_dest_codec && pref_dest_codec) {
|
|
ilogs(codec, LOG_DEBUG, "Switching sink codec for " STR_FORMAT " to "
|
|
STR_FORMAT " (%i) due to usage of audio player",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&pref_dest_codec->encoding_with_full_params),
|
|
pref_dest_codec->payload_type);
|
|
sink_pt = pref_dest_codec;
|
|
force_transcoding = true;
|
|
goto sink_pt_fixed;
|
|
}
|
|
}
|
|
// 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.codecs,
|
|
GINT_TO_POINTER(sink_pt->payload_type));
|
|
if (reverse_pt) {
|
|
if (!sink_pt->bitrate)
|
|
sink_pt->bitrate = reverse_pt->bitrate;
|
|
if (!sink_pt->codec_opts.len) {
|
|
str_free_dup(&sink_pt->codec_opts);
|
|
str_init_dup_str(&sink_pt->codec_opts, &reverse_pt->codec_opts);
|
|
}
|
|
}
|
|
is_transcoding = true;
|
|
if (!use_audio_player)
|
|
__make_transcoder(handler, sink_pt, output_transcoders,
|
|
sink_dtmf_pt ? sink_dtmf_pt->payload_type : -1,
|
|
pcm_dtmf_detect, sink_cn_pt ? sink_cn_pt->payload_type : -1);
|
|
else
|
|
__make_audio_player_decoder(handler, sink_pt, pcm_dtmf_detect);
|
|
// for DTMF delay: we pretend that there is no output DTMF payload type (sink_dtmf_pt == NULL)
|
|
// so that DTMF is converted to audio (so it can be replaced with silence). we still want
|
|
// to output DTMF event packets when we can though, so we need to remember the DTMF payload
|
|
// type here.
|
|
handler->real_dtmf_payload_type = real_sink_dtmf_pt ? real_sink_dtmf_pt->payload_type : -1;
|
|
__check_dtmf_injector(receiver, sink, handler, output_transcoders);
|
|
|
|
next:
|
|
l = l->next;
|
|
}
|
|
|
|
if (!use_audio_player) {
|
|
MEDIA_CLEAR(sink, AUDIO_PLAYER);
|
|
audio_player_stop(sink);
|
|
}
|
|
else if (!implicit_audio_player)
|
|
MEDIA_SET(sink, AUDIO_PLAYER);
|
|
|
|
if (is_transcoding) {
|
|
if (a.reset_transcoding && ms)
|
|
ms->attrs.transcoding = 1;
|
|
|
|
if (!use_audio_player) {
|
|
// we have to translate RTCP packets
|
|
receiver->rtcp_handler = rtcp_transcode_handler;
|
|
|
|
for (GList *l = receiver->codecs.codec_prefs.head; l; ) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
|
|
if (pt->codec_def) {
|
|
// supported
|
|
l = l->next;
|
|
continue;
|
|
}
|
|
|
|
ilogs(codec, LOG_DEBUG, "Stripping unsupported codec " STR_FORMAT
|
|
" due to active transcoding",
|
|
STR_FMT(&pt->encoding));
|
|
codec_touched(&receiver->codecs, pt);
|
|
l = __codec_store_delete_link(l, &receiver->codecs);
|
|
}
|
|
|
|
|
|
// 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;
|
|
__convert_passthrough_ssrc(handler);
|
|
passthrough_handlers = g_slist_delete_link(passthrough_handlers,
|
|
passthrough_handlers);
|
|
|
|
}
|
|
}
|
|
else {
|
|
receiver->rtcp_handler = rtcp_sink_handler;
|
|
MEDIA_CLEAR(receiver, RTCP_GEN);
|
|
|
|
// change all passthrough handlers also to transcoders
|
|
while (passthrough_handlers) {
|
|
struct codec_handler *handler = passthrough_handlers->data;
|
|
__make_audio_player_decoder(handler, pref_dest_codec, false);
|
|
passthrough_handlers = g_slist_delete_link(passthrough_handlers,
|
|
passthrough_handlers);
|
|
|
|
}
|
|
|
|
audio_player_setup(sink, pref_dest_codec, rtpe_config.audio_buffer_length,
|
|
rtpe_config.audio_buffer_delay);
|
|
if (a.flags && (a.flags->early_media || a.flags->opmode == OP_ANSWER))
|
|
audio_player_activate(sink);
|
|
}
|
|
}
|
|
|
|
g_slist_free(passthrough_handlers);
|
|
|
|
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 call_media *sink)
|
|
{
|
|
struct codec_handler *h;
|
|
|
|
if (payload_type < 0)
|
|
return NULL;
|
|
|
|
struct codec_handler lookup = __codec_handler_lookup_struct(payload_type, sink);
|
|
h = g_atomic_pointer_get(&m->codec_handler_cache);
|
|
if (G_LIKELY(h) && G_LIKELY(__codec_handler_eq(&lookup, h)))
|
|
return h;
|
|
|
|
if (G_UNLIKELY(!m->codec_handlers))
|
|
return NULL;
|
|
h = g_hash_table_lookup(m->codec_handlers, &lookup);
|
|
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
|
|
|
|
static void __mqtt_timer_free(void *p) {
|
|
struct mqtt_timer *mqt = p;
|
|
if (mqt->call)
|
|
obj_put(mqt->call);
|
|
}
|
|
static void __codec_mqtt_timer_schedule(struct mqtt_timer *mqt);
|
|
INLINE bool __mqtt_timer_common_call(struct mqtt_timer *mqt) {
|
|
struct call *call = mqt->call;
|
|
|
|
rwlock_lock_w(&call->master_lock);
|
|
|
|
if (!*mqt->self) {
|
|
rwlock_unlock_w(&call->master_lock);
|
|
return false;
|
|
}
|
|
|
|
log_info_call(call);
|
|
|
|
__codec_mqtt_timer_schedule(mqt);
|
|
|
|
rwlock_unlock_w(&call->master_lock);
|
|
|
|
return true;
|
|
}
|
|
static void __mqtt_timer_run_media(struct codec_timer *ct) {
|
|
struct mqtt_timer *mqt = (struct mqtt_timer *) ct;
|
|
if (!__mqtt_timer_common_call(mqt))
|
|
return;
|
|
mqtt_timer_run_media(mqt->call, mqt->media);
|
|
log_info_pop();
|
|
}
|
|
static void __mqtt_timer_run_call(struct codec_timer *ct) {
|
|
struct mqtt_timer *mqt = (struct mqtt_timer *) ct;
|
|
if (!__mqtt_timer_common_call(mqt))
|
|
return;
|
|
mqtt_timer_run_call(mqt->call);
|
|
log_info_pop();
|
|
}
|
|
static void __mqtt_timer_run_global(struct codec_timer *ct) {
|
|
struct mqtt_timer *mqt = (struct mqtt_timer *) ct;
|
|
if (!*mqt->self)
|
|
return;
|
|
__codec_mqtt_timer_schedule(mqt);
|
|
mqtt_timer_run_global();
|
|
}
|
|
static void __mqtt_timer_run_summary(struct codec_timer *ct) {
|
|
struct mqtt_timer *mqt = (struct mqtt_timer *) ct;
|
|
if (!*mqt->self)
|
|
return;
|
|
__codec_mqtt_timer_schedule(mqt);
|
|
mqtt_timer_run_summary();
|
|
}
|
|
static void __codec_mqtt_timer_schedule(struct mqtt_timer *mqt) {
|
|
timeval_add_usec(&mqt->ct.next, rtpe_config.mqtt_publish_interval * 1000);
|
|
timerthread_obj_schedule_abs(&mqt->ct.tt_obj, &mqt->ct.next);
|
|
}
|
|
// master lock held in W
|
|
void mqtt_timer_start(struct mqtt_timer **mqtp, struct call *call, struct call_media *media) {
|
|
if (*mqtp) // already scheduled
|
|
return;
|
|
|
|
struct mqtt_timer *mqt = *mqtp = obj_alloc0("mqtt_timer", sizeof(*mqt), __mqtt_timer_free);
|
|
mqt->ct.tt_obj.tt = &codec_timers_thread;
|
|
mqt->call = call ? obj_get(call) : NULL;
|
|
mqt->self = mqtp;
|
|
mqt->media = media;
|
|
mqt->ct.next = rtpe_now;
|
|
|
|
if (media)
|
|
mqt->ct.timer_func = __mqtt_timer_run_media;
|
|
else if (call)
|
|
mqt->ct.timer_func = __mqtt_timer_run_call;
|
|
else {
|
|
// global or summary
|
|
mqt->ct.timer_func = mqtt_publish_scope() == MPS_GLOBAL
|
|
? __mqtt_timer_run_global : __mqtt_timer_run_summary;
|
|
}
|
|
|
|
__codec_mqtt_timer_schedule(mqt);
|
|
}
|
|
|
|
|
|
// master lock held in W
|
|
static void codec_timer_stop(struct codec_timer **ctp) {
|
|
if (!ctp || !*ctp)
|
|
return;
|
|
obj_put(&(*ctp)->tt_obj);
|
|
*ctp = NULL;
|
|
}
|
|
// master lock held in W
|
|
void rtcp_timer_stop(struct rtcp_timer **rtp) {
|
|
codec_timer_stop((struct codec_timer **) rtp);
|
|
}
|
|
void mqtt_timer_stop(struct mqtt_timer **mqtp) {
|
|
codec_timer_stop((struct codec_timer **) mqtp);
|
|
}
|
|
|
|
|
|
|
|
|
|
// call must be locked in R
|
|
struct codec_handler *codec_handler_get(struct call_media *m, int payload_type, struct call_media *sink,
|
|
struct sink_handler *sh)
|
|
{
|
|
#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, sink);
|
|
else if (m->protocol->index == PROTO_UDPTL)
|
|
ret = codec_handler_get_udptl(m);
|
|
|
|
out:
|
|
if (ret)
|
|
return ret;
|
|
if (sh && sh->attrs.transcoding)
|
|
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);
|
|
#endif
|
|
}
|
|
|
|
|
|
static void codec_add_raw_packet_common(struct media_packet *mp, unsigned int clockrate,
|
|
struct codec_packet *p)
|
|
{
|
|
p->clockrate = clockrate;
|
|
if (mp->rtp && mp->ssrc_out) {
|
|
ssrc_ctx_hold(mp->ssrc_out);
|
|
p->ssrc_out = mp->ssrc_out;
|
|
if (!p->rtp)
|
|
p->rtp = mp->rtp;
|
|
}
|
|
g_queue_push_tail(&mp->packets_out, p);
|
|
}
|
|
void codec_add_raw_packet(struct media_packet *mp, unsigned int clockrate) {
|
|
struct codec_packet *p = g_slice_alloc0(sizeof(*p));
|
|
p->s = mp->raw;
|
|
p->free_func = NULL;
|
|
codec_add_raw_packet_common(mp, clockrate, p);
|
|
}
|
|
static void codec_add_raw_packet_dup(struct media_packet *mp, unsigned int clockrate) {
|
|
struct codec_packet *p = g_slice_alloc0(sizeof(*p));
|
|
str_init_dup_str(&p->s, &mp->raw);
|
|
p->free_func = free;
|
|
p->rtp = (struct rtp_header *) p->s.s;
|
|
codec_add_raw_packet_common(mp, clockrate, p);
|
|
}
|
|
static bool handler_silence_block(struct codec_handler *h, struct media_packet *mp) {
|
|
if (CALL_ISSET(mp->call, BLOCK_MEDIA) || ML_ISSET(mp->media->monologue, BLOCK_MEDIA) || mp->sink.attrs.block_media)
|
|
return false;
|
|
if (CALL_ISSET(mp->call, SILENCE_MEDIA) || ML_ISSET(mp->media->monologue, SILENCE_MEDIA) || mp->sink.attrs.silence_media) {
|
|
if (h->source_pt.codec_def && h->source_pt.codec_def->silence_pattern.len) {
|
|
if (h->source_pt.codec_def->silence_pattern.len == 1)
|
|
memset(mp->payload.s, h->source_pt.codec_def->silence_pattern.s[0],
|
|
mp->payload.len);
|
|
else {
|
|
for (size_t pos = 0; pos < mp->payload.len;
|
|
pos += h->source_pt.codec_def->silence_pattern.len)
|
|
memcpy(&mp->payload.s[pos], h->source_pt.codec_def->silence_pattern.s,
|
|
h->source_pt.codec_def->silence_pattern.len);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
static int handler_func_passthrough(struct codec_handler *h, struct media_packet *mp) {
|
|
if (!handler_silence_block(h, mp))
|
|
return 0;
|
|
|
|
uint32_t ts = 0;
|
|
if (mp->rtp) {
|
|
ts = ntohl(mp->rtp->timestamp);
|
|
codec_calc_jitter(mp->ssrc_in, ts, h->source_pt.clock_rate, &mp->tv);
|
|
codec_calc_lost(mp->ssrc_in, ntohs(mp->rtp->seq_num));
|
|
|
|
if (ML_ISSET(mp->media->monologue, BLOCK_SHORT) && h->source_pt.codec_def
|
|
&& h->source_pt.codec_def->fixed_sizes)
|
|
{
|
|
if (!h->payload_len)
|
|
h->payload_len = mp->payload.len;
|
|
else if (mp->payload.len < h->payload_len)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ML_CLEAR(mp->media->monologue, DTMF_INJECTION_ACTIVE);
|
|
|
|
__buffer_delay_raw(h->delay_buffer, h, codec_add_raw_packet, mp, h->source_pt.clock_rate);
|
|
|
|
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 void __seq_free(void *p) {
|
|
packet_sequencer_t *seq = p;
|
|
packet_sequencer_destroy(seq);
|
|
g_slice_free1(sizeof(*seq), seq);
|
|
}
|
|
|
|
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->packet_func || !h->input_handler->ssrc_hash) {
|
|
h->handler_func(h, mp);
|
|
__transcode_packet_free(packet);
|
|
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)) {
|
|
__transcode_packet_free(packet);
|
|
return 0;
|
|
}
|
|
|
|
// save RTP pointer - we clobber it below XXX this shouldn't be necessary to do
|
|
struct rtp_header *orig_rtp = mp->rtp;
|
|
|
|
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;
|
|
|
|
atomic64_inc(&ssrc_in->packets);
|
|
atomic64_add(&ssrc_in->octets, mp->payload.len);
|
|
atomic64_inc(&mp->sfd->local_intf->stats.in.packets);
|
|
atomic64_add(&mp->sfd->local_intf->stats.in.bytes, mp->payload.len);
|
|
|
|
struct codec_ssrc_handler *input_ch = get_ssrc(ssrc_in_p->h.ssrc, h->input_handler->ssrc_hash);
|
|
|
|
if (packet->bypass_seq) {
|
|
// bypass sequencer
|
|
__ssrc_lock_both(mp);
|
|
int ret = packet->packet_func(ch, input_ch ?: ch, packet, mp);
|
|
if (ret != 1)
|
|
__transcode_packet_free(packet);
|
|
goto out;
|
|
}
|
|
|
|
if (G_UNLIKELY(!input_ch)) {
|
|
__transcode_packet_free(packet);
|
|
goto out_ch;
|
|
}
|
|
|
|
__ssrc_lock_both(mp);
|
|
|
|
// get sequencer appropriate for our output
|
|
if (!ssrc_in_p->sequencers)
|
|
ssrc_in_p->sequencers = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, __seq_free);
|
|
packet_sequencer_t *seq = g_hash_table_lookup(ssrc_in_p->sequencers, mp->media_out);
|
|
if (!seq) {
|
|
seq = g_slice_alloc0(sizeof(*seq));
|
|
packet_sequencer_init(seq, (GDestroyNotify) __transcode_packet_free);
|
|
g_hash_table_insert(ssrc_in_p->sequencers, mp->media_out, seq);
|
|
}
|
|
|
|
uint16_t seq_ori = seq->seq;
|
|
int seq_ret = packet_sequencer_insert(seq, &packet->p);
|
|
if (seq_ret < 0) {
|
|
// dupe
|
|
int func_ret = 0;
|
|
if (packet->dup_func)
|
|
func_ret = packet->dup_func(ch, input_ch, packet, mp);
|
|
else
|
|
ilogs(transcoding, LOG_DEBUG, "Ignoring duplicate RTP packet");
|
|
if (func_ret != 1)
|
|
__transcode_packet_free(packet);
|
|
ssrc_in_p->duplicates++;
|
|
atomic64_inc(&mp->sfd->local_intf->stats.s.duplicates);
|
|
RTPE_STATS_INC(rtp_duplicates);
|
|
goto out;
|
|
}
|
|
|
|
if (seq_ret == 1)
|
|
RTPE_STATS_INC(rtp_seq_resets);
|
|
else if (seq_ret == 2)
|
|
RTPE_STATS_INC(rtp_reordered);
|
|
|
|
// got a new packet, run decoder
|
|
|
|
while (1) {
|
|
int func_ret = 0;
|
|
|
|
packet = packet_sequencer_next_packet(seq);
|
|
if (G_UNLIKELY(!packet)) {
|
|
if (!ch || !h->dest_pt.clock_rate || !ch->handler
|
|
|| !h->dest_pt.codec_def)
|
|
break;
|
|
|
|
uint32_t ts_diff = packet_ts - ch->csch.last_ts;
|
|
|
|
// if packet TS is larger than last tracked TS, we can force the next packet if packets were lost and the TS
|
|
// difference is too large. if packet TS is the same or lower (can happen for supplement codecs) we can wait
|
|
// for the next packet
|
|
if (ts_diff == 0 || ts_diff >= 0x80000000)
|
|
break;
|
|
|
|
unsigned long long ts_diff_us =
|
|
(unsigned long long) ts_diff * 1000000 / h->dest_pt.clock_rate;
|
|
if (ts_diff_us >= 60000) { // arbitrary value
|
|
packet = packet_sequencer_force_next_packet(seq);
|
|
if (!packet)
|
|
break;
|
|
ilogs(transcoding, LOG_DEBUG, "Timestamp difference too large (%llu ms) after lost packet, "
|
|
"forcing next packet", ts_diff_us / 1000);
|
|
RTPE_STATS_INC(rtp_skips);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (ch) {
|
|
uint32_t ts_diff = ch->csch.last_ts - packet->ts;
|
|
if (ts_diff < 0x80000000) { // ch->last_ts >= packet->ts
|
|
// multiple consecutive packets with same TS: this could be a compound packet, e.g. a large video frame, or
|
|
// it could be a supplemental audio codec with static timestamps, in which case we adjust the TS forward
|
|
// by one frame length. This is needed so that the next real audio packet (with real TS) is not mistakenly
|
|
// seen as overdue
|
|
if (h->source_pt.codec_def && h->source_pt.codec_def->supplemental)
|
|
ch->csch.last_ts += h->source_pt.clock_rate * (ch->ptime ?: 20) / 1000;
|
|
}
|
|
else
|
|
ch->csch.last_ts = packet->ts;
|
|
|
|
if (input_ch)
|
|
input_ch->csch.last_ts = ch->csch.last_ts;
|
|
}
|
|
|
|
|
|
// new packet might have different handlers
|
|
h = packet->handler;
|
|
if (ch)
|
|
obj_put(&ch->h);
|
|
if (input_ch)
|
|
obj_put(&input_ch->h);
|
|
input_ch = NULL;
|
|
ch = get_ssrc(ssrc_in_p->h.ssrc, h->ssrc_hash);
|
|
if (G_UNLIKELY(!ch))
|
|
goto next;
|
|
input_ch = get_ssrc(ssrc_in_p->h.ssrc, h->input_handler->ssrc_hash);
|
|
if (G_UNLIKELY(!input_ch)) {
|
|
obj_put(&ch->h);
|
|
ch = NULL;
|
|
goto next;
|
|
}
|
|
|
|
ssrc_in_p->packets_lost = seq->lost_count;
|
|
atomic64_set(&ssrc_in->last_seq, seq->ext_seq);
|
|
|
|
ilogs(transcoding, 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->packet_func(ch, input_ch, packet, mp);
|
|
if (func_ret < 0)
|
|
ilogs(transcoding, 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);
|
|
if (input_ch)
|
|
obj_put(&input_ch->h);
|
|
out_ch:
|
|
if (ch)
|
|
obj_put(&ch->h);
|
|
|
|
mp->rtp = orig_rtp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void codec_output_rtp(struct media_packet *mp, struct codec_scheduler *csch,
|
|
struct codec_handler *handler,
|
|
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,
|
|
unsigned long ts_delay)
|
|
{
|
|
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->clockrate = handler->dest_pt.clock_rate;
|
|
ssrc_ctx_hold(ssrc_out);
|
|
p->ssrc_out = ssrc_out;
|
|
|
|
long long ts_diff_us = 0;
|
|
|
|
// ignore scheduling if a sequence number was supplied. in that case we're just doing
|
|
// passthrough forwarding (or are handling some other prepared RTP stream) and want
|
|
// to send the packet out immediately.
|
|
if (seq != -1) {
|
|
p->ttq_entry.when = rtpe_now;
|
|
goto send;
|
|
}
|
|
|
|
// this packet is dynamically allocated, so we're able to schedule it.
|
|
// determine scheduled time to send
|
|
if (csch->first_send.tv_sec && handler->dest_pt.clock_rate) {
|
|
// scale first_send from first_send_ts to ts
|
|
p->ttq_entry.when = csch->first_send;
|
|
uint32_t ts_diff = (uint32_t) ts - (uint32_t) csch->first_send_ts; // allow for wrap-around
|
|
ts_diff += ts_delay;
|
|
ts_diff_us = (unsigned long long) ts_diff * 1000000 / handler->dest_pt.clock_rate;
|
|
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);
|
|
if (ts_diff_us > 1000000 || ts_diff_us < -1000000) // more than one second, can't be right
|
|
csch->first_send.tv_sec = 0; // fix it up below
|
|
}
|
|
if (!csch->first_send.tv_sec || !p->ttq_entry.when.tv_sec) {
|
|
p->ttq_entry.when = csch->first_send = rtpe_now;
|
|
csch->first_send_ts = ts;
|
|
}
|
|
|
|
ts_diff_us = timeval_diff(&p->ttq_entry.when, &rtpe_now);
|
|
|
|
csch->output_skew = csch->output_skew * 15 / 16 + ts_diff_us / 16;
|
|
if (csch->output_skew > 50000 && ts_diff_us > 10000) { // arbitrary value, 50 ms, 10 ms shift
|
|
ilogs(transcoding, LOG_DEBUG, "Steady clock skew of %li.%01li ms detected, shifting send timer back by 10 ms",
|
|
csch->output_skew / 1000,
|
|
(csch->output_skew % 1000) / 100);
|
|
timeval_add_usec(&p->ttq_entry.when, -10000);
|
|
csch->output_skew -= 10000;
|
|
csch->first_send_ts += handler->dest_pt.clock_rate / 100;
|
|
ts_diff_us = timeval_diff(&p->ttq_entry.when, &rtpe_now);
|
|
}
|
|
else if (ts_diff_us < 0) {
|
|
ts_diff_us *= -1;
|
|
ilogs(transcoding, LOG_DEBUG, "Negative clock skew of %lli.%01lli ms detected, shifting send timer forward",
|
|
ts_diff_us / 1000,
|
|
(ts_diff_us % 1000) / 100);
|
|
timeval_add_usec(&p->ttq_entry.when, ts_diff_us);
|
|
csch->output_skew += ts_diff_us;
|
|
csch->first_send_ts -= (long long) handler->dest_pt.clock_rate * ts_diff_us / 1000000;
|
|
ts_diff_us = timeval_diff(&p->ttq_entry.when, &rtpe_now); // should be 0 now
|
|
}
|
|
|
|
send:
|
|
ilogs(transcoding, LOG_DEBUG, "Scheduling to send RTP packet (seq %u TS %lu) in %s%lli.%01lli ms (at %lu.%06lu)",
|
|
ntohs(rh->seq_num),
|
|
ts,
|
|
ts_diff_us < 0 ? "-" : "",
|
|
llabs(ts_diff_us / 1000),
|
|
llabs((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
|
|
ilogs(transcoding, 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)) {
|
|
ilogs(transcoding, LOG_ERR | LOG_FLAG_LIMIT,
|
|
"Switched from input to output codec context, but no codec handler present");
|
|
obj_get(&ch->h);
|
|
return ch;
|
|
}
|
|
|
|
return new_ch;
|
|
}
|
|
|
|
static int codec_add_dtmf_packet(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet,
|
|
unsigned long ts_delay,
|
|
int payload_type,
|
|
struct media_packet *mp)
|
|
{
|
|
struct codec_handler *h = ch->handler;
|
|
struct codec_ssrc_handler *output_ch = NULL;
|
|
|
|
// grab our underlying PCM transcoder
|
|
output_ch = __output_ssrc_handler(input_ch, mp);
|
|
if (G_UNLIKELY(!output_ch->encoder))
|
|
goto skip;
|
|
|
|
ch->csch = output_ch->csch;
|
|
|
|
// 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 DTMF event duration
|
|
}
|
|
|
|
unsigned long ts = fraction_divl(output_ch->encoder->next_pts, &output_ch->encoder->clockrate_fact);
|
|
// 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->csch.first_ts;
|
|
|
|
ilogs(transcoding, LOG_DEBUG, "Scaling DTMF packet timestamp and duration: TS %lu -> %lu "
|
|
"(%u -> %u)",
|
|
packet->ts, packet_ts,
|
|
h->source_pt.clock_rate, h->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),
|
|
h->dest_pt.clock_rate, h->source_pt.clock_rate);
|
|
dtmf->duration = htons(duration);
|
|
|
|
// we can't directly use the RTP TS to schedule the send, as we have to adjust it
|
|
// by the duration
|
|
if (ch->dtmf_first_duration == 0 || duration < ch->dtmf_first_duration)
|
|
ch->dtmf_first_duration = duration;
|
|
ts_delay = duration - ch->dtmf_first_duration;
|
|
|
|
// shift forward our output RTP TS
|
|
output_ch->encoder->next_pts = fraction_multl(ts + duration, &output_ch->encoder->clockrate_fact);
|
|
output_ch->encoder->packet_pts += fraction_multl(duration - ch->last_dtmf_event_ts, &output_ch->encoder->clockrate_fact);
|
|
ch->last_dtmf_event_ts = duration;
|
|
}
|
|
payload_type = h->dtmf_payload_type;
|
|
if (payload_type == -1)
|
|
payload_type = h->real_dtmf_payload_type;
|
|
|
|
skip:
|
|
obj_put(&output_ch->h);
|
|
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->bypass_seq) // inject original seq
|
|
codec_output_rtp(mp, &ch->csch, packet->handler ? : h, buf, packet->payload->len, packet->ts,
|
|
packet->marker, packet->p.seq, -1, payload_type, ts_delay);
|
|
else // use our own sequencing
|
|
codec_output_rtp(mp, &ch->csch, packet->handler ? : h, buf, packet->payload->len, packet->ts,
|
|
packet->marker, -1, 0, payload_type, ts_delay);
|
|
mp->ssrc_out->parent->seq_diff++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// forwards DTMF input to DTMF output, plus rescaling duration
|
|
// returns 1 if packet has been consumed
|
|
static int packet_dtmf_fwd(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet,
|
|
struct media_packet *mp)
|
|
{
|
|
int payload_type = -1; // take from handler's output config
|
|
unsigned long ts_delay = 0;
|
|
struct codec_handler *h = ch->handler;
|
|
struct codec_handler *input_h = input_ch->handler;
|
|
|
|
int ret = __buffer_delay_packet(input_h->delay_buffer, ch, input_ch, packet, ts_delay, payload_type,
|
|
codec_add_dtmf_packet, mp, h->source_pt.clock_rate);
|
|
__buffer_delay_seq(input_h->delay_buffer, mp, -1);
|
|
return ret;
|
|
}
|
|
|
|
// returns the codec handler for the primary payload type - mostly determined by guessing
|
|
static struct codec_handler *__input_handler(struct codec_handler *h, struct media_packet *mp) {
|
|
if (!mp->ssrc_in)
|
|
return h;
|
|
|
|
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;
|
|
|
|
struct codec_handler *sequencer_h = codec_handler_get(mp->media, prim_pt, mp->media_out, NULL);
|
|
if (sequencer_h == h)
|
|
continue;
|
|
if (sequencer_h->source_pt.codec_def && sequencer_h->source_pt.codec_def->supplemental)
|
|
continue;
|
|
ilogs(transcoding, LOG_DEBUG, "Primary RTP payload type for handling %s is %i",
|
|
h->source_pt.codec_def->rtpname,
|
|
prim_pt);
|
|
return sequencer_h;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
// returns: -1 = error, 0 = processed ok, 1 = duplicate, already processed
|
|
static int packet_dtmf_event(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet, struct media_packet *mp)
|
|
{
|
|
LOCK(&mp->media->dtmf_lock);
|
|
|
|
if (mp->media->dtmf_ts == packet->ts)
|
|
return 1; // ignore already processed events
|
|
|
|
int ret = dtmf_event_packet(mp, packet->payload, ch->handler->source_pt.clock_rate, packet->ts);
|
|
if (G_UNLIKELY(ret == -1)) // error
|
|
return -1;
|
|
if (ret == 1) {
|
|
// END event
|
|
mp->media->dtmf_ts = packet->ts;
|
|
input_ch->dtmf_start_ts = 0;
|
|
}
|
|
else
|
|
input_ch->dtmf_start_ts = packet->ts ? packet->ts : 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int packet_dtmf(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet, struct media_packet *mp)
|
|
{
|
|
int dtmf_event_processed = packet_dtmf_event(ch, input_ch, packet, mp);
|
|
if (dtmf_event_processed == -1)
|
|
return -1;
|
|
|
|
|
|
enum block_dtmf_mode block_dtmf = dtmf_get_block_mode(mp->call, mp->media->monologue);
|
|
|
|
bool do_blocking = block_dtmf == BLOCK_DTMF_DROP;
|
|
|
|
if (packet->payload->len >= sizeof(struct telephone_event_payload)) {
|
|
struct telephone_event_payload *dtmf = (void *) packet->payload->s;
|
|
struct codec_handler *h = input_ch->handler;
|
|
// fudge up TS and duration values
|
|
uint64_t duration = (uint64_t) h->source_pt.clock_rate * h->source_pt.ptime / 1000;
|
|
uint64_t ts = packet->ts + ntohs(dtmf->duration) - duration;
|
|
|
|
// remember this as last "encoder" TS
|
|
atomic64_set(&mp->ssrc_in->last_ts, ts);
|
|
|
|
// provide an uninitialised buffer as potential output storage for DTMF
|
|
char buf[sizeof(struct telephone_event_payload)];
|
|
str ev_pl = STR_INIT_LEN(buf, sizeof(buf));
|
|
|
|
int is_dtmf = dtmf_event_payload(&ev_pl, &ts, duration,
|
|
&input_ch->dtmf_event, &input_ch->dtmf_events);
|
|
if (is_dtmf) {
|
|
// generate appropriate transcode_packets
|
|
unsigned int copies = 1;
|
|
if (dtmf_event_processed == 1) // discard duplicate end packets
|
|
copies = 0;
|
|
else if (is_dtmf == 3) // end event
|
|
copies = 3;
|
|
|
|
// fix up RTP header
|
|
struct rtp_header r;
|
|
r = *mp->rtp;
|
|
r.m_pt = h->dtmf_payload_type;
|
|
r.timestamp = htonl(ts);
|
|
|
|
for (; copies > 0; copies--) {
|
|
struct transcode_packet *dup = g_slice_alloc(sizeof(*dup));
|
|
*dup = *packet;
|
|
dup->payload = str_dup(&ev_pl);
|
|
dup->rtp = r;
|
|
dup->bypass_seq = 0;
|
|
dup->ts = ts;
|
|
if (is_dtmf == 1)
|
|
dup->marker = 1;
|
|
|
|
int ret = 0;
|
|
|
|
if (__buffer_dtx(input_ch->dtx_buffer, ch, input_ch, dup, mp, packet_dtmf_fwd))
|
|
ret = 1; // consumed
|
|
else
|
|
ret = packet_dtmf_fwd(ch, input_ch, dup, mp);
|
|
|
|
if (ret == 0)
|
|
__transcode_packet_free(dup);
|
|
}
|
|
|
|
// discard the received event
|
|
do_blocking = true;
|
|
|
|
|
|
}
|
|
else if (!input_ch->dtmf_events.length)
|
|
ML_CLEAR(mp->media->monologue, DTMF_INJECTION_ACTIVE);
|
|
|
|
}
|
|
|
|
int ret = 0;
|
|
|
|
if (do_blocking)
|
|
{ }
|
|
else {
|
|
// pass through
|
|
if (__buffer_dtx(input_ch->dtx_buffer, ch, input_ch, packet, mp, packet_dtmf_fwd))
|
|
ret = 1; // consumed
|
|
else
|
|
ret = packet_dtmf_fwd(ch, input_ch, packet, mp);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
static int packet_dtmf_dup(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet,
|
|
struct media_packet *mp)
|
|
{
|
|
enum block_dtmf_mode block_dtmf = dtmf_get_block_mode(mp->call, mp->media->monologue);
|
|
|
|
int ret = 0;
|
|
|
|
if (block_dtmf == BLOCK_DTMF_DROP)
|
|
{ }
|
|
else // pass through
|
|
ret = packet_dtmf_fwd(ch, input_ch, packet, mp);
|
|
return ret;
|
|
}
|
|
|
|
static int __handler_func_supplemental(struct codec_handler *h, struct media_packet *mp,
|
|
int (*packet_func)(struct codec_ssrc_handler *, struct codec_ssrc_handler *,
|
|
struct transcode_packet *, struct media_packet *),
|
|
int (*dup_func)(struct codec_ssrc_handler *, 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
|
|
|
|
ilogs(transcoding, LOG_DEBUG, "Received %s supplemental RTP packet: SSRC %" PRIx32
|
|
", PT %u, seq %u, TS %u, len %zu",
|
|
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 = __input_handler(h, mp);
|
|
|
|
h->input_handler = sequencer_h;
|
|
h->output_handler = sequencer_h;
|
|
|
|
struct transcode_packet *packet = g_slice_alloc0(sizeof(*packet));
|
|
packet->packet_func = packet_func;
|
|
packet->dup_func = dup_func;
|
|
packet->handler = h;
|
|
packet->rtp = *mp->rtp;
|
|
|
|
if (sequencer_h->passthrough || sequencer_h->kernelize) {
|
|
// bypass sequencer, directly pass it to forwarding function
|
|
packet->bypass_seq = 1;
|
|
}
|
|
|
|
return __handler_func_sequencer(mp, packet);
|
|
}
|
|
static int handler_func_dtmf(struct codec_handler *h, struct media_packet *mp) {
|
|
// DTMF input - can we do DTMF output?
|
|
if (h->dtmf_payload_type == -1)
|
|
return handler_func_transcode(h, 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);
|
|
if (p->plain_free_func && p->plain.s)
|
|
p->plain_free_func(p->plain.s);
|
|
ssrc_ctx_put(&p->ssrc_out);
|
|
g_slice_free1(sizeof(*p), p);
|
|
}
|
|
bool codec_packet_copy(struct codec_packet *p) {
|
|
char *buf = malloc(p->s.len + RTP_BUFFER_TAIL_ROOM);
|
|
if (!buf)
|
|
return false;
|
|
memcpy(buf, p->s.s, p->s.len);
|
|
p->s.s = buf;
|
|
p->free_func = free;
|
|
return true;
|
|
}
|
|
struct codec_packet *codec_packet_dup(struct codec_packet *p) {
|
|
struct codec_packet *dup = g_slice_alloc0(sizeof(*p));
|
|
*dup = *p;
|
|
codec_packet_copy(dup);
|
|
if (dup->ssrc_out)
|
|
ssrc_ctx_hold(dup->ssrc_out);
|
|
if (dup->rtp)
|
|
dup->rtp = (void *) dup->s.s;
|
|
return dup;
|
|
}
|
|
|
|
|
|
|
|
struct rtp_payload_type *codec_make_payload_type(const str *codec_str, enum media_type type) {
|
|
|
|
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 *pt = g_slice_alloc0(sizeof(*pt));
|
|
|
|
pt->payload_type = -1;
|
|
pt->encoding = codec;
|
|
pt->clock_rate = clockrate;
|
|
pt->channels = channels;
|
|
pt->bitrate = bitrate;
|
|
pt->ptime = ptime;
|
|
pt->format_parameters = fmt_params;
|
|
pt->codec_opts = codec_opts;
|
|
|
|
codec_init_payload_type(pt, type);
|
|
|
|
return pt;
|
|
}
|
|
|
|
void codec_init_payload_type(struct rtp_payload_type *pt, enum media_type type) {
|
|
#ifdef WITH_TRANSCODING
|
|
ensure_codec_def_type(pt, type);
|
|
codec_def_t *def = pt->codec_def;
|
|
|
|
if (def) {
|
|
if (!pt->clock_rate)
|
|
pt->clock_rate = def->default_clockrate;
|
|
if (!pt->channels)
|
|
pt->channels = def->default_channels;
|
|
if (pt->ptime <= 0)
|
|
pt->ptime = def->default_ptime;
|
|
if (!pt->format_parameters.s && def->default_fmtp)
|
|
str_init(&pt->format_parameters, (char *) def->default_fmtp);
|
|
|
|
codec_parse_fmtp(def, &pt->format, &pt->format_parameters, NULL);
|
|
|
|
if (def->init)
|
|
def->init(pt);
|
|
|
|
if (pt->payload_type == -1 && 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
|
|
&& (pt->clock_rate == 0 || pt->clock_rate == rfc_pt->clock_rate)
|
|
&& (pt->channels == 0 || pt->channels == rfc_pt->channels))
|
|
{
|
|
pt->payload_type = rfc_pt->payload_type;
|
|
if (!pt->clock_rate)
|
|
pt->clock_rate = rfc_pt->clock_rate;
|
|
if (!pt->channels)
|
|
pt->channels = rfc_pt->channels;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// init params strings
|
|
char full_encoding[64];
|
|
char full_full_encoding[64];
|
|
char params[32] = "";
|
|
|
|
snprintf(full_full_encoding, sizeof(full_full_encoding), STR_FORMAT "/%u/%i", STR_FMT(&pt->encoding),
|
|
pt->clock_rate,
|
|
pt->channels);
|
|
|
|
if (pt->channels > 1) {
|
|
strcpy(full_encoding, full_full_encoding);
|
|
snprintf(params, sizeof(params), "%i", pt->channels);
|
|
}
|
|
else
|
|
snprintf(full_encoding, sizeof(full_encoding), STR_FORMAT "/%u", STR_FMT(&pt->encoding),
|
|
pt->clock_rate);
|
|
|
|
// allocate strings
|
|
str_init_dup_str(&pt->encoding, &pt->encoding);
|
|
str_init_dup(&pt->encoding_with_params, full_encoding);
|
|
str_init_dup(&pt->encoding_with_full_params, full_full_encoding);
|
|
str_init_dup(&pt->encoding_parameters, params);
|
|
str_init_dup_str(&pt->format_parameters, &pt->format_parameters);
|
|
str_init_dup_str(&pt->codec_opts, &pt->codec_opts);
|
|
|
|
// allocate everything from the rtcp-fb list
|
|
for (GList *l = pt->rtcp_fb.head; l; l = l->next) {
|
|
str *fb = l->data;
|
|
l->data = str_dup(fb);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#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 (!handler_silence_block(h, mp))
|
|
return 0;
|
|
|
|
uint32_t ts = ntohl(mp->rtp->timestamp);
|
|
codec_calc_jitter(mp->ssrc_in, ts, h->source_pt.clock_rate, &mp->tv);
|
|
codec_calc_lost(mp->ssrc_in, ntohs(mp->rtp->seq_num));
|
|
|
|
// save original payload in case DTMF mangles it
|
|
str orig_raw = mp->raw;
|
|
|
|
// provide an uninitialised buffer as potential output storage for DTMF
|
|
char buf[sizeof(*mp->rtp) + sizeof(struct telephone_event_payload) + RTP_BUFFER_TAIL_ROOM];
|
|
|
|
// default function to return packets
|
|
void (*add_packet_fn)(struct media_packet *mp, unsigned int clockrate) = codec_add_raw_packet;
|
|
|
|
unsigned int duplicates = 0;
|
|
|
|
// check for DTMF injection
|
|
if (h->dtmf_payload_type != -1) {
|
|
struct codec_ssrc_handler *ch = get_ssrc(mp->ssrc_in->parent->h.ssrc, h->ssrc_hash);
|
|
if (ch) {
|
|
uint64_t ts64 = ntohl(mp->rtp->timestamp);
|
|
|
|
str ev_pl = { .s = buf + sizeof(*mp->rtp) };
|
|
|
|
int is_dtmf = dtmf_event_payload(&ev_pl, &ts64,
|
|
(uint64_t) h->source_pt.clock_rate * h->source_pt.ptime / 1000,
|
|
&ch->dtmf_event, &ch->dtmf_events);
|
|
if (is_dtmf) {
|
|
// fix up RTP header
|
|
struct rtp_header *r = (void *) buf;
|
|
*r = *mp->rtp;
|
|
r->m_pt = h->dtmf_payload_type;
|
|
r->timestamp = htonl(ts64);
|
|
if (is_dtmf == 1)
|
|
r->m_pt |= 0x80;
|
|
else if (is_dtmf == 3) // end event
|
|
duplicates = 2;
|
|
mp->rtp = r;
|
|
mp->raw.s = buf;
|
|
mp->raw.len = ev_pl.len + sizeof(*mp->rtp);
|
|
|
|
add_packet_fn = codec_add_raw_packet_dup;
|
|
}
|
|
else if (!ch->dtmf_events.length)
|
|
ML_CLEAR(mp->media->monologue, DTMF_INJECTION_ACTIVE);
|
|
|
|
obj_put(&ch->h);
|
|
}
|
|
}
|
|
|
|
// substitute out SSRC etc
|
|
mp->rtp->ssrc = htonl(mp->ssrc_out->parent->h.ssrc);
|
|
|
|
// to track our seq
|
|
unsigned short seq = ntohs(mp->rtp->seq_num);
|
|
|
|
while (true) {
|
|
mp->rtp->seq_num = htons(seq + mp->ssrc_out->parent->seq_diff);
|
|
|
|
// keep track of other stats here?
|
|
|
|
__buffer_delay_raw(h->delay_buffer, h, add_packet_fn, mp, h->source_pt.clock_rate);
|
|
|
|
if (duplicates == 0)
|
|
break;
|
|
duplicates--;
|
|
mp->ssrc_out->parent->seq_diff++;
|
|
}
|
|
|
|
// restore original in case it was mangled
|
|
mp->raw = orig_raw;
|
|
|
|
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;
|
|
ch->ptime = h->source_pt.ptime;
|
|
if (!ch->ptime)
|
|
ch->ptime = 20;
|
|
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, false);
|
|
}
|
|
|
|
void codec_add_dtmf_event(struct codec_ssrc_handler *ch, int code, int level, uint64_t ts, bool injected) {
|
|
struct dtmf_event new_ev = { .code = code, .volume = level, .ts = ts };
|
|
ilogs(transcoding, LOG_DEBUG, "DTMF event state change: code %i, volume %i, TS %lu",
|
|
new_ev.code, new_ev.volume, (unsigned long) ts);
|
|
dtmf_dsp_event(&new_ev, &ch->dtmf_state, ch->handler->media, ch->handler->source_pt.clock_rate,
|
|
ts + ch->csch.first_ts, injected);
|
|
|
|
// add to queue if we're doing PCM -> DTMF event conversion
|
|
// this does not capture events when doing DTMF delay (dtmf_payload_type == -1)
|
|
// unless this is an injected event, in which case we check the real payload type
|
|
if (ch->handler->dtmf_payload_type != -1 || (injected && ch->handler->real_dtmf_payload_type != -1)) {
|
|
struct dtmf_event *ev = g_slice_alloc(sizeof(*ev));
|
|
*ev = new_ev;
|
|
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, struct ssrc_ctx *ssrc_in) {
|
|
if (!ch || !ch->encoder) {
|
|
if (!ssrc_in)
|
|
return 0;
|
|
uint64_t cur = atomic64_get(&ssrc_in->last_ts);
|
|
// return the TS of the next expected packet
|
|
if (ch)
|
|
cur += (uint64_t) ch->ptime * ch->handler->source_pt.clock_rate / 1000;
|
|
return cur;
|
|
}
|
|
return ch->encoder->fifo_pts;
|
|
}
|
|
|
|
void codec_decoder_skip_pts(struct codec_ssrc_handler *ch, uint64_t pts) {
|
|
ilogs(transcoding, 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;
|
|
ilogs(transcoding, 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->encoder_callback.amr.cmr_in = GPOINTER_TO_UINT(ptr);
|
|
media->encoder_callback.amr.cmr_in_ts = rtpe_now;
|
|
break;
|
|
case CE_AMR_SEND_CMR:
|
|
// ignore locking and races for this
|
|
media->encoder_callback.amr.cmr_out = GPOINTER_TO_UINT(ptr);
|
|
media->encoder_callback.amr.cmr_out_ts = rtpe_now;
|
|
break;
|
|
case CE_EVS_CMR_RECV:
|
|
// ignore locking and races for this
|
|
media->encoder_callback.evs.cmr_in = GPOINTER_TO_UINT(ptr);
|
|
media->encoder_callback.evs.cmr_in_ts = rtpe_now;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// must be locked
|
|
static void __delay_buffer_schedule(struct delay_buffer *dbuf) {
|
|
if (dbuf->ct.next.tv_sec) // already scheduled?
|
|
return;
|
|
|
|
struct delay_frame *dframe = g_queue_peek_tail(&dbuf->frames);
|
|
if (!dframe)
|
|
return;
|
|
|
|
struct timeval to_run = dframe->mp.tv;
|
|
timeval_add_usec(&to_run, dbuf->delay * 1000);
|
|
dbuf->ct.next = to_run;
|
|
timerthread_obj_schedule_abs(&dbuf->ct.tt_obj, &dbuf->ct.next);
|
|
}
|
|
|
|
static bool __buffer_delay_do_direct(struct delay_buffer *dbuf) {
|
|
if (!dbuf)
|
|
return true;
|
|
LOCK(&dbuf->lock);
|
|
if (dbuf->delay == 0 && dbuf->frames.length == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static int delay_frame_cmp(const void *A, const void *B, void *ptr) {
|
|
const struct delay_frame *a = A, *b = B;
|
|
return -1 * timeval_cmp(&a->mp.tv, &b->mp.tv);
|
|
}
|
|
|
|
// consumes frame
|
|
// `frame` can be NULL (discarded/lost packet)
|
|
static void __buffer_delay_frame(struct delay_buffer *dbuf, struct codec_ssrc_handler *ch,
|
|
encoder_input_func_t input_func, AVFrame *frame, struct media_packet *mp, uint32_t ts)
|
|
{
|
|
if (__buffer_delay_do_direct(dbuf)) {
|
|
// input now
|
|
if (frame) {
|
|
input_func(ch->encoder, frame, ch->handler->packet_encoded, ch, mp);
|
|
av_frame_free(&frame);
|
|
}
|
|
return;
|
|
}
|
|
|
|
struct delay_frame *dframe = g_slice_alloc0(sizeof(*dframe));
|
|
dframe->frame = frame;
|
|
dframe->encoder_func = input_func;
|
|
dframe->ts = ts;
|
|
dframe->ch = obj_get(&ch->h);
|
|
dframe->handler = ch->handler;
|
|
media_packet_copy(&dframe->mp, mp);
|
|
|
|
LOCK(&dbuf->lock);
|
|
g_queue_insert_sorted(&dbuf->frames, dframe, delay_frame_cmp, NULL);
|
|
|
|
__delay_buffer_schedule(dbuf);
|
|
|
|
}
|
|
|
|
static void __buffer_delay_raw(struct delay_buffer *dbuf, struct codec_handler *handler,
|
|
raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate)
|
|
{
|
|
if (__buffer_delay_do_direct(dbuf)) {
|
|
// direct passthrough
|
|
input_func(mp, clockrate);
|
|
return;
|
|
}
|
|
|
|
struct delay_frame *dframe = g_slice_alloc0(sizeof(*dframe));
|
|
dframe->raw_func = input_func;
|
|
dframe->clockrate = clockrate;
|
|
dframe->handler = handler;
|
|
media_packet_copy(&dframe->mp, mp);
|
|
|
|
// also copy packet payload
|
|
dframe->mp.raw = mp->raw;
|
|
dframe->mp.raw.s = g_malloc(mp->raw.len + RTP_BUFFER_TAIL_ROOM);
|
|
memcpy(dframe->mp.raw.s, mp->raw.s, mp->raw.len);
|
|
|
|
LOCK(&dbuf->lock);
|
|
g_queue_insert_sorted(&dbuf->frames, dframe, delay_frame_cmp, NULL);
|
|
|
|
__delay_buffer_schedule(dbuf);
|
|
}
|
|
|
|
// returns 1 if packet has been consumed
|
|
static int __buffer_delay_packet(struct delay_buffer *dbuf,
|
|
struct codec_ssrc_handler *ch,
|
|
struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet,
|
|
unsigned long ts_delay,
|
|
int payload_type,
|
|
packet_input_func_t packet_func, struct media_packet *mp, unsigned int clockrate)
|
|
{
|
|
if (__buffer_delay_do_direct(dbuf)) {
|
|
// direct passthrough
|
|
packet_func(ch, input_ch, packet, ts_delay, payload_type, mp);
|
|
return 0;
|
|
}
|
|
|
|
struct delay_frame *dframe = g_slice_alloc0(sizeof(*dframe));
|
|
dframe->packet_func = packet_func;
|
|
dframe->clockrate = clockrate;
|
|
dframe->ch = ch ? obj_get(&ch->h) : NULL;
|
|
dframe->input_ch = input_ch ? obj_get(&input_ch->h) : NULL;
|
|
dframe->ts_delay = ts_delay;
|
|
dframe->payload_type = payload_type;
|
|
dframe->packet = packet;
|
|
dframe->ts = packet->ts;
|
|
dframe->handler = ch->handler;
|
|
media_packet_copy(&dframe->mp, mp);
|
|
|
|
LOCK(&dbuf->lock);
|
|
g_queue_insert_sorted(&dbuf->frames, dframe, delay_frame_cmp, NULL);
|
|
|
|
__delay_buffer_schedule(dbuf);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void __buffer_delay_seq(struct delay_buffer *dbuf, struct media_packet *mp, int seq_adj) {
|
|
if (!mp->ssrc_out)
|
|
return;
|
|
|
|
if (__buffer_delay_do_direct(dbuf)) {
|
|
mp->ssrc_out->parent->seq_diff += seq_adj;
|
|
return;
|
|
}
|
|
|
|
LOCK(&dbuf->lock);
|
|
|
|
// peg the adjustment to the most recent frame if any
|
|
struct delay_frame *dframe = g_queue_peek_head(&dbuf->frames);
|
|
if (!dframe) {
|
|
mp->ssrc_out->parent->seq_diff += seq_adj;
|
|
return;
|
|
}
|
|
|
|
dframe->seq_adj += seq_adj;
|
|
}
|
|
|
|
// consumes `packet` if buffered (returns 1)
|
|
// `packet` can be NULL (discarded packet for seq tracking)
|
|
static int __buffer_dtx(struct dtx_buffer *dtxb, struct codec_ssrc_handler *decoder_handler,
|
|
struct codec_ssrc_handler *input_handler,
|
|
struct transcode_packet *packet, struct media_packet *mp,
|
|
int (*dtx_func)(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet,
|
|
struct media_packet *mp))
|
|
{
|
|
if (!dtxb || !mp->sfd || !mp->ssrc_in || !mp->ssrc_out)
|
|
return 0;
|
|
|
|
unsigned long ts = packet ? packet->ts : 0;
|
|
|
|
// allocate packet object
|
|
struct dtx_packet *dtxp = g_slice_alloc0(sizeof(*dtxp));
|
|
dtxp->packet = packet;
|
|
dtxp->dtx_func = dtx_func;
|
|
if (decoder_handler)
|
|
dtxp->decoder_handler = obj_get(&decoder_handler->h);
|
|
if (input_handler)
|
|
dtxp->input_handler = obj_get(&input_handler->h);
|
|
media_packet_copy(&dtxp->mp, mp);
|
|
|
|
// add to processing queue
|
|
|
|
mutex_lock(&dtxb->lock);
|
|
|
|
dtxb->start = rtpe_now.tv_sec;
|
|
g_queue_push_tail(&dtxb->packets, dtxp);
|
|
ilogs(dtx, LOG_DEBUG, "Adding packet (TS %lu) to DTX buffer; now %i packets in DTX queue",
|
|
ts, dtxb->packets.length);
|
|
|
|
// schedule timer if not running yet
|
|
if (!dtxb->ct.next.tv_sec) {
|
|
if (!dtxb->ssrc)
|
|
dtxb->ssrc = mp->ssrc_in->parent->h.ssrc;
|
|
dtxb->ct.next = mp->tv;
|
|
timeval_add_usec(&dtxb->ct.next, rtpe_config.dtx_delay * 1000);
|
|
timerthread_obj_schedule_abs(&dtxb->ct.tt_obj, &dtxb->ct.next);
|
|
}
|
|
|
|
// packet now consumed if there was one
|
|
int ret = packet ? 1 : 0;
|
|
packet = NULL;
|
|
|
|
mutex_unlock(&dtxb->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void delay_frame_free(struct delay_frame *dframe) {
|
|
av_frame_free(&dframe->frame);
|
|
g_free(dframe->mp.raw.s);
|
|
media_packet_release(&dframe->mp);
|
|
if (dframe->ch)
|
|
obj_put(&dframe->ch->h);
|
|
if (dframe->input_ch)
|
|
obj_put(&dframe->input_ch->h);
|
|
if (dframe->packet)
|
|
__transcode_packet_free(dframe->packet);
|
|
g_slice_free1(sizeof(*dframe), dframe);
|
|
}
|
|
static void delay_frame_send(struct delay_frame *dframe) {
|
|
// XXX this should be unified with other instances of the same code
|
|
struct sink_handler *sh = &dframe->mp.sink;
|
|
struct packet_stream *sink = sh->sink;
|
|
|
|
if (!sink)
|
|
media_socket_dequeue(&dframe->mp, NULL); // just free
|
|
else {
|
|
if (sh->handler && media_packet_encrypt(sh->handler->out->rtp_crypt, sink, &dframe->mp))
|
|
ilogs(transcoding, LOG_ERR | LOG_FLAG_LIMIT, "Error encrypting buffered RTP media");
|
|
|
|
mutex_lock(&sink->out_lock);
|
|
if (media_socket_dequeue(&dframe->mp, sink))
|
|
ilogs(transcoding, LOG_ERR | LOG_FLAG_LIMIT,
|
|
"Error sending buffered media to RTP sink");
|
|
mutex_unlock(&sink->out_lock);
|
|
}
|
|
}
|
|
static void delay_frame_flush(struct delay_buffer *dbuf, struct delay_frame *dframe) {
|
|
// call is locked in W here
|
|
__delay_frame_process(dbuf, dframe);
|
|
delay_frame_send(dframe);
|
|
delay_frame_free(dframe);
|
|
}
|
|
static void dtx_packet_free(struct dtx_packet *dtxp) {
|
|
if (dtxp->packet)
|
|
__transcode_packet_free(dtxp->packet);
|
|
media_packet_release(&dtxp->mp);
|
|
if (dtxp->decoder_handler)
|
|
obj_put(&dtxp->decoder_handler->h);
|
|
if (dtxp->input_handler)
|
|
obj_put(&dtxp->input_handler->h);
|
|
g_slice_free1(sizeof(*dtxp), dtxp);
|
|
}
|
|
static void delay_buffer_stop(struct delay_buffer **pcmbp) {
|
|
codec_timer_stop((struct codec_timer **) pcmbp);
|
|
}
|
|
static void dtx_buffer_stop(struct dtx_buffer **dtxbp) {
|
|
codec_timer_stop((struct codec_timer **) dtxbp);
|
|
}
|
|
|
|
|
|
static void delay_frame_manipulate(struct delay_frame *dframe) {
|
|
struct call_media *media = dframe->mp.media;
|
|
if (!media)
|
|
return;
|
|
|
|
AVFrame *frame = dframe->frame;
|
|
|
|
struct call_monologue *ml = media->monologue;
|
|
enum block_dtmf_mode mode = dtmf_get_block_mode(dframe->mp.call, ml);
|
|
|
|
if (mode == BLOCK_DTMF_OFF && media->monologue->dtmf_delay == 0)
|
|
return;
|
|
|
|
mutex_lock(&media->dtmf_lock);
|
|
struct dtmf_event *dtmf_recv = is_in_dtmf_event(&media->dtmf_recv, dframe->ts, frame->sample_rate,
|
|
media->buffer_delay, media->buffer_delay);
|
|
struct dtmf_event *dtmf_send = is_in_dtmf_event(&media->dtmf_send, dframe->ts, frame->sample_rate,
|
|
0, 0);
|
|
mutex_unlock(&media->dtmf_lock);
|
|
|
|
if (mode == BLOCK_DTMF_OFF) {
|
|
if (!dtmf_send) {
|
|
mode = BLOCK_DTMF_SILENCE;
|
|
|
|
if (dframe->ch->handler->real_dtmf_payload_type != -1) {
|
|
// add end event to queue
|
|
if (dframe->ch->dtmf_event.code) {
|
|
struct dtmf_event *ev = g_slice_alloc0(sizeof(*ev));
|
|
uint64_t ts = dframe->ch->encoder ? dframe->ch->encoder->next_pts
|
|
: dframe->ts;
|
|
*ev = (struct dtmf_event) { .code = 0, .volume = 0, .ts = ts };
|
|
g_queue_push_tail(&dframe->ch->dtmf_events, ev);
|
|
}
|
|
}
|
|
|
|
if (!dtmf_recv)
|
|
return;
|
|
}
|
|
else
|
|
mode = dtmf_send->block_dtmf;
|
|
}
|
|
else if (!dtmf_recv)
|
|
return;
|
|
|
|
// XXX this should be used for DTMF injection instead of a separate codec handler
|
|
|
|
switch (mode) {
|
|
case BLOCK_DTMF_OFF:
|
|
// DTMF delay mode: play original DTMF
|
|
// `dtmf_send` is valid ONLY HERE
|
|
if (dframe->ch->handler->real_dtmf_payload_type != -1) {
|
|
// add event to handler queue so the packet can be translated
|
|
// to DTMF event packet.
|
|
memset(frame->extended_data[0], 0, frame->linesize[0]);
|
|
// XXX quite some redundant operations here: first the incoming
|
|
// DTMF event is decoded to audio, which is then later (maybe) replaced
|
|
// by silence. when the delayed DTMF is reproduced, the frame samples
|
|
// are first filled with silence, and then replaced
|
|
// by the DTMF event packet in packet_encoded_rtp().
|
|
if (dframe->ch->dtmf_event.code != dtmf_send->code) {
|
|
// XXX this should be switched to proper state tracking instead
|
|
// of using start/stop events
|
|
struct dtmf_event *ev = g_slice_alloc0(sizeof(*ev));
|
|
uint64_t ts = dframe->ch->encoder ? dframe->ch->encoder->next_pts
|
|
: dframe->ts;
|
|
*ev = (struct dtmf_event) { .code = dtmf_send->code,
|
|
.volume = -1 * dtmf_send->volume,
|
|
.ts = ts };
|
|
g_queue_push_tail(&dframe->ch->dtmf_events, ev);
|
|
}
|
|
}
|
|
else {
|
|
// fill with DTMF PCM
|
|
frame_fill_dtmf_samples(frame->format, frame->extended_data[0], dframe->ts,
|
|
frame->nb_samples, dtmf_code_from_char(dtmf_send->code),
|
|
dtmf_send->volume, frame->sample_rate,
|
|
GET_CHANNELS(frame));
|
|
}
|
|
break;
|
|
case BLOCK_DTMF_SILENCE:
|
|
memset(frame->extended_data[0], 0, frame->linesize[0]);
|
|
break;
|
|
case BLOCK_DTMF_TONE:;
|
|
unsigned int freq = 0;
|
|
if (ml->tone_freqs && ml->tone_freqs->len)
|
|
freq = g_array_index(ml->tone_freqs, unsigned int,
|
|
dtmf_recv->index % ml->tone_freqs->len);
|
|
frame_fill_tone_samples(frame->format, frame->extended_data[0], dframe->ts,
|
|
frame->nb_samples, freq ?: 400,
|
|
ml->tone_vol ? : 10, frame->sample_rate, GET_CHANNELS(frame));
|
|
break;
|
|
case BLOCK_DTMF_ZERO:
|
|
case BLOCK_DTMF_DTMF:
|
|
// if we have DTMF output, use silence, otherwise use a DTMF zero
|
|
if (dframe->ch->handler->dtmf_payload_type != -1)
|
|
memset(frame->extended_data[0], 0, frame->linesize[0]);
|
|
else
|
|
frame_fill_dtmf_samples(frame->format, frame->extended_data[0],
|
|
dframe->ts,
|
|
frame->nb_samples, dtmf_code_from_char(ml->dtmf_digit),
|
|
ml->tone_vol ? : 10, frame->sample_rate,
|
|
GET_CHANNELS(frame));
|
|
break;
|
|
case BLOCK_DTMF_RANDOM:
|
|
frame_fill_dtmf_samples(frame->format, frame->extended_data[0], dframe->ts,
|
|
frame->nb_samples, dtmf_recv->rand_code - '0',
|
|
10, frame->sample_rate,
|
|
GET_CHANNELS(frame));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
static void delay_packet_manipulate(struct delay_frame *dframe) {
|
|
struct call_media *media = dframe->mp.media;
|
|
if (!media)
|
|
return;
|
|
if (!dframe->handler)
|
|
return;
|
|
|
|
struct media_packet *mp = &dframe->mp;
|
|
|
|
if (is_in_dtmf_event(&media->dtmf_recv, dframe->ts, dframe->clockrate, media->buffer_delay,
|
|
media->buffer_delay))
|
|
{
|
|
// is this a DTMF event packet?
|
|
if (!dframe->handler->source_pt.codec_def || !dframe->handler->source_pt.codec_def->dtmf)
|
|
return;
|
|
|
|
struct call_monologue *ml = media->monologue;
|
|
enum block_dtmf_mode mode = dtmf_get_block_mode(dframe->mp.call, ml);
|
|
|
|
// this can be a "raw" or "packet" - get the appropriate payload
|
|
str *payload = &mp->raw;
|
|
if (dframe->packet)
|
|
payload = dframe->packet->payload;
|
|
|
|
struct telephone_event_payload *dtmf = (void *) payload->s;
|
|
if (payload->len < sizeof(*dtmf))
|
|
return;
|
|
|
|
switch (mode) {
|
|
case BLOCK_DTMF_ZERO:
|
|
case BLOCK_DTMF_DTMF:
|
|
dtmf->event = dtmf_code_from_char(ml->dtmf_digit);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
static void __delay_frame_process(struct delay_buffer *dbuf, struct delay_frame *dframe) {
|
|
struct codec_ssrc_handler *csh = dframe->ch;
|
|
|
|
if (csh && csh->handler && csh->encoder && dframe->encoder_func) {
|
|
delay_frame_manipulate(dframe);
|
|
dframe->encoder_func(csh->encoder, dframe->frame, csh->handler->packet_encoded,
|
|
csh, &dframe->mp);
|
|
}
|
|
else if (dframe->raw_func) {
|
|
delay_packet_manipulate(dframe);
|
|
dframe->raw_func(&dframe->mp, dframe->clockrate);
|
|
}
|
|
else if (dframe->packet_func && dframe->packet) {
|
|
delay_packet_manipulate(dframe);
|
|
dframe->packet_func(csh, dframe->input_ch, dframe->packet, dframe->ts_delay,
|
|
dframe->payload_type, &dframe->mp);
|
|
}
|
|
|
|
if (dframe->seq_adj)
|
|
dframe->mp.ssrc_out->parent->seq_diff += dframe->seq_adj;
|
|
}
|
|
static void __delay_send_later(struct codec_timer *ct) {
|
|
struct delay_buffer *dbuf = (void *) ct;
|
|
|
|
struct call *call = NULL;
|
|
struct delay_frame *dframe = NULL;
|
|
|
|
{
|
|
// short-term lock - copy out references to all relevant objects
|
|
LOCK(&dbuf->lock);
|
|
|
|
call = dbuf->call;
|
|
if (call)
|
|
obj_get(call);
|
|
|
|
dframe = g_queue_pop_tail(&dbuf->frames);
|
|
}
|
|
|
|
if (!call) // do nothing
|
|
goto out;
|
|
|
|
// we can now do a top-down lock
|
|
rwlock_lock_r(&call->master_lock);
|
|
log_info_call(call);
|
|
|
|
if (!dframe)
|
|
goto out;
|
|
|
|
__ssrc_lock_both(&dframe->mp);
|
|
|
|
__delay_frame_process(dbuf, dframe);
|
|
|
|
__ssrc_unlock_both(&dframe->mp);
|
|
|
|
delay_frame_send(dframe);
|
|
|
|
{
|
|
// schedule next run
|
|
LOCK(&dbuf->lock);
|
|
dbuf->ct.next.tv_sec = 0;
|
|
__delay_buffer_schedule(dbuf);
|
|
}
|
|
|
|
out:
|
|
// release all references
|
|
if (call) {
|
|
rwlock_unlock_r(&call->master_lock);
|
|
obj_put(call);
|
|
log_info_pop();
|
|
}
|
|
if (dframe)
|
|
delay_frame_free(dframe);
|
|
}
|
|
|
|
|
|
static bool __dtx_drift_shift(struct dtx_buffer *dtxb, unsigned long ts,
|
|
long tv_diff, long ts_diff,
|
|
struct codec_ssrc_handler *ch)
|
|
{
|
|
bool discard = false;
|
|
|
|
if (tv_diff < rtpe_config.dtx_delay * 1000) {
|
|
// timer underflow
|
|
ilogs(dtx, LOG_DEBUG, "Packet reception time has caught up with DTX timer "
|
|
"(%li ms < %i ms), "
|
|
"pushing DTX timer forward my %i ms",
|
|
tv_diff / 1000, rtpe_config.dtx_delay, rtpe_config.dtx_shift);
|
|
timeval_add_usec(&dtxb->ct.next, rtpe_config.dtx_shift * 1000);
|
|
}
|
|
else if (ts_diff < dtxb->tspp) {
|
|
// TS underflow
|
|
// special case: DTMF timestamps are static
|
|
if (ts_diff == 0 && ch->handler->source_pt.codec_def->dtmf) {
|
|
;
|
|
}
|
|
else {
|
|
ilogs(dtx, LOG_DEBUG, "Packet timestamps have caught up with DTX timer "
|
|
"(TS %lu, diff %li), "
|
|
"pushing DTX timer forward by %i ms and discarding packet",
|
|
ts, ts_diff, rtpe_config.dtx_shift);
|
|
timeval_add_usec(&dtxb->ct.next, rtpe_config.dtx_shift * 1000);
|
|
discard = true;
|
|
}
|
|
}
|
|
else if (dtxb->packets.length >= rtpe_config.dtx_buffer) {
|
|
// inspect TS is most recent packet
|
|
struct dtx_packet *dtxp_last = g_queue_peek_tail(&dtxb->packets);
|
|
ts_diff = dtxp_last->packet ? dtxp_last->packet->ts - ts : 0;
|
|
long long ts_diff_us = (long long) ts_diff * 1000000 / dtxb->clockrate;
|
|
if (ts_diff_us >= (long long) rtpe_config.dtx_lag * 1000) {
|
|
// overflow
|
|
ilogs(dtx, LOG_DEBUG, "DTX timer queue overflowing (%i packets in queue, "
|
|
"%lli ms delay), speeding up DTX timer by %i ms",
|
|
dtxb->packets.length, ts_diff_us / 1000, rtpe_config.dtx_shift);
|
|
timeval_add_usec(&dtxb->ct.next, rtpe_config.dtx_shift * -1000);
|
|
}
|
|
}
|
|
|
|
return discard;
|
|
}
|
|
static bool __dtx_drift_drop(struct dtx_buffer *dtxb, unsigned long ts,
|
|
long tv_diff, long ts_diff,
|
|
struct codec_ssrc_handler *ch)
|
|
{
|
|
bool discard = false;
|
|
|
|
if (ts_diff < dtxb->tspp) {
|
|
// TS underflow
|
|
// special case: DTMF timestamps are static
|
|
if (ts_diff == 0 && ch->handler->source_pt.codec_def->dtmf) {
|
|
;
|
|
}
|
|
else {
|
|
ilogs(dtx, LOG_DEBUG, "Packet timestamps have caught up with DTX timer "
|
|
"(TS %lu, diff %li), "
|
|
"adjusting input TS clock back by one frame (%i)",
|
|
ts, ts_diff, dtxb->tspp);
|
|
dtxb->head_ts -= dtxb->tspp;
|
|
}
|
|
}
|
|
else if (dtxb->packets.length >= rtpe_config.dtx_buffer) {
|
|
// inspect TS is most recent packet
|
|
struct dtx_packet *dtxp_last = g_queue_peek_tail(&dtxb->packets);
|
|
ts_diff = dtxp_last->packet ? dtxp_last->packet->ts - ts : 0;
|
|
long long ts_diff_us = (long long) ts_diff * 1000000 / dtxb->clockrate;
|
|
if (ts_diff_us >= (long long) rtpe_config.dtx_lag * 1000) {
|
|
// overflow
|
|
ilogs(dtx, LOG_DEBUG, "DTX timer queue overflowing (%i packets in queue, "
|
|
"%lli ms delay), discarding packet",
|
|
dtxb->packets.length, ts_diff_us / 1000);
|
|
discard = true;
|
|
}
|
|
}
|
|
|
|
return discard;
|
|
}
|
|
static bool __dtx_handle_drift(struct dtx_buffer *dtxb, unsigned long ts,
|
|
long tv_diff, long ts_diff,
|
|
struct codec_ssrc_handler *ch)
|
|
{
|
|
if (rtpe_config.dtx_shift)
|
|
return __dtx_drift_shift(dtxb, ts, tv_diff, ts_diff, ch);
|
|
return __dtx_drift_drop(dtxb, ts, tv_diff, ts_diff, ch);
|
|
}
|
|
static void __dtx_send_later(struct codec_timer *ct) {
|
|
struct dtx_buffer *dtxb = (void *) ct;
|
|
struct media_packet mp_copy = {0,};
|
|
int ret = 0;
|
|
unsigned long ts;
|
|
int p_left = 0;
|
|
long tv_diff = -1, ts_diff = 0;
|
|
|
|
mutex_lock(&dtxb->lock);
|
|
|
|
if (dtxb->call)
|
|
log_info_call(dtxb->call);
|
|
|
|
// vars assigned in the loop
|
|
struct dtx_packet *dtxp;
|
|
struct call *call;
|
|
struct codec_ssrc_handler *ch;
|
|
struct packet_stream *ps;
|
|
struct codec_ssrc_handler *input_ch;
|
|
|
|
while (true) {
|
|
// do we have a packet?
|
|
dtxp = g_queue_peek_head(&dtxb->packets);
|
|
if (dtxp) {
|
|
// inspect head packet and check TS, see if it's ready to be decoded
|
|
ts = dtxp->packet ? dtxp->packet->ts : dtxb->head_ts;
|
|
ts_diff = ts - dtxb->head_ts;
|
|
long long ts_diff_us = (long long) ts_diff * 1000000 / dtxb->clockrate;
|
|
|
|
if (!dtxb->head_ts)
|
|
; // first packet
|
|
else if (ts_diff < 0)
|
|
ilogs(dtx, LOG_DEBUG, "DTX timestamp reset (from %lu to %lu)", dtxb->head_ts, ts);
|
|
else if (ts_diff_us > MAX(20 * rtpe_config.dtx_delay, 200000))
|
|
ilogs(dtx, LOG_DEBUG, "DTX timestamp reset (from %lu to %lu = %lli ms)",
|
|
dtxb->head_ts, ts, ts_diff_us);
|
|
else if (ts_diff >= dtxb->tspp * 2) {
|
|
ilogs(dtx, LOG_DEBUG, "First packet in DTX buffer not ready yet (packet TS %lu, "
|
|
"DTX TS %lu, diff %li)",
|
|
ts, dtxb->head_ts, ts_diff);
|
|
dtxp = NULL;
|
|
}
|
|
|
|
// go or no go?
|
|
if (dtxp)
|
|
g_queue_pop_head(&dtxb->packets);
|
|
}
|
|
|
|
p_left = dtxb->packets.length;
|
|
|
|
if (dtxp) {
|
|
// save the `mp` for possible future DTX
|
|
media_packet_release(&dtxb->last_mp);
|
|
media_packet_copy(&dtxb->last_mp, &dtxp->mp);
|
|
media_packet_copy(&mp_copy, &dtxp->mp);
|
|
if (dtxb->head_ts)
|
|
ts_diff = dtxp->packet ? dtxp->packet->ts - dtxb->head_ts : 0;
|
|
else
|
|
ts_diff = dtxb->tspp; // first packet
|
|
if (dtxp->packet)
|
|
ts = dtxb->head_ts = dtxp->packet->ts;
|
|
else
|
|
ts = dtxb->head_ts;
|
|
tv_diff = timeval_diff(&rtpe_now, &mp_copy.tv);
|
|
}
|
|
else {
|
|
// no packet ready to decode: DTX
|
|
media_packet_copy(&mp_copy, &dtxb->last_mp);
|
|
// shift forward TS
|
|
dtxb->head_ts += dtxb->tspp;
|
|
ts = dtxb->head_ts;
|
|
}
|
|
ps = mp_copy.stream;
|
|
log_info_stream_fd(mp_copy.sfd);
|
|
|
|
// copy out other fields so we can unlock
|
|
ch = (dtxp && dtxp->decoder_handler) ? obj_get(&dtxp->decoder_handler->h)
|
|
: NULL;
|
|
if (!ch && dtxb->csh)
|
|
ch = obj_get(&dtxb->csh->h);
|
|
input_ch = (dtxp && dtxp->input_handler) ? obj_get(&dtxp->input_handler->h) : NULL;
|
|
call = dtxb->call ? obj_get(dtxb->call) : NULL;
|
|
|
|
// check but DTX buffer shutdown conditions
|
|
bool shutdown = false;
|
|
if (!call)
|
|
shutdown = true;
|
|
else if (!ch)
|
|
shutdown = true;
|
|
else if (!ps)
|
|
shutdown = true;
|
|
else if (!ps->ssrc_in[0])
|
|
shutdown = true;
|
|
else if (dtxb->ssrc != ps->ssrc_in[0]->parent->h.ssrc)
|
|
shutdown = true;
|
|
else if (dtxb->ct.next.tv_sec == 0)
|
|
shutdown = true;
|
|
else {
|
|
shutdown = true; // default if no most used PTs are known
|
|
|
|
for (int i = 0; i < ps->ssrc_in[0]->tracker.most_len; i++) {
|
|
unsigned char most_pt = ps->ssrc_in[0]->tracker.most[i];
|
|
shutdown = false;
|
|
// we are good if the most used PT is
|
|
// either us
|
|
if (ch->handler->source_pt.payload_type == most_pt)
|
|
break;
|
|
// or our input PT (which is the audio PT if we are supplemental)
|
|
if (ch->handler->input_handler && ch->handler->input_handler->source_pt.payload_type == most_pt)
|
|
break;
|
|
|
|
// looks like codec change, but...
|
|
shutdown = true;
|
|
|
|
// another possibility is that the most used PT is actually a supplemental type. check this,
|
|
// and if true move on to the next most used PT.
|
|
struct rtp_payload_type *pt = g_hash_table_lookup(ps->media->codecs.codecs, GUINT_TO_POINTER(most_pt));
|
|
if (pt && pt->codec_def && pt->codec_def->supplemental)
|
|
continue;
|
|
|
|
// all other cases: codec change
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shutdown) {
|
|
ilogs(dtx, LOG_DEBUG, "DTX buffer for %lx has been shut down", (unsigned long) dtxb->ssrc);
|
|
dtxb->ct.next.tv_sec = 0;
|
|
dtxb->head_ts = 0;
|
|
mutex_unlock(&dtxb->lock);
|
|
goto out; // shut down
|
|
}
|
|
|
|
if (!dtxp) // we need to do DTX
|
|
break;
|
|
|
|
bool discard = __dtx_handle_drift(dtxb, ts, tv_diff, ts_diff, ch);
|
|
|
|
if (!discard)
|
|
break;
|
|
|
|
// release and try again
|
|
mutex_unlock(&dtxb->lock);
|
|
|
|
if (call && mp_copy.ssrc_out) {
|
|
// packet consumed - track seq
|
|
rwlock_lock_r(&call->master_lock);
|
|
__ssrc_lock_both(&mp_copy);
|
|
mp_copy.ssrc_out->parent->seq_diff--;
|
|
__ssrc_unlock_both(&mp_copy);
|
|
rwlock_unlock_r(&call->master_lock);
|
|
}
|
|
if (call)
|
|
obj_put(call);
|
|
if (ch)
|
|
obj_put(&ch->h);
|
|
if (input_ch)
|
|
obj_put(&input_ch->h);
|
|
if (dtxp)
|
|
dtx_packet_free(dtxp);
|
|
media_packet_release(&mp_copy);
|
|
|
|
call = NULL;
|
|
ch = NULL;
|
|
input_ch = NULL;
|
|
dtxp = NULL;
|
|
ps = NULL;
|
|
|
|
mutex_lock(&dtxb->lock);
|
|
}
|
|
|
|
int ptime = dtxb->ptime;
|
|
time_t dtxb_start = dtxb->start;
|
|
|
|
mutex_unlock(&dtxb->lock);
|
|
|
|
rwlock_lock_r(&call->master_lock);
|
|
__ssrc_lock_both(&mp_copy);
|
|
|
|
if (dtxp) {
|
|
ilogs(dtx, LOG_DEBUG, "Decoding DTX-buffered RTP packet (TS %lu) now; "
|
|
"%i packets left in queue", ts, p_left);
|
|
|
|
mp_copy.ptime = -1;
|
|
ret = dtxp->dtx_func(ch, input_ch, dtxp->packet, &mp_copy);
|
|
if (!ret) {
|
|
if (mp_copy.ptime > 0)
|
|
ptime = mp_copy.ptime;
|
|
}
|
|
else
|
|
ilogs(dtx, LOG_WARN | LOG_FLAG_LIMIT,
|
|
"Decoder error while processing buffered RTP packet");
|
|
}
|
|
else {
|
|
int diff = rtpe_now.tv_sec - dtxb_start;
|
|
|
|
if (rtpe_config.max_dtx <= 0 || diff < rtpe_config.max_dtx) {
|
|
ilogs(dtx, LOG_DEBUG, "RTP media for TS %lu missing, triggering DTX", ts);
|
|
|
|
// synthetic packet
|
|
mp_copy.rtp->seq_num = htons(ntohs(mp_copy.rtp->seq_num) + 1);
|
|
|
|
ret = decoder_dtx(ch->decoder, ts, ptime,
|
|
ch->handler->packet_decoded, ch, &mp_copy);
|
|
if (ret)
|
|
ilogs(dtx, LOG_WARN | LOG_FLAG_LIMIT,
|
|
"Decoder error handling DTX/lost packet");
|
|
}
|
|
else {
|
|
ilogs(dtx, LOG_DEBUG, "Stopping DTX at TS %lu", ts);
|
|
|
|
mutex_lock(&dtxb->lock);
|
|
__dtx_shutdown(dtxb);
|
|
mutex_unlock(&dtxb->lock);
|
|
}
|
|
}
|
|
|
|
mutex_lock(&dtxb->lock);
|
|
|
|
if (ptime != dtxb->ptime) {
|
|
dtxb->ptime = ptime;
|
|
dtxb->tspp = ptime * dtxb->clockrate / 1000;
|
|
}
|
|
|
|
// schedule next run
|
|
timeval_add_usec(&dtxb->ct.next, dtxb->ptime * 1000);
|
|
timerthread_obj_schedule_abs(&dtxb->ct.tt_obj, &dtxb->ct.next);
|
|
|
|
mutex_unlock(&dtxb->lock);
|
|
|
|
__ssrc_unlock_both(&mp_copy);
|
|
|
|
if (mp_copy.packets_out.length && ret == 0) {
|
|
struct sink_handler *sh = &mp_copy.sink;
|
|
struct packet_stream *sink = sh->sink;
|
|
|
|
if (!sink)
|
|
media_socket_dequeue(&mp_copy, NULL); // just free
|
|
else {
|
|
if (sh->handler && media_packet_encrypt(sh->handler->out->rtp_crypt, sink, &mp_copy))
|
|
ilogs(dtx, LOG_ERR | LOG_FLAG_LIMIT, "Error encrypting buffered RTP media");
|
|
|
|
mutex_lock(&sink->out_lock);
|
|
if (media_socket_dequeue(&mp_copy, sink))
|
|
ilogs(dtx, LOG_ERR | LOG_FLAG_LIMIT,
|
|
"Error sending buffered media to RTP sink");
|
|
mutex_unlock(&sink->out_lock);
|
|
}
|
|
}
|
|
|
|
rwlock_unlock_r(&call->master_lock);
|
|
|
|
out:
|
|
if (call) {
|
|
obj_put(call);
|
|
log_info_pop();
|
|
}
|
|
if (ch)
|
|
obj_put(&ch->h);
|
|
if (input_ch)
|
|
obj_put(&input_ch->h);
|
|
if (dtxp)
|
|
dtx_packet_free(dtxp);
|
|
media_packet_release(&mp_copy);
|
|
}
|
|
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;
|
|
g_queue_clear_full(&dtxb->packets, (GDestroyNotify) dtx_packet_free);
|
|
}
|
|
static void __delay_buffer_shutdown(struct delay_buffer *dbuf, bool flush) {
|
|
if (flush) {
|
|
while (dbuf->frames.length) {
|
|
struct delay_frame *dframe = g_queue_pop_tail(&dbuf->frames);
|
|
delay_frame_flush(dbuf, dframe);
|
|
}
|
|
}
|
|
else
|
|
g_queue_clear_full(&dbuf->frames, (GDestroyNotify) delay_frame_free);
|
|
if (dbuf->call)
|
|
obj_put(dbuf->call);
|
|
dbuf->call = NULL;
|
|
}
|
|
static void __dtx_free(void *p) {
|
|
struct dtx_buffer *dtxb = p;
|
|
__dtx_shutdown(dtxb);
|
|
media_packet_release(&dtxb->last_mp);
|
|
mutex_destroy(&dtxb->lock);
|
|
}
|
|
static void __delay_buffer_free(void *p) {
|
|
struct delay_buffer *dbuf = p;
|
|
__delay_buffer_shutdown(dbuf, false);
|
|
mutex_destroy(&dbuf->lock);
|
|
}
|
|
static void __dtx_setup(struct codec_ssrc_handler *ch) {
|
|
if (!ch->decoder)
|
|
return;
|
|
if (!decoder_has_dtx(ch->decoder))
|
|
return;
|
|
|
|
if (!rtpe_config.dtx_delay)
|
|
return;
|
|
|
|
struct dtx_buffer *dtx = ch->dtx_buffer;
|
|
if (!dtx) {
|
|
dtx = ch->dtx_buffer = obj_alloc0("dtx_buffer", sizeof(*dtx), __dtx_free);
|
|
dtx->ct.tt_obj.tt = &codec_timers_thread;
|
|
dtx->ct.timer_func = __dtx_send_later;
|
|
mutex_init(&dtx->lock);
|
|
}
|
|
|
|
if (!dtx->csh)
|
|
dtx->csh = obj_get(&ch->h);
|
|
if (!dtx->call)
|
|
dtx->call = obj_get(ch->handler->media->call);
|
|
dtx->ptime = ch->ptime;
|
|
if (dtx->ptime <= 0)
|
|
dtx->ptime = ch->handler->source_pt.codec_def->default_ptime;
|
|
if (dtx->ptime <= 0)
|
|
dtx->ptime = 20;
|
|
ilogs(dtx, LOG_DEBUG, "Using DTX ptime %i based on handler=%i codec=%i", dtx->ptime,
|
|
ch->ptime, ch->handler->source_pt.codec_def->default_ptime);
|
|
dtx->clockrate = ch->handler->source_pt.clock_rate;
|
|
dtx->tspp = dtx->ptime * dtx->clockrate / 1000;
|
|
}
|
|
static void __dtx_buffer_restart(void *p, void *arg) {
|
|
struct codec_ssrc_handler *ch = p;
|
|
__dtx_setup(ch);
|
|
}
|
|
static void __dtx_restart(struct codec_handler *h) {
|
|
ssrc_hash_foreach(h->ssrc_hash, __dtx_buffer_restart, NULL);
|
|
}
|
|
static void __delay_buffer_setup(struct delay_buffer **dbufp,
|
|
struct codec_handler *h, struct call *call, unsigned int delay)
|
|
{
|
|
if (!dbufp)
|
|
return;
|
|
|
|
struct delay_buffer *dbuf = *dbufp;
|
|
|
|
if (!dbuf) {
|
|
if (!delay)
|
|
return;
|
|
dbuf = obj_alloc0("delay_buffer", sizeof(*dbuf), __delay_buffer_free);
|
|
dbuf->ct.tt_obj.tt = &codec_timers_thread;
|
|
dbuf->ct.timer_func = __delay_send_later;
|
|
dbuf->handler = h;
|
|
mutex_init(&dbuf->lock);
|
|
}
|
|
|
|
if (!dbuf->call)
|
|
dbuf->call = obj_get(call);
|
|
dbuf->delay = delay;
|
|
|
|
*dbufp = dbuf;
|
|
}
|
|
static void __ssrc_handler_stop(void *p, void *arg) {
|
|
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);
|
|
|
|
dtx_buffer_stop(&ch->dtx_buffer);
|
|
}
|
|
}
|
|
void codec_handlers_stop(GQueue *q) {
|
|
for (GList *l = q->head; l; l = l->next) {
|
|
struct codec_handler *h = l->data;
|
|
|
|
if (h->delay_buffer) {
|
|
mutex_lock(&h->delay_buffer->lock);
|
|
__delay_buffer_shutdown(h->delay_buffer, true);
|
|
mutex_unlock(&h->delay_buffer->lock);
|
|
|
|
delay_buffer_stop(&h->delay_buffer);
|
|
}
|
|
ssrc_hash_foreach(h->ssrc_hash, __ssrc_handler_stop, (void *) true);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
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++) { \
|
|
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) {
|
|
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:
|
|
ilogs(transcoding, 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 bool __ssrc_handler_decode_common(struct codec_ssrc_handler *ch, struct codec_handler *h,
|
|
const format_t *enc_format)
|
|
{
|
|
if (h->pcm_dtmf_detect) {
|
|
ilogs(codec, 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)
|
|
ilogs(codec, 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,
|
|
enc_format, &h->source_pt.format,
|
|
&h->source_pt.format_parameters, &h->source_pt.codec_opts);
|
|
if (!ch->decoder)
|
|
return false;
|
|
if (rtpe_config.dtx_cn_params.len) {
|
|
if (ch->decoder->def->amr) {
|
|
if (rtpe_config.amr_cn_dtx)
|
|
decoder_set_cn_dtx(ch->decoder, &rtpe_config.dtx_cn_params);
|
|
}
|
|
else
|
|
decoder_set_cn_dtx(ch->decoder, &rtpe_config.dtx_cn_params);
|
|
}
|
|
|
|
ch->decoder->event_data = h->media;
|
|
ch->decoder->event_func = codec_decoder_event;
|
|
|
|
__dtx_setup(ch);
|
|
|
|
return true;
|
|
}
|
|
static struct ssrc_entry *__ssrc_handler_transcode_new(void *p) {
|
|
struct codec_handler *h = p;
|
|
|
|
ilogs(codec, 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 dec_format = {
|
|
.clockrate = h->source_pt.clock_rate,
|
|
.channels = h->source_pt.channels,
|
|
.format = -1,
|
|
};
|
|
format_t enc_format = {
|
|
.clockrate = h->dest_pt.clock_rate,
|
|
.channels = h->dest_pt.channels,
|
|
.format = -1,
|
|
};
|
|
|
|
// see if there's a complete codec chain usable for this
|
|
if (!h->pcm_dtmf_detect)
|
|
ch->chain = codec_chain_new(h->source_pt.codec_def, &dec_format,
|
|
h->dest_pt.codec_def, &enc_format,
|
|
ch->bitrate, ch->ptime);
|
|
|
|
if (ch->chain) {
|
|
ilogs(codec, LOG_DEBUG, "Using codec chain to transcode from " STR_FORMAT " to " STR_FORMAT,
|
|
STR_FMT(&h->source_pt.encoding_with_params),
|
|
STR_FMT(&h->dest_pt.encoding_with_params));
|
|
|
|
return &ch->h;
|
|
}
|
|
|
|
ch->encoder = encoder_new();
|
|
if (!ch->encoder)
|
|
goto err;
|
|
if (encoder_config_fmtp(ch->encoder, h->dest_pt.codec_def,
|
|
ch->bitrate,
|
|
ch->ptime, &dec_format,
|
|
&enc_format, &ch->encoder_format, &h->dest_pt.format,
|
|
&h->dest_pt.format_parameters,
|
|
&h->dest_pt.codec_opts))
|
|
goto err;
|
|
|
|
if (!__ssrc_handler_decode_common(ch, h, &ch->encoder_format))
|
|
goto err;
|
|
|
|
ch->bytes_per_packet = (ch->encoder->samples_per_packet ? : ch->encoder->samples_per_frame)
|
|
* h->dest_pt.codec_def->bits_per_sample / 8;
|
|
|
|
ilogs(codec, 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 struct ssrc_entry *__ssrc_handler_decode_new(void *p) {
|
|
struct codec_handler *h = p;
|
|
|
|
ilogs(codec, LOG_DEBUG, "Creating SSRC decoder for %s/%u/%i",
|
|
h->source_pt.codec_def->rtpname, h->source_pt.clock_rate,
|
|
h->source_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;
|
|
|
|
format_t dest_format = {
|
|
.clockrate = h->dest_pt.clock_rate,
|
|
.channels = h->dest_pt.channels,
|
|
.format = AV_SAMPLE_FMT_S16,
|
|
};
|
|
|
|
if (!__ssrc_handler_decode_common(ch, h, &dest_format))
|
|
goto err;
|
|
|
|
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);
|
|
dtx_buffer_stop(&ch->dtx_buffer);
|
|
}
|
|
|
|
|
|
void packet_encoded_packetize(AVPacket *pkt, struct codec_ssrc_handler *ch, struct media_packet *mp,
|
|
packetizer_f pkt_f, void *pkt_f_data, const struct fraction *cr_fact,
|
|
void (*tx_f)(AVPacket *, struct codec_ssrc_handler *, struct media_packet *, str *,
|
|
char *, unsigned int, const struct fraction *cr_fact))
|
|
{
|
|
// run this through our packetizer
|
|
AVPacket *in_pkt = pkt;
|
|
|
|
while (true) {
|
|
// figure out how big of a buffer we need
|
|
unsigned int payload_len = MAX(MAX(pkt->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(payload, payload_len);
|
|
// and request a packet
|
|
if (in_pkt)
|
|
ilogs(transcoding, LOG_DEBUG, "Adding %i bytes to packetizer", in_pkt->size);
|
|
int ret = pkt_f(in_pkt,
|
|
ch->sample_buffer, &inout, pkt_f_data);
|
|
|
|
if (G_UNLIKELY(ret == -1 || pkt->pts == AV_NOPTS_VALUE)) {
|
|
// nothing
|
|
free(buf);
|
|
break;
|
|
}
|
|
|
|
ilogs(transcoding, LOG_DEBUG, "Received packet of %zu bytes from packetizer", inout.len);
|
|
|
|
tx_f(pkt, ch, mp, &inout, buf, pkt_len, cr_fact);
|
|
|
|
if (ret == 0) {
|
|
// no more to go
|
|
break;
|
|
}
|
|
|
|
// loop around and get more
|
|
in_pkt = NULL;
|
|
}
|
|
}
|
|
|
|
static void packet_encoded_tx(AVPacket *pkt, struct codec_ssrc_handler *ch, struct media_packet *mp,
|
|
str *inout, char *buf, unsigned int pkt_len, const struct fraction *cr_fact);
|
|
|
|
static int packet_encoded_rtp(encoder_t *enc, void *u1, void *u2) {
|
|
struct codec_ssrc_handler *ch = u1;
|
|
struct media_packet *mp = u2;
|
|
|
|
ilogs(transcoding, LOG_DEBUG, "RTP media successfully encoded: TS %llu, len %i",
|
|
(unsigned long long) enc->avpkt->pts, enc->avpkt->size);
|
|
|
|
packet_encoded_packetize(enc->avpkt, ch, mp, enc->def->packetizer, enc, &enc->clockrate_fact,
|
|
packet_encoded_tx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void packet_encoded_tx(AVPacket *pkt, struct codec_ssrc_handler *ch, struct media_packet *mp,
|
|
str *inout, char *buf, unsigned int pkt_len, const struct fraction *cr_fact)
|
|
{
|
|
// check special payloads
|
|
|
|
unsigned int repeats = 0;
|
|
int payload_type = -1;
|
|
int dtmf_pt = ch->handler->dtmf_payload_type;
|
|
if (dtmf_pt == -1)
|
|
dtmf_pt = ch->handler->real_dtmf_payload_type;
|
|
int is_dtmf = 0;
|
|
|
|
if (dtmf_pt != -1)
|
|
is_dtmf = dtmf_event_payload(inout, (uint64_t *) &pkt->pts, pkt->duration,
|
|
&ch->dtmf_event, &ch->dtmf_events);
|
|
if (is_dtmf) {
|
|
payload_type = dtmf_pt;
|
|
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, pkt->pts, pkt->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 codec_output_rtp consumes it
|
|
send_buf = malloc(pkt_len);
|
|
memcpy(send_buf, buf, pkt_len);
|
|
}
|
|
codec_output_rtp(mp, &ch->csch, ch->handler, send_buf, inout->len, ch->csch.first_ts
|
|
+ fraction_divl(pkt->pts, cr_fact),
|
|
ch->rtp_mark ? 1 : 0, -1, 0,
|
|
payload_type, 0);
|
|
mp->ssrc_out->parent->seq_diff++;
|
|
ch->rtp_mark = 0;
|
|
} while (repeats--);
|
|
}
|
|
|
|
|
|
|
|
static void __dtmf_detect(struct codec_ssrc_handler *ch, AVFrame *frame) {
|
|
if (!ch->dtmf_dsp)
|
|
return;
|
|
if (!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) {
|
|
ilogs(transcoding, LOG_ERR | LOG_FLAG_LIMIT, "Failed to resample audio for DTMF DSP");
|
|
return;
|
|
}
|
|
|
|
ilogs(transcoding, 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)
|
|
ilogs(transcoding, 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) {
|
|
ilogs(transcoding, 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,
|
|
encoder_input_func_t input_func)
|
|
{
|
|
struct codec_ssrc_handler *ch = u1;
|
|
struct media_packet *mp = u2;
|
|
|
|
ilogs(transcoding, 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->csch.first_ts)
|
|
new_ch->csch.first_ts = ch->csch.first_ts;
|
|
|
|
if (decoder->def->supplemental) {
|
|
// supp codecs return bogus timestamps. Adjust the frame's TS to be in
|
|
// line with the primary decoder
|
|
frame->pts -= new_ch->csch.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;
|
|
ilogs(transcoding, LOG_DEBUG, "Discarding %i samples", frame->nb_samples);
|
|
goto discard;
|
|
}
|
|
|
|
if (G_UNLIKELY(!ch->encoder)) {
|
|
ilogs(transcoding, 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->callback = mp->media_out->encoder_callback;
|
|
|
|
uint32_t ts = frame->pts + ch->csch.first_ts;
|
|
__buffer_delay_frame(h->input_handler ? h->input_handler->delay_buffer : h->delay_buffer,
|
|
ch, input_func, frame, mp, ts);
|
|
frame = NULL; // consumed
|
|
|
|
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_decoded_audio_player(decoder_t *decoder, AVFrame *frame, void *u1, void *u2) {
|
|
struct codec_ssrc_handler *ch = u1;
|
|
struct media_packet *mp = u2;
|
|
|
|
ilogs(transcoding, LOG_DEBUG, "RTP media decoded for audio player: TS %llu, samples %u",
|
|
(unsigned long long) frame->pts, frame->nb_samples);
|
|
|
|
struct call_media *m = mp->media_out;
|
|
if (!m || !m->audio_player) {
|
|
// discard XXX log?
|
|
return 0;
|
|
}
|
|
|
|
audio_player_add_frame(m->audio_player, ch->h.ssrc, frame);
|
|
// XXX error checking/reporting
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __rtp_decode(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet, struct media_packet *mp)
|
|
{
|
|
int ret = 0;
|
|
if (packet) {
|
|
if (ch->chain) {
|
|
static const struct fraction chain_fact = {1,1};
|
|
AVPacket *pkt = codec_chain_input_data(ch->chain, packet->payload, packet->ts);
|
|
assert(pkt != NULL);
|
|
packet_encoded_packetize(pkt, ch, mp, packetizer_passthrough, NULL, &chain_fact,
|
|
packet_encoded_tx);
|
|
av_packet_unref(pkt);
|
|
}
|
|
else
|
|
ret = decoder_input_data_ptime(ch->decoder, packet->payload, packet->ts, &mp->ptime,
|
|
ch->handler->packet_decoded,
|
|
ch, mp);
|
|
}
|
|
__buffer_delay_seq(input_ch->handler->delay_buffer, mp, -1);
|
|
return ret;
|
|
}
|
|
static int packet_decode(struct codec_ssrc_handler *ch, struct codec_ssrc_handler *input_ch,
|
|
struct transcode_packet *packet, struct media_packet *mp)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!ch->csch.first_ts)
|
|
ch->csch.first_ts = packet->ts;
|
|
|
|
if (ch->decoder && ch->decoder->def->dtmf) {
|
|
if (packet_dtmf_event(ch, input_ch, packet, mp) == -1)
|
|
goto out;
|
|
}
|
|
else {
|
|
if (input_ch->dtmf_start_ts && !rtpe_config.dtmf_no_suppress) {
|
|
if ((packet->ts > input_ch->dtmf_start_ts && packet->ts - input_ch->dtmf_start_ts > 80000) ||
|
|
(packet->ts < input_ch->dtmf_start_ts && input_ch->dtmf_start_ts - packet->ts > 80000)) {
|
|
ilogs(transcoding, LOG_DEBUG, "Resetting decoder DTMF state due to TS discrepancy");
|
|
input_ch->dtmf_start_ts = 0;
|
|
}
|
|
else
|
|
packet = NULL;
|
|
}
|
|
}
|
|
|
|
if (__buffer_dtx(input_ch->dtx_buffer, ch, input_ch, packet, mp, __rtp_decode))
|
|
ret = 1; // consumed
|
|
else {
|
|
ilogs(transcoding, LOG_DEBUG, "Decoding RTP packet now");
|
|
ret = __rtp_decode(ch, input_ch, packet, mp);
|
|
ret = ret ? -1 : 0;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
#else
|
|
|
|
// dummy/stub
|
|
static void __buffer_delay_raw(struct delay_buffer *dbuf, struct codec_handler *handler,
|
|
raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate)
|
|
{
|
|
input_func(mp, clockrate);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void codec_update_all_handlers(struct call_monologue *ml) {
|
|
for (GList *l = ml->subscribers.head; l; l = l->next) {
|
|
struct call_subscription *cs = l->data;
|
|
struct call_monologue *sink = cs->monologue;
|
|
|
|
// iterate both simultaneously
|
|
for (unsigned int i = 0; i < ml->medias->len && i < sink->medias->len; i++) {
|
|
struct call_media *source_media = ml->medias->pdata[i];
|
|
if (!source_media)
|
|
continue;
|
|
struct call_media *sink_media = sink->medias->pdata[i];
|
|
if (!sink_media)
|
|
continue;
|
|
codec_handlers_update(source_media, sink_media);
|
|
}
|
|
}
|
|
|
|
dialogue_unkernelize(ml, "updating codec handlers");
|
|
}
|
|
void codec_update_all_source_handlers(struct call_monologue *ml, const struct sdp_ng_flags *flags) {
|
|
|
|
for (int i = 0; i < ml->medias->len; i++)
|
|
{
|
|
struct call_media * sink_media = ml->medias->pdata[i];
|
|
if (!sink_media)
|
|
continue;
|
|
|
|
for (GList * sub = sink_media->media_subscriptions.head; sub; sub = sub->next)
|
|
{
|
|
struct media_subscription * ms = sub->data;
|
|
struct call_media * source_media = ms->media;
|
|
|
|
if (!source_media)
|
|
continue;
|
|
|
|
codec_handlers_update(source_media, sink_media, .flags = flags);
|
|
}
|
|
}
|
|
|
|
dialogue_unkernelize(ml, "updating codec source handlers");
|
|
}
|
|
|
|
|
|
void codec_calc_jitter(struct ssrc_ctx *ssrc, unsigned long ts, unsigned int clockrate,
|
|
const struct timeval *tv)
|
|
{
|
|
if (!ssrc || !clockrate)
|
|
return;
|
|
struct ssrc_entry_call *sec = ssrc->parent;
|
|
|
|
// RFC 3550 A.8
|
|
uint32_t transit = (((timeval_us(tv) / 1000) * clockrate) / 1000) - ts;
|
|
mutex_lock(&sec->h.lock);
|
|
int32_t d = 0;
|
|
if (sec->transit)
|
|
d = transit - sec->transit;
|
|
sec->transit = transit;
|
|
if (d < 0)
|
|
d = -d;
|
|
// ignore implausibly large values
|
|
if (d < 100000)
|
|
sec->jitter += d - ((sec->jitter + 8) >> 4);
|
|
mutex_unlock(&sec->h.lock);
|
|
}
|
|
static void codec_calc_lost(struct ssrc_ctx *ssrc, uint16_t seq) {
|
|
struct ssrc_entry_call *s = ssrc->parent;
|
|
|
|
LOCK(&s->h.lock);
|
|
|
|
// XXX shared code from kernel module
|
|
|
|
uint32_t last_seq = s->last_seq_tracked;
|
|
uint32_t new_seq = last_seq;
|
|
|
|
// old seq or seq reset?
|
|
uint16_t old_seq_trunc = last_seq & 0xffff;
|
|
uint16_t seq_diff = seq - old_seq_trunc;
|
|
if (seq_diff == 0 || seq_diff >= 0xfeff) // old/dup seq - ignore
|
|
;
|
|
else if (seq_diff > 0x100) {
|
|
// reset seq and loss tracker
|
|
new_seq = seq;
|
|
s->last_seq_tracked = seq;
|
|
s->lost_bits = -1;
|
|
}
|
|
else {
|
|
// seq wrap?
|
|
new_seq = (last_seq & 0xffff0000) | seq;
|
|
while (new_seq < last_seq) {
|
|
new_seq += 0x10000;
|
|
if ((new_seq & 0xffff0000) == 0) // ext seq wrapped
|
|
break;
|
|
}
|
|
seq_diff = new_seq - last_seq;
|
|
s->last_seq_tracked = new_seq;
|
|
|
|
// shift loss tracker bit field and count losses
|
|
if (seq_diff >= (sizeof(s->lost_bits) * 8)) {
|
|
// complete loss
|
|
s->packets_lost += sizeof(s->lost_bits) * 8;
|
|
s->lost_bits = -1;
|
|
}
|
|
else {
|
|
while (seq_diff) {
|
|
// shift out one bit and see if we lost it
|
|
if ((s->lost_bits & 0x80000000) == 0)
|
|
s->packets_lost++;
|
|
s->lost_bits <<= 1;
|
|
seq_diff--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// track this frame as being seen
|
|
seq_diff = (new_seq & 0xffff) - seq;
|
|
if (seq_diff < (sizeof(s->lost_bits) * 8))
|
|
s->lost_bits |= (1 << seq_diff);
|
|
}
|
|
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
|
|
|
|
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 (!handler_silence_block(h, mp))
|
|
return 0;
|
|
|
|
// use main codec handler for supp codecs
|
|
if (h->source_pt.codec_def->supplemental) {
|
|
h->input_handler = __input_handler(h, mp);
|
|
h->output_handler = h->input_handler;
|
|
}
|
|
else
|
|
h->input_handler = h;
|
|
|
|
// create new packet and insert it into sequencer queue
|
|
|
|
ilogs(transcoding, LOG_DEBUG, "Received RTP packet: SSRC %" PRIx32 ", PT %u, seq %u, TS %u, len %zu",
|
|
ntohl(mp->rtp->ssrc), mp->rtp->m_pt, ntohs(mp->rtp->seq_num),
|
|
ntohl(mp->rtp->timestamp), mp->payload.len);
|
|
|
|
codec_calc_jitter(mp->ssrc_in, ntohl(mp->rtp->timestamp), h->input_handler->source_pt.clock_rate,
|
|
&mp->tv);
|
|
|
|
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->packet_func = packet_decode;
|
|
packet->rtp = *mp->rtp;
|
|
packet->handler = h;
|
|
|
|
int ret = __handler_func_sequencer(mp, packet);
|
|
|
|
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) {
|
|
h->input_handler = __input_handler(h, mp);
|
|
h->output_handler = h->input_handler;
|
|
|
|
struct codec_ssrc_handler *ch = get_ssrc(mp->ssrc_in->parent->h.ssrc, h->ssrc_hash);
|
|
if (!ch)
|
|
return 0;
|
|
decoder_input_data(ch->decoder, &mp->payload, mp->rtp->timestamp,
|
|
h->packet_decoded, ch, mp);
|
|
obj_put(&ch->h);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
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 ? media->type_id : MT_UNKNOWN);
|
|
if (!ret)
|
|
goto err2;
|
|
|
|
#ifndef WITH_TRANSCODING
|
|
|
|
return ret;
|
|
|
|
#else
|
|
|
|
// check for type mismatch and don't warn if it is
|
|
if (!ret->codec_def || (media && media->type_id && ret->codec_def->media_type != media->type_id)) {
|
|
payload_type_free(ret);
|
|
return NULL;
|
|
}
|
|
// 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);
|
|
|
|
#endif
|
|
|
|
err2:
|
|
ilogs(codec, LOG_WARN, "Codec '" STR_FORMAT "' requested for transcoding is not supported",
|
|
STR_FMT(codec_str));
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
|
|
static struct rtp_payload_type *codec_add_payload_type_pt(struct rtp_payload_type *pt, struct call_media *media,
|
|
struct call_media *other_media, struct codec_store *extra_cs)
|
|
{
|
|
if (!pt)
|
|
return NULL;
|
|
pt->payload_type = __unused_pt_number(media, other_media, extra_cs, pt);
|
|
if (pt->payload_type < 0) {
|
|
ilogs(codec, 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;
|
|
}
|
|
static struct rtp_payload_type *codec_add_payload_type(const str *codec, struct call_media *media,
|
|
struct call_media *other_media, struct codec_store *extra_cs)
|
|
{
|
|
struct rtp_payload_type *pt = codec_make_payload_type_sup(codec, media);
|
|
return codec_add_payload_type_pt(pt, media, other_media, extra_cs);
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void payload_type_clear(struct rtp_payload_type *p) {
|
|
g_queue_clear_full(&p->rtcp_fb, free);
|
|
str_free_dup(&p->encoding);
|
|
str_free_dup(&p->encoding_parameters);
|
|
str_free_dup(&p->encoding_with_params);
|
|
str_free_dup(&p->encoding_with_full_params);
|
|
str_free_dup(&p->format_parameters);
|
|
str_free_dup(&p->codec_opts);
|
|
ZERO(*p);
|
|
p->payload_type = -1;
|
|
}
|
|
void payload_type_free(struct rtp_payload_type *p) {
|
|
payload_type_clear(p);
|
|
g_slice_free1(sizeof(*p), p);
|
|
}
|
|
void payload_type_destroy(struct rtp_payload_type **p) {
|
|
if (*p)
|
|
payload_type_free(*p);
|
|
*p = NULL;
|
|
}
|
|
|
|
|
|
// dst must be pre-initialised (zeroed)
|
|
static void rtp_payload_type_copy(struct rtp_payload_type *dst, const struct rtp_payload_type *src) {
|
|
payload_type_clear(dst);
|
|
|
|
*dst = *src;
|
|
|
|
// make shallow copy of lists
|
|
g_queue_init(&dst->rtcp_fb);
|
|
g_queue_append(&dst->rtcp_fb, &src->rtcp_fb);
|
|
|
|
// duplicate contents
|
|
codec_init_payload_type(dst, MT_UNKNOWN);
|
|
}
|
|
|
|
struct rtp_payload_type *rtp_payload_type_dup(const struct rtp_payload_type *pt) {
|
|
struct rtp_payload_type *pt_copy = g_slice_alloc0(sizeof(*pt));
|
|
rtp_payload_type_copy(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, GINT_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, GINT_TO_POINTER(pt->payload_type));
|
|
q = g_hash_table_lookup_queue_new(ht, str_dup(&pt->encoding_with_full_params), free);
|
|
g_queue_push_tail(q, GINT_TO_POINTER(pt->payload_type));
|
|
}
|
|
#ifdef WITH_TRANSCODING
|
|
static void __insert_codec_tracker(GHashTable *all_clockrates, GHashTable *all_supp_codecs,
|
|
struct codec_tracker *sct, GList *link)
|
|
{
|
|
struct rtp_payload_type *pt = link->data;
|
|
|
|
if (!pt->codec_def || !pt->codec_def->supplemental)
|
|
g_hash_table_replace(all_clockrates, GUINT_TO_POINTER(pt->clock_rate),
|
|
GUINT_TO_POINTER(GPOINTER_TO_UINT(
|
|
g_hash_table_lookup(all_clockrates,
|
|
GUINT_TO_POINTER(pt->clock_rate))) + 1));
|
|
else {
|
|
GHashTable *clockrates = g_hash_table_lookup(all_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(all_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 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, MT_UNKNOWN);
|
|
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_parsed->bitrate)
|
|
pt->bitrate = pt_parsed->bitrate;
|
|
if (!pt->codec_opts.len && pt_parsed->codec_opts.len) {
|
|
str_free_dup(&pt->codec_opts);
|
|
pt->codec_opts = pt_parsed->codec_opts;
|
|
pt_parsed->codec_opts = STR_NULL;
|
|
}
|
|
payload_type_free(pt_parsed);
|
|
return 1;
|
|
}
|
|
static void __codec_options_set(struct call *call, struct rtp_payload_type *pt, GHashTable *codec_set) {
|
|
if (!call)
|
|
return;
|
|
if (!codec_set)
|
|
return;
|
|
if (__codec_options_set1(call, pt, &pt->encoding_with_full_params, 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;
|
|
}
|
|
static void codec_tracker_destroy(struct codec_tracker **sct) {
|
|
#ifdef WITH_TRANSCODING
|
|
if (!*sct)
|
|
return;
|
|
g_hash_table_destroy((*sct)->touched);
|
|
g_slice_free1(sizeof(**sct), *sct);
|
|
*sct = NULL;
|
|
#endif
|
|
}
|
|
static struct codec_tracker *codec_tracker_init(void) {
|
|
#ifdef WITH_TRANSCODING
|
|
struct codec_tracker *ret = g_slice_alloc0(sizeof(*ret));
|
|
ret->touched = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
return ret;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
static void codec_touched_real(struct codec_store *cs, struct rtp_payload_type *pt) {
|
|
#ifdef WITH_TRANSCODING
|
|
if (pt->codec_def && pt->codec_def->supplemental)
|
|
return;
|
|
g_hash_table_replace(cs->tracker->touched, GUINT_TO_POINTER(pt->clock_rate), (void *) 0x1);
|
|
#endif
|
|
}
|
|
static void codec_touched(struct codec_store *cs, struct rtp_payload_type *pt) {
|
|
#ifdef WITH_TRANSCODING
|
|
if (pt->codec_def && pt->codec_def->supplemental) {
|
|
cs->tracker->all_touched = 1;
|
|
return;
|
|
}
|
|
g_hash_table_replace(cs->tracker->touched, GUINT_TO_POINTER(pt->clock_rate), (void *) 0x1);
|
|
#endif
|
|
}
|
|
static bool is_codec_touched_rate(struct codec_tracker *tracker, unsigned int clock_rate) {
|
|
#ifdef WITH_TRANSCODING
|
|
if (!tracker || !tracker->touched)
|
|
return false;
|
|
if (tracker->all_touched)
|
|
return true;
|
|
return g_hash_table_lookup(tracker->touched, GUINT_TO_POINTER(clock_rate)) ? true : false;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
static bool is_codec_touched(struct codec_store *cs, struct rtp_payload_type *pt) {
|
|
if (!cs)
|
|
return false;
|
|
return is_codec_touched_rate(cs->tracker, pt->clock_rate);
|
|
}
|
|
#ifdef WITH_TRANSCODING
|
|
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_update(struct codec_store *cs) {
|
|
if (!cs)
|
|
return;
|
|
struct codec_tracker *sct = cs->tracker;
|
|
if (!sct)
|
|
return;
|
|
|
|
ilogs(codec, LOG_DEBUG, "Updating supplemental codecs for " STR_FORMAT " #%u",
|
|
STR_FMT(&cs->media->monologue->tag),
|
|
cs->media->index);
|
|
|
|
// build our tables
|
|
GHashTable *all_clockrates = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
GHashTable *all_supp_codecs = g_hash_table_new_full(str_case_hash, str_case_equal, free,
|
|
(GDestroyNotify) g_hash_table_destroy);
|
|
for (GList *l = cs->codec_prefs.head; l; l = l->next)
|
|
__insert_codec_tracker(all_clockrates, all_supp_codecs, sct, l);
|
|
|
|
// get all supported audio clock rates
|
|
GList *clockrates = g_hash_table_get_keys(all_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(all_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(all_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(all_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 (!is_codec_touched_rate(sct, clockrate))
|
|
continue;
|
|
|
|
ilogs(codec, 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_s);
|
|
|
|
struct rtp_payload_type *pt = codec_add_payload_type(&pt_str, cs->media, NULL, NULL);
|
|
if (!pt)
|
|
continue;
|
|
pt->for_transcoding = 1;
|
|
|
|
codec_store_add_raw_order(cs, pt);
|
|
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);
|
|
while (to_remove) {
|
|
unsigned int clockrate = GPOINTER_TO_UINT(to_remove->data);
|
|
to_remove = g_list_delete_link(to_remove, to_remove);
|
|
|
|
// ignore if we haven't touched anything with that clock rate
|
|
if (!is_codec_touched_rate(sct, 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;
|
|
|
|
ilogs(codec, LOG_DEBUG, "Eliminating supplemental codec " STR_FORMAT " (%i) with "
|
|
"stray clock rate %u",
|
|
STR_FMT(&pt->encoding_with_params), pt->payload_type, clockrate);
|
|
__codec_store_delete_link(link, cs);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_list_free(supp_codecs);
|
|
g_list_free(clockrates);
|
|
g_hash_table_destroy(all_clockrates);
|
|
g_hash_table_destroy(all_supp_codecs);
|
|
}
|
|
#endif
|
|
|
|
void codec_store_cleanup(struct codec_store *cs) {
|
|
if (cs->codecs)
|
|
g_hash_table_destroy(cs->codecs);
|
|
if (cs->codec_names)
|
|
g_hash_table_destroy(cs->codec_names);
|
|
g_queue_clear_full(&cs->codec_prefs, (GDestroyNotify) payload_type_free);
|
|
cs->supp_link = NULL;
|
|
codec_tracker_destroy(&cs->tracker);
|
|
ZERO(*cs);
|
|
}
|
|
|
|
void codec_store_init(struct codec_store *cs, struct call_media *media) {
|
|
if (!media)
|
|
media = cs->media;
|
|
|
|
codec_store_cleanup(cs);
|
|
|
|
cs->codecs = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
cs->codec_names = g_hash_table_new_full(str_case_hash, str_case_equal, free,
|
|
(void (*)(void*)) g_queue_free);
|
|
cs->media = media;
|
|
cs->tracker = codec_tracker_init();
|
|
}
|
|
|
|
static void codec_store_move(struct codec_store *dst, struct codec_store *src) {
|
|
*dst = *src;
|
|
ZERO(*src);
|
|
codec_store_init(src, dst->media);
|
|
}
|
|
|
|
// `out_compat` must be initialised already, or NULL
|
|
// either `codec` or `pt_parsed` must be given (or both)
|
|
static void codec_store_find_matching_codecs(GQueue *out_compat, struct rtp_payload_type **out_exact,
|
|
struct codec_store *cs, const str *codec,
|
|
struct rtp_payload_type *pt_parsed)
|
|
{
|
|
AUTO_CLEANUP_NULL(struct rtp_payload_type *pt_store, payload_type_destroy);
|
|
struct rtp_payload_type *pt = NULL;
|
|
|
|
if (pt_parsed)
|
|
pt = pt_parsed;
|
|
else {
|
|
// parse out the codec params if any are given, otherwise just go with the name
|
|
if (str_chr(codec, '/'))
|
|
pt = pt_store = codec_make_payload_type_sup(codec, cs->media);
|
|
}
|
|
|
|
GQueue *pts = g_hash_table_lookup(cs->codec_names, codec);
|
|
if (pt) {
|
|
if (!pts)
|
|
pts = g_hash_table_lookup(cs->codec_names, &pt->encoding_with_params);
|
|
if (!pts)
|
|
pts = g_hash_table_lookup(cs->codec_names, &pt->encoding);
|
|
}
|
|
if (!pts)
|
|
return; // no matches
|
|
// see if given format parameters match
|
|
for (GList *k = pts->head; k; k = k->next) {
|
|
struct rtp_payload_type *pt2 = g_hash_table_lookup(cs->codecs, k->data);
|
|
if (!pt2)
|
|
continue;
|
|
ensure_codec_def(pt2, cs->media);
|
|
int match;
|
|
if (pt)
|
|
match = rtp_payload_type_fmt_cmp(pt, pt2);
|
|
else
|
|
match = (str_cmp_str(codec, &pt2->encoding) == 0) ? 0 : -1;
|
|
if (match == 0) {
|
|
if (out_exact && !*out_exact)
|
|
*out_exact = pt2;
|
|
if (out_compat)
|
|
g_queue_push_head(out_compat, pt2);
|
|
}
|
|
else if (out_compat && match == 1)
|
|
g_queue_push_tail(out_compat, pt2);
|
|
}
|
|
}
|
|
|
|
__attribute__((nonnull(1, 2)))
|
|
static void codec_store_add_raw_link(struct codec_store *cs, struct rtp_payload_type *pt, GList *link) {
|
|
// cs->media may be NULL
|
|
ensure_codec_def(pt, cs->media);
|
|
if (cs->media && cs->media->ptime > 0)
|
|
pt->ptime = cs->media->ptime;
|
|
|
|
ilogs(internals, LOG_DEBUG, "Adding codec '" STR_FORMAT "'/'" STR_FORMAT "'/'" STR_FORMAT "' at pos %p",
|
|
STR_FMT(&pt->encoding),
|
|
STR_FMT(&pt->encoding_with_params),
|
|
STR_FMT(&pt->encoding_with_full_params), link);
|
|
g_hash_table_insert(cs->codecs, GINT_TO_POINTER(pt->payload_type), pt);
|
|
__rtp_payload_type_add_name(cs->codec_names, pt);
|
|
if (!link)
|
|
g_queue_push_tail(&cs->codec_prefs, pt);
|
|
else
|
|
g_queue_insert_before(&cs->codec_prefs, link, pt);
|
|
pt->prefs_link = cs->codec_prefs.tail;
|
|
if (!cs->supp_link && pt->codec_def && pt->codec_def->supplemental)
|
|
cs->supp_link = pt->prefs_link;
|
|
}
|
|
|
|
// appends to the end, but before supplemental codecs
|
|
__attribute__((nonnull(1, 2)))
|
|
static void codec_store_add_raw_order(struct codec_store *cs, struct rtp_payload_type *pt) {
|
|
codec_store_add_raw_link(cs, pt, cs->supp_link);
|
|
}
|
|
// appends to the end
|
|
__attribute__((nonnull(1, 2)))
|
|
void codec_store_add_raw(struct codec_store *cs, struct rtp_payload_type *pt) {
|
|
codec_store_add_raw_link(cs, pt, NULL);
|
|
}
|
|
|
|
__attribute__((nonnull(1, 2)))
|
|
static struct rtp_payload_type *codec_store_add_link(struct codec_store *cs,
|
|
struct rtp_payload_type *pt, GList *link)
|
|
{
|
|
if (!cs->media)
|
|
return NULL;
|
|
|
|
ensure_codec_def(pt, cs->media);
|
|
if (proto_is_not_rtp(cs->media->protocol))
|
|
return NULL;
|
|
|
|
struct rtp_payload_type *copy = rtp_payload_type_dup(pt);
|
|
codec_store_add_raw_link(cs, copy, link);
|
|
return copy;
|
|
}
|
|
|
|
// appends to the end, but before supplemental codecs
|
|
__attribute__((nonnull(1, 2)))
|
|
static struct rtp_payload_type *codec_store_add_order(struct codec_store *cs, struct rtp_payload_type *pt) {
|
|
return codec_store_add_link(cs, pt, cs->supp_link);
|
|
}
|
|
// always add to end
|
|
__attribute__((nonnull(1, 2)))
|
|
static void codec_store_add_end(struct codec_store *cs, struct rtp_payload_type *pt) {
|
|
codec_store_add_link(cs, pt, NULL);
|
|
}
|
|
|
|
static struct rtp_payload_type *codec_store_find_compatible_q(struct codec_store *cs, GQueue *q,
|
|
const struct rtp_payload_type *pt)
|
|
{
|
|
if (!q)
|
|
return NULL;
|
|
for (GList *l = q->head; l; l = l->next) {
|
|
struct rtp_payload_type *ret = g_hash_table_lookup(cs->codecs, l->data);
|
|
if (rtp_payload_type_fmt_eq_compat(ret, pt))
|
|
return ret;
|
|
}
|
|
return NULL;
|
|
}
|
|
static struct rtp_payload_type *codec_store_find_compatible(struct codec_store *cs,
|
|
const struct rtp_payload_type *pt)
|
|
{
|
|
struct rtp_payload_type *ret;
|
|
ret = codec_store_find_compatible_q(cs,
|
|
g_hash_table_lookup(cs->codec_names, &pt->encoding_with_full_params),
|
|
pt);
|
|
if (ret)
|
|
return ret;
|
|
ret = codec_store_find_compatible_q(cs,
|
|
g_hash_table_lookup(cs->codec_names, &pt->encoding_with_params),
|
|
pt);
|
|
if (ret)
|
|
return ret;
|
|
ret = codec_store_find_compatible_q(cs,
|
|
g_hash_table_lookup(cs->codec_names, &pt->encoding),
|
|
pt);
|
|
if (ret)
|
|
return ret;
|
|
return NULL;
|
|
}
|
|
|
|
void __codec_store_populate_reuse(struct codec_store *dst, struct codec_store *src, struct codec_store_args a) {
|
|
// start fresh
|
|
struct call_media *media = dst->media;
|
|
struct call *call = media ? media->call : NULL;
|
|
|
|
for (GList *l = src->codec_prefs.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
struct rtp_payload_type *orig_pt = g_hash_table_lookup(dst->codecs,
|
|
GINT_TO_POINTER(pt->payload_type));
|
|
|
|
pt->reverse_payload_type = pt->payload_type;
|
|
|
|
if (orig_pt)
|
|
ilogs(codec, LOG_DEBUG, "Retaining codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
else {
|
|
if (!a.answer_only) {
|
|
ilogs(codec, LOG_DEBUG, "Adding codec " STR_FORMAT " (%i) to end of list",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
__codec_options_set(call, pt, a.codec_set);
|
|
codec_store_add_end(dst, pt);
|
|
}
|
|
else
|
|
ilogs(codec, LOG_DEBUG, "Not adding stray answer codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
}
|
|
}
|
|
if(dst->codec_prefs.head){
|
|
for (GList *l = dst->codec_prefs.head; l;) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
struct rtp_payload_type *orig_pt = g_hash_table_lookup(src->codecs,
|
|
GINT_TO_POINTER(pt->payload_type));
|
|
if(!orig_pt){
|
|
l = __codec_store_delete_link(l, dst);
|
|
}else{
|
|
l = l->next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void codec_store_check_empty(struct codec_store *dst, struct codec_store *src) {
|
|
if (dst->codec_prefs.length)
|
|
return;
|
|
|
|
ilog(LOG_WARN, "Usage error: List of codecs empty. Restoring original list of codecs. "
|
|
"Results may be unexpected.");
|
|
|
|
codec_store_populate(dst, src);
|
|
}
|
|
|
|
void __codec_store_populate(struct codec_store *dst, struct codec_store *src, struct codec_store_args a) {
|
|
// start fresh
|
|
struct codec_store orig_dst;
|
|
codec_store_move(&orig_dst, dst);
|
|
|
|
struct call_media *media = dst->media;
|
|
struct call *call = media ? media->call : NULL;
|
|
|
|
for (GList *l = src->codec_prefs.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
struct rtp_payload_type *orig_pt = g_hash_table_lookup(orig_dst.codecs,
|
|
GINT_TO_POINTER(pt->payload_type));
|
|
if (a.answer_only && !orig_pt) {
|
|
if (a.allow_asymmetric)
|
|
orig_pt = codec_store_find_compatible(&orig_dst, pt);
|
|
if (!orig_pt) {
|
|
ilogs(codec, LOG_DEBUG, "Not adding stray answer codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
continue;
|
|
}
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Adding codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
|
|
pt->reverse_payload_type = pt->payload_type;
|
|
|
|
if (orig_pt) {
|
|
// carry over existing options
|
|
pt->payload_type = orig_pt->payload_type;
|
|
pt->ptime = orig_pt->ptime;
|
|
pt->for_transcoding = orig_pt->for_transcoding;
|
|
pt->accepted = orig_pt->accepted;
|
|
pt->bitrate = orig_pt->bitrate;
|
|
str_free_dup(&pt->codec_opts);
|
|
pt->codec_opts = orig_pt->codec_opts;
|
|
orig_pt->codec_opts = STR_NULL;
|
|
if (pt->for_transcoding)
|
|
codec_touched(dst, pt);
|
|
}
|
|
__codec_options_set(call, pt, a.codec_set);
|
|
codec_store_add_end(dst, pt);
|
|
}
|
|
|
|
codec_store_cleanup(&orig_dst);
|
|
}
|
|
|
|
void codec_store_strip(struct codec_store *cs, GQueue *strip, GHashTable *except) {
|
|
for (GList *l = strip->head; l; l = l->next) {
|
|
str *codec = l->data;
|
|
if (!str_cmp(codec, "all") || !str_cmp(codec, "full")) {
|
|
if (!str_cmp(codec, "all"))
|
|
cs->strip_all = 1;
|
|
else
|
|
cs->strip_full = 1;
|
|
|
|
// strip all except ...
|
|
GList *link = cs->codec_prefs.head;
|
|
while (link) {
|
|
GList *next = link->next;
|
|
struct rtp_payload_type *pt = link->data;
|
|
if (except && g_hash_table_lookup(except, &pt->encoding))
|
|
;
|
|
else if (except && g_hash_table_lookup(except, &pt->encoding_with_params))
|
|
;
|
|
else if (except && g_hash_table_lookup(except, &pt->encoding_with_full_params))
|
|
;
|
|
else {
|
|
ilogs(codec, LOG_DEBUG, "Stripping codec " STR_FORMAT
|
|
" (%i) due to strip=all or strip=full",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
codec_touched_real(cs, pt);
|
|
next = __codec_store_delete_link(link, cs);
|
|
}
|
|
link = next;
|
|
}
|
|
continue;
|
|
}
|
|
// strip just this one
|
|
GQueue *pts = g_hash_table_lookup(cs->codec_names, codec);
|
|
if (!pts || !pts->length) {
|
|
ilogs(codec, LOG_DEBUG, "Codec " STR_FORMAT
|
|
" not present for stripping",
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
while (pts->length) {
|
|
int pt_num = GPOINTER_TO_INT(pts->head->data);
|
|
struct rtp_payload_type *pt = g_hash_table_lookup(cs->codecs, GINT_TO_POINTER(pt_num));
|
|
if (pt) {
|
|
ilogs(codec, LOG_DEBUG, "Stripping codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params), pt_num);
|
|
codec_touched_real(cs, pt);
|
|
__codec_store_delete_link(pt->prefs_link, cs);
|
|
// this removes pts->head
|
|
}
|
|
else {
|
|
ilogs(codec, LOG_DEBUG, "PT %i missing for stripping " STR_FORMAT, pt_num,
|
|
STR_FMT(codec));
|
|
break; // should not happen - don't continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void codec_store_offer(struct codec_store *cs, GQueue *offer, struct codec_store *orig) {
|
|
// restore stripped codecs in order: codecs must be present in `orig` but not present
|
|
// in `cs`
|
|
for (GList *l = offer->head; l; l = l->next) {
|
|
str *codec = l->data;
|
|
GQueue *pts = g_hash_table_lookup(cs->codec_names, codec);
|
|
if (pts && pts->length) {
|
|
ilogs(codec, LOG_DEBUG, "Codec " STR_FORMAT
|
|
" already present (%i)",
|
|
STR_FMT(codec), GPOINTER_TO_INT(pts->head->data));
|
|
continue;
|
|
}
|
|
GQueue *orig_list = g_hash_table_lookup(orig->codec_names, codec);
|
|
if (!orig_list || !orig_list->length) {
|
|
ilogs(codec, LOG_DEBUG, "Codec " STR_FORMAT
|
|
" not present for offering",
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
for (GList *k = orig_list->head; k; k = k->next) {
|
|
int pt_num = GPOINTER_TO_INT(k->data);
|
|
struct rtp_payload_type *orig_pt = g_hash_table_lookup(orig->codecs,
|
|
GINT_TO_POINTER(pt_num));
|
|
if (!orig_pt) {
|
|
ilogs(codec, LOG_DEBUG, "PT %i missing for offering " STR_FORMAT, pt_num,
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
if (g_hash_table_lookup(cs->codecs, GINT_TO_POINTER(pt_num))) {
|
|
ilogs(codec, LOG_DEBUG, "PT %i (" STR_FORMAT ") already preset", pt_num,
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Re-adding stripped codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&orig_pt->encoding_with_params), orig_pt->payload_type);
|
|
codec_touched(cs, orig_pt);
|
|
codec_store_add_order(cs, orig_pt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void codec_store_accept(struct codec_store *cs, GQueue *accept, struct codec_store *orig) {
|
|
// mark codecs as `for transcoding`
|
|
for (GList *l = accept->head; l; l = l->next) {
|
|
str *codec = l->data;
|
|
AUTO_CLEANUP(GQueue pts_matched, g_queue_clear) = G_QUEUE_INIT;
|
|
|
|
GQueue *pts = &pts_matched;
|
|
if (!str_cmp(codec, "all") || !str_cmp(codec, "full"))
|
|
pts = &cs->codec_prefs;
|
|
else
|
|
codec_store_find_matching_codecs(&pts_matched, NULL, cs, codec, NULL);
|
|
|
|
if (!pts->length) {
|
|
pts = &pts_matched;
|
|
// special case: strip=all, consume=X
|
|
if (orig)
|
|
codec_store_find_matching_codecs(&pts_matched, NULL, orig, codec, NULL);
|
|
if (!pts->length) {
|
|
ilogs(codec, LOG_DEBUG, "Codec " STR_FORMAT
|
|
" not present for accepting",
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
// re-add from orig, then mark as accepted below
|
|
GQueue pt_readded = G_QUEUE_INIT;
|
|
// XXX duplicate code
|
|
for (GList *k = pts->head; k; k = k->next) {
|
|
struct rtp_payload_type *orig_pt = k->data;
|
|
if (g_hash_table_lookup(cs->codecs, GINT_TO_POINTER(orig_pt->payload_type))) {
|
|
ilogs(codec, LOG_DEBUG, "PT %i (" STR_FORMAT ") already preset",
|
|
orig_pt->payload_type,
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Re-adding stripped codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&orig_pt->encoding_with_params), orig_pt->payload_type);
|
|
codec_touched(cs, orig_pt);
|
|
struct rtp_payload_type *added = codec_store_add_order(cs, orig_pt);
|
|
if (added)
|
|
g_queue_push_tail(&pt_readded, added);
|
|
}
|
|
g_queue_clear(&pts_matched);
|
|
pts_matched = pt_readded;
|
|
if (!pts_matched.length)
|
|
continue;
|
|
}
|
|
for (GList *k = pts->head; k; k = k->next) {
|
|
struct rtp_payload_type *fpt = k->data;
|
|
int pt_num = fpt->payload_type;
|
|
struct rtp_payload_type *pt = g_hash_table_lookup(cs->codecs,
|
|
GINT_TO_POINTER(pt_num));
|
|
if (!pt) {
|
|
ilogs(codec, LOG_DEBUG, "PT %i missing for accepting " STR_FORMAT, pt_num,
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Accepting codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params), pt->payload_type);
|
|
pt->for_transcoding = 1;
|
|
pt->accepted = 1;
|
|
codec_touched(cs, pt);
|
|
}
|
|
}
|
|
}
|
|
|
|
int codec_store_accept_one(struct codec_store *cs, GQueue *accept, bool accept_any) {
|
|
// local codec-accept routine: accept first supported codec, or first from "accept" list
|
|
// if given
|
|
|
|
struct rtp_payload_type *accept_pt = NULL;
|
|
|
|
for (GList *l = accept->head; l; l = l->next) {
|
|
// iterate through list and look for the first supported codec
|
|
str *codec = l->data;
|
|
if (!str_cmp(codec, "any")) {
|
|
accept_any = true;
|
|
continue;
|
|
}
|
|
GQueue *pts = g_hash_table_lookup(cs->codec_names, codec);
|
|
if (!pts)
|
|
continue;
|
|
for (GList *k = pts->head; k; k = k->next) {
|
|
int pt_num = GPOINTER_TO_INT(k->data);
|
|
struct rtp_payload_type *pt = g_hash_table_lookup(cs->codecs, GINT_TO_POINTER(pt_num));
|
|
if (!pt) {
|
|
ilogs(codec, LOG_DEBUG, "PT %i missing for accepting " STR_FORMAT, pt_num,
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
accept_pt = pt;
|
|
break;
|
|
}
|
|
if (accept_pt)
|
|
break;
|
|
}
|
|
|
|
if (!accept_pt) {
|
|
// none found yet - pick the first one
|
|
for (GList *l = cs->codec_prefs.head; l; l = l->next) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
if (!accept_any) {
|
|
ensure_codec_def(pt, cs->media);
|
|
if (!pt->codec_def)
|
|
continue;
|
|
}
|
|
accept_pt = pt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!accept_pt) {
|
|
ilogs(codec, LOG_WARN, "No acceptable codecs found from publisher");
|
|
return -1;
|
|
}
|
|
|
|
// delete all codecs except the accepted one
|
|
GList *link = cs->codec_prefs.head;
|
|
while (link) {
|
|
struct rtp_payload_type *pt = link->data;
|
|
if (pt == accept_pt) {
|
|
link = link->next;
|
|
continue;
|
|
}
|
|
link = __codec_store_delete_link(link, cs);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void codec_store_track(struct codec_store *cs, GQueue *q) {
|
|
#ifdef WITH_TRANSCODING
|
|
// just track all codecs from the list as "touched"
|
|
for (GList *l = q->head; l; l = l->next) {
|
|
str *codec = l->data;
|
|
if (!str_cmp(codec, "all") || !str_cmp(codec, "full")) {
|
|
cs->tracker->all_touched = 1;
|
|
continue;
|
|
}
|
|
GQueue *pts = g_hash_table_lookup(cs->codec_names, codec);
|
|
if (!pts)
|
|
continue;
|
|
for (GList *k = pts->head; k; k = k->next) {
|
|
int pt_num = GPOINTER_TO_INT(k->data);
|
|
struct rtp_payload_type *pt = g_hash_table_lookup(cs->codecs,
|
|
GINT_TO_POINTER(pt_num));
|
|
codec_touched(cs, pt);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void codec_store_transcode(struct codec_store *cs, GQueue *offer, struct codec_store *orig) {
|
|
#ifdef WITH_TRANSCODING
|
|
// special case of codec_store_offer(): synthesise codecs that were not already present
|
|
for (GList *l = offer->head; l; l = l->next) {
|
|
str *codec = l->data;
|
|
// parse out given codec string
|
|
AUTO_CLEANUP(struct rtp_payload_type *pt, payload_type_destroy)
|
|
= codec_make_payload_type_sup(codec, cs->media);
|
|
|
|
// find matching existing PT if one exists
|
|
struct rtp_payload_type *pt_match = NULL;
|
|
codec_store_find_matching_codecs(NULL, &pt_match, cs, codec, pt);
|
|
if (pt_match) {
|
|
ilogs(codec, LOG_DEBUG, "Codec " STR_FORMAT
|
|
" already present (%i)",
|
|
STR_FMT(codec), pt_match->payload_type);
|
|
continue;
|
|
}
|
|
GQueue *orig_list = g_hash_table_lookup(orig->codec_names, codec);
|
|
if (!orig_list || !orig_list->length || cs->strip_full) {
|
|
ilogs(codec, LOG_DEBUG, "Adding codec " STR_FORMAT
|
|
" for transcoding",
|
|
STR_FMT(codec));
|
|
// create new payload type
|
|
pt = codec_add_payload_type_pt(pt, cs->media, NULL, orig);
|
|
if (!pt)
|
|
continue;
|
|
pt->for_transcoding = 1;
|
|
|
|
ilogs(codec, LOG_DEBUG, "Codec " STR_FORMAT " added for transcoding with payload "
|
|
"type %i",
|
|
STR_FMT(&pt->encoding_with_params), pt->payload_type);
|
|
codec_touched(cs, pt);
|
|
codec_store_add_raw_order(cs, pt);
|
|
pt = NULL;
|
|
continue;
|
|
}
|
|
// XXX duplicate code
|
|
for (GList *k = orig_list->head; k; k = k->next) {
|
|
int pt_num = GPOINTER_TO_INT(k->data);
|
|
struct rtp_payload_type *orig_pt = g_hash_table_lookup(orig->codecs,
|
|
GINT_TO_POINTER(pt_num));
|
|
if (!orig_pt) {
|
|
ilogs(codec, LOG_DEBUG, "PT %i missing for offering " STR_FORMAT, pt_num,
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
if (g_hash_table_lookup(cs->codecs, GINT_TO_POINTER(pt_num))) {
|
|
ilogs(codec, LOG_DEBUG, "PT %i (" STR_FORMAT ") already preset", pt_num,
|
|
STR_FMT(codec));
|
|
continue;
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Re-adding stripped codec " STR_FORMAT " (%i)",
|
|
STR_FMT(&orig_pt->encoding_with_params), orig_pt->payload_type);
|
|
codec_touched(cs, orig_pt);
|
|
codec_store_add_order(cs, orig_pt);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void codec_store_answer(struct codec_store *dst, struct codec_store *src, struct sdp_ng_flags *flags) {
|
|
// retain existing setup for supplemental codecs, but start fresh otherwise
|
|
struct codec_store orig_dst;
|
|
codec_store_move(&orig_dst, dst);
|
|
|
|
struct call_media *src_media = src->media;
|
|
struct call_media *dst_media = dst->media;
|
|
if (!dst_media || !src_media)
|
|
goto out;
|
|
|
|
#ifdef WITH_TRANSCODING
|
|
// synthetic answer for T.38:
|
|
if (dst_media->type_id == MT_AUDIO && src_media->type_id == MT_IMAGE && dst->codec_prefs.length == 0) {
|
|
if (dst_media->t38_gateway && dst_media->t38_gateway->pcm_player
|
|
&& dst_media->t38_gateway->pcm_player->coder.handler) {
|
|
codec_store_add_order(dst, &dst_media->t38_gateway->pcm_player->coder.handler->dest_pt);
|
|
goto out;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
unsigned int num_codecs = 0;
|
|
//int codec_order = 0; // to track whether we've added supplemental codecs based on their media codecs
|
|
GQueue supp_codecs = G_QUEUE_INIT; // postpone actually adding them until the end
|
|
|
|
// populate dst via output PTs from src's codec handlers
|
|
for (GList *l = src->codec_prefs.head; l; l = l->next) {
|
|
bool add_codec = true;
|
|
if (flags->single_codec && num_codecs >= 1)
|
|
add_codec = false;
|
|
|
|
struct rtp_payload_type *pt = l->data;
|
|
struct codec_handler *h = codec_handler_get(src_media, pt->payload_type, dst_media, NULL);
|
|
|
|
bool is_supp = false;
|
|
if (pt->codec_def && pt->codec_def->supplemental)
|
|
is_supp = true;
|
|
|
|
if (!h || h->dest_pt.payload_type == -1) {
|
|
// passthrough or missing
|
|
if (pt->for_transcoding)
|
|
ilogs(codec, LOG_DEBUG, "Codec " STR_FORMAT
|
|
" (%i) is being transcoded",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
else {
|
|
if (add_codec) {
|
|
ilogs(codec, LOG_DEBUG, "Codec " STR_FORMAT
|
|
" (%i) is passthrough",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
if (!is_supp)
|
|
num_codecs++;
|
|
codec_store_add_end(dst, pt);
|
|
}
|
|
else
|
|
ilogs(codec, LOG_DEBUG, "Skipping passthrough codec " STR_FORMAT
|
|
" (%i) due to single-codec flag",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// supp codecs are handled in-line with their main media codecs
|
|
if (is_supp) {
|
|
if (pt->for_transcoding)
|
|
continue;
|
|
if (is_codec_touched(dst, pt))
|
|
continue;
|
|
if (is_codec_touched(src, pt))
|
|
continue;
|
|
if (is_codec_touched(&orig_dst, pt))
|
|
continue;
|
|
// except those that were not touched - we pass those through regardless
|
|
}
|
|
|
|
if (!add_codec && !is_supp) {
|
|
ilogs(codec, LOG_DEBUG, "Skipping reverse codec for " STR_FORMAT
|
|
" (%i) = " STR_FORMAT " (%i) due to single-codec flag",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type,
|
|
STR_FMT(&h->dest_pt.encoding_with_params),
|
|
h->dest_pt.payload_type);
|
|
continue;
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Reverse codec for " STR_FORMAT
|
|
" (%i) is " STR_FORMAT " (%i)",
|
|
STR_FMT(&pt->encoding_with_params),
|
|
pt->payload_type,
|
|
STR_FMT(&h->dest_pt.encoding_with_params),
|
|
h->dest_pt.payload_type);
|
|
if (!g_hash_table_lookup(dst->codecs, GINT_TO_POINTER(h->dest_pt.payload_type))) {
|
|
if (h->passthrough) {
|
|
struct rtp_payload_type copy = *pt;
|
|
copy.payload_type = pt->reverse_payload_type;
|
|
codec_store_add_end(dst, ©);
|
|
}
|
|
else
|
|
codec_store_add_end(dst, &h->dest_pt);
|
|
if (!is_supp)
|
|
num_codecs++;
|
|
}
|
|
|
|
// handle associated supplemental codecs
|
|
if (h->cn_payload_type != -1) {
|
|
pt = g_hash_table_lookup(orig_dst.codecs, GINT_TO_POINTER(h->cn_payload_type));
|
|
if (!pt)
|
|
ilogs(codec, LOG_DEBUG, "CN payload type %i is missing", h->cn_payload_type);
|
|
else
|
|
g_queue_push_tail(&supp_codecs, rtp_payload_type_dup(pt));
|
|
}
|
|
int dtmf_payload_type = h->dtmf_payload_type;
|
|
if (dtmf_payload_type == -1)
|
|
dtmf_payload_type = h->real_dtmf_payload_type;
|
|
if (dtmf_payload_type != -1) {
|
|
pt = g_hash_table_lookup(orig_dst.codecs, GINT_TO_POINTER(dtmf_payload_type));
|
|
if (!pt)
|
|
ilogs(codec, LOG_DEBUG, "DTMF payload type %i is missing", dtmf_payload_type);
|
|
else
|
|
g_queue_push_tail(&supp_codecs, rtp_payload_type_dup(pt));
|
|
}
|
|
}
|
|
|
|
while (supp_codecs.length) {
|
|
struct rtp_payload_type *pt = g_queue_pop_head(&supp_codecs);
|
|
if (g_hash_table_lookup(dst->codecs, GINT_TO_POINTER(pt->payload_type))) {
|
|
ilogs(codec, LOG_DEBUG, STR_FORMAT " payload type %i already present, skip",
|
|
STR_FMT(&pt->encoding_with_full_params), pt->payload_type);
|
|
payload_type_free(pt);
|
|
continue;
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Adding " STR_FORMAT " payload type %i",
|
|
STR_FMT(&pt->encoding_with_params), pt->payload_type);
|
|
codec_store_add_raw(dst, pt);
|
|
}
|
|
|
|
out:
|
|
codec_store_cleanup(&orig_dst);
|
|
}
|
|
|
|
// offer codecs for non-RTP transcoding scenarios
|
|
void codec_store_synthesise(struct codec_store *dst, struct codec_store *opposite) {
|
|
if (!dst->media || !opposite->media)
|
|
return;
|
|
if (dst->media->type_id == MT_AUDIO && opposite->media->type_id == MT_IMAGE) {
|
|
// audio <> T.38 transcoder
|
|
if (!dst->codec_prefs.length) {
|
|
// no codecs given: add defaults
|
|
static const str PCMU_str = STR_CONST_INIT("PCMU");
|
|
static const str PCMA_str = STR_CONST_INIT("PCMA");
|
|
codec_store_add_raw_order(dst, codec_make_payload_type(&PCMU_str, MT_AUDIO));
|
|
codec_store_add_raw_order(dst, codec_make_payload_type(&PCMA_str, MT_AUDIO));
|
|
|
|
ilogs(codec, LOG_DEBUG, "Using default codecs PCMU and PCMA for T.38 gateway");
|
|
}
|
|
else {
|
|
// we already have a list of codecs - make sure they're all supported by us
|
|
for (GList *l = dst->codec_prefs.head; l;) {
|
|
struct rtp_payload_type *pt = l->data;
|
|
if (pt->codec_def) {
|
|
l = l->next;
|
|
continue;
|
|
}
|
|
ilogs(codec, LOG_DEBUG, "Eliminating unsupported codec " STR_FORMAT
|
|
" for T.38 transcoding",
|
|
STR_FMT(&pt->encoding_with_params));
|
|
codec_touched(dst, pt);
|
|
l = __codec_store_delete_link(l, dst);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check all codecs listed in the source are also be present in the answer (dst)
|
|
bool codec_store_is_full_answer(const struct codec_store *src, const struct codec_store *dst) {
|
|
for (GList *l = src->codec_prefs.head; l; l = l->next) {
|
|
const struct rtp_payload_type *src_pt = l->data;
|
|
const struct rtp_payload_type *dst_pt = g_hash_table_lookup(dst->codecs,
|
|
GINT_TO_POINTER(src_pt->payload_type));
|
|
if (!dst_pt || !rtp_payload_type_eq_compat(src_pt, dst_pt)) {
|
|
ilogs(codec, LOG_DEBUG, "Source codec " STR_FORMAT " is not present in the answer",
|
|
STR_FMT(&src_pt->encoding_with_params));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
static void __codec_timer_callback_free(void *p) {
|
|
struct timer_callback *cb = p;
|
|
if (cb->call)
|
|
obj_put(cb->call);
|
|
}
|
|
static void __codec_timer_callback_fire(struct codec_timer *ct) {
|
|
struct timer_callback *cb = (void *) ct;
|
|
log_info_call(cb->call);
|
|
cb->timer_callback_func(cb->call, cb->ptr);
|
|
codec_timer_stop(&ct);
|
|
log_info_pop();
|
|
}
|
|
void codec_timer_callback(struct call *c, void (*func)(struct call *, void *), void *p, uint64_t delay) {
|
|
struct timer_callback *cb = obj_alloc0("codec_timer_callback", sizeof(*cb), __codec_timer_callback_free);
|
|
cb->ct.tt_obj.tt = &codec_timers_thread;
|
|
cb->call = obj_get(c);
|
|
cb->timer_callback_func = func;
|
|
cb->ptr = p;
|
|
cb->ct.timer_func = __codec_timer_callback_fire;
|
|
cb->ct.next = rtpe_now;
|
|
timeval_add_usec(&cb->ct.next, delay);
|
|
timerthread_obj_schedule_abs(&cb->ct.tt_obj, &cb->ct.next);
|
|
}
|
|
|
|
static void codec_timers_run(void *p) {
|
|
struct codec_timer *ct = p;
|
|
ct->timer_func(ct);
|
|
}
|
|
|
|
void codecs_init(void) {
|
|
timerthread_init(&codec_timers_thread, codec_timers_run);
|
|
}
|
|
void codecs_cleanup(void) {
|
|
timerthread_free(&codec_timers_thread);
|
|
}
|
|
void codec_timers_loop(void *p) {
|
|
timerthread_run(&codec_timers_thread);
|
|
}
|