[asterisk-commits] kpfleming: branch dhubbard/res_fax_spandsp r208742 - /team/dhubbard/res_fax_s...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri Jul 24 16:52:42 CDT 2009


Author: kpfleming
Date: Fri Jul 24 16:52:39 2009
New Revision: 208742

URL: http://svn.asterisk.org/svn-view/asterisk?view=rev&rev=208742
Log:
Bring in the latest res_fax changes... this still doesn't work with the current T.38 negotiation control frames, but that will be rectified soon


Modified:
    team/dhubbard/res_fax_spandsp/res/res_fax.c

Modified: team/dhubbard/res_fax_spandsp/res/res_fax.c
URL: http://svn.asterisk.org/svn-view/asterisk/team/dhubbard/res_fax_spandsp/res/res_fax.c?view=diff&rev=208742&r1=208741&r2=208742
==============================================================================
--- team/dhubbard/res_fax_spandsp/res/res_fax.c (original)
+++ team/dhubbard/res_fax_spandsp/res/res_fax.c Fri Jul 24 16:52:39 2009
@@ -370,7 +370,6 @@
 	    !tech->destroy_session ||
 	    !tech->read ||
 	    !tech->write ||
-	    !tech->session_frametype ||
 	    !tech->start_session ||
 	    !tech->cancel_session ||
 	    !tech->cli_show_capabilities ||
@@ -427,21 +426,6 @@
 	}
 	AST_RWLIST_TRAVERSE_SAFE_END;
 	AST_RWLIST_UNLOCK(&faxmodules);
-}
-
-/*! \brief unreference a fax session */
-void *ast_fax_session_unreference(struct ast_fax_session *fax)
-{
-	if (!fax) {
-		ast_log(LOG_ERROR, "no fax session to unreference!\n");
-		return NULL;
-	}
-	ao2_lock(faxregistry.container);
-	if (ao2_ref(fax, -1) == 2) {
-		ao2_unlink(faxregistry.container, fax);
-	}
-	ao2_unlock(faxregistry.container);
-	return NULL;
 }
 
 /*! \brief convert a ast_fax_state to a string */
@@ -456,6 +440,8 @@
 		return "Open";
 	case AST_FAX_STATE_ACTIVE:
 		return "Active";
+	case AST_FAX_STATE_CLOSING:
+		return "Closing";
 	case AST_FAX_STATE_COMPLETE:
 		return "Complete";
 	case AST_FAX_STATE_ZOMBIE:
@@ -502,35 +488,30 @@
 static void destroy_session(void *session)
 {
 	struct ast_fax_session *s = session;
-	int n = s->id;
-
-	s->tech->destroy_session(s);
+
+	if (s->tech) {
+		if (s->tech_pvt) {
+			s->tech->destroy_session(s);
+		}
+		ast_module_unref(s->tech->module);
+	}
 
 	if (s->details) {
 		ao2_ref(s->details, -1);
 	}
 	
-	if (s->tech_pvt) {
-		ast_debug(4, "freeing technology implementation structure.\n");
-		ast_free(s->tech_pvt);
-	}
-
 	if (s->debug_info) {
 		ast_debug(4, "freeing debug_info.\n");
 		ast_free(s->debug_info);
 	}
 
-	if (s->tech) {
-		ast_module_unref(s->tech->module);
-	}
-
 	if (s->smoother) {
 		ast_smoother_free(s->smoother);
 	}
 
 	ast_atomic_fetchadd_int(&faxregistry.active_sessions, -1);
 
-	ast_debug(4, "channel '%s' fax session '%d' destroyed\n", s->channame, n);
+	ast_debug(4, "channel '%s' fax session '%d' destroyed\n", s->channame, s->id);
 
 	ast_free(s->channame);
 }
@@ -577,6 +558,8 @@
 		return NULL;
 	}
 
+	ast_atomic_fetchadd_int(&faxregistry.active_sessions, 1);
+
 	if (details->option.debug && (details->caps & AST_FAX_TECH_AUDIO)) {
 		if (!(s->debug_info = ast_calloc(1, sizeof(*(s->debug_info))))) {
 			ao2_ref(s, -1);
@@ -594,7 +577,6 @@
 	ao2_ref(s->details, 1);
 	s->state = AST_FAX_STATE_UNINITIALIZED;
 
-	ast_atomic_fetchadd_int(&faxregistry.active_sessions, 1);
 	details->id = s->id = ast_atomic_fetchadd_int(&faxregistry.nextsessionname, 1);
 
 	/* locate a fax technology module that can handle said requirements */
@@ -680,6 +662,7 @@
 	do {	\
 		ast_log(LOG_ERROR, "channel '%s' fax session '%d' failure, reason: '%s'\n", chan->name, fax->id, reason); \
 		pbx_builtin_setvar_helper(chan, "FAXSTATUSSTRING", reason); \
+		if (ast_strlen_zero(fax->details->result)) ast_string_field_set(fax->details, result, "FAILED"); \
 		res = ms = -1; \
 	} while (0)
 
@@ -688,8 +671,9 @@
 {
 	int ms = 1000;
 	int timeout = RES_FAX_TIMEOUT;
-	int res = 0, chancount, ofd;
-	int exception, expected_frametype;
+	int res = 0, chancount;
+	int expected_frametype = -1;
+	int expected_framesubclass = -1;
 	char tbuf[10];
 	int t38negotiated = 0;
 	const char *tempvar;
@@ -697,6 +681,7 @@
 	struct ast_fax_session *fax = NULL;
 	struct ast_frame *frame = NULL;
 	struct ast_channel *c = chan;
+	enum ast_t38_state t38_state;
 
 	if (!chan || !details) {
 		ast_log(LOG_ERROR, "missing '%s' structure.\n", chan ? "session requirements" : "channel");
@@ -705,10 +690,11 @@
 	chancount = 1;
 
 	/* get the session capability requirements, and switch to T.38 if needed */
-	if (ast_channel_get_t38_state(chan) == T38_STATE_NEGOTIATED) {
+	if ((t38_state = ast_channel_get_t38_state(chan)) == T38_STATE_NEGOTIATED) {
 		details->caps |= AST_FAX_TECH_UDP;
 		t38negotiated = 1;
-	} else if ((details->option.request_t38 == AST_FAX_OPTFLAG_TRUE) &&
+	} else if ((t38_state != T38_STATE_UNAVAILABLE) &&
+		   (details->option.request_t38 == AST_FAX_OPTFLAG_TRUE) &&
 		   (t38control = AST_T38_REQUEST_NEGOTIATE, (ast_indicate_data(chan, AST_CONTROL_T38, &t38control, sizeof(t38control)) == 0))) {
 		/* wait up to five seconds for negotiation to complete */
 		unsigned int timeout = 5000;
@@ -740,16 +726,23 @@
 			if (frame->frametype == AST_FRAME_CONTROL && frame->subclass == AST_CONTROL_T38 && frame->datalen == sizeof(enum ast_control_t38)) {
 				t38control = *((enum ast_control_t38 *) frame->data.ptr);
 
-				ast_log(LOG_NOTICE, "got CONTROL_T38 with value %d\n", t38control);
-				if (t38control == AST_T38_NEGOTIATED) {
+				switch (t38control) {
+				case AST_T38_NEGOTIATED:
+					ast_log(LOG_NOTICE, "Negotiated T.38 for %s on %s\n", (details->caps & AST_FAX_TECH_SEND) ? "send" : "receive", chan->name);
 					details->caps |= AST_FAX_TECH_UDP;
 					t38negotiated = 1;
-				} else {
-					/* T.38 negotiation failed */
+					break;
+				case AST_T38_REFUSED:
+					ast_log(LOG_WARNING, "channel '%s' refused to negotiate T.38\n", chan->name);
+					res = -1;
+					break;
+				default:
 					ast_log(LOG_ERROR, "channel '%s' failed to negotiate T.38\n", chan->name);
-					ast_frfree(frame);
+					res = -1;
 					break;
 				}
+				ast_frfree(frame);
+				break;
 			}
 			ast_frfree(frame);
 		}
@@ -781,18 +774,24 @@
 		report_fax_status(chan, details, "No Available Resource");
 		return -1;
 	}
-	expected_frametype = fax->tech->session_frametype(fax);
-
 	if (details->caps & AST_FAX_TECH_AUDIO) {
+		expected_frametype = AST_FRAME_VOICE;;
+		expected_framesubclass = AST_FORMAT_SLINEAR;
 		if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
 			ast_log(LOG_ERROR, "channel '%s' failed to set write format to signed linear'.\n", chan->name);
-			ast_fax_session_unreference(fax);
+ 			ao2_lock(faxregistry.container);
+ 			ao2_unlink(faxregistry.container, fax);
+ 			ao2_unlock(faxregistry.container);
+ 			ao2_ref(fax, -1);
 			ast_channel_unlock(chan);
 			return -1;
 		}
 		if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
 			ast_log(LOG_ERROR, "channel '%s' failed to set read format to signed linear.\n", chan->name);
-			ast_fax_session_unreference(fax);
+ 			ao2_lock(faxregistry.container);
+ 			ao2_unlink(faxregistry.container, fax);
+ 			ao2_unlock(faxregistry.container);
+ 			ao2_ref(fax, -1);
 			ast_channel_unlock(chan);
 			return -1;
 		}
@@ -803,6 +802,9 @@
 		if (!(fax->smoother = ast_smoother_new(320))) {
 			ast_log(LOG_WARNING, "Channel '%s' fax session '%d' failed to obtain a smoother.\n", chan->name, fax->id);
 		}
+	} else if (details->caps & AST_FAX_TECH_UDP) {
+		expected_frametype = AST_FRAME_MODEM;
+		expected_framesubclass = AST_MODEM_T38;
 	}
 
 	fax->base_tv = ast_tvnow();
@@ -816,46 +818,13 @@
 
 	/* handle frames for the session */
 	while ((ms > -1) && (timeout > 0)) {
+		struct ast_channel *ready_chan;
+		int ofd, exception;
+
 		ms = 1000;
-		ast_waitfor_nandfds(&c, chancount, &fax->fds, fax->fdcount, &exception, &ofd, &ms);
-		if (ms < 0) {
-			/* bad stuff happened */
-			ast_log(LOG_WARNING, "something bad happened while channel '%s' was polling.\n", chan->name);
-			res = ms;
-			break;
-		}
-		if (!ms) {
-			/* nothing happened */
-			if (timeout > 0) {
-				timeout -= 1000;
-				continue;
-			} else {
-				ast_log(LOG_WARNING, "channel '%s' timed-out during the fax transmission.\n", chan->name);
-				GENERIC_FAX_EXEC_ERROR(fax, chan, "fax session timed-out");
-				break;
-			}
-		}
-		if (fax->fdcount && (ofd == fax->fds)) {
-			/* read a frame from the fax stack and send it out the channel.
- 			 * the fax stack will return a NULL if the fax session has already completed */
-			if (!(frame = fax->tech->read(fax))) {
-				break;
-			}
-
-			if (fax->debug_info && fax->debug_info->debug_frame_hook) {
-				ao2_lock(fax);
-				fax->debug_info->frame_s2c = frame;
-				fax->debug_info->debug_frame_hook(fax);
-				fax->debug_info->frame_s2c = NULL;
-				ao2_unlock(fax);
-			}
-
-			ast_write(chan, frame);
-			fax->frames_sent++;
-			ast_frfree(frame);
-			timeout = RES_FAX_TIMEOUT;
-			continue;
-		} else {
+		errno = 0;
+		ready_chan = ast_waitfor_nandfds(&c, chancount, &fax->fds, fax->fdcount, &exception, &ofd, &ms);
+		if (ready_chan) {
 			if (!(frame = ast_read(chan))) {
 				/* the channel is probably gone, so lets stop polling on it and let the
  				 * fax session complete before we exit the application.  BUT, we need to
@@ -864,6 +833,7 @@
 				c = NULL;
 				chancount = 0;
 				timeout -= (1000 - ms);
+				fax->tech->cancel_session(fax);
 				if (fax->tech->generate_silence) {
 					fax->tech->generate_silence(fax);
 				}
@@ -877,100 +847,154 @@
 				fax->debug_info->frame_c2s = NULL;
 				ao2_unlock(fax);
 			}
-		}
-
-		if (frame->frametype == AST_FRAME_CONTROL && frame->subclass == AST_CONTROL_T38 && frame->datalen == sizeof(enum ast_control_t38)) {
-			unsigned int was_t38 = t38negotiated;
-			t38control = *((enum ast_control_t38 *) frame->data.ptr);
-
-			switch (t38control) {
-			case AST_T38_REQUEST_NEGOTIATE:
-				/* the other end has requested a switch to T.38, so reply that we are willing, if we can
-				 * do T.38 as well
-				 */
-				t38control = AST_T38_NEGOTIATED;
-				ast_indicate_data(chan, AST_CONTROL_T38, &t38control, sizeof(t38control));
-				/* fall through to the NEGOTIATED case, since it has been */
-			case AST_T38_NEGOTIATED:
-				t38negotiated = 1;
-				break;
-			default:
-				break;
-			}
-			if (t38negotiated && !was_t38) {
-				/* time to cancel the existing audio fax session and create a new T.38 session
-				 * if possible. if not, we have to abort, since the channel has already
-				 * switched to T.38.
-				 */
-				details->option.switch_to_t38 = 1;
-				fax->tech->cancel_session(fax);
+
+			if (frame->frametype == AST_FRAME_CONTROL && frame->subclass == AST_CONTROL_T38 && frame->datalen == sizeof(enum ast_control_t38)) {
+				unsigned int was_t38 = t38negotiated;
+				t38control = *((enum ast_control_t38 *) frame->data.ptr);
 				
-				details->caps &= ~AST_FAX_TECH_AUDIO;
-				details->caps |= AST_FAX_TECH_UDP;
-
-				if (!(fax = fax_session_new(details, chan))) {
-					ast_log(LOG_ERROR, "Can't create a T.38 fax session, fax attempt failed.\n");
-					GENERIC_FAX_EXEC_ERROR(fax, chan, "Failed to create T.38 fax session");
-					ast_frfree(frame);
+				switch (t38control) {
+				case AST_T38_REQUEST_NEGOTIATE:
+					/* the other end has requested a switch to T.38, so reply that we are willing, if we can
+					 * do T.38 as well
+					 */
+					t38control = AST_T38_NEGOTIATED;
+					ast_indicate_data(chan, AST_CONTROL_T38, &t38control, sizeof(t38control));
+					/* fall through to the NEGOTIATED case, since it has been */
+				case AST_T38_NEGOTIATED:
+					t38negotiated = 1;
+					break;
+				default:
 					break;
 				}
-
-				expected_frametype = fax->tech->session_frametype(fax);
-				report_fax_status(chan, details, "T.38 Negotiated");
+				if (t38negotiated && !was_t38) {
+					/* time to cancel the existing audio fax session and create a new T.38 session
+					 * if possible. if not, we have to abort, since the channel has already
+					 * switched to T.38.
+					 */
+					details->option.switch_to_t38 = 1;
+					fax->tech->cancel_session(fax);
+					ao2_lock(faxregistry.container);
+					ao2_unlink(faxregistry.container, fax);
+					ao2_unlock(faxregistry.container);
+					ao2_ref(fax, -1);
+					
+					details->caps &= ~AST_FAX_TECH_AUDIO;
+					details->caps |= AST_FAX_TECH_UDP;
+					expected_frametype = AST_FRAME_MODEM;
+					expected_framesubclass = AST_MODEM_T38;
+					
+					if (!(fax = fax_session_new(details, chan))) {
+						ast_log(LOG_ERROR, "Can't create a T.38 fax session, fax attempt failed.\n");
+						pbx_builtin_setvar_helper(chan, "FAXSTATUSSTRING", "Failed to create T.38 fax session");
+						report_fax_status(chan, details, "Failed to switch to T.38");
+						ast_string_field_set(details, result, "FAILED");
+						res = ms = -1;
+						ast_frfree(frame);
+						break;
+					}
+					
+					report_fax_status(chan, details, "T.38 Negotiated");
+					
+					if (option_verbose > 3) {
+						ast_verbose(VERBOSE_PREFIX_3 "Channel '%s' switched to T.38 fax session '%d'.\n", chan->name, fax->id);
+					}
+					fax->base_tv = ast_tvnow();
+					if (fax->tech->start_session(fax) < 0) {
+						GENERIC_FAX_EXEC_ERROR(fax, chan, "Failed to start T.38 fax session");
+						ast_frfree(frame);
+						break;
+					} else {
+						report_fax_status(chan, details, "Fax Transmission In Progress");
+					}
+				}
+			} else if ((frame->frametype == expected_frametype) &&
+				   (frame->subclass == expected_framesubclass)) {
+				struct ast_frame *f;
 				
-				if (option_verbose > 3) {
-					ast_verbose(VERBOSE_PREFIX_3 "Channel '%s' switched to T.38 fax session '%d'.\n", chan->name, fax->id);
-				}
-				fax->base_tv = ast_tvnow();
-				if (fax->tech->start_session(fax) < 0) {
-					GENERIC_FAX_EXEC_ERROR(fax, chan, "Failed to start T.38 fax session");
-					ast_frfree(frame);
-					break;
+				if (fax->smoother) {
+					/* push the frame into a smoother */
+					if (ast_smoother_feed(fax->smoother, frame) < 0) {
+						GENERIC_FAX_EXEC_ERROR(fax, chan, "Failed to feed the smoother");
+					}
+					while ((f = ast_smoother_read(fax->smoother)) && (f->data.ptr)) {
+						/* write the frame to the fax stack */
+						fax->tech->write(fax, f);
+						fax->frames_received++;
+						timeout = RES_FAX_TIMEOUT;
+					}
 				} else {
-					report_fax_status(chan, fax->details, "Fax Transmission In Progress");
-				}
-			}
-		} else if (frame->frametype == expected_frametype) {
-			struct ast_frame *f;
-
-			if (fax->smoother) {
-				/* push the frame into a smoother */
-				if (ast_smoother_feed(fax->smoother, frame) < 0) {
-					GENERIC_FAX_EXEC_ERROR(fax, chan, "Failed to feed the smoother");
-				}
-				while ((f = ast_smoother_read(fax->smoother)) && (f->data.ptr)) {
 					/* write the frame to the fax stack */
-					fax->tech->write(fax, f);
+					fax->tech->write(fax, frame);
 					fax->frames_received++;
 					timeout = RES_FAX_TIMEOUT;
 				}
+			}
+			ast_frfree(frame);
+		} else if (ofd == fax->fds) {
+			/* read a frame from the fax stack and send it out the channel.
+ 			 * the fax stack will return a NULL if the fax session has already completed */
+			if (!(frame = fax->tech->read(fax))) {
+				break;
+			}
+
+			if (fax->debug_info && fax->debug_info->debug_frame_hook) {
+				ao2_lock(fax);
+				fax->debug_info->frame_s2c = frame;
+				fax->debug_info->debug_frame_hook(fax);
+				fax->debug_info->frame_s2c = NULL;
+				ao2_unlock(fax);
+			}
+
+			ast_write(chan, frame);
+			fax->frames_sent++;
+			ast_frfree(frame);
+			timeout = RES_FAX_TIMEOUT;
+		} else {
+			if (ms && (ofd < 0)) {
+				if ((errno == 0) || (errno == EINTR)) {
+					timeout -= (1000 - ms);
+					continue;
+				} else {
+					ast_log(LOG_WARNING, "something bad happened while channel '%s' was polling.\n", chan->name);
+					res = ms;
+					break;
+				}
 			} else {
-				/* write the frame to the fax stack */
-				fax->tech->write(fax, frame);
-				fax->frames_received++;
-				timeout = RES_FAX_TIMEOUT;
+				/* nothing happened */
+				if (timeout > 0) {
+					timeout -= 1000;
+					continue;
+				} else {
+					ast_log(LOG_WARNING, "channel '%s' timed-out during the fax transmission.\n", chan->name);
+					GENERIC_FAX_EXEC_ERROR(fax, chan, "fax session timed-out");
+					break;
+				}
 			}
 		}
-		ast_frfree(frame);
 	}
 	ast_debug(3, "channel '%s' - event loop stopped { timeout: %d, ms: %d, res: %d }\n", chan->name, timeout, ms, res);
 
-	pbx_builtin_setvar_helper(chan, "FAXSTATUS", fax->details->result);
-	pbx_builtin_setvar_helper(chan, "FAXERROR", fax->details->error);
-	pbx_builtin_setvar_helper(chan, "FAXSTATUSSTRING", fax->details->resultstr);
-	pbx_builtin_setvar_helper(chan, "REMOTESTATIONID", fax->details->remotestationid);
-	pbx_builtin_setvar_helper(chan, "FAXBITRATE", fax->details->transfer_rate);
-	pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", fax->details->resolution);
+	pbx_builtin_setvar_helper(chan, "FAXSTATUS", details->result);
+	pbx_builtin_setvar_helper(chan, "FAXERROR", details->error);
+	pbx_builtin_setvar_helper(chan, "FAXSTATUSSTRING", details->resultstr);
+	pbx_builtin_setvar_helper(chan, "REMOTESTATIONID", details->remotestationid);
+	pbx_builtin_setvar_helper(chan, "FAXBITRATE", details->transfer_rate);
+	pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", details->resolution);
 
 	snprintf(tbuf, sizeof(tbuf), "%d", details->pages_transferred);
 	pbx_builtin_setvar_helper(chan, "FAXPAGES", tbuf);
 
 	ast_atomic_fetchadd_int(&faxregistry.fax_complete, 1);
-	if (!strcasecmp(fax->details->result, "FAILED")) {
+	if (!strcasecmp(details->result, "FAILED")) {
 		ast_atomic_fetchadd_int(&faxregistry.fax_failures, 1);
 	}
 
-	ast_fax_session_unreference(fax);
+	if (fax) {
+		ao2_lock(faxregistry.container);
+		ao2_unlink(faxregistry.container, fax);
+		ao2_unlock(faxregistry.container);
+		ao2_ref(fax, -1);
+	}
 
 	/* return the chancount so the calling function can determine if the channel hungup during this fax session or not */
 	return chancount;
@@ -1561,6 +1585,7 @@
 static int acf_faxopt_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
 {
 	struct ast_fax_session_details *details = find_details(chan);
+	int res = 0;
 
 	if (!details) {
 		ast_log(LOG_ERROR, "channel '%s' can't read FAXOPT(%s) because it has never been written.\n", chan->name, data);
@@ -1598,18 +1623,17 @@
 		ast_fax_modem_to_str(details->modems, buf, len);
 	} else {
 		ast_log(LOG_WARNING, "channel '%s' can't read FAXOPT(%s) because it is unhandled!\n", chan->name, data);
-		ao2_ref(details, -1);
-		return -1;
+		res = -1;
 	}
 	ao2_ref(details, -1);
 
-	return 0;
+	return res;
 }
 
 /*! \brief FAXOPT write function modifies the contents of a fax option */
 static int acf_faxopt_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
 {
-	int res;
+	int res = 0;
 	struct ast_fax_session_details *details;
 
 	if (!(details = find_or_create_details(chan))) {
@@ -1633,24 +1657,29 @@
 	} else if (!strcasecmp(data, "t38_ecc_data")) {
 		if ((res = sscanf(value, "%d", &details->eccdata)) != 1) {
 			ast_log(LOG_ERROR, "Huh? channel '%s' scanf(%s) returned '%d'\n", chan->name, value, res);
+		} else {
+			res = 0;
 		}
 	} else if (!strcasecmp(data, "t38_ecc_signal")) {
 		if ((res = sscanf(value, "%d", &details->eccsignal)) != 1) {
 			ast_log(LOG_ERROR, "Huh? channel '%s' scanf(%s) returned '%d'\n", chan->name, value, res);
+		} else {
+			res = 0;
 		}
 	} else if (!strcasecmp(data, "t38_maxdelay")) {
 		if ((res = sscanf(value, "%d", &details->maxdelay)) != 1) {
 			ast_log(LOG_ERROR, "Huh? channel '%s' scanf(%s) returned '%d'\n", chan->name, value, res);
+		} else {
+			res = 0;
 		}
 	} else {
 		ast_log(LOG_WARNING, "channel '%s' set FAXOPT(%s) to '%s' is unhandled!\n", chan->name, data, value);
-		ao2_ref(details, -1);
-		return -1;
+		res = -1;
 	}
 
 	ao2_ref(details, -1);
 
-	return 0;
+	return res;
 }
 
 /*! \brief FAXOPT dialplan function */




More information about the asterisk-commits mailing list