mirror of https://github.com/asterisk/asterisk
Merge 9df7f2f907
into 8924258639
commit
3f0a34e45f
@ -0,0 +1,25 @@
|
||||
"""Add rtt options
|
||||
|
||||
Revision ID: 65b5ac60e54a
|
||||
Revises: abdc9ede147d
|
||||
Create Date: 2025-02-21 12:35:43.615049
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '65b5ac60e54a'
|
||||
down_revision = 'abdc9ede147d'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
def upgrade():
|
||||
op.add_column('ps_endpoints', sa.Column('tos_text', sa.String(10)))
|
||||
op.add_column('ps_endpoints', sa.Column('cos_text', sa.Integer))
|
||||
op.add_column('ps_endpoints', sa.Column('max_text_streams', sa.Integer))
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('ps_endpoints', 'tos_text')
|
||||
op.drop_column('ps_endpoints', 'cos_text')
|
||||
op.drop_column('ps_endpoints', 'max_text_streams')
|
||||
|
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2025, GILAWA Ltd
|
||||
*
|
||||
* Henning Westerholt <hw@gilawa.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \file
|
||||
* \brief RED format attribute interface
|
||||
*
|
||||
* \author Henning Westerholt <hw@gilawa.com>
|
||||
*
|
||||
* \note https://www.rfc-editor.org/rfc/rfc4103.html
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<support_level>core</support_level>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
#include <ctype.h> /* for tolower */
|
||||
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/format.h"
|
||||
#include "asterisk/logger.h" /* for ast_log, LOG_WARNING */
|
||||
#include "asterisk/strings.h" /* for ast_str_append */
|
||||
#include "asterisk/utils.h" /* for ast_free */
|
||||
#include "asterisk/rtp_engine.h" /* for AST_RED_MAX_GENERATION */
|
||||
|
||||
/*
|
||||
* From RFC 4103: "Therefore, text/t140 is RECOMMENDED
|
||||
* to be the only payload type in the RTP stream."
|
||||
* For this reason we only support uniform payload types.
|
||||
*
|
||||
* If in the future this attribute should be used also for other redundant rtp streams,
|
||||
* it needs to be adapted together with the other infrastructure supporting it.
|
||||
*/
|
||||
struct red_attr {
|
||||
int red_num_gen; /* Number of generations */
|
||||
int red_payload; /* Payload for each generation (actually related to t140) */
|
||||
int cps; /* Characters per second */
|
||||
};
|
||||
|
||||
static struct red_attr default_red_attr = {
|
||||
.red_num_gen = 2,
|
||||
.red_payload = 98, /* default type that two test clients used */
|
||||
.cps = 30
|
||||
};
|
||||
|
||||
static void red_destroy(struct ast_format *format)
|
||||
{
|
||||
struct red_attr *attr = ast_format_get_attribute_data(format);
|
||||
ast_free(attr);
|
||||
}
|
||||
|
||||
static int red_clone(const struct ast_format *src, struct ast_format *dst)
|
||||
{
|
||||
struct red_attr *original = ast_format_get_attribute_data(src);
|
||||
struct red_attr *attr = ast_malloc(sizeof(*attr));
|
||||
|
||||
if (!attr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (original) {
|
||||
*attr = *original;
|
||||
} else {
|
||||
*attr = default_red_attr;
|
||||
}
|
||||
|
||||
ast_format_set_attribute_data(dst, attr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_format *red_parse_sdp_fmtp(const struct ast_format *format, const char *attributes)
|
||||
{
|
||||
char *attribs = ast_strdupa(attributes);
|
||||
char *rest = NULL;
|
||||
struct ast_format *cloned;
|
||||
struct red_attr *attr;
|
||||
int red_num_gen = -1;
|
||||
int red_payload = -1;
|
||||
|
||||
cloned = ast_format_clone(format);
|
||||
if (!cloned) {
|
||||
return NULL;
|
||||
}
|
||||
attr = ast_format_get_attribute_data(cloned);
|
||||
|
||||
attribs = strtok_r(attribs, "/", &rest);
|
||||
/* number of redundant generations is one less of the attributes */
|
||||
while (attribs && red_num_gen++ < AST_RED_MAX_GENERATION-1) {
|
||||
if (sscanf(attribs, "%30u", &red_payload) == 1) {
|
||||
attr->red_payload = red_payload;
|
||||
}
|
||||
attribs = strtok_r(NULL, "/", &rest);
|
||||
}
|
||||
|
||||
attr->red_num_gen = red_num_gen;
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
static void red_generate_sdp_fmtp(const struct ast_format *format, unsigned int payload, struct ast_str **str)
|
||||
{
|
||||
struct red_attr *attr = ast_format_get_attribute_data(format);
|
||||
int base_fmtp_size;
|
||||
int original_size;
|
||||
if (!attr) {
|
||||
ast_log(LOG_ERROR, "Invalid RED attributes\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate the SDP fmtp line for RED
|
||||
* Assuming attr->red_payload holds the payload types for redundancy
|
||||
* and attr->red_num_gen holds the number of generations
|
||||
*/
|
||||
original_size = ast_str_strlen(*str);
|
||||
base_fmtp_size = ast_str_append(str, 0, "a=fmtp:%u ", payload);
|
||||
|
||||
if (attr->red_num_gen > 0) {
|
||||
/* attributes are one more as the number of generations */
|
||||
for (int i = 0; i < attr->red_num_gen + 1; i++) {
|
||||
ast_str_append(str, 0, "%d/", attr->red_payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (base_fmtp_size == ast_str_strlen(*str) - original_size) {
|
||||
ast_str_truncate(*str, original_size);
|
||||
} else {
|
||||
ast_str_truncate(*str, -1);
|
||||
ast_str_append(str, 0, "\r\n");
|
||||
}
|
||||
|
||||
ast_debug(3, "RED sdp written: %s\n", ast_str_buffer(*str));
|
||||
}
|
||||
|
||||
|
||||
static const void *red_attribute_get(const struct ast_format *format, const char *name)
|
||||
{
|
||||
struct red_attr *attr = ast_format_get_attribute_data(format);
|
||||
int *val;
|
||||
|
||||
if (!strcasecmp(name, "red_num_gen")) {
|
||||
val = &attr->red_num_gen;
|
||||
} else if (!strcasecmp(name, "red_payload")) {
|
||||
val = &attr->red_payload;
|
||||
} else if (!strcasecmp(name, "cps")) {
|
||||
val = &attr->cps;
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "unknown attribute type %s\n", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
static struct ast_format *red_attribute_set(const struct ast_format *format, const char *name, const char *value)
|
||||
{
|
||||
struct ast_format *cloned;
|
||||
struct red_attr *attr;
|
||||
unsigned int val;
|
||||
|
||||
if (sscanf(value, "%30u", &val) != 1) {
|
||||
ast_log(LOG_WARNING, "Unknown value '%s' for attribute type '%s'\n",
|
||||
value, name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cloned = ast_format_clone(format);
|
||||
if (!cloned) {
|
||||
return NULL;
|
||||
}
|
||||
attr = ast_format_get_attribute_data(cloned);
|
||||
|
||||
if (!strcasecmp(name, "red_num_gen")) {
|
||||
attr->red_num_gen = val;
|
||||
} else if (!strcasecmp(name, "red_payload")) {
|
||||
attr->red_payload = val;
|
||||
} else if (!strcasecmp(name, "cps")) {
|
||||
attr->cps = val;
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "unknown attribute type %s\n", name);
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
|
||||
static struct ast_format *red_getjoint(const struct ast_format *format1, const struct ast_format *format2)
|
||||
{
|
||||
struct red_attr *attr1 = ast_format_get_attribute_data(format1);
|
||||
struct red_attr *attr2 = ast_format_get_attribute_data(format2);
|
||||
struct ast_format *jointformat;
|
||||
struct red_attr *attr_res;
|
||||
/* To re-use offered RED payload types */
|
||||
unsigned int use_side = 0;
|
||||
|
||||
if (!attr1) {
|
||||
attr1 = &default_red_attr;
|
||||
use_side = 2;
|
||||
}
|
||||
|
||||
if (!attr2) {
|
||||
attr2 = &default_red_attr;
|
||||
use_side = 1;
|
||||
}
|
||||
|
||||
jointformat = ast_format_clone(use_side == 2 ? format2 : format1);
|
||||
if (!jointformat) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get the resulting attributes for the joint format */
|
||||
attr_res = ast_format_get_attribute_data(jointformat);
|
||||
|
||||
/* Determine joint attributes */
|
||||
if (attr1->red_num_gen == 0 || attr2->red_num_gen == 0) {
|
||||
/* If either format has no redundancy, set joint to no redundancy */
|
||||
attr_res->red_num_gen = 0;
|
||||
} else {
|
||||
/*
|
||||
* If both formats have redundancy, take the minimum number of generations.
|
||||
* Some tested clients only support e.g. 2 generations and it would be a waste
|
||||
* of bandwith or could even lead to incompatibilities.
|
||||
*/
|
||||
attr_res->red_num_gen = MIN(attr1->red_num_gen, attr2->red_num_gen);
|
||||
attr_res->red_payload = (use_side == 2) ? attr2->red_payload : attr1->red_payload;
|
||||
}
|
||||
|
||||
ast_debug(3, "RED final joint: generations %d, payload %d\n", attr_res->red_num_gen,
|
||||
attr_res->red_payload);
|
||||
|
||||
return jointformat;
|
||||
}
|
||||
|
||||
static struct ast_format_interface red_interface = {
|
||||
.format_destroy = red_destroy,
|
||||
.format_clone = red_clone,
|
||||
.format_cmp = NULL,
|
||||
.format_get_joint = red_getjoint,
|
||||
.format_attribute_set = red_attribute_set,
|
||||
.format_attribute_get = red_attribute_get,
|
||||
.format_parse_sdp_fmtp = red_parse_sdp_fmtp,
|
||||
.format_generate_sdp_fmtp = red_generate_sdp_fmtp,
|
||||
};
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
if (ast_format_interface_register("red", &red_interface)) {
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "RED Format Attribute Module",
|
||||
.support_level = AST_MODULE_SUPPORT_CORE,
|
||||
.load = load_module,
|
||||
.unload = unload_module,
|
||||
.load_pri = AST_MODPRI_CHANNEL_DEPEND,
|
||||
);
|
Loading…
Reference in new issue