base-call: handle multiple DTMF characters

Send each character through the state machine one at a time. Some
modems (various Quectel ones for example) can process multiple at
the same time, and while that should be easily implemented using
the new changes to send_dtmf() it's not done in any plugin yet.

Signed-off-by: Dan Williams <dan@ioncontrol.co>
This commit is contained in:
Dan Williams
2025-05-30 08:02:48 -05:00
parent 56fe8e7d31
commit ae3d37030a
5 changed files with 81 additions and 23 deletions
@@ -103,10 +103,15 @@
<!--
SendDtmf:
@dtmf: DTMF tone identifier [0-9A-D*#] or pause character [,].
@dtmf: A string of DTMF tone identifiers [0-9A-D*#] and/or pause characters [,].
Send a DTMF tone (Dual Tone Multi-Frequency) (only on supported modems).
Since 1.26 the comma [,] is interpreted as a two-second pause.
Send one or more DTMF tones (Dual Tone Multi-Frequency) (only on supported modems).
Before 1.26 only the first character in @dtmf was sent to the modem;
all others were discarded.
Since 1.26 up to 50 tone identifiers are accepted and each will be
sent to the modem in the order given. The comma [,] character pauses
DTMF tones for two-seconds then continues with the remaining characters.
Applicable only if state is <link linkend="MM-CALL-STATE-ACTIVE:CAPS"><constant>MM_CALL_STATE_ACTIVE</constant></link>.
+60 -9
View File
@@ -736,6 +736,7 @@ typedef enum {
DTMF_STEP_START,
DTMF_STEP_TIMEOUT,
DTMF_STEP_STOP,
DTMF_STEP_NEXT,
DTMF_STEP_LAST,
} DtmfStep;
@@ -743,7 +744,12 @@ typedef struct {
DtmfStep step;
GError *saved_error;
guint8 call_id;
gchar *dtmf;
/* Array of DTMF runs; split by pauses */
GPtrArray *dtmfs;
/* index into dtmfs */
guint cur_dtmf;
/* index into cur dtmf run string */
gchar *cur_tone;
guint timeout_id;
} SendDtmfContext;
@@ -760,7 +766,8 @@ static void
send_dtmf_context_free (SendDtmfContext *ctx)
{
send_dtmf_context_clear_timeout (ctx);
g_free (ctx->dtmf);
g_ptr_array_foreach (ctx->dtmfs, (GFunc) g_free, NULL);
g_ptr_array_free (ctx->dtmfs, TRUE);
g_assert (!ctx->saved_error);
g_slice_free (SendDtmfContext, ctx);
}
@@ -831,6 +838,10 @@ dtmf_timeout (GTask *task)
ctx = g_task_get_task_data (task);
/* If this was a pause character; move past it */
if (ctx->cur_tone[0] == MM_CALL_DTMF_PAUSE_CHAR)
ctx->cur_tone++;
send_dtmf_context_clear_timeout (ctx);
ctx->step++;
send_dtmf_task_step_next (task);
@@ -845,17 +856,21 @@ send_dtmf_ready (MMBaseCall *self,
{
SendDtmfContext *ctx;
GError *error = NULL;
gboolean success;
gssize num_sent;
ctx = g_task_get_task_data (task);
success = MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error);
num_sent = MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error);
if (ctx->step == DTMF_STEP_START) {
if (!success) {
if (num_sent < 0) {
g_propagate_error (&ctx->saved_error, error);
ctx->step = DTMF_STEP_LAST;
} else
} else {
g_assert (num_sent > 0);
g_assert ((guint) num_sent <= strlen (ctx->cur_tone));
ctx->cur_tone += num_sent;
ctx->step++;
}
send_dtmf_task_step_next (task);
}
@@ -875,7 +890,7 @@ send_dtmf_task_step_next (GTask *task)
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
is_pause = (ctx->dtmf[0] == MM_CALL_DTMF_PAUSE_CHAR);
is_pause = (ctx->cur_tone[0] == MM_CALL_DTMF_PAUSE_CHAR);
need_stop = MM_BASE_CALL_GET_CLASS (self)->stop_dtmf &&
MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish;
@@ -886,7 +901,7 @@ send_dtmf_task_step_next (GTask *task)
case DTMF_STEP_START:
if (!is_pause) {
MM_BASE_CALL_GET_CLASS (self)->send_dtmf (self,
ctx->dtmf,
ctx->cur_tone,
(GAsyncReadyCallback)send_dtmf_ready,
g_object_ref (task));
return;
@@ -914,6 +929,22 @@ send_dtmf_task_step_next (GTask *task)
return;
}
/* Fall through */
case DTMF_STEP_NEXT:
/* Advance to next DTMF run? */
if (ctx->cur_tone[0] == '\0') {
ctx->cur_dtmf++;
if (ctx->cur_dtmf < ctx->dtmfs->len)
ctx->cur_tone = g_ptr_array_index (ctx->dtmfs, ctx->cur_dtmf);
}
/* More to send? */
if (ctx->cur_tone[0]) {
ctx->step = DTMF_STEP_START;
send_dtmf_task_step_next (task);
return;
}
/* no more DTMF characters to send */
/* Fall through */
case DTMF_STEP_LAST:
send_dtmf_context_clear_timeout (ctx);
if (ctx->saved_error)
@@ -962,9 +993,13 @@ send_dtmf_task_new (MMBaseCall *self,
}
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (SendDtmfContext);
ctx->call_id = call_id;
ctx->dtmf = g_strdup (dtmf);
/* Split DTMF into runs of DTMF characters interrupted by pauses */
ctx->dtmfs = mm_dtmf_split (dtmf);
g_assert (ctx->dtmfs->len > 0);
ctx->cur_tone = g_ptr_array_index (ctx->dtmfs, ctx->cur_dtmf);
g_task_set_task_data (task, ctx, (GDestroyNotify) send_dtmf_context_free);
return task;
@@ -1021,6 +1056,22 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self));
/* Ensure there are DTMF characters to send */
if (!ctx->dtmf || !ctx->dtmf[0]) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"No DTMF characters given");
handle_send_dtmf_context_free (ctx);
return;
}
/* And that there aren't too many */
if (strlen (ctx->dtmf) > 50) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
"Too many DTMF characters");
handle_send_dtmf_context_free (ctx);
return;
}
/* Check if we do support doing DTMF at all */
if (!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf ||
!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf_finish) {
+2 -1
View File
@@ -95,7 +95,8 @@ struct _MMBaseCallClass {
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean (* send_dtmf_finish) (MMBaseCall *self,
/* Returns the number of DTMF characters sent, or < 1 on error */
gssize (* send_dtmf_finish) (MMBaseCall *self,
GAsyncResult *res,
GError **error);
+4 -3
View File
@@ -312,12 +312,12 @@ call_hangup (MMBaseCall *_self,
/*****************************************************************************/
/* Send DTMF tone to call */
static gboolean
static gssize
call_send_dtmf_finish (MMBaseCall *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
return g_task_propagate_int (G_TASK (res), error);
}
static void
@@ -338,7 +338,8 @@ call_send_dtmf_ready (MMBaseModem *modem,
return;
}
g_task_return_boolean (task, TRUE);
/* We sent one character */
g_task_return_int (task, 1);
g_object_unref (task);
}
+3 -3
View File
@@ -432,12 +432,12 @@ call_stop_dtmf (MMBaseCall *_self,
task);
}
static gboolean
static gssize
call_send_dtmf_finish (MMBaseCall *call,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
return g_task_propagate_int (G_TASK (res), error);
}
static void
@@ -456,7 +456,7 @@ voice_start_continuous_dtmf_ready (QmiClientVoice *client,
g_prefix_error (&error, "Couldn't send DTMF character: ");
g_task_return_error (task, error);
} else {
g_task_return_boolean (task, TRUE);
g_task_return_int (task, 1);
}
g_object_unref (task);
}