Compare commits

...

6 Commits

Author SHA1 Message Date
Naveen Albert
9a2a853fee func_math: Add DIGIT_SUM function.
Add a function (DIGIT_SUM) which returns the digit sum of a number.

Resolves: #1499

UserNote: The DIGIT_SUM function can be used to return the digit sum of
a number.
2025-10-06 19:26:17 +00:00
Naveen Albert
341cb16378 app_sf: Add post-digit timer option to ReceiveSF.
Add a sorely needed option to set a timeout between digits, rather than
for receiving the entire number. This is needed if the number of digits
being sent is unknown by the receiver in advance. Previously, we had
to wait for the entire timer to expire.

Resolves: #1493

UserNote: The 't' option for ReceiveSF now allows for a timer since
the last digit received, in addition to the number-wide timeout.
2025-10-06 19:24:57 +00:00
Naveen Albert
b8c9440e19 codec_builtin.c: Adjust some of the quality scores to reflect reality.
Among the lower-quality voice codecs, some of the quality scores did
not make sense relative to each other.

For instance, quality-wise, G.729 > G.723 > PLC10.
However, current scores do not uphold these relationships.

Tweak the scores slightly to reflect more accurate relationships.

Resolves: #1501
2025-10-06 15:46:30 +00:00
Naveen Albert
de46164a73 res_tonedetect: Fix formatting of XML documentation.
Fix the indentation in the documentation for the variable list.

Resolves: #1507
2025-10-06 15:41:22 +00:00
Naveen Albert
3c21fbf69c res_fax: Add XML documentation for channel variables.
Document the channel variables currently set by SendFAX and ReceiveFAX.

Resolves: #1505
2025-10-06 15:39:00 +00:00
George Joseph
c597c8b5c0 channelstorage_cpp_map_name_id: Add read locking around retrievals.
When we retrieve a channel from a C++ map, we actually get back a wrapper
object that points to the channel then right after we retrieve it, we bump its
reference count.  There's a tiny chance however that between those two
statements a delete and/or unref might happen which would cause the wrapper
object or the channel itself to become invalid resulting in a SEGV.  To avoid
this we now perform a read lock on the driver around those statements.

Resolves: #1491
2025-10-06 13:50:31 +00:00
6 changed files with 269 additions and 68 deletions

View File

@@ -78,6 +78,20 @@
<option name="m">
<para>Mute conference.</para>
</option>
<option name="t">
<para>Post-digit timeout, in seconds. If more than this amount of time
elapses after a digit, the number will be deemed finalized and
the application will return This is useful when reading a variable
number of digits that is unknown in advance.</para>
<para>This can be almost any positive decimal number of seconds, but must
be no less than 0.6 seconds, since this is the SF inter-digit timeout.
The default is 0.8, which should be compatible with <literal>SendSF</literal>.
Values lower than 0.75 will generally produce unreliable results.</para>
<para>Note this timer only kicks in after at least one digit has been received,
to account for sender/receiver synchronization.</para>
<para>Note that <literal>RECEIVESFSTATUS</literal> will still be
set to <literal>TIMEOUT</literal> if the post-digit timer expires.</para>
</option>
<option name="q">
<para>Quelch SF from in-band.</para>
</option>
@@ -146,18 +160,35 @@
</application>
***/
/* Default post-digit timer */
#define DEFAULT_POST_DIGIT_TIMER 0.8
/* Bell System Technical Journal 39 (Nov. 1960) */
#define SF_MIN_OFF 25
#define SF_ON 67
#define SF_BETWEEN 600
#define SF_MIN_DETECT 50
enum read_option_flags {
OPT_DELAY = (1 << 0),
OPT_MUTE = (1 << 1),
OPT_QUELCH = (1 << 2),
OPT_RELAXED = (1 << 3),
OPT_EXTRAPULSES = (1 << 4),
OPT_DIGIT_TIMEOUT = (1 << 5),
};
enum {
OPT_ARG_DIGIT_TIMEOUT,
/* note: this entry _MUST_ be the last one in the enum */
OPT_ARG_ARRAY_SIZE,
};
AST_APP_OPTIONS(read_app_options, {
AST_APP_OPTION('d', OPT_DELAY),
AST_APP_OPTION('e', OPT_EXTRAPULSES),
AST_APP_OPTION('m', OPT_MUTE),
AST_APP_OPTION_ARG('t', OPT_DIGIT_TIMEOUT, OPT_ARG_DIGIT_TIMEOUT),
AST_APP_OPTION('q', OPT_QUELCH),
AST_APP_OPTION('r', OPT_RELAXED),
});
@@ -172,6 +203,7 @@ static const char sendsf_name[] = "SendSF";
* \param buf Buffer in which to store digits
* \param buflen Size of buffer
* \param timeout ms to wait for all digits before giving up
* \param digit_timeout ms to wait for the next digit before giving up
* \param maxdigits Maximum number of digits
* \param freq Frequency to use
* \param features DSP features
@@ -180,13 +212,8 @@ static const char sendsf_name[] = "SendSF";
* \retval 0 if successful
* \retval -1 if unsuccessful (including hangup).
*/
static int read_sf_digits(struct ast_channel *chan, char *buf, int buflen, int timeout, int maxdigits, int freq, int features, int extrapulses) {
/* Bell System Technical Journal 39 (Nov. 1960) */
#define SF_MIN_OFF 25
#define SF_ON 67
#define SF_BETWEEN 600
#define SF_MIN_DETECT 50
static int read_sf_digits(struct ast_channel *chan, char *buf, int buflen, int timeout, int digit_timeout, int maxdigits, int freq, int features, int extrapulses)
{
struct ast_dsp *dsp = NULL;
struct ast_frame *frame = NULL;
struct timeval start, pulsetimer, digittimer;
@@ -212,6 +239,23 @@ static int read_sf_digits(struct ast_channel *chan, char *buf, int buflen, int t
if (timeout > 0) {
remaining_time = ast_remaining_ms(start, timeout);
if (remaining_time <= 0) {
ast_debug(1, "SF all-digit timer expired\n");
pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "TIMEOUT");
break;
}
}
/* If we haven't received a digit yet, don't apply the post-digit timer just yet,
* since the sender may not have started sending any digits.
*
* Note that we use the digit timer, which is reset for each SF pulse,
* as opposed to simply an entire digit being received.
* This is done because we only want to expire the timer if there has been no activity
* since the last digit. If we're in the middle of receiving a digit (e.g. 0)
* we may not have a full digit yet but that should not cause an expiration. */
if (digits_read > 0 && digit_timeout > 0) {
int remaining_time_for_next_digit = ast_remaining_ms(digittimer, digit_timeout);
if (remaining_time_for_next_digit <= 0) {
ast_debug(1, "SF post-digit timer expired (>= %d ms since last SF pulse)\n", digit_timeout);
pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "TIMEOUT");
break;
}
@@ -261,7 +305,7 @@ static int read_sf_digits(struct ast_channel *chan, char *buf, int buflen, int t
}
} else if (hits > 0 && ast_remaining_ms(digittimer, SF_BETWEEN) <= 0) {
/* has the digit finished? */
ast_debug(2, "Received SF digit: %d\n", hits);
ast_debug(2, "Received SF digit: %d\n", hits == 10 ? 0 : hits); /* Edge case for 10, since this is the digit '0' */
digits_read++;
if (hits > 10) {
if (extrapulses) {
@@ -321,10 +365,11 @@ static int read_sf_exec(struct ast_channel *chan, const char *data)
{
#define BUFFER_SIZE 256
char tmp[BUFFER_SIZE] = "";
double tosec;
double tosec, digitsec;
struct ast_flags flags = {0};
char *opt_args[OPT_ARG_ARRAY_SIZE];
char *argcopy = NULL;
int res, features = 0, digits = 0, to = 0, freq = 2600;
int res, features = 0, digits = 0, to = 0, digit_timeout = 0, freq = 2600;
AST_DECLARE_APP_ARGS(arglist,
AST_APP_ARG(variable);
@@ -344,7 +389,7 @@ static int read_sf_exec(struct ast_channel *chan, const char *data)
AST_STANDARD_APP_ARGS(arglist, argcopy);
if (!ast_strlen_zero(arglist.options)) {
ast_app_parse_options(read_app_options, &flags, NULL, arglist.options);
ast_app_parse_options(read_app_options, &flags, opt_args, arglist.options);
}
if (!ast_strlen_zero(arglist.timeout)) {
@@ -355,6 +400,18 @@ static int read_sf_exec(struct ast_channel *chan, const char *data)
to = tosec * 1000.0;
}
}
if (ast_test_flag(&flags, OPT_DIGIT_TIMEOUT)) {
digitsec = (!ast_strlen_zero(opt_args[OPT_ARG_DIGIT_TIMEOUT]) ? atof(opt_args[OPT_ARG_DIGIT_TIMEOUT]) : DEFAULT_POST_DIGIT_TIMER);
if (digitsec <= 0) {
digit_timeout = 0;
} else {
digit_timeout = digitsec * 1000.0;
if (digit_timeout < SF_BETWEEN) {
ast_log(LOG_WARNING, "SF post-digit timer (%d) cannot be less than the SF inter-digit timeout (%d ms)\n", digit_timeout, SF_BETWEEN);
digit_timeout = SF_BETWEEN; /* If we have a shorter timeout, it won't work at all */
}
}
}
if (!ast_strlen_zero(arglist.digits) && (ast_str_to_int(arglist.digits, &digits) || digits <= 0)) {
ast_log(LOG_WARNING, "Invalid number of digits: %s\n", arglist.digits);
@@ -387,7 +444,7 @@ static int read_sf_exec(struct ast_channel *chan, const char *data)
features |= DSP_DIGITMODE_RELAXDTMF;
}
res = read_sf_digits(chan, tmp, BUFFER_SIZE, to, digits, freq, features, ast_test_flag(&flags, OPT_EXTRAPULSES));
res = read_sf_digits(chan, tmp, BUFFER_SIZE, to, digit_timeout, digits, freq, features, ast_test_flag(&flags, OPT_EXTRAPULSES));
pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
if (!ast_strlen_zero(tmp)) {
ast_verb(3, "SF digits received: '%s'\n", tmp);

View File

@@ -177,6 +177,28 @@
</example>
</description>
</function>
<function name="DIGIT_SUM" language="en_US">
<since>
<version>23.1.0</version>
<version>22.7.0</version>
<version>20.17.0</version>
</since>
<synopsis>
Returns the sum of all the digits in a number.
</synopsis>
<syntax>
<parameter name="num" />
</syntax>
<description>
<para>Returns the numeric sum of all the individual digits in a number, summed up.</para>
<para>This can be useful for computing checksums based on the number,
where errors are typically digits being off by one.</para>
<example title="Get the sum of digits in 859">
same => n,Set(digitsum=${DIGIT_SUM(859)}) ; assigns digitsum=22
same => n,Set(checksum=$[${digitsum} % 10]) ; assigns checksum=2
</example>
</description>
</function>
***/
enum TypeOfFunctions {
@@ -634,6 +656,27 @@ static int acf_abs_exec(struct ast_channel *chan, const char *cmd,
return 0;
}
static int acf_digit_sum_exec(struct ast_channel *chan, const char *cmd,
char *parse, char *buffer, size_t buflen)
{
int sum = 0;
if (ast_strlen_zero(parse)) {
ast_log(LOG_WARNING, "Missing argument for number\n");
return -1;
}
for (; *parse; parse++) {
if (*parse < '0' || *parse > '9') {
continue;
}
sum += (*parse - '0');
}
snprintf(buffer, buflen, "%d", sum);
return 0;
}
static struct ast_custom_function math_function = {
.name = "MATH",
.read = math
@@ -667,6 +710,11 @@ static struct ast_custom_function acf_abs = {
.read_max = 12,
};
static struct ast_custom_function acf_digit_sum = {
.name = "DIGIT_SUM",
.read = acf_digit_sum_exec,
};
#ifdef TEST_FRAMEWORK
AST_TEST_DEFINE(test_MATH_function)
{
@@ -728,6 +776,7 @@ static int unload_module(void)
res |= ast_custom_function_unregister(&acf_min);
res |= ast_custom_function_unregister(&acf_max);
res |= ast_custom_function_unregister(&acf_abs);
res |= ast_custom_function_unregister(&acf_digit_sum);
AST_TEST_UNREGISTER(test_MATH_function);
return res;
@@ -743,6 +792,7 @@ static int load_module(void)
res |= ast_custom_function_register(&acf_min);
res |= ast_custom_function_register(&acf_max);
res |= ast_custom_function_register(&acf_abs);
res |= ast_custom_function_register(&acf_digit_sum);
AST_TEST_REGISTER(test_MATH_function);
return res;

View File

@@ -141,7 +141,17 @@ static int delete_channel(struct ast_channelstorage_instance *driver,
/*! \brief returns number of active/allocated channels */
static int active_channels(struct ast_channelstorage_instance *driver)
{
return driver ? getdb(driver).size() : 0;
int count = 0;
if (!driver) {
return 0;
}
rdlock(driver);
count = getdb(driver).size();
unlock(driver);
return count;
}
static struct ast_channel *callback(struct ast_channelstorage_instance *driver,
@@ -454,14 +464,17 @@ static struct ast_channel *get_by_uniqueid(struct ast_channelstorage_instance *d
{
struct ast_channel *chan = NULL;
char *search = uniqueid ? ast_str_to_lower(ast_strdupa(uniqueid)) : NULL;
if (ast_strlen_zero(uniqueid)) {
return NULL;
}
rdlock(driver);
auto rtn = map_by_id(driver).find(search);
if (rtn != map_by_id(driver).end()) {
chan = ao2_bump((struct ast_channel *)rtn->second);
}
unlock(driver);
return chan;
}
@@ -469,16 +482,21 @@ static struct ast_channel *get_by_uniqueid(struct ast_channelstorage_instance *d
static struct ast_channel *get_by_name_exact(struct ast_channelstorage_instance *driver,
const char *name)
{
struct ast_channel *chan = NULL;
char *search = name ? ast_str_to_lower(ast_strdupa(name)) : NULL;
if (ast_strlen_zero(name)) {
return NULL;
}
auto chan = getdb(driver).find(search);
if (chan != getdb(driver).end()) {
return ao2_bump((struct ast_channel *)chan->second);
}
return NULL;
rdlock(driver);
auto rtn = getdb(driver).find(search);
if (rtn != getdb(driver).end()) {
chan = ao2_bump((struct ast_channel *)rtn->second);
}
unlock(driver);
return chan;
}
static struct ast_channel *get_by_name_prefix(struct ast_channelstorage_instance *driver,
@@ -493,10 +511,14 @@ static struct ast_channel *get_by_name_prefix(struct ast_channelstorage_instance
}
l_name = ast_str_to_lower(ast_strdupa(name));
rdlock(driver);
auto rtn = getdb(driver).lower_bound(l_name);
if (rtn != getdb(driver).end()) {
chan = ao2_bump((struct ast_channel *)rtn->second);
}
unlock(driver);
return chan;
}

View File

@@ -216,7 +216,7 @@ static struct ast_codec gsm = {
.samples_count = gsm_samples,
.get_length = gsm_length,
.smooth = 1,
.quality = 60,
.quality = 40,
};
static int g726_samples(struct ast_frame *frame)
@@ -449,7 +449,7 @@ static struct ast_codec lpc10 = {
.minimum_bytes = 7,
.samples_count = lpc10_samples,
.smooth = 1,
.quality = 25,
.quality = 8,
};
static int g729_samples(struct ast_frame *frame)
@@ -475,7 +475,7 @@ static struct ast_codec g729a = {
.get_length = g729_length,
.smooth = 1,
.smoother_flags = AST_SMOOTHER_FLAG_G729,
.quality = 20,
.quality = 45,
};
static unsigned char get_n_bits_at(unsigned char *data, int n, int bit)
@@ -662,7 +662,7 @@ static struct ast_codec ilbc = {
.minimum_bytes = 38,
.samples_count = ilbc_samples,
.smooth = 0,
.quality = 45,
.quality = 35,
};
static struct ast_codec g722 = {
@@ -777,7 +777,7 @@ static struct ast_codec opus = {
.default_ms = 20,
.samples_count = opus_samples,
.minimum_bytes = 10,
.quality = 50,
.quality = 75,
};
static struct ast_codec jpeg = {

View File

@@ -120,8 +120,44 @@
<para>This application is provided by res_fax, which is a FAX technology agnostic module
that utilizes FAX technology resource modules to complete a FAX transmission.</para>
<para>Session arguments can be set by the FAXOPT function and to check results of the ReceiveFAX() application.</para>
<variablelist>
<variable name="FAXSTATUS">
<para>Whether the fax transmission was successful</para>
<value name="SUCCESS"/>
<value name="FAILURE"/>
</variable>
<variable name="FAXERROR">
<para>Description of what caused the fax to fail</para>
<value name="MEMORY_ERROR"/>
<value name="Channel Problems"/>
</variable>
<variable name="FAXSTATUSSTRING">
<para>Detailed description of the status of the fax transmission</para>
</variable>
<variable name="LOCALSTATIONID">
<para>Local Station ID</para>
</variable>
<variable name="REMOTESTATIONID">
<para>Remote Station ID</para>
</variable>
<variable name="FAXPAGES">
<para>Number of pages in the fax</para>
</variable>
<variable name="FAXBITRATE">
<para>Bit rate of the fax transmission</para>
</variable>
<variable name="FAXRESOLUTION">
<para>Resolution of the fax document</para>
</variable>
<variable name="FAXMODE">
<para>Fax transmission mode</para>
<value name="audio"/>
<value name="T38"/>
</variable>
</variablelist>
</description>
<see-also>
<ref type="application">SendFAX</ref>
<ref type="function">FAXOPT</ref>
</see-also>
</application>
@@ -162,8 +198,44 @@
<para>This application is provided by res_fax, which is a FAX technology agnostic module
that utilizes FAX technology resource modules to complete a FAX transmission.</para>
<para>Session arguments can be set by the FAXOPT function and to check results of the SendFAX() application.</para>
<variablelist>
<variable name="FAXSTATUS">
<para>Whether the fax transmission was successful</para>
<value name="SUCCESS"/>
<value name="FAILURE"/>
</variable>
<variable name="FAXERROR">
<para>Description of what caused the fax to fail</para>
<value name="MEMORY_ERROR"/>
<value name="Channel Problems"/>
</variable>
<variable name="FAXSTATUSSTRING">
<para>Detailed description of the status of the fax transmission</para>
</variable>
<variable name="LOCALSTATIONID">
<para>Local Station ID</para>
</variable>
<variable name="REMOTESTATIONID">
<para>Remote Station ID</para>
</variable>
<variable name="FAXPAGES">
<para>Number of pages in the fax</para>
</variable>
<variable name="FAXBITRATE">
<para>Bit rate of the fax transmission</para>
</variable>
<variable name="FAXRESOLUTION">
<para>Resolution of the fax document</para>
</variable>
<variable name="FAXMODE">
<para>Fax transmission mode</para>
<value name="audio"/>
<value name="T38"/>
</variable>
</variablelist>
</description>
<see-also>
<ref type="application">ReceiveFAX</ref>
<ref type="function">FAXOPT</ref>
</see-also>
</application>

View File

@@ -85,14 +85,14 @@
<description>
<para>Waits for a single-frequency tone to be detected before dialplan execution continues.</para>
<variablelist>
<variable name="WAITFORTONESTATUS">
<para>This indicates the result of the wait.</para>
<value name="SUCCESS"/>
<value name="ERROR"/>
<value name="TIMEOUT"/>
<value name="HANGUP"/>
</variable>
</variablelist>
<variable name="WAITFORTONESTATUS">
<para>This indicates the result of the wait.</para>
<value name="SUCCESS"/>
<value name="ERROR"/>
<value name="TIMEOUT"/>
<value name="HANGUP"/>
</variable>
</variablelist>
</description>
<see-also>
<ref type="application">PlayTones</ref>
@@ -136,43 +136,43 @@
Unlike a conventional scanner, this is not currently capable of
scanning for modem carriers.</para>
<variablelist>
<variable name="TONESCANSTATUS">
This indicates the result of the scan.
<value name="RINGING">
Audible ringback tone
</value>
<value name="BUSY">
Busy tone
</value>
<value name="SIT">
Special Information Tones
</value>
<value name="VOICE">
Human voice detected
</value>
<value name="DTMF">
DTMF digit
</value>
<value name="FAX">
Fax (answering)
</value>
<value name="MODEM">
Modem (answering)
</value>
<value name="DIALTONE">
Dial tone
</value>
<value name="NUT">
UK Number Unobtainable tone
</value>
<value name="TIMEOUT">
Timeout reached before any positive detection
</value>
<value name="HANGUP">
Caller hung up before any positive detection
</value>
</variable>
</variablelist>
<variable name="TONESCANSTATUS">
This indicates the result of the scan.
<value name="RINGING">
Audible ringback tone
</value>
<value name="BUSY">
Busy tone
</value>
<value name="SIT">
Special Information Tones
</value>
<value name="VOICE">
Human voice detected
</value>
<value name="DTMF">
DTMF digit
</value>
<value name="FAX">
Fax (answering)
</value>
<value name="MODEM">
Modem (answering)
</value>
<value name="DIALTONE">
Dial tone
</value>
<value name="NUT">
UK Number Unobtainable tone
</value>
<value name="TIMEOUT">
Timeout reached before any positive detection
</value>
<value name="HANGUP">
Caller hung up before any positive detection
</value>
</variable>
</variablelist>
</description>
<see-also>
<ref type="application">WaitForTone</ref>