Xenofon Karamanos 1 week ago committed by GitHub
commit 3f0a34e45f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -121,6 +121,7 @@ struct ast_channel_tech chan_pjsip_tech = {
.read_stream = chan_pjsip_read_stream, .read_stream = chan_pjsip_read_stream,
.write = chan_pjsip_write, .write = chan_pjsip_write,
.write_stream = chan_pjsip_write_stream, .write_stream = chan_pjsip_write_stream,
.write_text = chan_pjsip_write,
.exception = chan_pjsip_read_stream, .exception = chan_pjsip_read_stream,
.indicate = chan_pjsip_indicate, .indicate = chan_pjsip_indicate,
.transfer = chan_pjsip_transfer, .transfer = chan_pjsip_transfer,
@ -1021,6 +1022,18 @@ static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, stru
break; break;
case AST_FRAME_CNG: case AST_FRAME_CNG:
break; break;
case AST_FRAME_TEXT:
if (!media) {
return 0;
} else if (media->type != AST_MEDIA_TYPE_TEXT) {
ast_debug(3, "Channel %s stream %d is of type '%s', not text!\n",
ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
return 0;
} else if (media->write_callback) {
ast_debug(3, "Text data to write %.*s\n",frame->datalen,(char*)frame->data.ptr);
res = media->write_callback(session, media, frame);
}
break;
case AST_FRAME_RTCP: case AST_FRAME_RTCP:
/* We only support writing out feedback */ /* We only support writing out feedback */
if (frame->subclass.integer != AST_RTP_RTCP_PSFB || !media) { if (frame->subclass.integer != AST_RTP_RTCP_PSFB || !media) {
@ -1043,6 +1056,9 @@ static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, stru
static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame) static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
{ {
if (frame->frametype == AST_FRAME_TEXT) {
return chan_pjsip_write_stream(ast, frame->stream_num, frame);
}
return chan_pjsip_write_stream(ast, -1, frame); return chan_pjsip_write_stream(ast, -1, frame);
} }

@ -91,6 +91,8 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch
media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]; media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
} else if (!strcmp(field, "video")) { } else if (!strcmp(field, "video")) {
media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]; media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
} else if (!strcmp(field, "text")) {
media = session->active_media_state->default_session[AST_MEDIA_TYPE_TEXT];
} else { } else {
ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field); ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field);
return -1; return -1;

@ -799,8 +799,10 @@
;sdp_session=Asterisk ; String used for the SDP session s line (default: ;sdp_session=Asterisk ; String used for the SDP session s line (default:
; "Asterisk") ; "Asterisk")
;tos_audio=0 ; DSCP TOS bits for audio streams (default: "0") ;tos_audio=0 ; DSCP TOS bits for audio streams (default: "0")
;tos_text=0 ; DSCP TOS bits for text streams (default: "0")
;tos_video=0 ; DSCP TOS bits for video streams (default: "0") ;tos_video=0 ; DSCP TOS bits for video streams (default: "0")
;cos_audio=0 ; Priority for audio streams (default: "0") ;cos_audio=0 ; Priority for audio streams (default: "0")
;cos_text=0 ; Priority for text streams (default: "0")
;cos_video=0 ; Priority for video streams (default: "0") ;cos_video=0 ; Priority for video streams (default: "0")
;allow_subscribe=yes ; Determines if endpoint is allowed to initiate ;allow_subscribe=yes ; Determines if endpoint is allowed to initiate
; subscriptions with Asterisk (default: "yes") ; subscriptions with Asterisk (default: "yes")
@ -903,6 +905,8 @@
; ringing devices. ; ringing devices.
;max_audio_streams= ; The maximum number of allowed negotiated audio streams ;max_audio_streams= ; The maximum number of allowed negotiated audio streams
; (default: 1) ; (default: 1)
;max_text_streams= ; The maximum number of allowed negotiated text streams
; (default: 1)
;max_video_streams= ; The maximum number of allowed negotiated video streams ;max_video_streams= ; The maximum number of allowed negotiated video streams
; (default: 1) ; (default: 1)
;webrtc= ; When set to "yes" this also enables the following values that are needed ;webrtc= ; When set to "yes" this also enables the following values that are needed

@ -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')

@ -1043,6 +1043,12 @@ struct ast_sip_endpoint_media_configuration {
struct ast_stream_codec_negotiation_prefs codec_prefs_incoming_answer; struct ast_stream_codec_negotiation_prefs codec_prefs_incoming_answer;
/*! Codec negotiation prefs for outgoing answers */ /*! Codec negotiation prefs for outgoing answers */
struct ast_stream_codec_negotiation_prefs codec_prefs_outgoing_answer; struct ast_stream_codec_negotiation_prefs codec_prefs_outgoing_answer;
/*! DSCP TOS bits for text streams */
unsigned int tos_text;
/*! Priority for text streams */
unsigned int cos_text;
/*! Maximum number of text streams to offer/accept */
unsigned int max_text_streams;
}; };
/*! /*!

@ -1029,7 +1029,7 @@ void ast_rtp_instance_set_data(struct ast_rtp_instance *instance, void *data);
* *
* \code * \code
* struct *blob = ast_rtp_instance_get_data(instance); * struct *blob = ast_rtp_instance_get_data(instance);
( \endcode * \endcode
* *
* This gets the data pointer on the RTP instance pointed to by 'instance'. * This gets the data pointer on the RTP instance pointed to by 'instance'.
* *

@ -4793,6 +4793,7 @@ int ast_sendtext_data(struct ast_channel *chan, struct ast_msg_data *msg)
f.frametype = AST_FRAME_TEXT; f.frametype = AST_FRAME_TEXT;
f.datalen = body_len; f.datalen = body_len;
f.mallocd = AST_MALLOCD_DATA; f.mallocd = AST_MALLOCD_DATA;
f.stream_num = ast_stream_get_position(ast_channel_get_default_stream(chan, AST_MEDIA_TYPE_TEXT));
f.data.ptr = ast_strdup(body); f.data.ptr = ast_strdup(body);
if (f.data.ptr) { if (f.data.ptr) {
res = ast_channel_tech(chan)->write_text(chan, &f); res = ast_channel_tech(chan)->write_text(chan, &f);

@ -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,
);

@ -448,6 +448,45 @@
</enumlist> </enumlist>
</description> </description>
</configOption> </configOption>
<configOption name="max_text_streams" default="1">
<since>
<version>22.4.0</version>
<version>21.9.0</version>
<version>20.14.0</version>
</since>
<synopsis>
Maximum number of text streams
</synopsis>
<description>
<para>
Maximum number of text streams to accept. This is useful in situations
where you want to limit the number of text streams that can be sent to
the client.
</para>
</description>
</configOption>
<configOption name="tos_text" default="0">
<since>
<version>22.4.0</version>
<version>21.9.0</version>
<version>20.14.0</version>
</since>
<synopsis>DSCP TOS bits for text streams</synopsis>
<description><para>
See https://docs.asterisk.org/Configuration/Channel-Drivers/IP-Quality-of-Service for more information about QoS settings
</para></description>
</configOption>
<configOption name="cos_text" default="0">
<since>
<version>22.4.0</version>
<version>21.9.0</version>
<version>20.14.0</version>
</since>
<synopsis>Priority for text streams</synopsis>
<description><para>
See https://docs.asterisk.org/Configuration/Channel-Drivers/IP-Quality-of-Service for more information about QoS settings
</para></description>
</configOption>
<configOption name="direct_media_method" default="invite"> <configOption name="direct_media_method" default="invite">
<since> <since>
<version>12.2.0</version> <version>12.2.0</version>

@ -1082,6 +1082,8 @@ static int tos_handler(const struct aco_option *opt,
endpoint->media.tos_audio = value; endpoint->media.tos_audio = value;
} else if (!strcmp(var->name, "tos_video")) { } else if (!strcmp(var->name, "tos_video")) {
endpoint->media.tos_video = value; endpoint->media.tos_video = value;
} else if (!strcmp(var->name, "tos_text")) {
endpoint->media.tos_text = value;
} else { } else {
/* If we reach this point, someone called the tos_handler when they shouldn't have. */ /* If we reach this point, someone called the tos_handler when they shouldn't have. */
ast_assert(0); ast_assert(0);
@ -1110,6 +1112,16 @@ static int tos_video_to_str(const void *obj, const intptr_t *args, char **buf)
return 0; return 0;
} }
static int tos_text_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct ast_sip_endpoint *endpoint = obj;
if (ast_asprintf(buf, "%u", endpoint->media.tos_text) == -1) {
return -1;
}
return 0;
}
static int from_user_handler(const struct aco_option *opt, static int from_user_handler(const struct aco_option *opt,
struct ast_variable *var, void *obj) struct ast_variable *var, void *obj)
{ {
@ -2244,8 +2256,10 @@ int ast_res_pjsip_initialize_configuration(void)
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sdp_session", "Asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.sdpsession)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sdp_session", "Asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.sdpsession));
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_audio", "0", tos_handler, tos_audio_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_audio", "0", tos_handler, tos_audio_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_video", "0", tos_handler, tos_video_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_video", "0", tos_handler, tos_video_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_text", "0", tos_handler, tos_text_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_audio", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_audio)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_audio", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_audio));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_video", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_video)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_video", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_video));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_text", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_text));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_subscribe", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.allow)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_subscribe", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.allow));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sub_min_expiry", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, subscription.minexpiry)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sub_min_expiry", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, subscription.minexpiry));
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "from_user", "", from_user_handler, from_user_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "from_user", "", from_user_handler, from_user_to_str, NULL, 0, 0);
@ -2286,6 +2300,7 @@ int ast_res_pjsip_initialize_configuration(void)
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_audio_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_audio_streams)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_audio_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_audio_streams));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_video_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_video_streams)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_video_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_video_streams));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_text_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_text_streams));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "bundle", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.bundle)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "bundle", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.bundle));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "webrtc", "no", OPT_YESNO_T, 1, FLDSET(struct ast_sip_endpoint, media.webrtc)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "webrtc", "no", OPT_YESNO_T, 1, FLDSET(struct ast_sip_endpoint, media.webrtc));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "incoming_mwi_mailbox", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, incoming_mwi_mailbox)); ast_sorcery_object_field_register(sip_sorcery, "endpoint", "incoming_mwi_mailbox", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, incoming_mwi_mailbox));

@ -500,12 +500,18 @@
<parameter name="TosVideo"> <parameter name="TosVideo">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='tos_video']/synopsis/node())"/></para> <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='tos_video']/synopsis/node())"/></para>
</parameter> </parameter>
<parameter name="TosText">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='tos_text']/synopsis/node())"/></para>
</parameter>
<parameter name="CosAudio"> <parameter name="CosAudio">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='cos_audio']/synopsis/node())"/></para> <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='cos_audio']/synopsis/node())"/></para>
</parameter> </parameter>
<parameter name="CosVideo"> <parameter name="CosVideo">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='cos_video']/synopsis/node())"/></para> <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='cos_video']/synopsis/node())"/></para>
</parameter> </parameter>
<parameter name="CosText">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='cos_text']/synopsis/node())"/></para>
</parameter>
<parameter name="AllowSubscribe"> <parameter name="AllowSubscribe">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='allow_subscribe']/synopsis/node())"/></para> <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='allow_subscribe']/synopsis/node())"/></para>
</parameter> </parameter>
@ -647,6 +653,9 @@
<parameter name="MaxAudioStreams"> <parameter name="MaxAudioStreams">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='max_audio_streams']/synopsis/node())"/></para> <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='max_audio_streams']/synopsis/node())"/></para>
</parameter> </parameter>
<parameter name="MaxTextStreams">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='max_text_streams']/synopsis/node())"/></para>
</parameter>
<parameter name="MaxVideoStreams"> <parameter name="MaxVideoStreams">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='max_video_streams']/synopsis/node())"/></para> <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='max_video_streams']/synopsis/node())"/></para>
</parameter> </parameter>

@ -67,6 +67,7 @@ static struct ast_sockaddr address_rtp;
static const char STR_AUDIO[] = "audio"; static const char STR_AUDIO[] = "audio";
static const char STR_VIDEO[] = "video"; static const char STR_VIDEO[] = "video";
static const char STR_TEXT[] = "text";
static int send_keepalive(const void *data) static int send_keepalive(const void *data)
{ {
@ -290,6 +291,10 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me
(session->endpoint->media.tos_audio || session->endpoint->media.cos_audio)) { (session->endpoint->media.tos_audio || session->endpoint->media.cos_audio)) {
ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_audio, ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_audio,
session->endpoint->media.cos_audio, "SIP RTP Audio"); session->endpoint->media.cos_audio, "SIP RTP Audio");
} else if (session_media->type == AST_MEDIA_TYPE_TEXT &&
(session->endpoint->media.tos_text || session->endpoint->media.cos_text)) {
ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_text,
session->endpoint->media.cos_text, "SIP RTP Text");
} else if (session_media->type == AST_MEDIA_TYPE_VIDEO) { } else if (session_media->type == AST_MEDIA_TYPE_VIDEO) {
ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_RECV, session->endpoint->media.webrtc); ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_RECV, session->endpoint->media.webrtc);
ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_SEND, session->endpoint->media.webrtc); ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_RETRANS_SEND, session->endpoint->media.webrtc);
@ -373,7 +378,6 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp
struct ast_format *format_parsed; struct ast_format *format_parsed;
ast_copy_pj_str(fmt_param, &fmtp.fmt_param, sizeof(fmt_param)); ast_copy_pj_str(fmt_param, &fmtp.fmt_param, sizeof(fmt_param));
format_parsed = ast_format_parse_sdp_fmtp(format, fmt_param); format_parsed = ast_format_parse_sdp_fmtp(format, fmt_param);
if (format_parsed) { if (format_parsed) {
ast_rtp_codecs_payload_replace_format(codecs, num, format_parsed); ast_rtp_codecs_payload_replace_format(codecs, num, format_parsed);
@ -1539,6 +1543,45 @@ static void set_session_media_remotely_held(struct ast_sip_session_media *sessio
} }
} }
/*! \brief Function which checks for RED in a session and initalize it if needed */
static void check_red_support(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
struct ast_format *format_parsed)
{
/*
* To keep API compatibility, we are using an array here, even if the red codec
* is using only an integer internally, similar as the other codec attributes.
* The rtp red support from asterisk expect the primary payload also added to
* the array, therefore we need one more element.
*/
int red_pt_array[AST_RED_MAX_GENERATION+1];
memset(red_pt_array, 0, sizeof(red_pt_array));
if (session == NULL || session_media == NULL || format_parsed == NULL) {
ast_log(LOG_ERROR, "Invalid parameter for initializing RED\n");
return;
}
if (ast_format_cap_has_type(session->endpoint->media.codecs, AST_MEDIA_TYPE_TEXT)) {
if (ast_format_attribute_get(format_parsed, "red_payload") != NULL &&
strcasecmp(ast_format_get_name(format_parsed), "RED") == 0) {
int red_payload = *(int*)ast_format_attribute_get(format_parsed, "red_payload");
int red_num_gen = *(int*)ast_format_attribute_get(format_parsed, "red_num_gen");
if (red_payload != -1 && red_num_gen != -1) {
for (int x = 0; x < red_num_gen+1; x++) {
red_pt_array[x] = red_payload;
}
if (ast_rtp_red_init(session_media->rtp, 300, red_pt_array, red_num_gen) < 0) {
ast_log(LOG_ERROR, "Failed to initialize RED on rtp instance for stream %s\n",
ast_sip_session_get_name(session));
}
}
ast_debug(3, "RED init success for stream %s with %d payload and %d generations\n",
ast_sip_session_get_name(session), red_payload, red_num_gen);
}
}
}
/*! \brief Function which negotiates an incoming media stream */ /*! \brief Function which negotiates an incoming media stream */
static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp, struct ast_sip_session_media *session_media, const pjmedia_sdp_session *sdp,
@ -1809,6 +1852,8 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
AST_VECTOR(, int) sample_rates; AST_VECTOR(, int) sample_rates;
/* In case we can't init the sample rates, still try to do the rest. */ /* In case we can't init the sample rates, still try to do the rest. */
int build_dtmf_sample_rates = 1; int build_dtmf_sample_rates = 1;
/* Temporary store the t140 payload for redundant text payload adaptions */
int t140_payload = 0;
SCOPE_ENTER(1, "%s Type: %s %s\n", ast_sip_session_get_name(session), SCOPE_ENTER(1, "%s Type: %s %s\n", ast_sip_session_get_name(session),
ast_codec_media_type2str(media_type), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); ast_codec_media_type2str(media_type), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
@ -1967,6 +2012,28 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
build_dtmf_sample_rates = 0; build_dtmf_sample_rates = 0;
} }
/* if we are using text with rtp redundancy, we need to match the t140 payload inside the RED sdp */
for (index = 0; index < ast_format_cap_count(caps); ++index) {
struct ast_format *format = ast_format_cap_get_format(caps, index);
if (ast_format_get_type(format) == AST_MEDIA_TYPE_TEXT && strcasecmp(ast_format_get_name(format), "T140") == 0) {
if (session_media_transport != session_media) {
if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media_transport->rtp), 1, format, 0)) == -1) {
ast_log(LOG_WARNING, "Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
ao2_ref(format, -1);
continue;
}
} else {
if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0)) == -1) {
ast_log(LOG_WARNING, "Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
ao2_ref(format, -1);
continue;
}
}
t140_payload = rtp_code;
ast_debug(3, "Got t140 payload %d\n", t140_payload);
}
}
for (index = 0; index < ast_format_cap_count(caps); ++index) { for (index = 0; index < ast_format_cap_count(caps); ++index) {
struct ast_format *format = ast_format_cap_get_format(caps, index); struct ast_format *format = ast_format_cap_get_format(caps, index);
@ -2004,6 +2071,16 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
} }
} }
/* if we are using text with rtp redundancy, we need to match the t140 payload inside the RED sdp */
if (ast_format_get_type(format) == AST_MEDIA_TYPE_TEXT && strncasecmp(ast_format_get_name(format), "RED", 3) == 0) {
if (ast_format_attribute_get(format, "red_payload") != NULL &&
(*(int *)ast_format_attribute_get(format, "red_payload") != t140_payload)) {
char tmp[10];
snprintf(tmp, 10, "%d", t140_payload);
format = ast_format_attribute_set(format, "red_payload", tmp);
ast_debug(3, "Set RED payload to %s\n", tmp);
}
}
if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 1, format, 0))) { if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 1, format, 0))) {
int i, added = 0; int i, added = 0;
int newrate = ast_rtp_lookup_sample_rate2(1, format, 0); int newrate = ast_rtp_lookup_sample_rate2(1, format, 0);
@ -2224,6 +2301,8 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
int res; int res;
int rtp_timeout; int rtp_timeout;
struct ast_sip_session_media *session_media_transport; struct ast_sip_session_media *session_media_transport;
struct ast_format_cap *joint;
struct ast_format *format_parsed;
SCOPE_ENTER(1, "%s Stream: %s\n", ast_sip_session_get_name(session), SCOPE_ENTER(1, "%s Stream: %s\n", ast_sip_session_get_name(session),
ast_str_tmp(128, ast_stream_to_str(asterisk_stream, &STR_TMP))); ast_str_tmp(128, ast_stream_to_str(asterisk_stream, &STR_TMP)));
@ -2298,6 +2377,14 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
SCOPE_EXIT_RTN_VALUE(-1, "set_caps failed\n"); SCOPE_EXIT_RTN_VALUE(-1, "set_caps failed\n");
} }
joint = set_incoming_call_offer_cap(session, session_media, remote_stream);
/* If RED support is enabled we need to set it up */
format_parsed = ast_format_cap_get_compatible_format(joint, ast_format_t140_red);
if (format_parsed) {
check_red_support(session, session_media, format_parsed);
}
/* Set the channel uniqueid on the RTP instance now that it is becoming active */ /* Set the channel uniqueid on the RTP instance now that it is becoming active */
ast_channel_lock(session->channel); ast_channel_lock(session->channel);
ast_rtp_instance_set_channel_id(session_media->rtp, ast_channel_uniqueid(session->channel)); ast_rtp_instance_set_channel_id(session_media->rtp, ast_channel_uniqueid(session->channel));
@ -2452,6 +2539,17 @@ static struct ast_sip_session_sdp_handler video_sdp_handler = {
.stream_destroy = stream_destroy, .stream_destroy = stream_destroy,
}; };
/*! \brief SDP handler for 'text' media stream */
static struct ast_sip_session_sdp_handler text_sdp_handler = {
.id = STR_TEXT,
.negotiate_incoming_sdp_stream = negotiate_incoming_sdp_stream,
.create_outgoing_sdp_stream = create_outgoing_sdp_stream,
.apply_negotiated_sdp_stream = apply_negotiated_sdp_stream,
.change_outgoing_sdp_stream_media_address = change_outgoing_sdp_stream_media_address,
.stream_stop = stream_stop,
.stream_destroy = stream_destroy,
};
static int video_info_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata) static int video_info_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
{ {
struct pjsip_transaction *tsx; struct pjsip_transaction *tsx;
@ -2485,6 +2583,7 @@ static int unload_module(void)
ast_sip_session_unregister_supplement(&video_info_supplement); ast_sip_session_unregister_supplement(&video_info_supplement);
ast_sip_session_unregister_sdp_handler(&video_sdp_handler, STR_VIDEO); ast_sip_session_unregister_sdp_handler(&video_sdp_handler, STR_VIDEO);
ast_sip_session_unregister_sdp_handler(&audio_sdp_handler, STR_AUDIO); ast_sip_session_unregister_sdp_handler(&audio_sdp_handler, STR_AUDIO);
ast_sip_session_unregister_sdp_handler(&text_sdp_handler, STR_TEXT);
if (sched) { if (sched) {
ast_sched_context_destroy(sched); ast_sched_context_destroy(sched);
@ -2531,6 +2630,11 @@ static int load_module(void)
goto end; goto end;
} }
if (ast_sip_session_register_sdp_handler(&text_sdp_handler, STR_TEXT)) {
ast_log(LOG_ERROR, "Unable to register SDP handler for %s stream type\n", STR_TEXT);
goto end;
}
ast_sip_session_register_supplement(&video_info_supplement); ast_sip_session_register_supplement(&video_info_supplement);
return AST_MODULE_LOAD_SUCCESS; return AST_MODULE_LOAD_SUCCESS;

@ -608,8 +608,9 @@ static int is_stream_limitation_reached(enum ast_media_type type, const struct a
case AST_MEDIA_TYPE_IMAGE: case AST_MEDIA_TYPE_IMAGE:
/* We don't have an option for image (T.38) streams so cap it to one. */ /* We don't have an option for image (T.38) streams so cap it to one. */
return (type_streams[type] > 0); return (type_streams[type] > 0);
case AST_MEDIA_TYPE_UNKNOWN:
case AST_MEDIA_TYPE_TEXT: case AST_MEDIA_TYPE_TEXT:
return !(type_streams[type] < endpoint->media.max_text_streams);
case AST_MEDIA_TYPE_UNKNOWN:
default: default:
/* We don't want any unknown or "other" streams on our endpoint, /* We don't want any unknown or "other" streams on our endpoint,
* so always just say we've reached the limit * so always just say we've reached the limit

@ -127,14 +127,40 @@ struct ast_format_cap *ast_sip_create_joint_call_cap(const struct ast_format_cap
return joint; return joint;
} }
/*
* Fix preferences for real time text calls when we are supporting t140 and red codecs
* If the the remote party only supports t140, we should not offer redundancy
*/
static void fix_t140_red_pref(const struct ast_sip_session *session, enum ast_media_type media_type) {
int res = 0;
struct ast_flags *pref = &session->endpoint->media.outgoing_call_offer_pref;
if (media_type == AST_MEDIA_TYPE_TEXT && session->call_direction == AST_SIP_SESSION_OUTGOING_CALL) {
if (strcmp(ast_sip_call_codec_pref_to_str(*pref), "local_merge") == 0) {
ast_debug(3, "text call with local_merge preference, change to local\n");
res = ast_sip_call_codec_str_to_pref(pref, "local", 1);
} else {
if (strcmp(ast_sip_call_codec_pref_to_str(*pref), "remote_merge") == 0) {
ast_debug(3, "text call with remote_merge preference, change to remote\n");
res = ast_sip_call_codec_str_to_pref(pref, "remote", 1);
}
}
}
if (res != 0) {
ast_log(LOG_ERROR, "Could not fix preferences for real time text calls\n");
}
}
struct ast_stream *ast_sip_session_create_joint_call_stream(const struct ast_sip_session *session, struct ast_stream *ast_sip_session_create_joint_call_stream(const struct ast_sip_session *session,
struct ast_stream *remote_stream) struct ast_stream *remote_stream)
{ {
struct ast_stream *joint_stream = ast_stream_clone(remote_stream, NULL); struct ast_stream *joint_stream = ast_stream_clone(remote_stream, NULL);
const struct ast_format_cap *remote = ast_stream_get_formats(remote_stream); const struct ast_format_cap *remote = ast_stream_get_formats(remote_stream);
enum ast_media_type media_type = ast_stream_get_type(remote_stream); enum ast_media_type media_type = ast_stream_get_type(remote_stream);
struct ast_format_cap *joint;
struct ast_format_cap *joint = ast_sip_create_joint_call_cap(remote, fix_t140_red_pref(session, media_type);
joint = ast_sip_create_joint_call_cap(remote,
session->endpoint->media.codecs, media_type, session->endpoint->media.codecs, media_type,
session->call_direction == AST_SIP_SESSION_OUTGOING_CALL session->call_direction == AST_SIP_SESSION_OUTGOING_CALL
? session->endpoint->media.outgoing_call_offer_pref ? session->endpoint->media.outgoing_call_offer_pref
@ -152,7 +178,10 @@ struct ast_format_cap *ast_sip_session_create_joint_call_cap(
const struct ast_sip_session *session, enum ast_media_type media_type, const struct ast_sip_session *session, enum ast_media_type media_type,
const struct ast_format_cap *remote) const struct ast_format_cap *remote)
{ {
struct ast_format_cap *joint = ast_sip_create_joint_call_cap(remote, struct ast_format_cap *joint;
fix_t140_red_pref(session, media_type);
joint = ast_sip_create_joint_call_cap(remote,
session->endpoint->media.codecs, media_type, session->endpoint->media.codecs, media_type,
session->call_direction == AST_SIP_SESSION_OUTGOING_CALL session->call_direction == AST_SIP_SESSION_OUTGOING_CALL
? session->endpoint->media.outgoing_call_offer_pref ? session->endpoint->media.outgoing_call_offer_pref

@ -618,9 +618,10 @@ struct ast_rtcp {
struct rtp_red { struct rtp_red {
struct ast_frame t140; /*!< Primary data */ struct ast_frame t140; /*!< Primary data */
struct ast_frame t140red; /*!< Redundant t140*/ struct ast_frame t140red; /*!< Redundant t140*/
unsigned char pt[AST_RED_MAX_GENERATION]; /*!< Payload types for redundancy data */ /* We are encoding the primary payload type also in this array, so we need one more element */
unsigned char ts[AST_RED_MAX_GENERATION]; /*!< Time stamps */ unsigned char pt[AST_RED_MAX_GENERATION+1]; /*!< Payload types for redundancy data */
unsigned char len[AST_RED_MAX_GENERATION]; /*!< length of each generation */ unsigned char ts[AST_RED_MAX_GENERATION+1]; /*!< Time stamps */
unsigned char len[AST_RED_MAX_GENERATION+1]; /*!< length of each generation */
int num_gen; /*!< Number of generations */ int num_gen; /*!< Number of generations */
int schedid; /*!< Timer id */ int schedid; /*!< Timer id */
unsigned char t140red_data[64000]; unsigned char t140red_data[64000];
@ -5579,7 +5580,7 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
} }
if (rtp->red) { if (rtp->red) {
/* return 0; */ ast_rtp_red_buffer(instance, frame);
/* no primary data or generations to send */ /* no primary data or generations to send */
if ((frame = red_t140_to_red(rtp->red)) == NULL) if ((frame = red_t140_to_red(rtp->red)) == NULL)
return 0; return 0;
@ -8008,7 +8009,9 @@ static struct ast_frame *ast_rtp_interpret(struct ast_rtp_instance *instance, st
*data = 0xBD; *data = 0xBD;
} }
if (ast_format_cmp(rtp->f.subclass.format, ast_format_t140_red) == AST_FORMAT_CMP_EQUAL) { if (rtp->f.datalen <= 0) {
return AST_LIST_FIRST(&frames) ? AST_LIST_FIRST(&frames) : &ast_null_frame;
} else if (ast_format_cmp(rtp->f.subclass.format, ast_format_t140_red) == AST_FORMAT_CMP_EQUAL) {
unsigned char* data = rtp->f.data.ptr; unsigned char* data = rtp->f.data.ptr;
unsigned char* header_end; unsigned char* header_end;
int num_generations; int num_generations;
@ -8053,6 +8056,9 @@ static struct ast_frame *ast_rtp_interpret(struct ast_rtp_instance *instance, st
rtp->f.data.ptr += len; rtp->f.data.ptr += len;
rtp->f.datalen -= len; rtp->f.datalen -= len;
} }
if (rtp->f.datalen < 0) {
return AST_LIST_FIRST(&frames) ? AST_LIST_FIRST(&frames) : &ast_null_frame;
}
} }
if (ast_format_get_type(rtp->f.subclass.format) == AST_MEDIA_TYPE_AUDIO) { if (ast_format_get_type(rtp->f.subclass.format) == AST_MEDIA_TYPE_AUDIO) {
@ -9136,12 +9142,16 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct
static int red_write(const void *data) static int red_write(const void *data)
{ {
struct ast_rtp_instance *instance = (struct ast_rtp_instance *) data; struct ast_rtp_instance *instance = (struct ast_rtp_instance *) data;
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); struct ast_rtp *rtp;
ao2_lock(instance); ao2_lock(instance);
if (rtp->red->t140.datalen > 0) { rtp = ast_rtp_instance_get_data(instance);
ast_rtp_write(instance, &rtp->red->t140); if (!rtp || !rtp->red) {
ao2_unlock(instance);
ast_log(LOG_ERROR, "Could not get rtp from instance or RED handler\n");
return 0;
} }
ao2_unlock(instance); ao2_unlock(instance);
return 1; return 1;
@ -9174,7 +9184,14 @@ static int rtp_red_init(struct ast_rtp_instance *instance, int buffer_time, int
rtp->red->t140red_data[x*4] = rtp->red->pt[x]; rtp->red->t140red_data[x*4] = rtp->red->pt[x];
} }
rtp->red->t140red_data[x*4] = rtp->red->pt[x] = payloads[x]; /* primary pt */ rtp->red->t140red_data[x*4] = rtp->red->pt[x] = payloads[x]; /* primary pt */
ao2_ref(instance, +1);
rtp->red->schedid = ast_sched_add(rtp->sched, buffer_time, red_write, instance); rtp->red->schedid = ast_sched_add(rtp->sched, buffer_time, red_write, instance);
if (rtp->red->schedid < 0) {
ao2_ref(instance, -1);
ast_log(LOG_WARNING, "scheduling red_write failed.\n");
}
ast_debug(3, "Init RED for stream %s, primary payload %d with %d generations\n",
ast_rtp_instance_get_cname(instance), payloads[x], generations);
return 0; return 0;
} }
@ -9191,6 +9208,14 @@ static int rtp_red_buffer(struct ast_rtp_instance *instance, struct ast_frame *f
if (frame->datalen > 0) { if (frame->datalen > 0) {
if (red->t140.datalen > 0) { if (red->t140.datalen > 0) {
/*
* Avoid merging command and regular T.140 text packets by flushing the
* previous T.140 packets. When realtime text packets are to be sent, the
* text is accumulated in a buffer and sent regularly by a timer. It can
* happen that commands such as a backspace, CR, or LF get merged with
* regular text. This breaks some UAs. In recent tests it could not be
* reproduced, but its probably better to keep it.
*/
const unsigned char *primary = red->buf_data; const unsigned char *primary = red->buf_data;
/* There is something already in the T.140 buffer */ /* There is something already in the T.140 buffer */
@ -9379,7 +9404,9 @@ static void ast_rtp_stop(struct ast_rtp_instance *instance)
if (rtp->red) { if (rtp->red) {
ao2_unlock(instance); ao2_unlock(instance);
AST_SCHED_DEL(rtp->sched, rtp->red->schedid); if (!ast_sched_del(rtp->sched, rtp->red->schedid)) {
ao2_ref(instance, -1);
}
ao2_lock(instance); ao2_lock(instance);
ast_free(rtp->red); ast_free(rtp->red);
rtp->red = NULL; rtp->red = NULL;

Loading…
Cancel
Save