Mod spandsp

From FreeSWITCH Wiki
Jump to: navigation, search



The family of FreeSWITCH modules including mod_fax, mod_t38gateway, and the mod_voipcodecs have now been merged into one module called mod_spandsp which takes advantage of all the DSP features found in the spandsp library including T.38 endpoint and gateway functionality.


mod_spandsp is enabled by default in modules.conf and therefore compiled automatically. Its also enabled by default in modules.conf.xml.

  • Ensure that you have libtiff development files installed.
    • For Debian/Ubuntu you can type
Type This
apt-get install libtiff4-dev

  • For CentOS 5.x
Type This
yum install libtiff-devel libjpeg-devel

Informational Tip

If you get a complaint about TIFF library, try the following:

make tiff-reconf


  • For FreeBSD
Type This
cd /usr/ports/graphics/tiff
make install clean

  • Compile Errors/Problems

If you get an error about /usr/bin/ld: cannot find -ljpeg (FreeBSD) or something similar then double check to see if you have the tiff library installed, look above for how to install for your OS.

  • For SUSE
Type This
zypper install libtiff-devel libjpeg-devel

  • General Build Errors

If you get errors loading mod_spandsp try

Type This
make spandsp-reconf && make install

to rebuild the spandsp library.



It's important that you create the config XML file since the spool directory for faxes can be configured only there.

Things to be done:

  • Fire out an event on every fax received/sent.
  • Fire out an event on every page fax received/sent.

Also, mod_spandsp requires that you use a G.711 (PCMA/PCMU) codec for T.30 fax or it won't work. If you are using txfax for sending and rxfax for receiving, remember to use the same codec at both ends.

Invoking the app from the XML dialplan

You can invoke the rxfax application and use the data option to pass it the TIFF file name. If you do not pass any information in the data option mod_spandsp will create the file name for you and place the received fax into your spool directory.

(This section need to be completed)

For receiving a fax

   <extension name="test_rxfax_stream">
     <condition field="destination_number" expression="^\*90012$">
       <action application="answer" />
       <action application="playback" data="silence_stream://2000"/>
       <action application="rxfax" data="//my_directory//rxfax.tiff"/>
       <action application="hangup"/>

If you want each fax have a unique name you can use this variation:

   <extension name="fax_receive">
     <condition field="destination_number" expression="^9978$">
       <action application="answer" />
       <action application="playback" data="silence_stream://2000"/>
       <action application="rxfax" data="/tmp/FAX-${uuid}.tif"/>
       <action application="hangup"/>

If you want mod_spandsp to send the re-INVITE for T.38 (per the standard) use this:

   <extension name="fax_receive">
     <condition field="destination_number" expression="^9978$">
       <action application="answer" />
       <action application="playback" data="silence_stream://2000"/>
       <action application="set" data="fax_enable_t38_request=true"/>
       <action application="set" data="fax_enable_t38=true"/>
       <action application="rxfax" data="/tmp/FAX-${uuid}.tif"/>
       <action application="hangup"/>

For transmitting a fax

A PDF file can be converted to TIFF format using ImageMagick, here is an example:

convert -density 204x98 -units PixelsPerInch -resize 1728x1186\! -monochrome -compress Fax txfax.pdf txfax.tiff

NOTE: Be explicit about the units (on my machine it defaulted to the metric system). Also, the exclamation mark forces a specific ratio. You may consider using graphicsmagick, since it's generally faster and supports Group4 compression. Keep in mind that these tools still rely on ghostscript to do conversion from pdf using gs's pnm device. The quality of gm was actually better than that produced by ghostscript which does too much dithering. I was able to successfully use width 2156 with graphicsmagick, even though there are different widths specified here for imagemagick and ghostscript.

You can use Ghostscript for PDF/PS files. Here is an example, producing a standard resolution TIFF file:

gs -q -r204x98 -g1728x1078 -dNOPAUSE -dBATCH -dSAFER -sDEVICE=tiffg3 -sOutputFile=txfax.tiff -- txfax.pdf

Here is an example producing a fine resolution TIFF file:

gs -q -r204x196 -g1728x2156 -dNOPAUSE -dBATCH -dSAFER -sDEVICE=tiffg3 -sOutputFile=txfax.tiff -- txfax.pdf

NOTE: Ghostscript won't put your document in the correct orientation. This is not really a problem when sending to a remote paper machine, but can be a nuisance when receiving a fax on a computer.

The use of -g1728x1078 and -g1728x2156 ensures that the resulting TIFF file contains pages of a size compatible with FAX machines. If the PDF file contains bit images, rather than scalable content, the images in resulting TIFF file may not fit the pages.

You can transmit a Fax using the following dialplan:

   <extension name="test_txfax_stream">
     <condition field="destination_number" expression="^\*90012$">
       <action application="txfax" data="txfax.tiff"/>
       <action application="hangup"/>

Manual fax transmission

when sending to a telefax, where a live operator gives you a fax tone, bind_meta_app comes handy.

  <extension name="pstn-fxo-dialout">
    <condition field="${toll_allow}" expression="local,domestic"/>
    <condition field="destination_number" expression="^9([2-9]\d{6})$">
	<action application="set" data="effective_caller_id_number=${outbound_caller_id_number}"/>
	<action application="set" data="effective_caller_id_name=${outbound_caller_id_name}"/>
	<action application="bind_meta_app" data="5 a o transfer:: 9179 XML default"/> 
	<action application="set" data="hold_music=silence"/>
	<action application="answer"/>
	<action application="sleep" data="50"/>
	<action application="playback" data="ivr/ivr-hold_connect_call.wav"/>
      <action application="bridge" data="sofia/gateway/fxo/$1@"/>

Execute based on fax session outcome

(Requires git Wed Jun 29 18:22:57 2011 -0500 or later)

Channel variables that allow you to take action based on the success or failure of a fax transmission.

Variables available are execute_on_fax_success, execute_on_fax_result, execute_on_fax_failure, execute_on_fax_detect . Insert as required before your rxfax application call.

Example: Run an lua script when mod_spandsp reports a successful transmission

<action application="set" data="execute_on_fax_success=lua process_fax.lua"/>

Example: Run a shell command when mod_spandsp reports a failed transmission

<action application="set" data="execute_on_fax_failure=system /bin/rm tmp/FAX-${uuid}.tif"/>

Invoking the app from the CLI

For transmitting a fax

In the CLI, for sending a fax via profile external using gateway and fax machine number 100, you'll have to use this.

originate sofia/external/100@ &txfax(/path_to_fax_file)

Or to specify the gateway to use:

originate sofia/gateway/<gateway name>/<phone number> &txfax(/path_to_fax_file)

For transmitting a fax over ulaw from a SIP T.38 source

PLEASE reformat this info, it just raw information:

  • Make sure you have at least FreeSWITCH Git dating after 25 May 2010.
  • You also need mod_spandsp loaded.
  • Add param enable-t38 to true in conf/autoload_configs/spandsp.conf.xml.
    • NOTE: older versions of FreeSWITCH used fax.conf.xml instead. Newer versions only use fax.conf.xml if they don't find spandsp.conf.xml.
  • Just before executing bridge, execute: t38_gateway with argument equal 'self'. This will monitor for the FAX tone generated so it can switch to T.38 if needed.

I tested it using a Linksys SPA3102, You need to have T.38 mode to be in ReInvite.

fax2mail: Emailing the fax upon receipt

  • In recent versions of FreeSWITCH, the dialplan ends once rxfax completes, api_hangup_hook executes the script in this case. It is of utmost importance to set api_hangup_hook before rxfax in dialplan. Also if you use any fax vars for the system script, you have to escape them with \\\ (example: \\\${fax_image_size})
    • This dialplan also illustrates some possible regex's for specific UAs that only have dedicated fax's attached so they send they fax as an email attachment to the online fax service.
    <extension name="outbound_fax">
      <condition field="caller_id_number" expression="^(100[23])$"/>
      <condition field="destination_number" expression="^(1\d{10})$">
        <action application="set" data="api_hangup_hook=system ${base_dir}/scripts/ $1 /tmp/${uuid}.rxfax.tiff"/>    
        <action application="answer"/>
        <action application="playback" data="silence_stream://2000"/>
        <action application="rxfax" data="/tmp/${uuid}.rxfax.tiff"/>
        <action application="hangup"/>
  • Uuencode is no longer well supported or as good a format as mime. Using mutt to send the attachments solves this but presents some configuration hurdles to format the email. This solution presents a way to deal with every such case. Edit the paths and domain to match your fax service. Create the ${base_dir}/scripts/ file as follows:
# $1 is email alias (The dialed #)
# $2 is filename
mutt -n -f /dev/null -F /opt/freeswitch/scripts/muttrc -s "Fax to $" $ -a $2 < /dev/null
  • Now create the muttrc file. This configuration allows the sending of mail without a local mailbox which the user running freeswitch probably doesn't have (you can add more config to tune the headers of your email as needed):
set from = 'alias@DOMAIN'
set realname = 'YOUR ORG NAME'
set folder = /dev/null

Here is another example, using a python script called, via Dialplan XML:

   <extension name="test_rxfax_python">
     <condition field="destination_number" expression="^\*90012$">
       <action application="set" data=""/> 
       <action application="python" data="process-rxfax"/> 
       <action application="hangup"/>

This python script gets put into your FreeSWITCH's mod_python path, and will launch mod_spandsp to receive the fax and then convert it to a PDF and email it. The script requires that you have the ps2pdf utility (from Ghostscript) and tiff2ps installed. In FreeBSD these are in Ports and are also available under most Linux distributions via package management (apt-get, yum, rpm etc). This python script also requires a working mod_python and mod_spandsp installation. More information here.

mail2fax: Faxing a received mail

There are several pieces:

Install email2pdf into /usr/local/email2pdf. Link the main script into the path of the email user (/usr/local/bin is used in the script below). Edit header.html to be however you want it.

Create the file /usr/local/email2pdf/emails and put the authorized originating emails you want into it.

If using postfix, you can setup the fax delivery agent by adding these lines into /etc/postfix/

email2fax       unix    -       n       n       -       -       pipe
        flags= user=nobody      argv=/usr/local/bin/email2fax $mailbox $sender

Then add this line to /etc/aliases and run postalias:

mail2fax: |/usr/local/bin/email2fax

Then configure your email server to forward a new domain (, for example) to the mail2fax alias. In postfix this will probably involve adding a transport map (transport_maps) as well as a relay recipient (relay_recipient_maps). How you do it will depend on your mail server configuration.

The script email2fax may look like this:


set -e



#check a file for the originating email address
#and abort if it isn't in that file
grep -i "$2" "/usr/local/email2pdf/emails" >/dev/null

if [ $? -ne 0]; then
  echo "User not allowed!"
  exit 1;

cat > $TMPMAIL
cat $TMPMAIL | email2pdf --header=/usr/local/email2pdf/header.html - $TMPPDF >> $TMPLOG


gs -q -r204x98 -g1728x1078 -dNOPAUSE -dBATCH -dSAFER -sDEVICE=tiffg3 -sOutputFile=$TMPFAX -- $TMPPDF >> $TMPLOG


chmod o+r $TMPFAX

/opt/freeswitch/bin/fs_cli \
 --execute="originate {fax_verbose=true}$DEST &txfax($TMPFAX)" >> $TMPLOG

#This sends the log to the sender along with a copy of the fax as translated

#change .tiff to .tif for fusionpbx, remove if you don't use that
FAXBASE=$(basename "$TMPFAX")

#This sends an email with the status result
#use it if you don't have mpack
#sendmail -v $2 -f -r <$TMPLOG

#This sends an email with the fax as an attachment, requires mpack
mpack -s "Successful Fax to $1" -d $TMPLOG $TMPFAX $2

#puts the fax file into the outbound folder - change "1010" to your fax extension
#this is the default location for fusionpbx, YMMV
cp $TMPFAX "/usr/local/freeswitch/storage/fax/1010/sent/$NEWFAX"

#delete the fax

Configuring the app

App can be controlled globally using spandsp.conf.xml

<configuration name="spandsp.conf" description="FAX application configuration">
       <param name="use-ecm"       value="true"/>
       <param name="verbose"       value="true"/>
       <param name="disable-v17"   value="false"/>
       <param name="ident"         value="SpanDSP Fax Ident"/>
       <param name="header"        value="SpanDSP Fax Header"/>
       <param name="spool-dir"     value="/tmp"/>
       <param name="file-prefix"   value="faxrx"/>

Controlling the app

You can set the following channel's variables to control the behavior of the mod application:

By mod_spandsp.c

  • t38_gateway_detect_timeout - N/A

By mod_spandsp_fax.c

  • FAX_DISABLE_ECM - Disabling ECM
  • fax_disable_v17 - Disable V17 modem that is: use lower speed modems (lower speeds are auto-negotiated with the remote party and cannot be forced. That's a work that the spandsp modem handles on its own.)
  • fax_enable_t38 - Enable T.38 on a per call basis
  • fax_enable_t38_insist - N/A
  • fax_enable_t38_request - Send a T.38-ReINVITE when a fax was detected by tone detection
  • fax_end_page - A sent or receives document will end at specified page
  • fax_force_caller - Force to act as caller or receiver; Mode: Tx=1 or Rx=0
  • fax_ident - The FAX identity should be set to the telephone number to be used within the FAX exchange. This will typically appear on an LCD display at the far end. In theory it should be limited to digits, spaces, + and one or two other characters appropriate to telephone numbers. In practice FAX machines are usually happy with any text, up to 20 characters long. This string may also play a part in page headers.
  • fax_header - If fax_header is set to a non-null string, a header line will be inserted at the start of each page, just like a typical FAX machine does. The fax_ident, fax_header and the page number will be used to form the text of this line. If you are forwarding FAXes, you probably don't want to add a header line, as there will already be one that was inserted by the original source. If you are sending a locally generated FAX, you probably do want to add header lines to each page.
  • fax_prefix - Prefix added to the file name for received faxes.
  • fax_start_page - A sent document will start at the specified page
  • fax_use_ecm - Forces the use of ECM if globally disabled, on a per call basis
  • fax_v17_disabled - Same as fax_disable_v17??? This seems to be for T.30 fax mode
  • fax_verbose - Be verbose when printing logs (per call basis)
  • t38_peer - N/A
  • t38_trace - N/A

Checking the results

Rx/Tx fax will set the following channel variables when it terminates:

By mod_spandsp.c

  • t38_leg - N/A

By mod_spandsp_fax.c

  • fax_bad_rows - N/A
  • fax_document_total_pages - N/A
  • fax_document_transferred_pages - N/A
  • fax_ecm_requested - 0/1
  • fax_ecm_used - "on" or "off";
  • fax_filename - File name of the received fax
  • fax_image_resolution - XxY
  • fax_image_size
  • fax_local_station_id
  • fax_result_code - 0 on error otherwise >= 1;
  • fax_result_text - fax error string, provide info where an error has happened;
  • fax_remote_station_id - N/A
  • fax_success - 0 on error, 1 on success;
  • fax_transfer_rate - speed expressed in bauds (bit per seconds) like 14.400, 9.600, etc.;
  • fax_v17_disabled - 0/1 for T.30 mode?
  • jitterbuffer_msec - Always 0
  • rtp_autoflush_during_bridge - N/A
  • t38_gateway_format - "audio" or "udptl"
  • t38_peer - "self" or "peer"
  • t38_trace_read - N/A


Fax Result Codes


t38_gateway app will allow freeswitch to transcode audio<->t.38. It converts to T38 Gateway if tones are heard.

<extension name="audio_aleg_t38_bleg">
    <action application="set" data="fax_enable_t38=true"/>
    <action application="set" data="execute_on_answer=t38_gateway peer"/>
    <action application="bridge" data="sofia/external/1234@host"/>

You can also make freeswitch listen in and send a reinvite back to the a-leg or forward to the b-leg with the t38_gateway app. For example, this will transcode incoming T.38 on A-leg to ULAW on b-leg. (e.g. send incoming T.38 fax to Hylafax on Asterisk)

<extension name="t38_reinvite">
    <action application="set" data="fax_enable_t38=true"/> <!-- Enable t.38 for this call --> 
    <action application="set" data="fax_enable_t38_request=true"/> <!-- Enable t38_gateway to send a t.38 reinvite when a fax tone is detected.  If using t38_gateway peer then you need to export this variable instead of set  --> 
    <action application="set" data="execute_on_answer=t38_gateway self"/> <!--Execute t38_gateway on answer. self or peer. self: send a reinvite back to the a-leg. peer reinvite forward to the b-leg -->    
    <action application="bridge" data="sofia/external/1234@host"/>

If you want to transcode T.38 on the A-leg to T.30 audio on the B-leg, but you want to react to an incoming T.38 invite/re-invite on A-leg instead of detecting cng tones: Note, you may have to increase FaxT2Timer on hylafax to make this reliable

<extension name="t38_transcode">
     <action application="set" data="fax_enable_t38=true"/>
    <action application="set" data="sip_execute_on_image=t38_gateway peer nocng"/> <!--Execute t38_gateway on t.38 invite on A-leg. "nocng" means don't detect the CNG tones. Just start transcoding to the b-leg -->    
    <action application="bridge" data="sofia/external/1234@host"/>

If you want to transcode audio on the A-leg to T.38 on the B-leg, from a T.38 invite on the B-leg:

<extension name="t38_transcode">
   <action application="set" data="fax_enable_t38=true"/>
  <action application="bridge" data="{sip_execute_on_image='t38_gateway self nocng'}sofia/internal/ext@host"/> <!-- "nocng" means don't detect the CNG tones. Just start transcoding -->   

If you want to transcode t.38 on leg a to audio on leg B when leg a invites t.38, but refuse all t.38 offers from leg b :

<extension name="t38_test" continue="true">
 <action application="set" data="sip_execute_on_image=t38_gateway self nocng"/>
 <action application="bridge" data="{refuse_t38=true},sofia/gateway/external/$1" />

Allow t.38 between leg A and leg B, but only if the t.38 re-invite comes from leg a

<extension name="passthrough">
 <action application="set" data="t38_passthru=true"/>
 <action application="bridge" data="{refuse_t38=true},sofia/gateway/external/$1"/>


Fax receive application


rxfax [filename]

If filename is missing, a default name is created by as:



  • spool is set by the "spool-dir" configuration
  • prefix is set by the "fax_prefix" channel variable or the "file-prefix" configuration
  • number is a count of number of received faxes
  • timestamp is the current time in microseconds

Example usage:

<action application="set" data="fax_prefix=${uuid}"/>
<action application="rxfax" data=""/>


Fax transmit application. It is recommended to have ignore_early_media set to true to avoid some packet reordering issues.


txfax <filename>

Example usage:

<action application="txfax" data="/tmp/test.tif"/>


<action application="spandsp_start_fax_detect" data="<app> [<arg>][ <timeout>][ <tone_type>]"/>

Uses the spandsp code to detect fax tones which should be more reliable than using tone_detect. By default, the application detects CNG tones (sending side).

tone_type can be set to "ced" to detect CED tones (receiving side).

Example usage:

<action application="spandsp_start_fax_detect" data="transfer 'fax XML default' 3"/>

This will listen for 3 seconds after the application is invoked and if heard, will transfer the call to the "fax" extension found in the "XML" dial plan in the "default" context. (see the Samples section below for an example dialplan for the "fax" extension)

Example usage:

<action application="set" data="execute_on_answer=spandsp_start_fax_detect transfer 'fax XML default' 3"/>

This will listen for 3 seconds after extension is answered (by user or by other application such as voicemail) and if heard, will transfer the call to the "fax" extension found in the "XML" dial plan in the "default" context. This is closest in setup to "fax machine on same analog line as regular phone".

Full Example:
We need to answer the call to let FreeSwitch receive the audio to start detecting FAX tones. Because a bridge after an answer is actually a transfer, the Ringback sent to the caller is now defined by transfer_ringback

 <extension name="group_dial_sales">
    <condition field="destination_number" expression="^sales$">	
       <action application="answer"/>
	<action application="set" data="transfer_ringback=${us-ring}"/>
        <action application="spandsp_start_fax_detect" data="transfer 'FAX XML default' 6"/>
        <action application="bridge" data="${group_call(sales@${domain_name})}"/>


Stop fax detect


Here is a working example using FreeSWITCH as a T.38-gateway.

I used the following software and devices:

  • FAX_B: Infotec IF2100e
  • ATA_B: Cisco 186 v3.2.1
  • FAX_A: RICOH FAX2000L (Super G3, 14.400 Baud)
  • ATA_A: Grandstream HandyTone 502
    • Product model: HT-502 V1.2A
    • Firmware: Program-- Bootloader-- Core-- Base--
  • FreeSWITCH: git head of 2010-11-19
  • PRI E1-Stack: openzap
  • Sangoma-Wanpipe: version 3.5.17
  • TDM-Card: Sangoma A104d


FXS-Port 1

  • Fax Mode: T.38 (Auto Detect)
  • Fax tone detection mode: "Callee or Caller"
  • Preferred Vocoder: PCMA
  • Disable Line Echo Canceller (LEC): Yes
  • Jitter Buffer: High
  • Gain: Tx=0dB, Rx=0dB


<configuration name="spandsp.conf" description="Fax example">
        <param name="use-ecm"           value="true"/>
        <param name="verbose"           value="true"/>
        <param name="disable-v17"       value="false"/>
        <param name="enable-t38"        value="false"/>
        <param name="enable-t38-request"        value="false"/>
        <param name="ident"             value="SpanDSP Fax Ident"/>
        <param name="header"            value="SpanDSP Fax Header"/>
        <param name="spool-dir"         value="/tmp"/>
        <param name="file-prefix"       value="faxrx"/>

Outbound Direction: T.38->TDM

Fax_A->ATA_B->SIP->t38_gateway->FreeSWITCH->mod_openzap->ISDN/PRI E1->Fax

I used a XML dialplan here. In opposite to the documentation above I have to answer the call and then starting calling the t38_gateway app in dialplan instead of putting it into "execute_on_answer" didn't worked for me.

       <extension name="Fax_test2">
                <condition field="caller_id_number" expression="^(4919)$"/>
                <condition field="destination_number" expression="^(.*)$"/>
                        <action application="set" data="absolute_codec_string=PCMA"/>
                        <action application="set" data="fax_enable_t38=true"/>
                        <action application="set" data="fax_enable_t38_request=true"/>
                        <action application="answer"/>
                        <action application="t38_gateway" data="self"/>
                        <action application="bridge" data="openzap/1/a/$1"/>
                        <action application="hangup"/>

Inbound Direction: TDM->T.38

I used a Lua dialplan here. In opposite to the documentation above "peer" mode doesn't work.

Fax_B->ISDN/PRI E1->mod_openzap->FreeSWITCH->t38_gateway->SIP->ATA_A->Fax_A (4919)
        if (dialed_ext == "4919") then
                session:execute("export", "nolocal:fax_enable_t38=true")
                session:execute("export", "nolocal:fax_enable_t38_request=true")
                session:execute("export", "nolocal:execute_on_answer=t38_gateway self")
                session:execute("export", "nolocal:absolute_codec_string=PCMA")
                session:execute("bridge", "user/".."4919@"..sip_domain)

"dialed_ext" contains the destination number of the ATA resp Fax. "sip_domain" contains FreeSWITCH's SIP-Domain.

Internal T.38 to T.38

I used t38 passthrough here. So FS has only to act as a rtp/udptl proxy.

Fax_A (4919)->ATA_A->SIP->FreeSWITCH->SIP->ATA (same as ATA_A)->Fax_B (2799)
        <extension name="Fax_test t.38 to t.38 via t38-passthru">
                <condition field="caller_id_number" expression="^(4919)$"/ break="on-true"/>
                <condition field="destination_number" expression="^(2799)$">
                        <action application="set" data="absolute_codec_string=PCMA"/>
                        <action application="set" data="proxy_media=true"/>
                        <action application="set" data="bypass_media=false"/>
                        <action application="bridge" data="user/2799@<sipdoamin>"/>
                        <action application="hangup"/>


I've sent an 8-page fax successfully in both directions. The complete transport way was this:

Fax_A <-> ATA_A <-> SIP/UDP <-> t38_gateway <-> FreeSWITCH <-> mod_openzap <----> ISDN/PRI E1 --|
                                               ----------------------------                     |
                                              /                            \               Ayaya-PBX
                                             /                              \                   |
Fax_B <-> ATA_B <-> SIP/UDP/G711a     <-----/                                \<-> ISDN/PRI E1 --|

Questions I got during testing are:

  • Where is the difference between "peer" and "self" mode and why does "peer" not work?
  • Why does in T.38->TDM direction calling "t38_gateway self" via execute_on_answer not work? t38_gateway is called, it sets a media BUG but seems to hang listening for a tone and aborting after a 20 seconds timeout.







This event is fired when a fax finishes sending (either when it succeeds or when it fails). A header indicates whether the fax was successful.

In addition to the sessions headers it contains:


These are documented above.


This event is fired when a fax finishes being received (either when it succeeds or when it fails). A header indicates whether the fax was successful.

In addition to the sessions headers it contains:


These are documented above.


Faxing can be a big pain over VOIP and despites FS's great attempt its hard to match the reliability of a PSTN line. Using the methods outlined below with a decent T38 supporting provider I can get somewhere in the 90-95% range with a bit higher with multiple retries. If the remote fax machine supports T38 there is a better chance it will work fine.

Common Issues

  • Remote machine doesn't support T38 (properly or at all), this is going to make things worse and is obviously common with PSTN machines.
  • A T38 gateway (or multiple) end up inbetween each transcode is going to hurt the chances of success
  • Carrier/Provider doesn't support T38 well or is poor quality for standard codecs - Some carriers end up using several other carriers and the more carriers a call goes through or the lower the quality of carrier/route the harder (and sometimes much harder) faxing can be. A quality carrier is extremely important.
  • Some remote fax machines and PSTN lines can also be of low quality so while you may eventually be able to get good success overall some machines may prove to always be a challenge.
  • Make sure your tiff is at the right size, there are many sizes supported by fax machines but 1728x1078 is probably the most common (and is at standard resolution). Its roughly 204x98dpi, "super fine" faxes are at higher resolutions. Ensuring the proper resolution and size is an important step to working with many remote fax machines.
  • Ensure you have ignore_early_media enabled, without it random/out of order media can screw things up
  • For your codecs you generally want to force PCMU/PCMA unless you know of success with something else better thats generally going to give you the best results.
  • Requesting T38 can sometimes hurt your chances by things that don't properly handle or understand T38 causing it to drop.

Suggested Process

  • First try the optimal methods, t38 enabled and requested, ECM on
  • Second if that fails lets turn t38 requests off and ecm off and try again
  • If that still fails lets leave T38 and ecm off and just try again, if still failing chance of future success is low.


An example to start a fax from the command line:

originate {ignore_early_media=true,absolute_codec_string='PCMU,PCMA',fax_enable_t38=true,fax_verbose=true,fax_use_ecm=true,fax_enable_t38_request=true}sofia/gateway/default/1231231234 &txfax('test_fax.tif')

And if this fails further retries with:

originate {ignore_early_media=true,absolute_codec_string='PCMU,PCMA',fax_enable_t38=true,fax_verbose=true,fax_use_ecm=false,fax_enable_t38_request=false}sofia/gateway/default/1231231234 &txfax('test_fax.tif')

Further debugging

setting fax_verbose to true will dump a lot of data about faxes, make sure to go through it carefully as there can always be just one line that has the issue. Console debug logging can also be helpful like normal.

Tone Detection

mod_spandsp offers better tone detection than what is provided by default in the FreeSWITCH core.


Inband DTMF detectors in mod_spandsp can detect duration in addition to frequency. The teletone detector in FS core is based on an old version of spandsp and should be replaced with this detector.

Channel Variables


Enables verbose logging of Spandsp DTMF detector. Default is false. Set this variable prior to executing spandsp_start_dtmf.


<action application="set" data="dtmf_verbose=true"/>
<action application="spandsp_start_dtmf" />


Duplicate inband DTMF that starts sooner than this time will be ignored. That is, this is the minimum gap from the end of the first digit and the start of the repeated digit required for two digits to be detected. This value is 0 by default. Set this variable prior to executing spandsp_start_dtmf.


<action application="set" data="min_dup_digit_spacing_ms=40"/>
<action application="spandsp_start_dtmf" />


Sets the threshold parameter in the spandsp DTMF detector. Threshold is set to -42 dBm0 by default. Set this variable prior to executing spandsp_start_dtmf.


<action application="set" data="spandsp_dtmf_rx_threshold=-42"/>
<action application="spandsp_start_dtmf" />


Sets the twist parameter in the spandsp DTMF detector. Twist is set to 8 dB by default. Set this variable prior to executing spandsp_start_dtmf.


<action application="set" data="spandsp_dtmf_rx_twist=8"/>
<action application="spandsp_start_dtmf" />


Sets the reverse twist setting in the spandsp DTMF detector. Reverse twist is set to 4 dB by default. This value can be safely increased up to 6 or 7 without a significant increase in talk-off to allow DTMFs that exceed this threshold to be detected. Set this variable prior to executing spandsp_start_dtmf.


<action application="set" data="spandsp_dtmf_rx_reverse_twist=6"/>
<action application="spandsp_start_dtmf" />


Sets the filter dialtone parameter in the spandsp DTMF detector. Dialtone filtering is disabled by default. Set this variable prior to executing spandsp_start_dtmf.


<action application="set" data="spandsp_dtmf_rx_filter_dialtone=true"/>
<action application="spandsp_start_dtmf" />

Dialplan Applications


Detect inband dtmf on the session

<action application="spandsp_start_dtmf"/>


Stop detecting inband dtmf

<action application="spandsp_stop_dtmf"/>

Call Progress

mod_spandsp provides the tools to create a call progress tone detector. By accounting for cadence in addition to frequency, this call progress detector can distinguish between North American BUSY and REORDER, which only differ in cadence.


conf/autoload_configs/spandsp.conf.xml defines the tone detector descriptors. Each descriptor defines a named grouping of tones to detect. When starting the tone detector, you specify the group of tones you wish to detect.

This is a sample configuration for detecting some call progress tones in North America:

  <configuration name="spandsp.conf" description="Tone detector descriptors">
   <descriptors debug-level="0">
     <!-- These tones are defined in Annex to ITU Operational Bulletin No. 781 - 1.II.2003 -->
     <!-- Various Tones Used in National Networks (According to ITU-T Recommendation E.180)(03/1998) -->

     <!-- North America -->
     <descriptor name="1">
       <tone name="CED_TONE" description="ANS / ANSam">
         <element freq1="2100" freq2="0" min="700" max="0"/>
       <tone name="SIT" description="Special Information Tone">
         <element freq1="950" freq2="0" min="256" max="400"/>
         <element freq1="1400" freq2="0" min="256" max="400"/>
         <element freq1="1800" freq2="0" min="256" max="400"/>

       <tone name="RING_TONE" description="North America ring">
         <element freq1="440" freq2="480" min="1200" max="0"/>
       <tone name="REORDER_TONE" description="North America reorder">
         <element freq1="480" freq2="620" min="224" max="316"/>
         <element freq1="0" freq2="0" min="168" max="352"/>
         <element freq1="480" freq2="620" min="224" max="316"/>

       <tone name="BUSY_TONE" description="North America busy">
         <element freq1="480" freq2="620" min="464" max="536"/>
         <element freq1="0" freq2="0" min="464" max="572"/>
         <element freq1="480" freq2="620" min="464" max="536"/>

Each descriptor defines the tones to detect. Each tone is composed of elements defining the frequencies and cadence of the tone. The tone name will be reported in the DETECTED_TONE event. Each element can be composed of 0 (silence), 1, or 2 frequencies. min and max define the minimum and maximum element durations in milliseconds.

Tuning the detector

It takes much trial and error to figure out the tone configuration that works well without introducing talk-off. Debug level be increased by changing the debug-level param in the descriptors tag of spandsp.conf.xml. Set the level to 2 to report additional detection information to assist in this process.

The "Tone report" log will tell you when a tone was detected. The "Tone segment" log will tell you each segment that is detected. The f1 and f2 values identify which frequencies were matched in that segment.

Dialplan Applications


Start background tone detection with cadence

 <!-- start detection for North American call progress tones -->
 <action application="start_tone_detect" data="1"/>


Stop background tone detection with cadence

<action application="stop_tone_detect"/>



Start background tone detection with cadence

 start_tone_detect <uuid> <descriptor name>


Stop background tone detection with cadence

 stop_tone_detect <uuid>



This event is fired when the tone detector detects a tone.

The following headers are set:


The Detected-Tone header will contain the name of the tone, as specified in the configuration file. The Unique-ID will contain the session UUID.


Spandsp implements the following codecs:

  • G.726
  • G.722
  • G.711
  • GSM
  • LPC-10

Software Modem

SpanDSP can also act as a softmodem. To enable the softmodem feature, see the HylaFax integration page. Setup should be identical, but you can send commands to it as if it was a regular dial up modem.

COM Port Setting for Windows

SpanDSP uses COM ports starting from COM4. To loopback the COM ports to an application, an external piece of software such as com0com may be required.

See also

Here are some more informations about T.38

Various Tones Used in National Networks

fax on AudioCodes Mediant