mirror of https://github.com/sipwise/rtpengine.git
Change-Id: Iabf8f8f4de3d45a92933c658084b3f9b72a9b414pull/1126/merge
parent
250df2af5e
commit
92b0b1104d
@ -0,0 +1,34 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t ac3 = {
|
||||
.rtpname = "ac3",
|
||||
.avcodec_id = AV_CODEC_ID_AC3,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
static const codec_def_t eac3 = {
|
||||
.rtpname = "eac3",
|
||||
.avcodec_id = AV_CODEC_ID_EAC3,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&ac3);
|
||||
codeclib_register_codec(&eac3);
|
||||
}
|
||||
@ -0,0 +1,708 @@
|
||||
#include "codecmod.h"
|
||||
#include "loglib.h"
|
||||
#include "bitstr.h"
|
||||
|
||||
|
||||
static int codeclib_set_av_opt_intstr(encoder_t *enc, const char *opt, str *val) {
|
||||
int i = val ? str_to_i(val, -1) : -1;
|
||||
if (i == -1) {
|
||||
ilog(LOG_WARN, "Failed to parse '" STR_FORMAT "' as integer value for ffmpeg option '%s'",
|
||||
STR_FMT0(val), opt);
|
||||
return -1;
|
||||
}
|
||||
return codeclib_set_av_opt_int(enc, opt, i);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const unsigned int amr_bitrates[AMR_FT_TYPES] = {
|
||||
4750, // 0
|
||||
5150, // 1
|
||||
5900, // 2
|
||||
6700, // 3
|
||||
7400, // 4
|
||||
7950, // 5
|
||||
10200, // 6
|
||||
12200, // 7
|
||||
0, // comfort noise // 8
|
||||
0, // comfort noise // 9
|
||||
0, // comfort noise // 10
|
||||
0, // comfort noise // 11
|
||||
0, // invalid // 12
|
||||
0, // invalid // 13
|
||||
};
|
||||
static const unsigned int amr_bits_per_frame[AMR_FT_TYPES] = {
|
||||
95, // 4.75 kbit/s // 0
|
||||
103, // 5.15 kbit/s // 1
|
||||
118, // 5.90 kbit/s // 2
|
||||
134, // 6.70 kbit/s // 3
|
||||
148, // 7.40 kbit/s // 4
|
||||
159, // 7.95 kbit/s // 5
|
||||
204, // 10.2 kbit/s // 6
|
||||
244, // 12.2 kbit/s // 7
|
||||
40, // comfort noise // 8
|
||||
40, // comfort noise // 9
|
||||
40, // comfort noise // 10
|
||||
40, // comfort noise // 11
|
||||
0, // invalid // 12
|
||||
0, // invalid // 13
|
||||
};
|
||||
static const unsigned int amr_wb_bitrates[AMR_FT_TYPES] = {
|
||||
6600, // 0
|
||||
8850, // 1
|
||||
12650, // 2
|
||||
14250, // 3
|
||||
15850, // 4
|
||||
18250, // 5
|
||||
19850, // 6
|
||||
23050, // 7
|
||||
23850, // 8
|
||||
0, // comfort noise // 9
|
||||
0, // invalid // 10
|
||||
0, // invalid // 11
|
||||
0, // invalid // 12
|
||||
0, // invalid // 13
|
||||
};
|
||||
static const unsigned int amr_wb_bits_per_frame[AMR_FT_TYPES] = {
|
||||
132, // 6.60 kbit/s // 0
|
||||
177, // 8.85 kbit/s // 1
|
||||
253, // 12.65 kbit/s // 2
|
||||
285, // 14.25 kbit/s // 3
|
||||
317, // 15.85 kbit/s // 4
|
||||
365, // 18.25 kbit/s // 5
|
||||
397, // 19.85 kbit/s // 6
|
||||
461, // 23.05 kbit/s // 7
|
||||
477, // 23.85 kbit/s // 8
|
||||
40, // comfort noise // 9
|
||||
0, // invalid // 10
|
||||
0, // invalid // 11
|
||||
0, // invalid // 12
|
||||
0, // invalid // 13
|
||||
};
|
||||
static void amr_parse_format_cb(str *key, str *token, void *data) {
|
||||
union codec_format_options *opts = data;
|
||||
|
||||
switch (__csh_lookup(key)) {
|
||||
case CSH_LOOKUP("octet-align"):
|
||||
if (token->len == 1 && token->s[0] == '1')
|
||||
opts->amr.octet_aligned = 1;
|
||||
break;
|
||||
case CSH_LOOKUP("crc"):
|
||||
if (token->len == 1 && token->s[0] == '1') {
|
||||
opts->amr.octet_aligned = 1;
|
||||
opts->amr.crc = 1;
|
||||
}
|
||||
break;
|
||||
case CSH_LOOKUP("robust-sorting"):
|
||||
if (token->len == 1 && token->s[0] == '1') {
|
||||
opts->amr.octet_aligned = 1;
|
||||
opts->amr.robust_sorting = 1;
|
||||
}
|
||||
break;
|
||||
case CSH_LOOKUP("interleaving"):
|
||||
opts->amr.octet_aligned = 1;
|
||||
opts->amr.interleaving = str_to_i(token, 0);
|
||||
break;
|
||||
case CSH_LOOKUP("mode-set"):;
|
||||
str mode;
|
||||
while (str_token_sep(&mode, token, ',')) {
|
||||
int m = str_to_i(&mode, -1);
|
||||
if (m < 0 || m >= AMR_FT_TYPES)
|
||||
continue;
|
||||
opts->amr.mode_set |= (1 << m);
|
||||
}
|
||||
break;
|
||||
case CSH_LOOKUP("mode-change-period"):
|
||||
opts->amr.mode_change_period = str_to_i(token, 0);
|
||||
break;
|
||||
case CSH_LOOKUP("mode-change-neighbor"):
|
||||
if (token->len == 1 && token->s[0] == '1')
|
||||
opts->amr.mode_change_neighbor = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
static bool amr_format_parse(struct rtp_codec_format *f, const str *fmtp) {
|
||||
codeclib_key_value_parse(fmtp, true, amr_parse_format_cb, f);
|
||||
return true;
|
||||
}
|
||||
static void amr_set_encdec_options(codec_options_t *opts, codec_def_t *def) {
|
||||
if (!strcmp(def->rtpname, "AMR")) {
|
||||
opts->amr.bits_per_frame = amr_bits_per_frame;
|
||||
opts->amr.bitrates = amr_bitrates;
|
||||
}
|
||||
else {
|
||||
opts->amr.bits_per_frame = amr_wb_bits_per_frame;
|
||||
opts->amr.bitrates = amr_wb_bitrates;
|
||||
}
|
||||
}
|
||||
static void amr_set_dec_codec_options(str *key, str *value, void *data) {
|
||||
decoder_t *dec = data;
|
||||
|
||||
if (!str_cmp(key, "CMR-interval"))
|
||||
dec->codec_options.amr.cmr_interval_us = str_to_i(value, 0) * 1000L;
|
||||
else if (!str_cmp(key, "mode-change-interval"))
|
||||
dec->codec_options.amr.mode_change_interval_us = str_to_i(value, 0) * 1000L;
|
||||
|
||||
}
|
||||
static void amr_set_enc_codec_options(str *key, str *value, void *data) {
|
||||
encoder_t *enc = data;
|
||||
|
||||
if (!str_cmp(key, "CMR-interval"))
|
||||
; // not an encoder option
|
||||
else if (!str_cmp(key, "mode-change-interval"))
|
||||
; // not an encoder option
|
||||
else {
|
||||
// our string might not be null terminated
|
||||
char *s = g_strdup_printf(STR_FORMAT, STR_FMT(key));
|
||||
codeclib_set_av_opt_intstr(enc, s, value);
|
||||
g_free(s);
|
||||
}
|
||||
}
|
||||
static void amr_set_enc_options(encoder_t *enc, const str *codec_opts) {
|
||||
amr_set_encdec_options(&enc->codec_options, enc->def);
|
||||
|
||||
codeclib_key_value_parse(codec_opts, true, amr_set_enc_codec_options, enc);
|
||||
|
||||
// if a mode-set was given, pick the highest supported bitrate
|
||||
if (enc->format_options.amr.mode_set) {
|
||||
int max_bitrate = enc->avc.avcctx->bit_rate;
|
||||
int use_bitrate = 0;
|
||||
for (int i = 0; i < AMR_FT_TYPES; i++) {
|
||||
if (!(enc->format_options.amr.mode_set & (1 << i)))
|
||||
continue;
|
||||
unsigned int br = enc->codec_options.amr.bitrates[i];
|
||||
// we depend on the list being in ascending order, with
|
||||
// invalid modes at the end
|
||||
if (!br) // end of list
|
||||
break;
|
||||
if (br > max_bitrate && use_bitrate) // done
|
||||
break;
|
||||
use_bitrate = br;
|
||||
}
|
||||
if (!use_bitrate)
|
||||
ilog(LOG_WARN, "Unable to determine a valid bitrate from %s mode-set, using default",
|
||||
enc->def->rtpname);
|
||||
else {
|
||||
ilog(LOG_DEBUG, "Using %i as initial %s bitrate based on mode-set",
|
||||
use_bitrate, enc->def->rtpname);
|
||||
enc->avc.avcctx->bit_rate = use_bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void amr_set_dec_options(decoder_t *dec, const str *codec_opts) {
|
||||
amr_set_encdec_options(&dec->codec_options, dec->def);
|
||||
codeclib_key_value_parse(codec_opts, true, amr_set_dec_codec_options, dec);
|
||||
}
|
||||
static int amr_mode_set_cmp(unsigned int a, unsigned int b) {
|
||||
if (a && b) {
|
||||
// `a` must be broader than `b`:
|
||||
// `b` must not have any bits set that `a` has set
|
||||
if (a == b)
|
||||
return 0;
|
||||
else if ((b & ~a) == 0)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
else if (!a && b) // `a` is broader (allow anything) than `b` (restricted)
|
||||
return 1;
|
||||
else if (a && !b)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
static int amr_format_cmp(const struct rtp_payload_type *A, const struct rtp_payload_type *B) {
|
||||
// params must have been parsed successfully
|
||||
if (!A->format.fmtp_parsed || !B->format.fmtp_parsed)
|
||||
return -1;
|
||||
|
||||
__auto_type a = &A->format.parsed.amr;
|
||||
__auto_type b = &B->format.parsed.amr;
|
||||
|
||||
// reject anything that is outright incompatible (RFC 4867, 8.3.1)
|
||||
if (a->octet_aligned != b->octet_aligned)
|
||||
return -1;
|
||||
if (a->crc != b->crc)
|
||||
return -1;
|
||||
if (a->interleaving != b->interleaving)
|
||||
return -1;
|
||||
if (a->robust_sorting != b->robust_sorting)
|
||||
return -1;
|
||||
|
||||
// determine whether codecs are compatible
|
||||
int compat = 0;
|
||||
|
||||
if (a->mode_change_neighbor != b->mode_change_neighbor)
|
||||
compat++;
|
||||
if (a->mode_change_period != b->mode_change_period)
|
||||
compat++;
|
||||
|
||||
int match = amr_mode_set_cmp(a->mode_set, b->mode_set);
|
||||
if (match == 1)
|
||||
compat++;
|
||||
else if (match == -1)
|
||||
return -1;
|
||||
|
||||
return (compat == 0) ? 0 : 1;
|
||||
}
|
||||
|
||||
static void amr_bitrate_tracker(decoder_t *dec, unsigned int ft) {
|
||||
if (dec->codec_options.amr.cmr_interval_us <= 0)
|
||||
return;
|
||||
|
||||
if (dec->avc.amr.tracker_end
|
||||
&& dec->avc.amr.tracker_end >= rtpe_now) {
|
||||
// analyse the data we gathered
|
||||
int next_highest = -1;
|
||||
int lowest_used = -1;
|
||||
for (int i = 0; i < AMR_FT_TYPES; i++) {
|
||||
unsigned int br = dec->codec_options.amr.bitrates[i];
|
||||
if (!br)
|
||||
break; // end of list
|
||||
|
||||
// ignore restricted modes
|
||||
if (dec->format_options.amr.mode_set) {
|
||||
if (!(dec->format_options.amr.mode_set & (1 << i)))
|
||||
continue;
|
||||
}
|
||||
|
||||
// would this be a "next step up" mode?
|
||||
if (next_highest == -1)
|
||||
next_highest = i;
|
||||
|
||||
// did we see any frames?
|
||||
if (!dec->avc.amr.bitrate_tracker[i])
|
||||
continue;
|
||||
|
||||
next_highest = -1;
|
||||
lowest_used = i;
|
||||
}
|
||||
|
||||
if (lowest_used != -1 && next_highest != -1) {
|
||||
// we can request a switch up
|
||||
ilog(LOG_DEBUG, "Sending %s CMR to request upping bitrate to %u",
|
||||
dec->def->rtpname, dec->codec_options.amr.bitrates[next_highest]);
|
||||
decoder_event(dec, CE_AMR_SEND_CMR, GINT_TO_POINTER(next_highest));
|
||||
}
|
||||
|
||||
// and reset tracker
|
||||
ZERO(dec->avc.amr.tracker_end);
|
||||
}
|
||||
|
||||
if (!dec->avc.amr.tracker_end) {
|
||||
// init
|
||||
ZERO(dec->avc.amr.bitrate_tracker);
|
||||
dec->avc.amr.tracker_end = rtpe_now;
|
||||
dec->avc.amr.tracker_end += dec->codec_options.amr.cmr_interval_us;
|
||||
}
|
||||
|
||||
dec->avc.amr.bitrate_tracker[ft]++;
|
||||
}
|
||||
static int amr_decoder_input(decoder_t *dec, const str *data, GQueue *out) {
|
||||
const char *err = NULL;
|
||||
g_auto(GQueue) toc = G_QUEUE_INIT;
|
||||
|
||||
if (!data || !data->s)
|
||||
goto err;
|
||||
|
||||
bitstr d;
|
||||
bitstr_init(&d, data);
|
||||
|
||||
unsigned int ill = 0, ilp = 0;
|
||||
|
||||
unsigned char cmr_chr[2];
|
||||
str cmr = STR_CONST_BUF(cmr_chr);
|
||||
err = "no CMR";
|
||||
if (bitstr_shift_ret(&d, 4, &cmr))
|
||||
goto err;
|
||||
|
||||
unsigned int cmr_int = cmr_chr[0] >> 4;
|
||||
if (cmr_int != 15) {
|
||||
decoder_event(dec, CE_AMR_CMR_RECV, GUINT_TO_POINTER(cmr_int));
|
||||
dec->avc.amr.last_cmr = rtpe_now;
|
||||
}
|
||||
else if (dec->codec_options.amr.mode_change_interval_us) {
|
||||
// no CMR, check if we're due to do our own mode change
|
||||
if (!dec->avc.amr.last_cmr) // start tracking now
|
||||
dec->avc.amr.last_cmr = rtpe_now;
|
||||
else if (rtpe_now - dec->avc.amr.last_cmr
|
||||
>= dec->codec_options.amr.mode_change_interval_us) {
|
||||
// switch up if we can
|
||||
decoder_event(dec, CE_AMR_CMR_RECV, GUINT_TO_POINTER(0xffff));
|
||||
dec->avc.amr.last_cmr = rtpe_now;
|
||||
}
|
||||
}
|
||||
|
||||
if (dec->format_options.amr.octet_aligned) {
|
||||
if (bitstr_shift(&d, 4))
|
||||
goto err;
|
||||
|
||||
if (dec->format_options.amr.interleaving) {
|
||||
unsigned char ill_ilp_chr[2];
|
||||
str ill_ilp = STR_CONST_BUF(ill_ilp_chr);
|
||||
err = "no ILL/ILP";
|
||||
if (bitstr_shift_ret(&d, 8, &ill_ilp))
|
||||
goto err;
|
||||
ill = ill_ilp_chr[0] >> 4;
|
||||
ilp = ill_ilp_chr[0] & 0xf;
|
||||
}
|
||||
}
|
||||
|
||||
err = "ILP > ILL";
|
||||
if (ilp > ill)
|
||||
goto err;
|
||||
err = "interleaving unimplemented";
|
||||
if (ill)
|
||||
goto err;
|
||||
|
||||
// TOC
|
||||
int num_crcs = 0;
|
||||
while (1) {
|
||||
unsigned char toc_byte[2];
|
||||
str toc_entry = STR_CONST_BUF(toc_byte);
|
||||
err = "missing TOC entry";
|
||||
if (bitstr_shift_ret(&d, 6, &toc_entry))
|
||||
goto err;
|
||||
|
||||
if (dec->format_options.amr.octet_aligned)
|
||||
if (bitstr_shift(&d, 2))
|
||||
goto err;
|
||||
|
||||
unsigned char ft = (toc_byte[0] >> 3) & 0xf;
|
||||
if (ft != 14 && ft != 15) {
|
||||
num_crcs++;
|
||||
err = "invalid frame type";
|
||||
if (ft >= AMR_FT_TYPES)
|
||||
goto err;
|
||||
if (dec->codec_options.amr.bits_per_frame[ft] == 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
g_queue_push_tail(&toc, GUINT_TO_POINTER(toc_byte[0]));
|
||||
|
||||
// no F bit = last TOC entry
|
||||
if (!(toc_byte[0] & 0x80))
|
||||
break;
|
||||
}
|
||||
|
||||
if (dec->format_options.amr.crc) {
|
||||
// CRCs is one byte per frame
|
||||
err = "missing CRC entry";
|
||||
if (bitstr_shift(&d, num_crcs * 8))
|
||||
goto err;
|
||||
// XXX use/check CRCs
|
||||
}
|
||||
|
||||
while (toc.length) {
|
||||
unsigned char toc_byte = GPOINTER_TO_UINT(g_queue_pop_head(&toc));
|
||||
unsigned char ft = (toc_byte >> 3) & 0xf;
|
||||
if (ft >= AMR_FT_TYPES) // invalid
|
||||
continue;
|
||||
|
||||
unsigned int bits = dec->codec_options.amr.bits_per_frame[ft];
|
||||
|
||||
// AMR decoder expects an octet aligned TOC byte plus the payload
|
||||
unsigned char frame_buf[(bits + 7) / 8 + 1 + 1];
|
||||
str frame = STR_CONST_BUF(frame_buf);
|
||||
str_shift(&frame, 1);
|
||||
err = "short frame";
|
||||
if (bitstr_shift_ret(&d, bits, &frame))
|
||||
goto err;
|
||||
|
||||
// add TOC byte
|
||||
str_unshift(&frame, 1);
|
||||
frame.s[0] = toc_byte & 0x7c; // strip F bit, keep FT and Q, zero padding (01111100)
|
||||
|
||||
if (dec->format_options.amr.octet_aligned && (bits % 8) != 0) {
|
||||
unsigned int padding_bits = 8 - (bits % 8);
|
||||
if (bitstr_shift(&d, padding_bits))
|
||||
goto err;
|
||||
}
|
||||
|
||||
err = "failed to decode AMR data";
|
||||
if (bits == 40) {
|
||||
// SID
|
||||
if (dec->dtx.method_id == DTX_NATIVE) {
|
||||
if (avc_decoder_input(dec, &frame, out))
|
||||
goto err;
|
||||
}
|
||||
else {
|
||||
// use the DTX generator to replace SID
|
||||
if (dec->dtx.do_dtx(dec, out, 20))
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (avc_decoder_input(dec, &frame, out))
|
||||
goto err;
|
||||
}
|
||||
|
||||
amr_bitrate_tracker(dec, ft);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (err)
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Error unpacking AMR packet: %s", err);
|
||||
|
||||
return -1;
|
||||
}
|
||||
static unsigned int amr_encoder_find_next_mode(encoder_t *enc) {
|
||||
int mode = -1;
|
||||
for (int i = 0; i < AMR_FT_TYPES; i++) {
|
||||
int br = enc->codec_options.amr.bitrates[i];
|
||||
if (!br) // end of list
|
||||
break;
|
||||
if (br == enc->avc.avcctx->bit_rate) {
|
||||
mode = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mode == -1)
|
||||
return -1;
|
||||
int next_mode = mode + 1;
|
||||
// if modes are restricted, find the next one up
|
||||
if (enc->format_options.amr.mode_set) {
|
||||
// is there anything?
|
||||
if ((1 << next_mode) > enc->format_options.amr.mode_set)
|
||||
return -1;
|
||||
int next_up = -1;
|
||||
for (; next_mode < AMR_FT_TYPES; next_mode++) {
|
||||
if (!(enc->format_options.amr.mode_set & (1 << next_mode)))
|
||||
continue;
|
||||
next_up = next_mode;
|
||||
break;
|
||||
}
|
||||
if (next_up == -1)
|
||||
return -1;
|
||||
next_mode = next_up;
|
||||
}
|
||||
// valid mode?
|
||||
if (next_mode >= AMR_FT_TYPES || enc->codec_options.amr.bitrates[next_mode] == 0)
|
||||
return -1;
|
||||
return next_mode;
|
||||
}
|
||||
static void amr_encoder_mode_change(encoder_t *enc) {
|
||||
if (enc->callback.amr.cmr_in_ts == enc->avc.amr.cmr_in_ts)
|
||||
return;
|
||||
// mode change requested: check if this is allowed right now
|
||||
if (enc->format_options.amr.mode_change_period == 2 && (enc->avc.amr.pkt_seq & 1) != 0)
|
||||
return;
|
||||
unsigned int cmr = enc->callback.amr.cmr_in;
|
||||
if (cmr == 0xffff)
|
||||
cmr = amr_encoder_find_next_mode(enc);
|
||||
if (cmr >= AMR_FT_TYPES)
|
||||
return;
|
||||
// ignore CMR for invalid modes
|
||||
if (enc->format_options.amr.mode_set && !(enc->format_options.amr.mode_set & (1 << cmr)))
|
||||
return;
|
||||
int req_br = enc->codec_options.amr.bitrates[cmr];
|
||||
if (!req_br)
|
||||
return;
|
||||
int cmr_done = 1;
|
||||
if (enc->format_options.amr.mode_change_neighbor) {
|
||||
// handle non-neighbour mode changes
|
||||
int cur_br = enc->avc.avcctx->bit_rate;
|
||||
// step up or down from the requested bitrate towards the current one
|
||||
int cmr_diff = (req_br > cur_br) ? -1 : 1;
|
||||
int neigh_br = req_br;
|
||||
int cmr_br = req_br;
|
||||
while (1) {
|
||||
// step up or down towards the current bitrate
|
||||
cmr += cmr_diff;
|
||||
// still in bounds?
|
||||
if (cmr >= AMR_FT_TYPES)
|
||||
break;
|
||||
cmr_br = enc->codec_options.amr.bitrates[cmr];
|
||||
if (cmr_br == cur_br)
|
||||
break;
|
||||
// allowed by mode set?
|
||||
if (enc->format_options.amr.mode_set) {
|
||||
if (!(enc->format_options.amr.mode_set & (1 << cmr)))
|
||||
continue; // go to next mode
|
||||
}
|
||||
// valid bitrate - continue stepping
|
||||
neigh_br = cmr_br;
|
||||
}
|
||||
// did we finish stepping or is there more to go?
|
||||
if (neigh_br != req_br)
|
||||
cmr_done = 0;
|
||||
req_br = neigh_br; // set to this
|
||||
}
|
||||
enc->avc.avcctx->bit_rate = req_br;
|
||||
if (cmr_done)
|
||||
enc->avc.amr.cmr_in_ts = enc->callback.amr.cmr_in_ts;
|
||||
}
|
||||
static void amr_encoder_got_packet(encoder_t *enc) {
|
||||
amr_encoder_mode_change(enc);
|
||||
enc->avc.amr.pkt_seq++;
|
||||
}
|
||||
static int packetizer_amr(AVPacket *pkt, GString *buf, str *output, size_t num_bytes, encoder_t *enc,
|
||||
int64_t *__restrict pts, int64_t *__restrict duration)
|
||||
{
|
||||
assert(pkt->size >= 1);
|
||||
|
||||
// CMR + TOC byte (already included) + optional ILL/ILP + optional CRC + payload
|
||||
if (output->len < pkt->size + 3) {
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Output AMR packet size too small (%zu < %i + 3)",
|
||||
output->len, pkt->size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned char toc = pkt->data[0];
|
||||
unsigned char ft = (toc >> 3) & 0xf;
|
||||
if (ft > 15) {
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Received bogus AMR FT %u from encoder", ft);
|
||||
return -1;
|
||||
}
|
||||
if (ft >= 14) {
|
||||
// NO_DATA or SPEECH_LOST
|
||||
return -1;
|
||||
}
|
||||
assert(ft < AMR_FT_TYPES); // internal bug
|
||||
unsigned int bits = enc->codec_options.amr.bits_per_frame[ft];
|
||||
if (bits == 0) {
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Received bogus AMR FT %u from encoder", ft);
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned char *s = (unsigned char *) output->s; // for safe bit shifting
|
||||
|
||||
*pts = pkt->pts;
|
||||
*duration = enc->actual_format.clockrate * 20LL / 1000; // 160 or 320
|
||||
|
||||
s[0] = '\xf0'; // no CMR req (4 bits)
|
||||
|
||||
// or do we have a CMR?
|
||||
if (!enc->avc.amr.cmr_out_seq) {
|
||||
if (enc->avc.amr.cmr_out_ts != enc->callback.amr.cmr_out_ts) {
|
||||
enc->avc.amr.cmr_out_seq += 3; // make this configurable?
|
||||
enc->avc.amr.cmr_out_ts = enc->callback.amr.cmr_out_ts;
|
||||
}
|
||||
}
|
||||
if (enc->avc.amr.cmr_out_seq) {
|
||||
enc->avc.amr.cmr_out_seq--;
|
||||
unsigned int cmr = enc->callback.amr.cmr_out;
|
||||
if (cmr < AMR_FT_TYPES && enc->codec_options.amr.bitrates[cmr])
|
||||
s[0] = cmr << 4;
|
||||
}
|
||||
|
||||
if (enc->format_options.amr.octet_aligned) {
|
||||
unsigned int offset = 1; // CMR byte
|
||||
if (enc->format_options.amr.interleaving)
|
||||
s[offset++] = 0; // no interleaving
|
||||
if (enc->format_options.amr.crc)
|
||||
s[offset++] = 0; // not implemented
|
||||
memcpy(s + offset, pkt->data, pkt->size);
|
||||
output->len = pkt->size + offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// bit shift TOC byte in (6 bits)
|
||||
s[0] |= pkt->data[0] >> 4;
|
||||
s[1] = (pkt->data[0] & 0x0c) << 4;
|
||||
|
||||
// bit shift payload in (shifted by 4+6 = 10 bits = 1 byte + 2 bits
|
||||
for (int i = 1; i < pkt->size; i++) {
|
||||
s[i] |= pkt->data[i] >> 2;
|
||||
s[i+1] = pkt->data[i] << 6;
|
||||
}
|
||||
|
||||
// is the last byte just padding?
|
||||
bits += 4 + 6; // CMR and TOC
|
||||
unsigned int bytes = (bits + 7) / 8;
|
||||
output->len = bytes;
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int amr_dtx(decoder_t *dec, GQueue *out, int ptime) {
|
||||
// ignore ptime, must be 20
|
||||
ilog(LOG_DEBUG, "pushing empty/lost frame to AMR decoder");
|
||||
unsigned char frame_buf[1];
|
||||
frame_buf[0] = 0xf << 3; // no data
|
||||
str frame = STR_CONST_BUF(frame_buf);
|
||||
if (avc_decoder_input(dec, &frame, out))
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Error while writing 'no data' frame to AMR decoder");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static const codec_type_t codec_type_amr = {
|
||||
.def_init = avc_def_init,
|
||||
.decoder_init = avc_decoder_init,
|
||||
.decoder_input = amr_decoder_input,
|
||||
.decoder_close = avc_decoder_close,
|
||||
.encoder_init = avc_encoder_init,
|
||||
.encoder_input = avc_encoder_input,
|
||||
.encoder_got_packet = amr_encoder_got_packet,
|
||||
.encoder_close = avc_encoder_close,
|
||||
};
|
||||
|
||||
static const dtx_method_t dtx_method_amr = {
|
||||
.method_id = DTX_NATIVE,
|
||||
.do_dtx = amr_dtx,
|
||||
};
|
||||
|
||||
static const codec_def_t amr = {
|
||||
.rtpname = "AMR",
|
||||
.avcodec_id = AV_CODEC_ID_AMR_NB,
|
||||
.avcodec_name_enc = "libopencore_amrnb",
|
||||
.avcodec_name_dec = "libopencore_amrnb",
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_bitrate = 6700,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.format_parse = amr_format_parse,
|
||||
.format_cmp = amr_format_cmp,
|
||||
.default_fmtp = "octet-align=1;mode-change-capability=2",
|
||||
.packetizer = packetizer_amr,
|
||||
.bits_per_sample = 2, // max is 12200 / 8000 = 1.525 bits per sample, rounded up
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_amr,
|
||||
.set_enc_options = amr_set_enc_options,
|
||||
.set_dec_options = amr_set_dec_options,
|
||||
.amr = 1,
|
||||
.dtx_methods = {
|
||||
[DTX_NATIVE] = &dtx_method_amr,
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
|
||||
};
|
||||
static const codec_def_t amr_wb = {
|
||||
.rtpname = "AMR-WB",
|
||||
.avcodec_id = AV_CODEC_ID_AMR_WB,
|
||||
.avcodec_name_enc = "libvo_amrwbenc",
|
||||
.avcodec_name_dec = "libopencore_amrwb",
|
||||
.default_clockrate = 16000,
|
||||
.default_channels = 1,
|
||||
.default_bitrate = 14250,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.format_parse = amr_format_parse,
|
||||
.format_cmp = amr_format_cmp,
|
||||
.default_fmtp = "octet-align=1;mode-change-capability=2",
|
||||
.packetizer = packetizer_amr,
|
||||
.bits_per_sample = 2, // max is 23850 / 16000 = 1.490625 bits per sample, rounded up
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_amr,
|
||||
.set_enc_options = amr_set_enc_options,
|
||||
.set_dec_options = amr_set_dec_options,
|
||||
.amr = 1,
|
||||
.dtx_methods = {
|
||||
[DTX_NATIVE] = &dtx_method_amr,
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&amr);
|
||||
codeclib_register_codec(&amr_wb);
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t atrac3 = {
|
||||
.rtpname = "ATRAC3",
|
||||
.avcodec_id = AV_CODEC_ID_ATRAC3,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
static const codec_def_t atrac_x = {
|
||||
.rtpname = "ATRAC-X",
|
||||
.avcodec_id = AV_CODEC_ID_ATRAC3P,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&atrac3);
|
||||
codeclib_register_codec(&atrac_x);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,36 @@
|
||||
#ifndef _CODECMOD_H_
|
||||
#define _CODECMOD_H_
|
||||
|
||||
|
||||
#include "codeclib.h"
|
||||
|
||||
|
||||
extern const codec_type_t codec_type_avcodec;
|
||||
|
||||
extern const dtx_method_t dtx_method_silence;
|
||||
extern const dtx_method_t dtx_method_cn;
|
||||
|
||||
packetizer_f packetizer_samplestream; // flat stream of samples
|
||||
|
||||
int format_cmp_ignore(const struct rtp_payload_type *, const struct rtp_payload_type *);
|
||||
|
||||
|
||||
void avc_def_init(struct codec_def_s *);
|
||||
const char *avc_decoder_init(decoder_t *, const str *);
|
||||
int avc_decoder_input(decoder_t *dec, const str *data, GQueue *out);
|
||||
void avc_decoder_close(decoder_t *);
|
||||
const char *avc_encoder_init(encoder_t *enc, const str *);
|
||||
int avc_encoder_input(encoder_t *enc, AVFrame **frame);
|
||||
void avc_encoder_close(encoder_t *enc);
|
||||
|
||||
int codeclib_set_av_opt_int(encoder_t *enc, const char *opt, int64_t val);
|
||||
void codeclib_key_value_parse(const str *instr, bool need_value,
|
||||
void (*cb)(str *key, str *value, void *data), void *data);
|
||||
|
||||
void *dlsym_assert(void *handle, const char *sym, const char *fn);
|
||||
|
||||
|
||||
void codeclib_register_codec(const codec_def_t *);
|
||||
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,53 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 0, 0)
|
||||
|
||||
static const codec_def_t evrc = {
|
||||
.rtpname = "EVRC",
|
||||
.avcodec_id = AV_CODEC_ID_EVRC,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
static const codec_def_t evrc0 = {
|
||||
.rtpname = "EVRC0",
|
||||
.avcodec_id = AV_CODEC_ID_EVRC,
|
||||
.default_clockrate = 8000,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
static const codec_def_t evrc1 = {
|
||||
.rtpname = "EVRC1",
|
||||
.avcodec_id = AV_CODEC_ID_EVRC,
|
||||
.default_clockrate = 8000,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&evrc);
|
||||
codeclib_register_codec(&evrc0);
|
||||
codeclib_register_codec(&evrc1);
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,48 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t pcma = {
|
||||
.rtpname = "PCMA",
|
||||
.avcodec_id = AV_CODEC_ID_PCM_ALAW,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_samplestream,
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.bits_per_sample = 8,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.silence_pattern = STR_CONST("\xd5"),
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
|
||||
static const codec_def_t pcmu = {
|
||||
.rtpname = "PCMU",
|
||||
.avcodec_id = AV_CODEC_ID_PCM_MULAW,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_samplestream,
|
||||
.bits_per_sample = 8,
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.silence_pattern = STR_CONST("\xff"),
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
|
||||
};
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&pcma);
|
||||
codeclib_register_codec(&pcmu);
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
static const codec_def_t g722 = {
|
||||
.rtpname = "G722",
|
||||
.avcodec_id = AV_CODEC_ID_ADPCM_G722,
|
||||
.default_clockrate_fact = {2,1},
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.packetizer = packetizer_samplestream,
|
||||
.bits_per_sample = 4,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.silence_pattern = STR_CONST("\xfa"),
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&g722);
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
static const codec_def_t g723 = {
|
||||
.rtpname = "G723",
|
||||
.avcodec_id = AV_CODEC_ID_G723_1,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 30,
|
||||
.minimum_ptime = 30,
|
||||
.default_bitrate = 6300,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&g723);
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const char *g726_encoder_init(encoder_t *enc, const str *extra_opts) {
|
||||
const char *err = avc_encoder_init(enc, extra_opts);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
enc->samples_per_packet = enc->ptime * 8;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const codec_type_t codec_type_g726 = {
|
||||
.def_init = avc_def_init,
|
||||
.decoder_init = avc_decoder_init,
|
||||
.decoder_input = avc_decoder_input,
|
||||
.decoder_close = avc_decoder_close,
|
||||
.encoder_init = g726_encoder_init,
|
||||
.encoder_input = avc_encoder_input,
|
||||
.encoder_close = avc_encoder_close,
|
||||
};
|
||||
|
||||
|
||||
static const codec_def_t g726_16 = {
|
||||
.rtpname = "G726-16",
|
||||
.avcodec_id = AV_CODEC_ID_ADPCM_G726,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.default_bitrate = 16000,
|
||||
.packetizer = packetizer_samplestream,
|
||||
.bits_per_sample = 2,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_g726,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
static const codec_def_t g726_24 = {
|
||||
.rtpname = "G726-24",
|
||||
.avcodec_id = AV_CODEC_ID_ADPCM_G726,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.default_bitrate = 24000,
|
||||
.packetizer = packetizer_samplestream,
|
||||
.bits_per_sample = 3,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_g726,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
static const codec_def_t g726_32 = {
|
||||
.rtpname = "G726-32",
|
||||
.avcodec_id = AV_CODEC_ID_ADPCM_G726,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.default_bitrate = 32000,
|
||||
.packetizer = packetizer_samplestream,
|
||||
.bits_per_sample = 4,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_g726,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
static const codec_def_t g726_40 = {
|
||||
.rtpname = "G726-40",
|
||||
.avcodec_id = AV_CODEC_ID_ADPCM_G726,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.default_bitrate = 40000,
|
||||
.packetizer = packetizer_samplestream,
|
||||
.bits_per_sample = 5,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_g726,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&g726_16);
|
||||
codeclib_register_codec(&g726_24);
|
||||
codeclib_register_codec(&g726_32);
|
||||
codeclib_register_codec(&g726_40);
|
||||
}
|
||||
@ -0,0 +1,275 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
#ifndef HAVE_BCG729
|
||||
|
||||
static const codec_def_t g729 = {
|
||||
.rtpname = "G729",
|
||||
.avcodec_id = AV_CODEC_ID_G729,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
|
||||
static const codec_def_t g729a = {
|
||||
.rtpname = "G729a",
|
||||
.avcodec_id = AV_CODEC_ID_G729,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
#include <bcg729/encoder.h>
|
||||
#include <bcg729/decoder.h>
|
||||
#include "fix_frame_channel_layout.compat"
|
||||
#include "loglib.h"
|
||||
|
||||
|
||||
static packetizer_f packetizer_g729; // aggregate some frames into packets
|
||||
|
||||
static void bcg729_def_init(struct codec_def_s *);
|
||||
static const char *bcg729_decoder_init(decoder_t *, const str *);
|
||||
static int bcg729_decoder_input(decoder_t *dec, const str *data, GQueue *out);
|
||||
static void bcg729_decoder_close(decoder_t *);
|
||||
static const char *bcg729_encoder_init(encoder_t *enc, const str *);
|
||||
static int bcg729_encoder_input(encoder_t *enc, AVFrame **frame);
|
||||
static void bcg729_encoder_close(encoder_t *enc);
|
||||
|
||||
static const codec_type_t codec_type_bcg729 = {
|
||||
.def_init = bcg729_def_init,
|
||||
.decoder_init = bcg729_decoder_init,
|
||||
.decoder_input = bcg729_decoder_input,
|
||||
.decoder_close = bcg729_decoder_close,
|
||||
.encoder_init = bcg729_encoder_init,
|
||||
.encoder_input = bcg729_encoder_input,
|
||||
.encoder_close = bcg729_encoder_close,
|
||||
};
|
||||
|
||||
static const codec_def_t g729 = {
|
||||
.rtpname = "G729",
|
||||
.avcodec_id = -1,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.default_fmtp = "annexb=yes",
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.packetizer = packetizer_g729,
|
||||
.bits_per_sample = 1, // 10 ms frame has 80 samples and encodes as (max) 10 bytes = 80 bits
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_bcg729,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
|
||||
static const codec_def_t g729a = {
|
||||
.rtpname = "G729a",
|
||||
.avcodec_id = -1,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.default_fmtp = "annexb=no",
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.packetizer = packetizer_g729,
|
||||
.bits_per_sample = 1, // 10 ms frame has 80 samples and encodes as (max) 10 bytes = 80 bits
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_bcg729,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.fixed_sizes = 1,
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void bcg729_def_init(struct codec_def_s *def) {
|
||||
// test init
|
||||
bcg729EncoderChannelContextStruct *e = initBcg729EncoderChannel(0);
|
||||
bcg729DecoderChannelContextStruct *d = initBcg729DecoderChannel();
|
||||
if (e) {
|
||||
def->support_encoding = 1;
|
||||
closeBcg729EncoderChannel(e);
|
||||
}
|
||||
if (d) {
|
||||
def->support_decoding = 1;
|
||||
closeBcg729DecoderChannel(d);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *bcg729_decoder_init(decoder_t *dec, const str *extra_opts) {
|
||||
dec->bcg729 = initBcg729DecoderChannel();
|
||||
if (!dec->bcg729)
|
||||
return "failed to initialize bcg729";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int bcg729_decoder_input(decoder_t *dec, const str *data, GQueue *out) {
|
||||
str input = *data;
|
||||
uint64_t pts = dec->pts;
|
||||
|
||||
while (input.len >= 2) {
|
||||
int frame_len = input.len >= 10 ? 10 : 2;
|
||||
str inp_frame = input;
|
||||
inp_frame.len = frame_len;
|
||||
str_shift(&input, frame_len);
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
frame->nb_samples = 80;
|
||||
frame->format = AV_SAMPLE_FMT_S16;
|
||||
frame->sample_rate = dec->in_format.clockrate; // 8000
|
||||
DEF_CH_LAYOUT(&frame->CH_LAYOUT, dec->in_format.channels);
|
||||
frame->pts = pts;
|
||||
if (av_frame_get_buffer(frame, 0) < 0)
|
||||
abort();
|
||||
|
||||
pts += frame->nb_samples;
|
||||
|
||||
// XXX handle lost packets and comfort noise
|
||||
bcg729Decoder(dec->bcg729, (void *) inp_frame.s, inp_frame.len, 0, 0, 0,
|
||||
(void *) frame->extended_data[0]);
|
||||
|
||||
g_queue_push_tail(out, frame);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bcg729_decoder_close(decoder_t *dec) {
|
||||
if (dec->bcg729)
|
||||
closeBcg729DecoderChannel(dec->bcg729);
|
||||
dec->bcg729 = NULL;
|
||||
}
|
||||
|
||||
static const char *bcg729_encoder_init(encoder_t *enc, const str *extra_opts) {
|
||||
enc->bcg729 = initBcg729EncoderChannel(0); // no VAD
|
||||
if (!enc->bcg729)
|
||||
return "failed to initialize bcg729";
|
||||
|
||||
enc->actual_format.format = AV_SAMPLE_FMT_S16;
|
||||
enc->actual_format.channels = 1;
|
||||
enc->actual_format.clockrate = 8000;
|
||||
enc->samples_per_frame = 80;
|
||||
enc->samples_per_packet = enc->actual_format.clockrate * enc->ptime / 1000;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int bcg729_encoder_input(encoder_t *enc, AVFrame **frame) {
|
||||
if (!*frame)
|
||||
return 0;
|
||||
|
||||
if ((*frame)->nb_samples != 80) {
|
||||
ilog(LOG_ERR | LOG_FLAG_LIMIT, "bcg729: input %u samples instead of 80", (*frame)->nb_samples);
|
||||
return -1;
|
||||
}
|
||||
|
||||
av_new_packet(enc->avpkt, 10);
|
||||
unsigned char len = 0;
|
||||
|
||||
bcg729Encoder(enc->bcg729, (void *) (*frame)->extended_data[0], enc->avpkt->data, &len);
|
||||
if (!len) {
|
||||
av_packet_unref(enc->avpkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
enc->avpkt->size = len;
|
||||
enc->avpkt->pts = (*frame)->pts;
|
||||
enc->avpkt->duration = len * 8; // Duration is used by encoder_input_data for pts calculation
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bcg729_encoder_close(encoder_t *enc) {
|
||||
if (enc->bcg729)
|
||||
closeBcg729EncoderChannel(enc->bcg729);
|
||||
enc->bcg729 = NULL;
|
||||
}
|
||||
|
||||
static int packetizer_g729(AVPacket *pkt, GString *buf, str *input_output, size_t num_bytes, encoder_t *enc,
|
||||
int64_t *__restrict pts, int64_t *__restrict duration)
|
||||
{
|
||||
// how many frames do we want?
|
||||
int want_frames = input_output->len / 10;
|
||||
|
||||
// easiest case: we only want one frame. return what we got
|
||||
if (want_frames == 1 && pkt)
|
||||
return packetizer_passthrough(pkt, buf, input_output, num_bytes, enc, pts, duration);
|
||||
|
||||
// any other case, we go through our buffer
|
||||
str output = *input_output; // remaining output buffer
|
||||
if (pkt)
|
||||
g_string_append_len(buf, (char *) pkt->data, pkt->size);
|
||||
|
||||
// how many frames do we have?
|
||||
int have_audio_frames = buf->len / 10;
|
||||
int have_noise_frames = (buf->len % 10) / 2;
|
||||
// we have enough?
|
||||
// special case: 4 noise frames (8 bytes) must be returned now, as otherwise
|
||||
// (5 noise frames) they might become indistinguishable from an audio frame
|
||||
if (have_audio_frames + have_noise_frames < want_frames
|
||||
&& have_noise_frames != 4)
|
||||
return -1;
|
||||
|
||||
int64_t dur = 0;
|
||||
|
||||
// return non-silence/noise frames while we can
|
||||
while (buf->len >= 10 && want_frames && output.len >= 10) {
|
||||
memcpy(output.s, buf->str, 10);
|
||||
g_string_erase(buf, 0, 10);
|
||||
want_frames--;
|
||||
str_shift(&output, 10);
|
||||
dur += 80;
|
||||
}
|
||||
|
||||
// append silence/noise frames if we can
|
||||
while (buf->len >= 2 && want_frames && output.len >= 2) {
|
||||
memcpy(output.s, buf->str, 2);
|
||||
g_string_erase(buf, 0, 2);
|
||||
want_frames--;
|
||||
str_shift(&output, 2);
|
||||
dur += 80;
|
||||
}
|
||||
|
||||
*pts = enc->packet_pts;
|
||||
*duration = dur;
|
||||
enc->packet_pts += dur;
|
||||
|
||||
if (output.len == input_output->len)
|
||||
return -1; // got nothing
|
||||
input_output->len = output.s - input_output->s;
|
||||
return buf->len >= 2 ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&g729);
|
||||
codeclib_register_codec(&g729a);
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t gsm = {
|
||||
.rtpname = "GSM",
|
||||
.avcodec_id = AV_CODEC_ID_GSM,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
//.default_bitrate = 13200,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&gsm);
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
#include "codecmod.h"
|
||||
#include "loglib.h"
|
||||
|
||||
|
||||
static bool ilbc_format_parse(struct rtp_codec_format *f, const str *fmtp) {
|
||||
switch (__csh_lookup(fmtp)) {
|
||||
case CSH_LOOKUP("mode=20"):
|
||||
f->parsed.ilbc.mode = 20;
|
||||
break;
|
||||
case CSH_LOOKUP("mode=30"):
|
||||
f->parsed.ilbc.mode = 30;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ilbc_mode(int ptime, const union codec_format_options *fmtp, const char *direction) {
|
||||
int mode = 0;
|
||||
if (fmtp)
|
||||
mode = fmtp->ilbc.mode;
|
||||
|
||||
if (!mode) {
|
||||
switch (ptime) {
|
||||
case 20:
|
||||
case 40:
|
||||
case 60:
|
||||
case 80:
|
||||
case 100:
|
||||
case 120:
|
||||
mode = 20;
|
||||
ilog(LOG_DEBUG, "Setting iLBC %s mode to 20 ms based on ptime %i",
|
||||
direction, ptime);
|
||||
break;
|
||||
case 30:
|
||||
case 90:
|
||||
mode = 30;
|
||||
ilog(LOG_DEBUG, "Setting iLBC %s mode to 30 ms based on ptime %i",
|
||||
direction, ptime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mode) {
|
||||
mode = 20;
|
||||
ilog(LOG_WARNING, "No iLBC %s mode specified, setting to 20 ms", direction);
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static void ilbc_set_enc_options(encoder_t *enc, const str *codec_opts) {
|
||||
int mode = ilbc_mode(enc->ptime, &enc->format_options, "encoder");
|
||||
codeclib_set_av_opt_int(enc, "mode", mode);
|
||||
}
|
||||
|
||||
static void ilbc_set_dec_options(decoder_t *dec, const str *codec_opts) {
|
||||
int mode = ilbc_mode(dec->ptime, &dec->format_options, "decoder");
|
||||
if (mode == 20)
|
||||
dec->avc.avcctx->block_align = 38;
|
||||
else if (mode == 30)
|
||||
dec->avc.avcctx->block_align = 50;
|
||||
else
|
||||
ilog(LOG_WARN, "Unsupported iLBC mode %i", mode);
|
||||
}
|
||||
|
||||
static int ilbc_decoder_input(decoder_t *dec, const str *data, GQueue *out) {
|
||||
int mode = 0, block_align = 0;
|
||||
static const union codec_format_options mode_20 = { .ilbc = { 20 } };
|
||||
static const union codec_format_options mode_30 = { .ilbc = { 30 } };
|
||||
const union codec_format_options *fmtp;
|
||||
|
||||
if (data->len % 50 == 0) {
|
||||
mode = 30;
|
||||
block_align = 50;
|
||||
fmtp = &mode_30;
|
||||
}
|
||||
else if (data->len % 38 == 0) {
|
||||
mode = 20;
|
||||
block_align = 38;
|
||||
fmtp = &mode_20;
|
||||
}
|
||||
else
|
||||
ilog(LOG_WARNING | LOG_FLAG_LIMIT, "iLBC received %i bytes packet, does not match "
|
||||
"one of the block sizes", (int) data->len);
|
||||
|
||||
if (block_align && dec->avc.avcctx->block_align != block_align) {
|
||||
ilog(LOG_INFO | LOG_FLAG_LIMIT, "iLBC decoder set to %i bytes blocks, but received packet "
|
||||
"of %i bytes, therefore resetting decoder and switching to %i bytes "
|
||||
"block mode (%i ms mode)",
|
||||
(int) dec->avc.avcctx->block_align, (int) data->len, block_align, mode);
|
||||
avc_decoder_close(dec);
|
||||
dec->format_options = *fmtp;
|
||||
avc_decoder_init(dec, NULL);
|
||||
}
|
||||
|
||||
return avc_decoder_input(dec, data, out);
|
||||
}
|
||||
|
||||
|
||||
static const codec_type_t codec_type_ilbc = {
|
||||
.def_init = avc_def_init,
|
||||
.decoder_init = avc_decoder_init,
|
||||
.decoder_input = ilbc_decoder_input,
|
||||
.decoder_close = avc_decoder_close,
|
||||
.encoder_init = avc_encoder_init,
|
||||
.encoder_input = avc_encoder_input,
|
||||
.encoder_close = avc_encoder_close,
|
||||
};
|
||||
|
||||
|
||||
static const codec_def_t ilbc = {
|
||||
.rtpname = "iLBC",
|
||||
.avcodec_id = AV_CODEC_ID_ILBC,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 30,
|
||||
.default_fmtp = "mode=30",
|
||||
.format_parse = ilbc_format_parse,
|
||||
//.default_bitrate = 15200,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_ilbc,
|
||||
.set_enc_options = ilbc_set_enc_options,
|
||||
.set_dec_options = ilbc_set_dec_options,
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&ilbc);
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t l16 = {
|
||||
.rtpname = "L16",
|
||||
.avcodec_id = AV_CODEC_ID_PCM_S16BE,
|
||||
.default_clockrate = 44100,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.bits_per_sample = 16,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
};
|
||||
static const codec_def_t x_l16 = {
|
||||
.rtpname = "X-L16",
|
||||
.avcodec_id = AV_CODEC_ID_PCM_S16LE,
|
||||
.default_clockrate = 44100,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.bits_per_sample = 16,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
};
|
||||
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&l16);
|
||||
codeclib_register_codec(&x_l16);
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t mp3 = {
|
||||
.rtpname = "MP3",
|
||||
.avcodec_id = AV_CODEC_ID_MP3,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&mp3);
|
||||
}
|
||||
@ -0,0 +1,471 @@
|
||||
#include "codecmod.h"
|
||||
#include <opus.h>
|
||||
#include "loglib.h"
|
||||
#include "fix_frame_channel_layout.compat"
|
||||
|
||||
|
||||
|
||||
static void opus_init(struct rtp_payload_type *pt) {
|
||||
if (pt->clock_rate != 48000) {
|
||||
ilog(LOG_WARN, "Opus is only supported with a clock rate of 48 kHz");
|
||||
pt->clock_rate = 48000;
|
||||
}
|
||||
|
||||
switch (pt->ptime) {
|
||||
case 5:
|
||||
case 10:
|
||||
case 20:
|
||||
case 40:
|
||||
case 60:
|
||||
break;
|
||||
default:
|
||||
;
|
||||
int np;
|
||||
if (pt->ptime < 10)
|
||||
np = 5;
|
||||
else if (pt->ptime < 20)
|
||||
np = 10;
|
||||
else if (pt->ptime < 40)
|
||||
np = 20;
|
||||
else if (pt->ptime < 60)
|
||||
np = 40;
|
||||
else
|
||||
np = 60;
|
||||
ilog(LOG_INFO, "Opus doesn't support a ptime of %i ms; using %i ms instead",
|
||||
pt->ptime, np);
|
||||
pt->ptime = np;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pt->bitrate) {
|
||||
if (pt->bitrate < 6000) {
|
||||
ilog(LOG_DEBUG, "Opus bitrate %i bps too small, assuming %i kbit/s",
|
||||
pt->bitrate, pt->bitrate);
|
||||
pt->bitrate *= 1000;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (pt->channels == 1)
|
||||
pt->bitrate = 24000;
|
||||
else if (pt->channels == 2)
|
||||
pt->bitrate = 32000;
|
||||
else
|
||||
pt->bitrate = 64000;
|
||||
ilog(LOG_DEBUG, "Using default bitrate of %i bps for %i-channel Opus", pt->bitrate, pt->channels);
|
||||
}
|
||||
|
||||
static const char *libopus_decoder_init(decoder_t *dec, const str *extra_opts) {
|
||||
if (dec->in_format.channels != 1 && dec->in_format.channels != 2)
|
||||
return "invalid number of channels";
|
||||
switch (dec->in_format.clockrate) {
|
||||
case 48000:
|
||||
case 24000:
|
||||
case 16000:
|
||||
case 12000:
|
||||
case 8000:
|
||||
break;
|
||||
default:
|
||||
return "invalid clock rate";
|
||||
}
|
||||
|
||||
int err = 0;
|
||||
dec->opus = opus_decoder_create(dec->in_format.clockrate, dec->in_format.channels, &err);
|
||||
if (!dec->opus) {
|
||||
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error from libopus: %s", opus_strerror(err));
|
||||
return "failed to alloc codec context";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
static void libopus_decoder_close(decoder_t *dec) {
|
||||
opus_decoder_destroy(dec->opus);
|
||||
}
|
||||
static int libopus_decoder_input(decoder_t *dec, const str *data, GQueue *out) {
|
||||
// get frame with buffer large enough for the max
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
frame->nb_samples = 960;
|
||||
frame->format = AV_SAMPLE_FMT_S16;
|
||||
frame->sample_rate = dec->in_format.clockrate;
|
||||
DEF_CH_LAYOUT(&frame->CH_LAYOUT, dec->in_format.channels);
|
||||
frame->pts = dec->pts;
|
||||
if (av_frame_get_buffer(frame, 0) < 0)
|
||||
abort();
|
||||
|
||||
int ret = opus_decode(dec->opus, (unsigned char *) data->s, data->len,
|
||||
(int16_t *) frame->extended_data[0], frame->nb_samples, 0);
|
||||
if (ret < 0) {
|
||||
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error decoding Opus packet: %s", opus_strerror(ret));
|
||||
av_frame_free(&frame);
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame->nb_samples = ret;
|
||||
g_queue_push_tail(out, frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct libopus_encoder_options {
|
||||
int complexity;
|
||||
int vbr;
|
||||
int vbr_constraint;
|
||||
int pl;
|
||||
int application;
|
||||
};
|
||||
static void libopus_set_enc_opts(str *key, str *val, void *p) {
|
||||
struct libopus_encoder_options *opts = p;
|
||||
|
||||
switch (__csh_lookup(key)) {
|
||||
case CSH_LOOKUP("complexity"):
|
||||
case CSH_LOOKUP("compression_level"):
|
||||
opts->complexity = str_to_i(val, -1);
|
||||
break;
|
||||
case CSH_LOOKUP("application"):
|
||||
switch (__csh_lookup(val)) {
|
||||
case CSH_LOOKUP("VOIP"):
|
||||
case CSH_LOOKUP("VoIP"):
|
||||
case CSH_LOOKUP("voip"):
|
||||
opts->application = OPUS_APPLICATION_VOIP;
|
||||
break;
|
||||
case CSH_LOOKUP("audio"):
|
||||
opts->application = OPUS_APPLICATION_AUDIO;
|
||||
break;
|
||||
case CSH_LOOKUP("low-delay"):
|
||||
case CSH_LOOKUP("low delay"):
|
||||
case CSH_LOOKUP("lowdelay"):
|
||||
opts->application = OPUS_APPLICATION_RESTRICTED_LOWDELAY;
|
||||
break;
|
||||
default:
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unknown Opus application: '"
|
||||
STR_FORMAT "'", STR_FMT(val));
|
||||
};
|
||||
break;
|
||||
case CSH_LOOKUP("vbr"):
|
||||
case CSH_LOOKUP("VBR"):
|
||||
// aligned with ffmpeg vbr=0/1/2 option
|
||||
opts->vbr = str_to_i(val, -1);
|
||||
if (opts->vbr == 2) {
|
||||
opts->vbr = 1;
|
||||
opts->vbr_constraint = 1;
|
||||
}
|
||||
break;
|
||||
case CSH_LOOKUP("packet_loss"):
|
||||
case CSH_LOOKUP("packet loss"):
|
||||
opts->pl = str_to_i(val, -1);
|
||||
break;
|
||||
default:
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unknown Opus encoder option encountered: '"
|
||||
STR_FORMAT "'", STR_FMT(key));
|
||||
}
|
||||
}
|
||||
static const char *libopus_encoder_init(encoder_t *enc, const str *extra_opts) {
|
||||
if (enc->requested_format.channels != 1 && enc->requested_format.channels != 2)
|
||||
return "invalid number of channels";
|
||||
|
||||
if (enc->requested_format.format == -1)
|
||||
enc->requested_format.format = AV_SAMPLE_FMT_S16;
|
||||
else if (enc->requested_format.format != AV_SAMPLE_FMT_S16)
|
||||
return "invalid sample format";
|
||||
|
||||
switch (enc->requested_format.clockrate) {
|
||||
case 48000:
|
||||
case 24000:
|
||||
case 16000:
|
||||
case 12000:
|
||||
case 8000:
|
||||
break;
|
||||
default:
|
||||
return "invalid clock rate";
|
||||
}
|
||||
|
||||
struct libopus_encoder_options opts = { .vbr = 1, .complexity = 10, .application = OPUS_APPLICATION_VOIP };
|
||||
codeclib_key_value_parse(extra_opts, true, libopus_set_enc_opts, &opts);
|
||||
|
||||
int err;
|
||||
enc->opus = opus_encoder_create(enc->requested_format.clockrate, enc->requested_format.channels,
|
||||
opts.application, &err);
|
||||
if (!enc->opus) {
|
||||
ilog(LOG_ERR, "Error from libopus: %s", opus_strerror(err));
|
||||
return "failed to alloc codec context";
|
||||
}
|
||||
|
||||
enc->actual_format = enc->requested_format;
|
||||
|
||||
enc->samples_per_frame = enc->actual_format.clockrate * enc->ptime / 1000;
|
||||
enc->samples_per_packet = enc->samples_per_frame;
|
||||
|
||||
err = opus_encoder_ctl(enc->opus, OPUS_SET_BITRATE(enc->bitrate));
|
||||
if (err != OPUS_OK)
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Failed to set Opus bitrate to %i: %s", enc->bitrate,
|
||||
opus_strerror(err));
|
||||
|
||||
err = opus_encoder_ctl(enc->opus, OPUS_SET_COMPLEXITY(opts.complexity));
|
||||
if (err != OPUS_OK)
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Failed to set Opus complexity to %i': %s",
|
||||
opts.complexity, opus_strerror(err));
|
||||
err = opus_encoder_ctl(enc->opus, OPUS_SET_VBR(opts.vbr));
|
||||
if (err != OPUS_OK)
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Failed to set Opus VBR to %i': %s",
|
||||
opts.vbr, opus_strerror(err));
|
||||
err = opus_encoder_ctl(enc->opus, OPUS_SET_VBR_CONSTRAINT(opts.vbr_constraint));
|
||||
if (err != OPUS_OK)
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Failed to set Opus VBR constraint to %i': %s",
|
||||
opts.vbr_constraint, opus_strerror(err));
|
||||
err = opus_encoder_ctl(enc->opus, OPUS_SET_PACKET_LOSS_PERC(opts.pl));
|
||||
if (err != OPUS_OK)
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Failed to set Opus PL%% to %i': %s",
|
||||
opts.pl, opus_strerror(err));
|
||||
err = opus_encoder_ctl(enc->opus, OPUS_SET_INBAND_FEC(enc->format_options.opus.fec_send >= 0));
|
||||
if (err != OPUS_OK)
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Failed to set Opus FEC to %i': %s",
|
||||
enc->format_options.opus.fec_send >= 0, opus_strerror(err));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
static void libopus_encoder_close(encoder_t *enc) {
|
||||
opus_encoder_destroy(enc->opus);
|
||||
}
|
||||
#define MAX_OPUS_FRAME_SIZE 1275 /* 20 ms at 510 kbps */
|
||||
#define MAX_OPUS_FRAMES_PER_PACKET 6 /* 120 ms = 6 * 20 ms */
|
||||
#define MAX_OPUS_HEADER_SIZE 7
|
||||
static int libopus_encoder_input(encoder_t *enc, AVFrame **frame) {
|
||||
if (!*frame)
|
||||
return 0;
|
||||
|
||||
// max length of Opus packet:
|
||||
av_new_packet(enc->avpkt, MAX_OPUS_FRAME_SIZE * MAX_OPUS_FRAMES_PER_PACKET + MAX_OPUS_HEADER_SIZE);
|
||||
|
||||
int ret = opus_encode(enc->opus, (int16_t *) (*frame)->extended_data[0], (*frame)->nb_samples,
|
||||
enc->avpkt->data, enc->avpkt->size);
|
||||
if (ret < 0) {
|
||||
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Error encoding Opus packet: %s", opus_strerror(ret));
|
||||
av_packet_unref(enc->avpkt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
enc->avpkt->size = ret;
|
||||
enc->avpkt->pts = (*frame)->pts;
|
||||
enc->avpkt->duration = (*frame)->nb_samples;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// opus RTP always runs at 48 kHz
|
||||
static void opus_select_encoder_format(encoder_t *enc, format_t *req_format, const format_t *f,
|
||||
const struct rtp_codec_format *fmtp)
|
||||
{
|
||||
if (req_format->clockrate != 48000)
|
||||
return; // bail - encoder will fail to initialise
|
||||
|
||||
// check against natively supported rates first
|
||||
switch (f->clockrate) {
|
||||
case 48000:
|
||||
case 24000:
|
||||
case 16000:
|
||||
case 12000:
|
||||
case 8000:
|
||||
enc->clockrate_fact = (struct fraction) {1, 48000 / f->clockrate};
|
||||
break;
|
||||
default:
|
||||
// resample to next best rate
|
||||
if (f->clockrate > 24000)
|
||||
enc->clockrate_fact = (struct fraction) {1,1};
|
||||
else if (f->clockrate > 16000)
|
||||
enc->clockrate_fact = (struct fraction) {1,2};
|
||||
else if (f->clockrate > 12000)
|
||||
enc->clockrate_fact = (struct fraction) {1,3};
|
||||
else if (f->clockrate > 8000)
|
||||
enc->clockrate_fact = (struct fraction) {1,4};
|
||||
else
|
||||
enc->clockrate_fact = (struct fraction) {1,6};
|
||||
break;
|
||||
}
|
||||
|
||||
// honour remote stereo=0/1 flag if given,
|
||||
// otherwise go with the input format
|
||||
if (fmtp && fmtp->parsed.opus.stereo_send == -1)
|
||||
req_format->channels = 1;
|
||||
else if (fmtp && fmtp->parsed.opus.stereo_send == 1)
|
||||
req_format->channels = 2;
|
||||
else if (req_format->channels == 2 && f->channels == 1)
|
||||
req_format->channels = 1;
|
||||
}
|
||||
static void opus_select_decoder_format(decoder_t *dec, const struct rtp_codec_format *fmtp) {
|
||||
if (dec->in_format.clockrate != 48000)
|
||||
return;
|
||||
|
||||
// check against natively supported rates first
|
||||
switch (dec->dest_format.clockrate) {
|
||||
case 48000:
|
||||
case 24000:
|
||||
case 16000:
|
||||
case 12000:
|
||||
case 8000:
|
||||
dec->clockrate_fact = (struct fraction) {1, 48000 / dec->dest_format.clockrate};
|
||||
break;
|
||||
default:
|
||||
// resample to next best rate
|
||||
if (dec->dest_format.clockrate > 24000)
|
||||
dec->clockrate_fact = (struct fraction) {1,1};
|
||||
else if (dec->dest_format.clockrate > 16000)
|
||||
dec->clockrate_fact = (struct fraction) {1,2};
|
||||
else if (dec->dest_format.clockrate > 12000)
|
||||
dec->clockrate_fact = (struct fraction) {1,3};
|
||||
else if (dec->dest_format.clockrate > 8000)
|
||||
dec->clockrate_fact = (struct fraction) {1,4};
|
||||
else
|
||||
dec->clockrate_fact = (struct fraction) {1,6};
|
||||
break;
|
||||
}
|
||||
|
||||
// switch to mono decoding if possible
|
||||
if (dec->in_format.channels == 2 && dec->dest_format.channels == 1)
|
||||
dec->in_format.channels = 1;
|
||||
}
|
||||
static void opus_parse_format_cb(str *key, str *token, void *data) {
|
||||
union codec_format_options *opts = data;
|
||||
__auto_type o = &opts->opus;
|
||||
|
||||
switch (__csh_lookup(key)) {
|
||||
#define YNFLAG(flag, varname) \
|
||||
case flag: \
|
||||
if (token->len == 1 && token->s[0] == '1') \
|
||||
o->varname = 1; \
|
||||
else if (token->len == 1 && token->s[0] == '0') \
|
||||
o->varname = -1; \
|
||||
break;
|
||||
YNFLAG(CSH_LOOKUP("stereo"), stereo_recv)
|
||||
YNFLAG(CSH_LOOKUP("sprop-stereo"), stereo_send)
|
||||
YNFLAG(CSH_LOOKUP("useinbandfec"), fec_recv)
|
||||
YNFLAG(CSH_LOOKUP("cbr"), cbr)
|
||||
YNFLAG(CSH_LOOKUP("usedtx"), usedtx)
|
||||
#undef YNFLAG
|
||||
case CSH_LOOKUP("maxplaybackrate"):
|
||||
opts->opus.maxplaybackrate = str_to_i(token, 0);
|
||||
break;
|
||||
case CSH_LOOKUP("sprop-maxcapturerate"):
|
||||
opts->opus.sprop_maxcapturerate = str_to_i(token, 0);
|
||||
break;
|
||||
case CSH_LOOKUP("maxaveragebitrate"):
|
||||
opts->opus.maxaveragebitrate = str_to_i(token, 0);
|
||||
break;
|
||||
case CSH_LOOKUP("minptime"):
|
||||
opts->opus.minptime = str_to_i(token, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
static bool opus_format_parse(struct rtp_codec_format *f, const str *fmtp) {
|
||||
codeclib_key_value_parse(fmtp, true, opus_parse_format_cb, &f->parsed);
|
||||
return true;
|
||||
}
|
||||
static GString *opus_format_print(const struct rtp_payload_type *p) {
|
||||
if (!p->format.fmtp_parsed)
|
||||
return NULL;
|
||||
|
||||
GString *s = g_string_new("");
|
||||
__auto_type f = &p->format.parsed.opus;
|
||||
|
||||
if (f->stereo_recv)
|
||||
g_string_append_printf(s, "stereo=%i; ", f->stereo_recv == -1 ? 0 : 1);
|
||||
if (f->stereo_send)
|
||||
g_string_append_printf(s, "sprop-stereo=%i; ", f->stereo_send == -1 ? 0 : 1);
|
||||
if (f->fec_recv)
|
||||
g_string_append_printf(s, "useinbandfec=%i; ", f->fec_recv == -1 ? 0 : 1);
|
||||
if (f->usedtx)
|
||||
g_string_append_printf(s, "usedtx=%i; ", f->usedtx == -1 ? 0 : 1);
|
||||
if (f->cbr)
|
||||
g_string_append_printf(s, "cbr=%i; ", f->cbr == -1 ? 0 : 1);
|
||||
if (f->maxplaybackrate)
|
||||
g_string_append_printf(s, "maxplaybackrate=%i; ", f->maxplaybackrate);
|
||||
if (f->maxaveragebitrate)
|
||||
g_string_append_printf(s, "maxaveragebitrate=%i; ", f->maxaveragebitrate);
|
||||
if (f->sprop_maxcapturerate)
|
||||
g_string_append_printf(s, "sprop-maxcapturerate=%i; ", f->sprop_maxcapturerate);
|
||||
if (f->minptime)
|
||||
g_string_append_printf(s, "minptime=%i; ", f->minptime);
|
||||
|
||||
if (s->len != 0)
|
||||
g_string_truncate(s, s->len - 2);
|
||||
|
||||
return s;
|
||||
}
|
||||
static void opus_format_answer(struct rtp_payload_type *p, const struct rtp_payload_type *src) {
|
||||
if (!p->format.fmtp_parsed)
|
||||
return;
|
||||
|
||||
__auto_type f = &p->format.parsed.opus;
|
||||
|
||||
// swap send/recv
|
||||
|
||||
int t = f->stereo_send;
|
||||
f->stereo_send = f->stereo_recv;
|
||||
f->stereo_recv = t;
|
||||
|
||||
t = f->fec_send;
|
||||
f->fec_send = f->fec_recv;
|
||||
f->fec_recv = t;
|
||||
|
||||
// if stereo recv is unset, base it on input format
|
||||
if (f->stereo_recv == 0)
|
||||
f->stereo_recv = src->channels == 1 ? -1 : 1;
|
||||
|
||||
// we can always use FEC, unless we've been told that we should lie
|
||||
if (f->fec_recv == 0)
|
||||
f->fec_recv = 1;
|
||||
|
||||
// set everything unsupported to 0
|
||||
f->usedtx = 0;
|
||||
f->cbr = 0;
|
||||
f->maxplaybackrate = 0;
|
||||
f->sprop_maxcapturerate = 0;
|
||||
f->maxaveragebitrate = 0;
|
||||
f->minptime = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static const codec_type_t codec_type_libopus = {
|
||||
.decoder_init = libopus_decoder_init,
|
||||
.decoder_input = libopus_decoder_input,
|
||||
.decoder_close = libopus_decoder_close,
|
||||
.encoder_init = libopus_encoder_init,
|
||||
.encoder_input = libopus_encoder_input,
|
||||
.encoder_close = libopus_encoder_close,
|
||||
};
|
||||
|
||||
|
||||
static const codec_def_t opus = {
|
||||
.rtpname = "opus",
|
||||
.avcodec_id = -1,
|
||||
.default_clockrate = 48000,
|
||||
.default_channels = 2,
|
||||
.default_bitrate = 32000,
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_libopus,
|
||||
.init = opus_init,
|
||||
.default_fmtp = "useinbandfec=1",
|
||||
.format_parse = opus_format_parse,
|
||||
.format_print = opus_format_print,
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.format_answer = opus_format_answer,
|
||||
.select_encoder_format = opus_select_encoder_format,
|
||||
.select_decoder_format = opus_select_decoder_format,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
.support_encoding = 1,
|
||||
.support_decoding = 1,
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&opus);
|
||||
}
|
||||
@ -0,0 +1,225 @@
|
||||
#include "codecmod.h"
|
||||
#include <arpa/inet.h>
|
||||
#include "loglib.h"
|
||||
#include "fix_frame_channel_layout.compat"
|
||||
#include "dtmflib.h"
|
||||
|
||||
|
||||
static const char *dtmf_decoder_init(decoder_t *dec, const str *extra_opts) {
|
||||
dec->dtmf.event = -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static AVFrame *dtmf_frame_int16_t_mono(unsigned long frame_ts, unsigned long num_samples, unsigned int event,
|
||||
unsigned int volume,
|
||||
unsigned int sample_rate)
|
||||
{
|
||||
// synthesise PCM
|
||||
// first get our frame and figure out how many samples we need, and the start offset
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
frame->nb_samples = num_samples;
|
||||
frame->format = AV_SAMPLE_FMT_S16;
|
||||
frame->sample_rate = sample_rate;
|
||||
frame->CH_LAYOUT = (CH_LAYOUT_T) MONO_LAYOUT;
|
||||
frame->pts = frame_ts;
|
||||
if (av_frame_get_buffer(frame, 0) < 0)
|
||||
abort();
|
||||
|
||||
// fill samples
|
||||
dtmf_samples_int16_t_mono(frame->extended_data[0], frame_ts, frame->nb_samples, event,
|
||||
volume, sample_rate);
|
||||
|
||||
return frame;
|
||||
|
||||
}
|
||||
|
||||
static int dtmf_decoder_input(decoder_t *dec, const str *data, GQueue *out) {
|
||||
struct telephone_event_payload *dtmf;
|
||||
if (data->len < sizeof(*dtmf)) {
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Short DTMF event packet (len %zu)", data->len);
|
||||
return -1;
|
||||
}
|
||||
dtmf = (void *) data->s;
|
||||
|
||||
// init if we need to
|
||||
if (dtmf->event != dec->dtmf.event || dec->rtp_ts != dec->dtmf.start_ts) {
|
||||
ZERO(dec->dtmf);
|
||||
dec->dtmf.event = dtmf->event;
|
||||
dec->dtmf.start_ts = dec->rtp_ts;
|
||||
ilog(LOG_DEBUG, "New DTMF event starting: %u at TS %lu", dtmf->event, dec->rtp_ts);
|
||||
}
|
||||
|
||||
unsigned long duration = ntohs(dtmf->duration);
|
||||
unsigned long frame_ts = dec->rtp_ts - dec->dtmf.start_ts + dec->dtmf.duration;
|
||||
long num_samples = duration - dec->dtmf.duration;
|
||||
|
||||
ilog(LOG_DEBUG, "Generate DTMF samples for event %u, start TS %lu, TS now %lu, frame TS %lu, "
|
||||
"duration %lu, "
|
||||
"old duration %lu, num samples %li",
|
||||
dtmf->event, dec->dtmf.start_ts, dec->rtp_ts, frame_ts,
|
||||
duration, dec->dtmf.duration, num_samples);
|
||||
|
||||
if (num_samples <= 0)
|
||||
return 0;
|
||||
if (num_samples > dec->in_format.clockrate) {
|
||||
ilog(LOG_ERR, "Cannot generate %li DTMF samples (clock rate %u)", num_samples,
|
||||
dec->in_format.clockrate);
|
||||
return -1;
|
||||
}
|
||||
|
||||
AVFrame *frame = dtmf_frame_int16_t_mono(frame_ts, num_samples, dtmf->event, dtmf->volume,
|
||||
dec->in_format.clockrate);
|
||||
frame->pts += dec->dtmf.start_ts;
|
||||
g_queue_push_tail(out, frame);
|
||||
|
||||
dec->dtmf.duration = duration;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const char *cn_decoder_init(decoder_t *dec, const str *opts) {
|
||||
// the ffmpeg cngdec always runs at 8000
|
||||
dec->in_format.clockrate = 8000;
|
||||
dec->in_format.channels = 1;
|
||||
dec->resampler.no_filter = true;
|
||||
return avc_decoder_init(dec, opts);
|
||||
}
|
||||
static int cn_decoder_input(decoder_t *dec, const str *data, GQueue *out) {
|
||||
// generate one set of ptime worth of samples
|
||||
int ptime = dec->ptime;
|
||||
if (ptime <= 0)
|
||||
ptime = 20; // ?
|
||||
int samples = dec->in_format.clockrate * ptime / 1000;
|
||||
int max_size = dec->avc.avcctx->frame_size;
|
||||
|
||||
AVFrame *aframe = NULL;
|
||||
|
||||
do {
|
||||
if (samples < max_size)
|
||||
dec->avc.avcctx->frame_size = samples;
|
||||
int ret = avc_decoder_input(dec, data, out);
|
||||
dec->avc.avcctx->frame_size = max_size;
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!out->length)
|
||||
return -1;
|
||||
|
||||
AVFrame *oframe = out->head->data;
|
||||
|
||||
// one-shot handling if fewer samples than the CNG's frame size are requested
|
||||
if (!aframe && out->length == 1) {
|
||||
if (oframe->nb_samples >= samples) {
|
||||
oframe->nb_samples = samples;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// consume frames and merge into single output frame
|
||||
|
||||
if (!aframe) {
|
||||
aframe = av_frame_alloc();
|
||||
aframe->nb_samples = samples;
|
||||
assert(oframe->format == AV_SAMPLE_FMT_S16);
|
||||
aframe->format = oframe->format;
|
||||
assert(oframe->sample_rate == 8000);
|
||||
aframe->sample_rate = oframe->sample_rate;
|
||||
aframe->CH_LAYOUT = oframe->CH_LAYOUT; // should be mono
|
||||
aframe->pts = oframe->pts;
|
||||
aframe->pkt_dts = oframe->pkt_dts;
|
||||
if (av_frame_get_buffer(aframe, 0) < 0)
|
||||
abort();
|
||||
|
||||
aframe->nb_samples = 0; // to track progress
|
||||
}
|
||||
|
||||
while (out->length) {
|
||||
oframe = g_queue_pop_head(out);
|
||||
|
||||
if (oframe->nb_samples <= 0) // error
|
||||
return -1; // XXX leaves frames in `out`
|
||||
|
||||
// use as much as we have and as much as we need
|
||||
int rsamples = MIN(oframe->nb_samples, samples);
|
||||
|
||||
memcpy(aframe->extended_data[0] + aframe->nb_samples * 2,
|
||||
oframe->extended_data[0], rsamples * 2);
|
||||
|
||||
aframe->nb_samples += rsamples;
|
||||
samples -= rsamples; // drop to zero when finished
|
||||
|
||||
av_frame_free(&oframe);
|
||||
};
|
||||
} while (samples > 0);
|
||||
|
||||
g_queue_push_tail(out, aframe);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const codec_type_t codec_type_dtmf = {
|
||||
.decoder_init = dtmf_decoder_init,
|
||||
.decoder_input = dtmf_decoder_input,
|
||||
};
|
||||
static const codec_type_t codec_type_cn = {
|
||||
.def_init = avc_def_init,
|
||||
.decoder_init = cn_decoder_init,
|
||||
.decoder_input = cn_decoder_input,
|
||||
.decoder_close = avc_decoder_close,
|
||||
};
|
||||
|
||||
static const codec_def_t dtmf = {
|
||||
.rtpname = "telephone-event",
|
||||
.avcodec_id = -1,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.supplemental = 1,
|
||||
.dtmf = 1,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_fmtp = "0-15",
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.codec_type = &codec_type_dtmf,
|
||||
.support_encoding = 1,
|
||||
.support_decoding = 1,
|
||||
};
|
||||
|
||||
static const codec_def_t cn = {
|
||||
.rtpname = "CN",
|
||||
.avcodec_id = AV_CODEC_ID_COMFORT_NOISE,
|
||||
.avcodec_name_enc = "comfortnoise",
|
||||
.avcodec_name_dec = "comfortnoise",
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.supplemental = 1,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.default_ptime = 20,
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.codec_type = &codec_type_cn,
|
||||
};
|
||||
|
||||
static const codec_def_t red = {
|
||||
.rtpname = "red",
|
||||
.avcodec_id = -1,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.supplemental = 1,
|
||||
.default_clockrate = 8000,
|
||||
.default_channels = 1,
|
||||
.format_cmp = format_cmp_ignore,
|
||||
.support_encoding = 1,
|
||||
.support_decoding = 1,
|
||||
};
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&dtmf);
|
||||
codeclib_register_codec(&cn);
|
||||
codeclib_register_codec(&red);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
static const codec_def_t qcelp = {
|
||||
.rtpname = "QCELP",
|
||||
.avcodec_id = AV_CODEC_ID_QCELP,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&qcelp);
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t speex = {
|
||||
.rtpname = "speex",
|
||||
.avcodec_id = AV_CODEC_ID_SPEEX,
|
||||
.default_clockrate = 16000,
|
||||
.default_channels = 1,
|
||||
.default_bitrate = 11000,
|
||||
.default_ptime = 20,
|
||||
.minimum_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&speex);
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t u8 = {
|
||||
.rtpname = "PCM-U8",
|
||||
.avcodec_id = AV_CODEC_ID_PCM_U8,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&u8);
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
#include "codecmod.h"
|
||||
|
||||
|
||||
static const codec_def_t vorbis = {
|
||||
.rtpname = "vorbis",
|
||||
.avcodec_id = AV_CODEC_ID_VORBIS,
|
||||
.avcodec_name_enc = "libvorbis",
|
||||
.avcodec_name_dec = "libvorbis",
|
||||
.default_ptime = 20,
|
||||
.packetizer = packetizer_passthrough,
|
||||
.media_type = MT_AUDIO,
|
||||
.codec_type = &codec_type_avcodec,
|
||||
.dtx_methods = {
|
||||
[DTX_SILENCE] = &dtx_method_silence,
|
||||
[DTX_CN] = &dtx_method_cn,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void init(void) {
|
||||
codeclib_register_codec(&vorbis);
|
||||
}
|
||||
Loading…
Reference in new issue