[Asterisk-code-review] app_mf: Add full MF/SF support (asterisk[master])

N A asteriskteam at digium.com
Fri Sep 10 19:42:55 CDT 2021


N A has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/16481 )


Change subject: app_mf: Add full MF/SF support
......................................................................

app_mf: Add full MF/SF support

Adds tech agnostic support for MF and SF signaling.
The Dial application is also extended to support
these signal types.

ASTERISK-29496

Change-Id: I05ba61ce49092d3f131ade9dc4d97dcc9887fa1d
---
M apps/app_dial.c
A apps/app_mf.c
A doc/CHANGES-staging/app_mf_full.txt
M include/asterisk/app.h
M include/asterisk/channel.h
M main/app.c
M main/channel.c
7 files changed, 1,316 insertions(+), 18 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/81/16481/1

diff --git a/apps/app_dial.c b/apps/app_dial.c
index 51f5f45..a61cfa0 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -156,6 +156,10 @@
 					<argument name="called" />
 					<argument name="calling" />
 					<argument name="progress" />
+					<argument name="mfprogress" />
+					<argument name="mfwink" />
+					<argument name="sfprogress" />
+					<argument name="sfwink" />
 					<para>Send the specified DTMF strings <emphasis>after</emphasis> the called
 					party has answered, but before the call gets bridged.  The
 					<replaceable>called</replaceable> DTMF string is sent to the called party, and the
@@ -163,6 +167,20 @@
 					can be used alone.  If <replaceable>progress</replaceable> is specified, its DTMF is sent
 					to the called party immediately after receiving a <literal>PROGRESS</literal> message.</para>
 					<para>See <literal>SendDTMF</literal> for valid digits.</para>
+					<para>If <replaceable>mfprogress</replaceable> is specified, its MF is sent
+					to the called party immediately after receiving a <literal>PROGRESS</literal> message.
+					If <replaceable>mfwink</replaceable> is specified, its MF is sent
+					to the called party immediately after receiving a <literal>WINK</literal> message.</para>
+					<para>See <literal>SendMF</literal> for valid digits.</para>
+					<para>If <replaceable>sfprogress</replaceable> is specified, its SF is sent
+					to the called party immediately after receiving a <literal>PROGRESS</literal> message.
+					If <replaceable>sfwink</replaceable> is specified, its SF is sent
+					to the called party immediately after receiving a <literal>WINK</literal> message.</para>
+					<para>See <literal>SendSF</literal> for valid digits.</para>
+				</option>
+				<option name="E">
+					<para>Enable echoing of sent MF or SF digits back to caller (e.g. "hearpulsing").
+					Used in conjunction with the D option.</para>
 				</option>
 				<option name="e">
 					<para>Execute the <literal>h</literal> extension for peer after the call ends</para>
@@ -499,7 +517,6 @@
 			answered, if it has not already been answered. These two channels will then
 			be active in a bridged call. All other channels that were requested will then
 			be hung up.</para>
-
 			<para>Unless there is a timeout specified, the Dial application will wait
 			indefinitely until one of the called channels answers, the user hangs up, or
 			if all of the called channels are busy or unavailable. Dialplan execution will
@@ -512,7 +529,6 @@
 			If the <variable>OUTBOUND_GROUP_ONCE</variable> variable is set, all peer channels created by this
 			application will be put into that group (as in <literal>Set(GROUP()=...</literal>). Unlike <variable>OUTBOUND_GROUP</variable>,
 			however, the variable will be unset after use.</para>
-
 			<example title="Dial with 30 second timeout">
 			 same => n,Dial(PJSIP/alice,30)
 			</example>
@@ -534,28 +550,22 @@
 			</example>
 			<example title="Dial with pre-dial subroutines">
 			[default]
-
 			exten => callee_channel,1,NoOp(ARG1=${ARG1} ARG2=${ARG2})
 			 same => n,Log(NOTICE, I'm called on channel ${CHANNEL} prior to it starting the dial attempt)
 			 same => n,Return()
-
 			exten => called_channel,1,NoOp(ARG1=${ARG1} ARG2=${ARG2})
 			 same => n,Log(NOTICE, I'm called on outbound channel ${CHANNEL} prior to it being used to dial someone)
 			 same => n,Return()
-
 			exten => _X.,1,NoOp()
 			 same => n,Dial(PJSIP/alice,,b(default^called_channel^1(my_gosub_arg1^my_gosub_arg2))B(default^callee_channel^1(my_gosub_arg1^my_gosub_arg2)))
 			 same => n,Hangup()
 			</example>
 			<example title="Dial with post-answer subroutine executed on outbound channel">
 			[my_gosub_routine]
-
 			exten => s,1,NoOp(ARG1=${ARG1} ARG2=${ARG2})
 			 same => n,Playback(hello)
 			 same => n,Return()
-
 			[default]
-
 			exten => _X.,1,NoOp()
 			 same => n,Dial(PJSIP/alice,,U(my_gosub_routine^my_gosub_arg1^my_gosub_arg2))
 			 same => n,Hangup()
@@ -717,6 +727,7 @@
 #define OPT_PREDIAL_CALLER   (1LLU << 42)
 #define OPT_RING_WITH_EARLY_MEDIA (1LLU << 43)
 #define OPT_HANGUPCAUSE      (1LLU << 44)
+#define OPT_HEARPULSING      (1LLU << 45)
 
 enum {
 	OPT_ARG_ANNOUNCE = 0,
@@ -752,6 +763,7 @@
 	AST_APP_OPTION('c', OPT_CANCEL_ELSEWHERE),
 	AST_APP_OPTION('d', OPT_DTMF_EXIT),
 	AST_APP_OPTION_ARG('D', OPT_SENDDTMF, OPT_ARG_SENDDTMF),
+	AST_APP_OPTION('E', OPT_HEARPULSING),
 	AST_APP_OPTION('e', OPT_PEER_H),
 	AST_APP_OPTION_ARG('f', OPT_FORCECLID, OPT_ARG_FORCECLID),
 	AST_APP_OPTION_ARG('F', OPT_CALLEE_GO_ON, OPT_ARG_CALLEE_GO_ON),
@@ -1209,6 +1221,8 @@
 	char *opt_args[],
 	struct privacy_args *pa,
 	const struct cause_args *num_in, int *result, char *dtmf_progress,
+	char *mf_progress, char *mf_wink, char *sf_progress, char *sf_wink,
+	const int hearpulsing,
 	const int ignore_cc,
 	struct ast_party_id *forced_clid, struct ast_party_id *stored_clid,
 	struct ast_bridge_config *config)
@@ -1228,7 +1242,7 @@
 	int cc_frame_received = 0;
 	int num_ringing = 0;
 	int sent_ring = 0;
-	int sent_progress = 0;
+	int sent_progress = 0, sent_wink = 0;
 	struct timeval start = ast_tvnow();
 	SCOPE_ENTER(3, "%s\n", ast_channel_name(in));
 
@@ -1566,6 +1580,22 @@
 						ast_channel_unlock(in);
 						sent_progress = 1;
 
+						if (!ast_strlen_zero(mf_progress)) {
+							ast_verb(3,
+								"Sending MF '%s' to %s as result of "
+								"receiving a PROGRESS message.\n",
+								mf_progress, hearpulsing ? "parties" : "called party");
+							ast_mf_stream(c, (hearpulsing ? NULL : in),
+							(hearpulsing ? in : NULL), mf_progress, 50, 55, 120, 65, 0);
+						}
+						if (!ast_strlen_zero(sf_progress)) {
+							ast_verb(3,
+								"Sending SF '%s' to %s as result of "
+								"receiving a PROGRESS message.\n",
+								sf_progress, (hearpulsing ? "parties" : "called party"));
+							ast_sf_stream(c, (hearpulsing ? NULL : in),
+							(hearpulsing ? in : NULL), sf_progress, 0, 0);
+						}
 						if (!ast_strlen_zero(dtmf_progress)) {
 							ast_verb(3,
 								"Sending DTMF '%s' to the called party as result of "
@@ -1576,6 +1606,28 @@
 					}
 					ast_channel_publish_dial(in, c, NULL, "PROGRESS");
 					break;
+				case AST_CONTROL_WINK:
+					ast_verb(3, "%s winked, passing it to %s\n", ast_channel_name(c), ast_channel_name(in));
+					if (!sent_wink) {
+						sent_wink = 1;
+						if (!ast_strlen_zero(mf_wink)) {
+							ast_verb(3,
+								"Sending MF '%s' to %s as result of "
+								"receiving a WINK message.\n",
+								mf_wink, (hearpulsing ? "parties" : "called party"));
+							ast_mf_stream(c, (hearpulsing ? NULL : in),
+							(hearpulsing ? in : NULL), mf_wink, 50, 55, 120, 65, 0);
+						}
+						if (!ast_strlen_zero(sf_wink)) {
+							ast_verb(3,
+								"Sending SF '%s' to %s as result of "
+								"receiving a WINK message.\n",
+								sf_wink, (hearpulsing ? "parties" : "called party"));
+							ast_sf_stream(c, (hearpulsing ? NULL : in),
+							(hearpulsing ? in : NULL), sf_wink, 0, 0);
+						}
+					}
+					break;
 				case AST_CONTROL_VIDUPDATE:
 				case AST_CONTROL_SRCUPDATE:
 				case AST_CONTROL_SRCCHANGE:
@@ -2130,9 +2182,7 @@
 			/* the file doesn't exist yet. Let the caller submit his
 			   vocal intro for posterity */
 			/* priv-recordintro script:
-
 			   "At the tone, please say your name:"
-
 			*/
 			int silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
 			ast_answer(chan);
@@ -2249,7 +2299,8 @@
 
 	struct ast_bridge_config config = { { 0, } };
 	struct timeval calldurationlimit = { 0, };
-	char *dtmfcalled = NULL, *dtmfcalling = NULL, *dtmf_progress=NULL;
+	char *dtmfcalled = NULL, *dtmfcalling = NULL, *dtmf_progress = NULL;
+	char *mf_progress = NULL, *mf_wink = NULL, *sf_progress = NULL, *sf_wink = NULL;
 	struct privacy_args pa = {
 		.sentringing = 0,
 		.privdb_val = 0,
@@ -2384,9 +2435,13 @@
 	}
 
 	if (ast_test_flag64(&opts, OPT_SENDDTMF) && !ast_strlen_zero(opt_args[OPT_ARG_SENDDTMF])) {
-		dtmf_progress = opt_args[OPT_ARG_SENDDTMF];
-		dtmfcalled = strsep(&dtmf_progress, ":");
-		dtmfcalling = strsep(&dtmf_progress, ":");
+		sf_wink = opt_args[OPT_ARG_SENDDTMF];
+		dtmfcalled = strsep(&sf_wink, ":");
+		dtmfcalling = strsep(&sf_wink, ":");
+		dtmf_progress = strsep(&sf_wink, ":");
+		mf_progress = strsep(&sf_wink, ":");
+		mf_wink = strsep(&sf_wink, ":");
+		sf_progress = strsep(&sf_wink, ":");
 	}
 
 	if (ast_test_flag64(&opts, OPT_DURATION_LIMIT) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_LIMIT])) {
@@ -2863,7 +2918,8 @@
 	}
 
 	peer = wait_for_answer(chan, &out_chans, &to, peerflags, opt_args, &pa, &num, &result,
-		dtmf_progress, ignore_cc, &forced_clid, &stored_clid, &config);
+		dtmf_progress, mf_progress, mf_wink, sf_progress, sf_wink, (ast_test_flag64(&opts, OPT_HEARPULSING) ? 1 : 0),
+		ignore_cc, &forced_clid, &stored_clid, &config);
 
 	if (!peer) {
 		if (result) {
@@ -3536,4 +3592,4 @@
 	.load = load_module,
 	.unload = unload_module,
 	.requires = "ccss",
-);
+);
\ No newline at end of file
diff --git a/apps/app_mf.c b/apps/app_mf.c
new file mode 100644
index 0000000..d68f180
--- /dev/null
+++ b/apps/app_mf.c
@@ -0,0 +1,839 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Naveen Albert
+ *
+ * Naveen Albert <asterisk at phreaknet.org>
+ *
+ * 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 MF sender and receiver applications
+ *
+ * \author Naveen Albert <asterisk at phreaknet.org>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/file.h"
+#include "asterisk/pbx.h"
+#include "asterisk/channel.h"
+#include "asterisk/dsp.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+#include "asterisk/indications.h"
+#include "asterisk/conversions.h"
+
+/*** DOCUMENTATION
+	<application name="ReceiveSF" language="en_US">
+		<synopsis>
+			Detects SF digits on a channel and saves them to a variable.
+		</synopsis>
+		<syntax>
+			<parameter name="variable" required="true">
+				<para>The input digits will be stored in the given
+				<replaceable>variable</replaceable> name.</para>
+			</parameter>
+			<parameter name="digits" required="false">
+				<para>Maximum number of digits to read. Default is unlimited.</para>
+			</parameter>
+			<parameter name="timeout">
+				<para>The number of seconds to wait for all digits, if greater
+				than <literal>0</literal>. Can be floating point. Default
+				is no timeout.</para>
+			</parameter>
+			<parameter name="frequency">
+				<para>The frequency for which to detect pulsed digits.
+				Default is 2600 Hz.</para>
+			</parameter>
+			<parameter name="options">
+				<optionlist>
+					<option name="d">
+						<para>Delay audio by a frame to try to extra quelch.</para>
+					</option>
+					<option name="e">
+						<para>Allow receiving extra pulses 11 through 16.</para>
+					</option>
+					<option name="m">
+						<para>Mute conference.</para>
+					</option>
+					<option name="q">
+						<para>Quelch SF from in-band.</para>
+					</option>
+					<option name="r">
+						<para>"Radio" mode (relaxed SF).</para>
+					</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Reads SF digits from the user in to the given
+			<replaceable>variable</replaceable>.</para>
+			<para>This application does not automatically answer the channel and
+			should be preceded with <literal>Answer</literal> or
+			<literal>Progress</literal> as needed.</para>
+			<variablelist>
+				<variable name="RECEIVESFSTATUS">
+					<para>This is the status of the read operation.</para>
+					<value name="START" />
+					<value name="ERROR" />
+					<value name="HANGUP" />
+					<value name="TIMEOUT" />
+				</variable>
+			</variablelist>
+		</description>
+		<see-also>
+			<ref type="application">ReceiveMF</ref>
+			<ref type="application">SendMF</ref>
+			<ref type="application">Read</ref>
+		</see-also>
+	</application>
+	<application name="ReceiveMF" language="en_US">
+		<synopsis>
+			Detects MF digits on a channel and saves them to a variable.
+		</synopsis>
+		<syntax>
+			<parameter name="variable" required="true">
+				<para>The input digits will be stored in the given
+				<replaceable>variable</replaceable> name.</para>
+			</parameter>
+			<parameter name="timeout">
+				<para>The number of seconds to wait for all digits, if greater
+				than <literal>0</literal>. Can be floating point. Default
+				is no timeout.</para>
+			</parameter>
+			<parameter name="options">
+				<optionlist>
+					<option name="d">
+						<para>Delay audio by a frame to try to extra quelch.</para>
+					</option>
+					<option name="l">
+						<para>Receive digits even if a key pulse (KP) has not yet
+						been received. By default, this application will ignore
+						all other digits until a KP has been received.</para>
+					</option>
+					<option name="k">
+						<para>Do not return a character for the KP digit.</para>
+					</option>
+					<option name="m">
+						<para>Mute conference.</para>
+					</option>
+					<option name="o">
+						<para>Enable override. Repeated KPs will clear all previous digits.</para>
+					</option>
+					<option name="q">
+						<para>Quelch MF from in-band.</para>
+					</option>
+					<option name="r">
+						<para>"Radio" mode (relaxed MF).</para>
+					</option>
+					<option name="s">
+						<para>Do not return a character for ST digits.</para>
+					</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Reads a ST, STP, ST2P, or ST3P-terminated string of MF digits from
+			the user in to the given <replaceable>variable</replaceable>.</para>
+			<para>This application does not automatically answer the channel and
+			should be preceded with <literal>Answer</literal> or
+			<literal>Progress</literal> as needed.</para>
+			<variablelist>
+				<variable name="RECEIVEMFSTATUS">
+					<para>This is the status of the read operation.</para>
+					<value name="START" />
+					<value name="ERROR" />
+					<value name="HANGUP" />
+					<value name="TIMEOUT" />
+				</variable>
+			</variablelist>
+		</description>
+		<see-also>
+			<ref type="application">Read</ref>
+			<ref type="application">ReceiveSF</ref>
+			<ref type="application">SendMF</ref>
+			<ref type="application">SendSF</ref>
+		</see-also>
+	</application>
+	<application name="SendSF" language="en_US">
+		<synopsis>
+			Sends arbitrary SF digits on the current or specified channel.
+		</synopsis>
+		<syntax>
+			<parameter name="digits" required="true">
+				<para>List of digits 0-9 to send; also f or F for a flash-hook
+				if the channel supports flash-hook, and w or W for a wink if the channel
+				supports wink.</para>
+			</parameter>
+			<parameter name="frequency" required="false">
+				<para>Frequency to use. (defaults to 2600 Hz).</para>
+			</parameter>
+			<parameter name="channel" required="false">
+				<para>Channel where digits will be played</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>It will send all digits or terminate if it encounters an error.</para>
+		</description>
+		<see-also>
+			<ref type="application">SendDTMF</ref>
+			<ref type="application">SendMF</ref>
+			<ref type="application">ReceiveMF</ref>
+			<ref type="application">ReceiveSF</ref>
+		</see-also>
+	</application>
+	<application name="SendMF" language="en_US">
+		<synopsis>
+			Sends arbitrary MF digits on the current or specified channel.
+		</synopsis>
+		<syntax>
+			<parameter name="digits" required="true">
+				<para>List of digits 0-9,*#ABC to send; also f or F for a flash-hook
+				if the channel supports flash-hook, and w or W for a wink if the channel
+				supports wink.</para>
+				<para>Key pulse and start digits are not included automatically.
+				* is used for KP, # for ST, A for STP, B for ST2P, and C for ST3P.</para>
+			</parameter>
+			<parameter name="timeout_ms" required="false">
+				<para>Amount of time to wait in ms between tones. (defaults to 50ms).</para>
+			</parameter>
+			<parameter name="duration_ms" required="false">
+				<para>Duration of each numeric digit (defaults to 55ms).</para>
+			</parameter>
+			<parameter name="duration_ms_kp" required="false">
+				<para>Duration of KP digits (defaults to 120ms).</para>
+			</parameter>
+			<parameter name="duration_ms_st" required="false">
+				<para>Duration of ST, STP, ST2P, and ST3P digits (defaults to 65ms).</para>
+			</parameter>
+			<parameter name="channel" required="false">
+				<para>Channel where digits will be played</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>It will send all digits or terminate if it encounters an error.</para>
+		</description>
+		<see-also>
+			<ref type="application">ReceiveMF</ref>
+			<ref type="application">SendDTMF</ref>
+			<ref type="application">SendMF</ref>
+		</see-also>
+	</application>
+	<manager name="PlayMF" language="en_US">
+		<synopsis>
+			Play MF digit on a specific channel.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Channel" required="true">
+				<para>Channel name to send digit to.</para>
+			</parameter>
+			<parameter name="Digit" required="true">
+				<para>The MF digit to play.</para>
+			</parameter>
+			<parameter name="Duration" required="false">
+				<para>The duration, in milliseconds, of the digit to be played.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Plays an MF digit on the specified channel.</para>
+		</description>
+	</manager>
+ ***/
+
+enum read_option_flags {
+	OPT_DELAY = (1 << 0),
+	OPT_MUTE = (1 << 1),
+	OPT_QUELCH = (1 << 2),
+	OPT_RELAXED = (1 << 3),
+	OPT_LAX_KP = (1 << 4),
+	OPT_PROCESS = (1 << 5),
+	OPT_NO_KP = (1 << 6),
+	OPT_NO_ST = (1 << 7),
+	OPT_KP_OVERRIDE = (1 << 8),
+	OPT_EXTRAPULSES = (1 << 9),
+};
+
+AST_APP_OPTIONS(read_app_options, {
+	AST_APP_OPTION('d', OPT_DELAY),
+	AST_APP_OPTION('e', OPT_EXTRAPULSES), /* SF only */
+	AST_APP_OPTION('l', OPT_LAX_KP), /* MF only */
+	AST_APP_OPTION('k', OPT_NO_KP), /* MF only */
+	AST_APP_OPTION('m', OPT_MUTE),
+	AST_APP_OPTION('o', OPT_KP_OVERRIDE), /* MF only */
+	AST_APP_OPTION('p', OPT_PROCESS), /* MF only */
+	AST_APP_OPTION('q', OPT_QUELCH),
+	AST_APP_OPTION('r', OPT_RELAXED),
+	AST_APP_OPTION('s', OPT_NO_ST), /* MF only */
+});
+
+static const char *readmf_name = "ReceiveMF";
+static const char *readsf_name = "ReceiveSF";
+static const char sendmf_name[] = "SendMF";
+static const char sendsf_name[] = "SendSF";
+
+#define MF_BETWEEN_MS 50
+#define MF_DURATION 55
+#define MF_KP_DURATION 120
+#define MF_ST_DURATION 65
+
+/*!
+ * \brief Detects MF digits on channel using DSP, terminated by ST, STP, ST2P, or ST3P
+ * \return 0 if successful, -1 if unsuccessful.
+ * \param chan channel on which to read digits
+ * \param str buffer in which to store digits
+ * \param timeout ms to wait for all digits before giving up
+ */
+static int read_mf_digits(struct ast_channel *chan, char *buf, int timeout, int features, int laxkp, int override, int no_kp, int no_st) {
+	struct ast_dsp *dsp;
+	struct ast_frame *frame = NULL;
+	struct timeval start;
+	int remaining_time = timeout;
+	int digits_read = 0;
+	int is_start_digit = 0;
+	char *str = buf;
+
+	if (!(dsp = ast_dsp_new())) {
+		ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
+		pbx_builtin_setvar_helper(chan, "RECEIVEMFSTATUS", "ERROR");
+		return -1;
+	}
+	ast_dsp_set_features(dsp, DSP_FEATURE_DIGIT_DETECT);
+	ast_dsp_set_digitmode(dsp, DSP_DIGITMODE_MF | features);
+
+	start = ast_tvnow();
+	*str = 0; /* start with empty output buffer */
+
+	/* based on app_read and generic_fax_exec from res_fax */
+	while (timeout == 0 || remaining_time > 0) {
+		if (timeout > 0) {
+			remaining_time = ast_remaining_ms(start, timeout);
+			if (remaining_time <= 0) {
+				pbx_builtin_setvar_helper(chan, "RECEIVEMFSTATUS", "TIMEOUT");
+				break;
+			}
+		}
+		/* ast_waitfordigit only waits for DTMF frames, we need to do DSP on voice frames */
+		if (ast_waitfor(chan, 1000) > 0) {
+			frame = ast_read(chan);
+			if (!frame) {
+				ast_debug(1, "Channel '%s' did not return a frame; probably hung up.\n", ast_channel_name(chan));
+				pbx_builtin_setvar_helper(chan, "RECEIVEMFSTATUS", "HANGUP");
+				break;
+			} else if (frame->frametype == AST_FRAME_VOICE) {
+				frame = ast_dsp_process(chan, dsp, frame);
+				/* AST_FRAME_DTMF is used all over the DSP code for DTMF, MF, fax, etc.
+					It's used because we can use the frame to store the digit detected.
+					All this means is that we received something we care about. */
+				if (frame->frametype == AST_FRAME_DTMF) {
+					char result = frame->subclass.integer;
+					if (digits_read == 0 && !laxkp && result != '*') {
+						ast_debug(1, "Received MF digit, but no KP yet, ignoring: %c\n", result);
+						continue;
+					}
+					ast_debug(1, "Received MF digit: %c\n", result);
+					if (result == '*') {
+						/* We received an additional KP, start over? */
+						if (override && digits_read > 0) {
+							ast_debug(1, "Received another KP, starting over\n");
+							str = buf;
+							*str = 0;
+							digits_read = 1; /* we just detected a KP */
+						} else {
+							digits_read++;
+						}
+						/* if we were told not to include the KP digit in the output string, then skip it */
+						if (no_kp) {
+							continue;
+						}
+					} else {
+						digits_read++;
+					}
+					is_start_digit = (strchr("#", result) || strchr("A", result) || strchr("B", result) || strchr("C", result));
+					/* if we were told not to include the ST digit in the output string, then skip it */
+					if (!no_st || !is_start_digit) {
+						*str++ = result;
+						*str = 0;
+					}
+					/* we received a ST digit (ST, STP, ST2P, or ST3P), so we're done */
+					if (is_start_digit) {
+						pbx_builtin_setvar_helper(chan, "RECEIVEMFSTATUS", "START");
+						break;
+					}
+				}
+			}
+		} else {
+			pbx_builtin_setvar_helper(chan, "RECEIVEMFSTATUS", "HANGUP");
+		}
+	}
+	ast_dsp_free(dsp);
+	ast_debug(3, "channel '%s' - event loop stopped { timeout: %d, remaining_time: %d }\n", ast_channel_name(chan), timeout, remaining_time);
+	return 0;
+}
+
+static int read_sf_digits(struct ast_channel *chan, char *buf, 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
+
+	struct ast_dsp *dsp = NULL;
+	struct ast_frame *frame = NULL;
+	struct timeval start, pulsetimer, digittimer;
+	int remaining_time = timeout;
+	char *str = buf;
+	int hits = 0, digits_read = 0;
+	unsigned short int sf_on = 0;
+
+	if (!(dsp = ast_dsp_new())) {
+		ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
+		pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "ERROR");
+		return -1;
+	}
+	ast_dsp_set_features(dsp, DSP_FEATURE_FREQ_DETECT);
+	/* tolerance is 46 to 76% make break at 8 to 12 pps */
+	ast_dsp_set_freqmode(dsp, freq, SF_MIN_DETECT, 16, 0);
+
+	start = ast_tvnow();
+	*str = 0; /* start with empty output buffer */
+
+	while (timeout == 0 || remaining_time > 0) {
+		if (timeout > 0) {
+			remaining_time = ast_remaining_ms(start, timeout);
+			if (remaining_time <= 0) {
+				pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "TIMEOUT");
+				break;
+			}
+		}
+		if (ast_waitfor(chan, 1000) > 0) {
+			frame = ast_read(chan);
+			if (!frame) {
+				ast_debug(1, "Channel '%s' did not return a frame; probably hung up.\n", ast_channel_name(chan));
+				pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "HANGUP");
+				break;
+			} else if (frame->frametype == AST_FRAME_VOICE) {
+				frame = ast_dsp_process(chan, dsp, frame);
+				if (frame->frametype == AST_FRAME_DTMF) {
+					char result = frame->subclass.integer;
+					if (result == 'q') {
+						sf_on = 1;
+						pulsetimer = ast_tvnow(); /* reset the pulse timer */
+						/* now, we need at least a 33ms pause to register the pulse */
+					}
+				} else {
+					if (sf_on) {
+						int timeleft = ast_remaining_ms(pulsetimer, SF_MIN_OFF);
+						if (timeleft <= 0) {
+							sf_on = 0;
+							/* The pulse needs to end no more than 30ms after we detected it */
+							if (timeleft > -30) {
+								hits++;
+								digittimer = ast_tvnow(); /* reset the digit timer */
+								ast_debug(5, "Detected SF pulse (pulse #%d)\n", hits);
+								if (!(dsp = ast_dsp_new())) {
+									ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
+									pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "ERROR");
+									return -1;
+								}
+								ast_dsp_set_features(dsp, DSP_FEATURE_FREQ_DETECT);
+								ast_dsp_set_freqmode(dsp, freq, SF_MIN_DETECT, 16, 0);
+							} else {
+								ast_debug(5, "SF noise, ignoring, time elapsed was %d ms\n", timeleft);
+							}
+						}
+					} else if (hits > 0 && ast_remaining_ms(digittimer, SF_BETWEEN) <= 0) {
+						/* has the digit finished? */
+						ast_debug(2, "Received SF digit: %d\n", hits);
+						digits_read++;
+						if (hits > 10) {
+							if (extrapulses) {
+								/* dahdi-base.c translates 11 to * and 12 to # */
+								if (hits == 11) {
+									hits = '*';
+								} else if (hits == 12) {
+									hits = '#';
+								} else if (hits == 13) {
+									hits = 'D';
+								} else if (hits == 14) {
+									hits = 'C';
+								} else if (hits == 15) {
+									hits = 'B';
+								} else if (hits == 16) {
+									hits = 'A';
+								} else {
+									ast_debug(3, "Got %d SF pulses, is someone playing with the phone?\n", hits);
+									hits = 'A';
+								}
+								*str++ = hits;
+							} else {
+								ast_debug(2, "Got more than 10 pulses, truncating to 10\n");
+								hits = 0; /* 10 dial pulses = digit 0 */
+								*str++ = hits + '0';
+							}
+						} else {
+							if (hits == 10) {
+								hits = 0; /* 10 dial pulses = digit 0 */
+							}
+							*str++ = hits + '0';
+						}
+						*str = 0;
+						hits = 0;
+						if (maxdigits > 0 && digits_read >= maxdigits) {
+							pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "START");
+							break;
+						}
+					}
+				}
+			}
+		} else {
+			pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "HANGUP");
+		}
+	}
+	if (dsp) {
+		ast_dsp_free(dsp);
+	}
+	ast_debug(3, "channel '%s' - event loop stopped { timeout: %d, remaining_time: %d }\n", ast_channel_name(chan), timeout, remaining_time);
+	return 0;
+}
+
+static int read_sf_exec(struct ast_channel *chan, const char *data)
+{
+	char tmp[256] = "";
+	double tosec;
+	struct ast_flags flags = {0};
+	char *argcopy = NULL;
+	int features = 0, digits = 0, to = 0, freq = 2600;
+
+	AST_DECLARE_APP_ARGS(arglist,
+		AST_APP_ARG(variable);
+		AST_APP_ARG(digits);
+		AST_APP_ARG(timeout);
+		AST_APP_ARG(freq);
+		AST_APP_ARG(options);
+	);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "ReceiveSF requires an argument (variable)\n");
+		return -1;
+	}
+
+	argcopy = ast_strdupa(data);
+
+	AST_STANDARD_APP_ARGS(arglist, argcopy);
+
+	if (!ast_strlen_zero(arglist.options)) {
+		ast_app_parse_options(read_app_options, &flags, NULL, arglist.options);
+	}
+
+	if (!ast_strlen_zero(arglist.timeout)) {
+		tosec = atof(arglist.timeout);
+		if (tosec <= 0) {
+			to = 0;
+		} else {
+			to = tosec * 1000.0;
+		}
+	}
+
+	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);
+		return -1;
+	}
+
+	if (!ast_strlen_zero(arglist.freq) && (ast_str_to_int(arglist.freq, &freq) || freq <= 0)) {
+		ast_log(LOG_WARNING, "Invalid freq: %s\n", arglist.freq);
+		return -1;
+	}
+
+	if (ast_strlen_zero(arglist.variable)) {
+		ast_log(LOG_WARNING, "Invalid! Usage: ReceiveSF(variable[,timeout][,option])\n");
+		return -1;
+	}
+
+	if (ast_test_flag(&flags, OPT_DELAY)) {
+		features |= DSP_DIGITMODE_MUTEMAX;
+	}
+
+	if (ast_test_flag(&flags, OPT_MUTE)) {
+		features |= DSP_DIGITMODE_MUTECONF;
+	}
+
+	if (!ast_test_flag(&flags, OPT_QUELCH)) {
+		features |= DSP_DIGITMODE_NOQUELCH;
+	}
+
+	if (ast_test_flag(&flags, OPT_RELAXED)) {
+		features |= DSP_DIGITMODE_RELAXDTMF;
+	}
+
+	read_sf_digits(chan, tmp, to, 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, "MF digits received: '%s'\n", tmp);
+	} else {
+		ast_verb(3, "No MF digits received.\n");
+	}
+	return 0;
+}
+
+static int read_mf_exec(struct ast_channel *chan, const char *data)
+{
+	char tmp[256] = "";
+	int to = 0;
+	double tosec;
+	struct ast_flags flags = {0};
+	char *argcopy = NULL;
+	int features = 0;
+
+	AST_DECLARE_APP_ARGS(arglist,
+		AST_APP_ARG(variable);
+		AST_APP_ARG(timeout);
+		AST_APP_ARG(options);
+	);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "ReceiveMF requires an argument (variable)\n");
+		return -1;
+	}
+
+	argcopy = ast_strdupa(data);
+
+	AST_STANDARD_APP_ARGS(arglist, argcopy);
+
+	if (!ast_strlen_zero(arglist.options)) {
+		ast_app_parse_options(read_app_options, &flags, NULL, arglist.options);
+	}
+
+	if (!ast_strlen_zero(arglist.timeout)) {
+		tosec = atof(arglist.timeout);
+		if (tosec <= 0) {
+			to = 0;
+		} else {
+			to = tosec * 1000.0;
+		}
+	}
+
+	if (ast_strlen_zero(arglist.variable)) {
+		ast_log(LOG_WARNING, "Invalid! Usage: ReceiveMF(variable[,timeout][,option])\n");
+		return -1;
+	}
+
+	if (ast_test_flag(&flags, OPT_DELAY)) {
+		features |= DSP_DIGITMODE_MUTEMAX;
+	}
+
+	if (ast_test_flag(&flags, OPT_MUTE)) {
+		features |= DSP_DIGITMODE_MUTECONF;
+	}
+
+	if (!ast_test_flag(&flags, OPT_QUELCH)) {
+		features |= DSP_DIGITMODE_NOQUELCH;
+	}
+
+	if (ast_test_flag(&flags, OPT_RELAXED)) {
+		features |= DSP_DIGITMODE_RELAXDTMF;
+	}
+
+	read_mf_digits(chan, tmp, to, features, (ast_test_flag(&flags, OPT_LAX_KP)),
+		(ast_test_flag(&flags, OPT_KP_OVERRIDE)), (ast_test_flag(&flags, OPT_NO_KP)), (ast_test_flag(&flags, OPT_NO_ST)));
+	pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
+	if (!ast_strlen_zero(tmp)) {
+		ast_verb(3, "MF digits received: '%s'\n", tmp);
+	} else {
+		ast_verb(3, "No MF digits received.\n");
+	}
+	return 0;
+}
+
+static int sendsf_exec(struct ast_channel *chan, const char *vdata)
+{
+	int res;
+	char *data;
+	int frequency = 2600;
+	struct ast_channel *chan_found = NULL;
+	struct ast_channel *chan_dest = chan;
+	struct ast_channel *chan_autoservice = NULL;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(digits);
+		AST_APP_ARG(frequency);
+		AST_APP_ARG(channel);
+	);
+
+	if (ast_strlen_zero(vdata)) {
+		ast_log(LOG_WARNING, "SendSF requires an argument\n");
+		return 0;
+	}
+
+	data = ast_strdupa(vdata);
+	AST_STANDARD_APP_ARGS(args, data);
+
+	if (ast_strlen_zero(args.digits)) {
+		ast_log(LOG_WARNING, "The digits argument is required (0-9,wf)\n");
+		return 0;
+	}
+	if (!ast_strlen_zero(args.frequency) && (ast_str_to_int(args.frequency, &frequency) || frequency < 1)) {
+		ast_log(LOG_WARNING, "Invalid duration: %s\n", args.frequency);
+		return -1;
+	}
+	if (!ast_strlen_zero(args.channel)) {
+		chan_found = ast_channel_get_by_name(args.channel);
+		if (!chan_found) {
+			ast_log(LOG_WARNING, "No such channel: %s\n", args.channel);
+			return 0;
+		}
+		chan_dest = chan_found;
+		if (chan_found != chan) {
+			chan_autoservice = chan;
+		}
+	}
+	res = ast_sf_stream(chan_dest, chan_autoservice, NULL, args.digits, frequency, 0);
+	ast_channel_cleanup(chan_found);
+
+	return chan_autoservice ? 0 : res;
+}
+
+static int sendmf_exec(struct ast_channel *chan, const char *vdata)
+{
+	int res;
+	char *data;
+	int dinterval = 0, duration = 0, durationkp = 0, durationst = 0;
+	struct ast_channel *chan_found = NULL;
+	struct ast_channel *chan_dest = chan;
+	struct ast_channel *chan_autoservice = NULL;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(digits);
+		AST_APP_ARG(dinterval);
+		AST_APP_ARG(duration);
+		AST_APP_ARG(durationkp);
+		AST_APP_ARG(durationst);
+		AST_APP_ARG(channel);
+	);
+
+	if (ast_strlen_zero(vdata)) {
+		ast_log(LOG_WARNING, "SendMF requires an argument\n");
+		return 0;
+	}
+
+	data = ast_strdupa(vdata);
+	AST_STANDARD_APP_ARGS(args, data);
+
+	if (ast_strlen_zero(args.digits)) {
+		ast_log(LOG_WARNING, "The digits argument is required (0-9,*#ABC,wf)\n");
+		return 0;
+	}
+	if (!ast_strlen_zero(args.dinterval)) {
+		ast_app_parse_timelen(args.dinterval, &dinterval, TIMELEN_MILLISECONDS);
+	}
+	if (!ast_strlen_zero(args.duration)) {
+		ast_app_parse_timelen(args.duration, &duration, TIMELEN_MILLISECONDS);
+	}
+	if (!ast_strlen_zero(args.durationkp)) {
+		ast_app_parse_timelen(args.durationkp, &durationkp, TIMELEN_MILLISECONDS);
+	}
+	if (!ast_strlen_zero(args.durationst)) {
+		ast_app_parse_timelen(args.durationst, &durationst, TIMELEN_MILLISECONDS);
+	}
+	if (!ast_strlen_zero(args.channel)) {
+		chan_found = ast_channel_get_by_name(args.channel);
+		if (!chan_found) {
+			ast_log(LOG_WARNING, "No such channel: %s\n", args.channel);
+			return 0;
+		}
+		chan_dest = chan_found;
+		if (chan_found != chan) {
+			chan_autoservice = chan;
+		}
+	}
+	res = ast_mf_stream(chan_dest, chan_autoservice, NULL, args.digits, dinterval <= 0 ? MF_BETWEEN_MS : dinterval,
+		duration <= 0 ? MF_DURATION : duration, durationkp <= 0 ? MF_KP_DURATION : durationkp,
+		durationst <= 0 ? MF_ST_DURATION : durationst, 0);
+	ast_channel_cleanup(chan_found);
+
+	return chan_autoservice ? 0 : res;
+}
+
+static int manager_play_mf(struct mansession *s, const struct message *m)
+{
+	const char *channel = astman_get_header(m, "Channel");
+	const char *digit = astman_get_header(m, "Digit");
+	const char *duration = astman_get_header(m, "Duration");
+	struct ast_channel *chan;
+	unsigned int duration_ms = MF_DURATION;
+
+	if (!(chan = ast_channel_get_by_name(channel))) {
+		astman_send_error(s, m, "Channel not found");
+		return 0;
+	}
+
+	if (ast_strlen_zero(digit)) {
+		astman_send_error(s, m, "No digit specified");
+		chan = ast_channel_unref(chan);
+		return 0;
+	}
+
+	/* Override default duration with KP or ST-specific default durations */
+	if (!strcmp(digit, "*"))
+		duration_ms = MF_KP_DURATION;
+
+	if (!strcmp(digit, "#") || !strcmp(digit, "A") || !strcmp(digit, "B") || !strcmp(digit, "C"))
+		duration_ms = MF_ST_DURATION;
+
+	if (!ast_strlen_zero(duration) && (sscanf(duration, "%30u", &duration_ms) != 1)) {
+		astman_send_error(s, m, "Could not convert Duration parameter");
+		chan = ast_channel_unref(chan);
+		return 0;
+	}
+
+	ast_mf_stream(chan, NULL, NULL, digit, 0, duration_ms, duration_ms, duration_ms, 1);
+
+	chan = ast_channel_unref(chan);
+
+	astman_send_ack(s, m, "MF successfully queued");
+
+	return 0;
+}
+
+static int unload_module(void)
+{
+	int res;
+
+	res = ast_unregister_application(readsf_name);
+	res |= ast_unregister_application(readmf_name);
+	res |= ast_unregister_application(sendmf_name);
+	res |= ast_unregister_application(sendsf_name);
+	res |= ast_manager_unregister("PlayMF");
+
+	return res;
+}
+
+static int load_module(void)
+{
+	int res;
+
+	res = ast_register_application_xml(readsf_name, read_sf_exec);
+	res |= ast_register_application_xml(readmf_name, read_mf_exec);
+	res |= ast_register_application_xml(sendmf_name, sendmf_exec);
+	res |= ast_register_application_xml(sendsf_name, sendsf_exec);
+	res |= ast_manager_register_xml("PlayMF", EVENT_FLAG_CALL, manager_play_mf);
+
+	return res;
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "MF Sender and Receiver Applications");
diff --git a/doc/CHANGES-staging/app_mf_full.txt b/doc/CHANGES-staging/app_mf_full.txt
new file mode 100644
index 0000000..256b09b
--- /dev/null
+++ b/doc/CHANGES-staging/app_mf_full.txt
@@ -0,0 +1,4 @@
+Subject: app_mf
+
+Adds applications to send and receive MF and SF signaling,
+along with integration in the Dial application.
diff --git a/include/asterisk/app.h b/include/asterisk/app.h
index 7690364..5a8ca86 100644
--- a/include/asterisk/app.h
+++ b/include/asterisk/app.h
@@ -923,6 +923,67 @@
 void ast_unreplace_sigchld(void);
 
 /*!
+ * \brief Send a string of SF digits to a channel
+ *
+ * \param chan    The channel that will receive the SF digits
+ * \param peer    (optional) Peer channel that will be autoserviced while the
+ *                primary channel is receiving SF
+ * \param chan2   A second channel that will simultaneously receive SF digits.
+ *                This option may only be used if is_external is 0.
+ * \param digits  This is a string of characters representing the SF digits
+ *                to be sent to the channel.  Valid characters are
+ *                "0123456789".  Note: You can pass arguments 'f' or
+ *                'F', if you want to Flash the channel (if supported by the
+ *                channel), or 'w' or 'W' to add a wink (if supported by the
+ *                channel).
+ * \param between This is the number of milliseconds to wait in between each
+ *                SF digit.  If zero milliseconds is specified, then the
+ *                default value of 50 will be used.
+ * \param duration This is the duration that each numeric SF digit should have.
+ *                 Default value is 55.
+ * \param is_external 1 if called by a thread that is not the channel's media
+ *                handler thread, 0 if called by the channel's media handler
+ *                thread.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure or a channel hung up.
+ */
+int ast_sf_stream(struct ast_channel *chan, struct ast_channel *peer, struct ast_channel *chan2, const char *digits, int frequency, int is_external);
+
+/*!
+ * \brief Send a string of MF digits to a channel
+ *
+ * \param chan    The channel that will receive the MF digits.
+ * \param peer    (optional) Peer channel that will be autoserviced while the
+ *                primary channel is receiving MF
+ * \param chan2   A second channel that will simultaneously receive MF digits.
+ *                This option may only be used if is_external is 0.
+ * \param digits  This is a string of characters representing the MF digits
+ *                to be sent to the channel.  Valid characters are
+ *                "0123456789*#abcdABCD".  Note: You can pass arguments 'f' or
+ *                'F', if you want to Flash the channel (if supported by the
+ *                channel), or 'w' or 'W' to add a wink (if supported by the
+ *                channel).
+ * \param between This is the number of milliseconds to wait in between each
+ *                MF digit.  If zero milliseconds is specified, then the
+ *                default value of 50 will be used.
+ * \param duration This is the duration that each numeric MF digit should have.
+ *                 Default value is 55.
+ * \param durationkp This is the duration that each KP digit should have. Default
+ *                is 120.
+ * \param durationst This is the duration that each ST, STP, ST2P, or ST3P digit
+ *                should have. Default is 65.
+ * \param is_external 1 if called by a thread that is not the channel's media
+ *                handler thread, 0 if called by the channel's media handler
+ *                thread.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure or a channel hung up.
+ */
+int ast_mf_stream(struct ast_channel *chan, struct ast_channel *peer, struct ast_channel *chan2, const char *digits,
+	int between, unsigned int duration, unsigned int durationkp, unsigned int durationst, int is_external);
+
+/*!
  * \brief Send a string of DTMF digits to a channel
  *
  * \param chan    The channel that will receive the DTMF frames
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index b699c74..4a08699 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -2255,6 +2255,30 @@
 int ast_recvchar(struct ast_channel *chan, int timeout);
 
 /*!
+ * \brief End sending an MF digit to a channel.
+ * \param chan channel to act upon
+ * \return Returns 0 on success, -1 on failure
+ */
+int ast_senddigit_mf_end(struct ast_channel *chan);
+
+/*!
+ * \brief Send an MF digit to a channel.
+ *
+ * \param chan channel to act upon
+ * \param digit the MF digit to send, encoded in ASCII
+ * \param duration the duration of a numeric digit ending in ms
+ * \param duration the duration of a KP digit ending in ms
+ * \param duration the duration of a ST, STP, ST2P, or ST3P digit ending in ms
+ * \param is_external 1 if called by a thread that is not the channel's media
+ *                handler thread, 0 if called by the channel's media handler
+ *                thread.
+ *
+ * \return 0 on success, -1 on failure
+ */
+int ast_senddigit_mf(struct ast_channel *chan, char digit, unsigned int duration,
+	unsigned int durationkp, unsigned int durationst, int is_external);
+
+/*!
  * \brief Send a DTMF digit to a channel.
  *
  * \param chan channel to act upon
@@ -2282,6 +2306,14 @@
 int ast_senddigit_external(struct ast_channel *chan, char digit, unsigned int duration);
 
 /*!
+ * \brief Send an MF digit to a channel.
+ * \param chan channel to act upon
+ * \param digit the MF digit to send, encoded in ASCII
+ * \return 0 on success, -1 on failure
+ */
+int ast_senddigit_mf_begin(struct ast_channel *chan, char digit);
+
+/*!
  * \brief Send a DTMF digit to a channel.
  * \param chan channel to act upon
  * \param digit the DTMF digit to send, encoded in ASCII
diff --git a/main/app.c b/main/app.c
index 09c0123..b1cd077 100644
--- a/main/app.c
+++ b/main/app.c
@@ -833,6 +833,208 @@
 	return 0;
 }
 
+static int sf_stream(struct ast_channel *chan, struct ast_channel *chan2, const char *digits, int frequency, int is_external)
+{
+	/* Bell System Technical Journal 39 (Nov. 1960) */
+	#define SF_ON 67
+	#define SF_OFF 33
+	#define SF_BETWEEN 600
+
+	const char *ptr;
+	int res;
+	struct ast_silence_generator *silgen = NULL, *silgen2 = NULL;
+	char *freq;
+	int (*my_sleep)(struct ast_channel *chan, int ms);
+
+	if (is_external) {
+		my_sleep = external_sleep;
+	} else {
+		my_sleep = ast_safe_sleep;
+	}
+
+	/* Need a quiet time before sending digits. */
+	if (ast_opt_transmit_silence) {
+		silgen = ast_channel_start_silence_generator(chan);
+		if (chan2) {
+			silgen2 = ast_channel_start_silence_generator(chan2);
+		}
+	}
+	if (chan2) {
+		ast_autoservice_start(chan2);
+	}
+	res = my_sleep(chan, 100);
+	if (chan2) {
+		ast_autoservice_stop(chan2);
+	}
+	if (res) {
+		goto sf_stream_cleanup;
+	}
+
+	freq = ast_malloc(32);
+	/* pauses need to send audio, so send 0 Hz */
+	snprintf(freq, 31, "%d/%d,%d/%d", frequency, SF_ON, 0, SF_OFF);
+
+	for (ptr = digits; *ptr; ptr++) {
+		if (strchr("0123456789*#ABCDabcdwWfF", *ptr)) {
+			if (*ptr == 'f' || *ptr == 'F') {
+				/* ignore return values if not supported by channel */
+				ast_indicate(chan, AST_CONTROL_FLASH);
+			} else if (*ptr == 'w' || *ptr == 'W') {
+				/* ignore return values if not supported by channel */
+				ast_indicate(chan, AST_CONTROL_WINK);
+			} else {
+				/* Character represents valid SF */
+				int beeps;
+				if (*ptr == '*') {
+					beeps = 11;
+				} else if (*ptr == '#') {
+					beeps = 12;
+				} else if (*ptr == 'D') {
+					beeps = 13;
+				} else if (*ptr == 'C') {
+					beeps = 14;
+				} else if (*ptr == 'B') {
+					beeps = 15;
+				} else if (*ptr == 'A') {
+					beeps = 16;
+				} else {
+					beeps = (*ptr == '0') ? 10 : *ptr - '0';
+				}
+				while (beeps-- > 0) {
+					ast_playtones_start(chan, 0, freq, 0);
+					if (chan2) {
+						ast_playtones_start(chan2, 0, freq, 0);
+						ast_autoservice_start(chan2);
+					}
+					res = my_sleep(chan, SF_ON + SF_OFF);
+					ast_senddigit_mf_end(chan);
+					if (chan2) {
+						ast_autoservice_stop(chan2);
+						ast_senddigit_mf_end(chan2);
+					}
+					if (res) {
+						break;
+					}
+				}
+			}
+			/* pause between digits */
+			ast_playtones_start(chan, 0, "0", 0);
+			if (chan2) {
+				ast_playtones_start(chan2, 0, "0", 0);
+				ast_autoservice_start(chan2);
+			}
+			res = my_sleep(chan, SF_BETWEEN);
+			if (chan2) {
+				ast_autoservice_stop(chan2);
+				ast_senddigit_mf_end(chan2);
+			}
+			ast_senddigit_mf_end(chan);
+			if (res) {
+				break;
+			}
+		} else {
+			ast_log(LOG_WARNING, "Illegal SF character '%c' in string. (0-9A-DwWfF allowed)\n", *ptr);
+		}
+	}
+	ast_free(freq);
+
+sf_stream_cleanup:
+	if (silgen) {
+		ast_channel_stop_silence_generator(chan, silgen);
+	}
+	if (silgen2) {
+		ast_channel_stop_silence_generator(chan2, silgen2);
+	}
+
+	return res;
+}
+
+static int mf_stream(struct ast_channel *chan, struct ast_channel *chan2, const char *digits, int between, unsigned int duration,
+	unsigned int durationkp, unsigned int durationst, int is_external)
+{
+	const char *ptr;
+	int res;
+	struct ast_silence_generator *silgen = NULL, *silgen2 = NULL;
+	int (*my_sleep)(struct ast_channel *chan, int ms);
+
+	if (is_external) {
+		my_sleep = external_sleep;
+	} else {
+		my_sleep = ast_safe_sleep;
+	}
+
+	if (!between) {
+		between = 100;
+	}
+
+	/* Need a quiet time before sending digits. */
+	if (ast_opt_transmit_silence) {
+		silgen = ast_channel_start_silence_generator(chan);
+		if (chan2) {
+			silgen2 = ast_channel_start_silence_generator(chan2);
+		}
+	}
+	if (chan2) {
+		ast_autoservice_start(chan2);
+	}
+	res = my_sleep(chan, 100);
+	if (chan2) {
+		ast_autoservice_stop(chan2);
+	}
+	if (res) {
+		goto mf_stream_cleanup;
+	}
+
+	for (ptr = digits; *ptr; ptr++) {
+		if (strchr("0123456789*#ABCwWfF", *ptr)) {
+			if (*ptr == 'f' || *ptr == 'F') {
+				/* ignore return values if not supported by channel */
+				ast_indicate(chan, AST_CONTROL_FLASH);
+			} else if (*ptr == 'w' || *ptr == 'W') {
+				/* ignore return values if not supported by channel */
+				ast_indicate(chan, AST_CONTROL_WINK);
+			} else {
+				/* Character represents valid MF */
+				ast_senddigit_mf(chan, *ptr, duration, durationkp, durationst, is_external);
+				if (chan2) {
+					ast_senddigit_mf(chan2, *ptr, duration, durationkp, durationst, is_external);
+				}
+			}
+			/* pause between digits */
+			/* The DSP code in Asterisk does not currently properly receive repeated tones
+				if no audio is sent in the middle. Simply sending audio (even 0 Hz)
+				works around this limitation and guarantees the correct behavior.
+				*/
+			ast_playtones_start(chan, 0, "0", 0);
+			if (chan2) {
+				ast_playtones_start(chan2, 0, "0", 0);
+				ast_autoservice_start(chan2);
+			}
+			res = my_sleep(chan, between);
+			ast_senddigit_mf_end(chan);
+			if (chan2) {
+				ast_autoservice_stop(chan2);
+				ast_senddigit_mf_end(chan2);
+			}
+			if (res) {
+				break;
+			}
+		} else {
+			ast_log(LOG_WARNING, "Illegal MF character '%c' in string. (0-9*#ABCwWfF allowed)\n", *ptr);
+		}
+	}
+
+mf_stream_cleanup:
+	if (silgen) {
+		ast_channel_stop_silence_generator(chan, silgen);
+	}
+	if (silgen2) {
+		ast_channel_stop_silence_generator(chan2, silgen2);
+	}
+
+	return res;
+}
+
 static int dtmf_stream(struct ast_channel *chan, const char *digits, int between, unsigned int duration, int is_external)
 {
 	const char *ptr;
@@ -901,6 +1103,36 @@
 	return res;
 }
 
+int ast_sf_stream(struct ast_channel *chan, struct ast_channel *peer, struct ast_channel *chan2, const char *digits, int frequency, int is_external)
+{
+	int res;
+	if (frequency <= 0) {
+		frequency = 2600;
+	}
+	if (!is_external && !chan2 && peer && ast_autoservice_start(peer)) {
+		return -1;
+	}
+	res = sf_stream(chan, chan2, digits, frequency, is_external);
+	if (!is_external && !chan2 && peer && ast_autoservice_stop(peer)) {
+		res = -1;
+	}
+	return res;
+}
+
+int ast_mf_stream(struct ast_channel *chan, struct ast_channel *peer, struct ast_channel *chan2, const char *digits,
+	int between, unsigned int duration, unsigned int durationkp, unsigned int durationst, int is_external)
+{
+	int res;
+	if (!is_external && !chan2 && peer && ast_autoservice_start(peer)) {
+		return -1;
+	}
+	res = mf_stream(chan, chan2, digits, between, duration, durationkp, durationst, is_external);
+	if (!is_external && !chan2 && peer && ast_autoservice_stop(peer)) {
+		res = -1;
+	}
+	return res;
+}
+
 int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const char *digits, int between, unsigned int duration)
 {
 	int res;
diff --git a/main/channel.c b/main/channel.c
index 9e33fb2..31dd696 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -97,10 +97,14 @@
 AST_THREADSTORAGE(state2str_threadbuf);
 #define STATE2STR_BUFSIZE   32
 
-/*! Default amount of time to use when emulating a digit as a begin and end
+/*! Default amount of time to use when emulating a DTMF digit as a begin and end
  *  100ms */
 #define AST_DEFAULT_EMULATE_DTMF_DURATION 100
 
+/*! Default amount of time to use when emulating an MF digit as a begin and end
+ *  55ms */
+#define DEFAULT_EMULATE_MF_DURATION 55
+
 #define DEFAULT_AMA_FLAGS AST_AMA_DOCUMENTATION
 
 /*! Minimum amount of time between the end of the last digit and the beginning
@@ -4864,6 +4868,45 @@
 	return rc;
 }
 
+int ast_senddigit_mf_begin(struct ast_channel *chan, char digit)
+{
+	static const char * const mf_tones[] = {
+		"1300+1500", /* 0 */
+		"700+900",   /* 1 */
+		"700+1100",  /* 2 */
+		"900+1100",  /* 3 */
+		"700+1300",  /* 4 */
+		"900+1300",  /* 5 */
+		"1100+1300", /* 6 */
+		"700+1500",  /* 7 */
+		"900+1500",  /* 8 */
+		"1100+1500", /* 9 */
+		"1100+1700", /* * (KP) */
+		"1500+1700", /* # (ST) */
+		"900+1700",  /* A (STP) */
+		"1300+1700", /* B (ST2P) */
+		"700+1700"   /* C (ST3P) */
+	};
+
+	if (digit >= '0' && digit <='9') {
+		ast_playtones_start(chan, 0, mf_tones[digit-'0'], 0);
+	} else if (digit == '*') {
+		ast_playtones_start(chan, 0, mf_tones[10], 0);
+	} else if (digit == '#') {
+		ast_playtones_start(chan, 0, mf_tones[11], 0);
+	} else if (digit == 'A') {
+		ast_playtones_start(chan, 0, mf_tones[12], 0);
+	} else if (digit == 'B') {
+		ast_playtones_start(chan, 0, mf_tones[13], 0);
+	} else if (digit == 'C') {
+		ast_playtones_start(chan, 0, mf_tones[14], 0);
+	} else {
+		/* not handled */
+		ast_log(LOG_WARNING, "Unable to generate MF tone '%c' for '%s'\n", digit, ast_channel_name(chan));
+	}
+	return 0;
+}
+
 int ast_senddigit_begin(struct ast_channel *chan, char digit)
 {
 	/* Device does not support DTMF tones, lets fake
@@ -4933,6 +4976,37 @@
 	return 0;
 }
 
+int ast_senddigit_mf_end(struct ast_channel *chan)
+{
+	if (ast_channel_generator(chan)) {
+		ast_playtones_stop(chan);
+		return 0;
+	}
+	return -1;
+}
+
+int ast_senddigit_mf(struct ast_channel *chan, char digit, unsigned int duration,
+	unsigned int durationkp, unsigned int durationst, int is_external)
+{
+	if (duration < DEFAULT_EMULATE_MF_DURATION) {
+		duration = DEFAULT_EMULATE_MF_DURATION;
+	}
+	if (ast_channel_tech(chan)->send_digit_begin) {
+		if (digit == '*') {
+			duration = durationkp;
+		} else if (digit == '#' || digit == 'A' || digit == 'B' || digit == 'C') {
+			duration = durationst;
+		}
+		ast_senddigit_mf_begin(chan, digit);
+		if (is_external) {
+			usleep(duration * 1000);
+		} else {
+			ast_safe_sleep(chan, duration);
+		}
+	}
+	return ast_senddigit_mf_end(chan);
+}
+
 int ast_senddigit(struct ast_channel *chan, char digit, unsigned int duration)
 {
 	if (duration < AST_DEFAULT_EMULATE_DTMF_DURATION) {

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/16481
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: I05ba61ce49092d3f131ade9dc4d97dcc9887fa1d
Gerrit-Change-Number: 16481
Gerrit-PatchSet: 1
Gerrit-Owner: N A <mail at interlinked.x10host.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20210910/2cb31282/attachment-0001.html>


More information about the asterisk-code-review mailing list