mirror of
https://github.com/asterisk/asterisk.git
synced 2025-10-23 21:19:09 +00:00
Compare commits
5 Commits
certified/
...
1.6.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a187d0c3f1 | ||
|
|
3eb28e7735 | ||
|
|
18a0385d23 | ||
|
|
acc92aeff8 | ||
|
|
bcad1ed930 |
1
.lastclean
Normal file
1
.lastclean
Normal file
@@ -0,0 +1 @@
|
||||
33
|
||||
1
trunk/.cleancount
Normal file
1
trunk/.cleancount
Normal file
@@ -0,0 +1 @@
|
||||
33
|
||||
22
trunk/BUGS
Normal file
22
trunk/BUGS
Normal file
@@ -0,0 +1,22 @@
|
||||
Asterisk Bug Tracking Information
|
||||
=================================
|
||||
|
||||
To learn about and report Asterisk bugs, please visit
|
||||
the official Asterisk Bug Tracker at:
|
||||
|
||||
http://bugs.digium.com
|
||||
|
||||
For more information on using the bug tracker, or to
|
||||
learn how you can contribute by acting as a bug marshal
|
||||
please see:
|
||||
|
||||
http://www.asterisk.org/developers/bug-guidelines
|
||||
|
||||
If you would like to submit a feature request, please
|
||||
resist the temptation to post it to the bug tracker.
|
||||
Feature requests should be posted to the asterisk-dev
|
||||
mailing list, located at:
|
||||
|
||||
http://lists.digium.com
|
||||
|
||||
Thank you!
|
||||
496
trunk/CHANGES
Normal file
496
trunk/CHANGES
Normal file
@@ -0,0 +1,496 @@
|
||||
------------------------------------------------------------------------------
|
||||
--- Functionality changes since Asterisk 1.4-beta was branched ----------------
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
AMI - The manager (TCP/TLS/HTTP)
|
||||
--------------------------------
|
||||
* Manager has undergone a lot of changes, all of them documented
|
||||
in doc/manager_1_1.txt
|
||||
* Manager version has changed to 1.1
|
||||
* Added a new action 'CoreShowChannels' to list currently defined channels
|
||||
and some information about them.
|
||||
* Added a new action 'SIPshowregistry' to list SIP registrations.
|
||||
* Added TLS support for the manager interface and HTTP server
|
||||
* Added the URI redirect option for the built-in HTTP server
|
||||
* The output of CallerID in Manager events is now more consistent.
|
||||
CallerIDNum is used for number and CallerIDName for name.
|
||||
* Enable https support for builtin web server.
|
||||
See configs/http.conf.sample for details.
|
||||
* Added a new action, GetConfigJSON, which can return the contents of an
|
||||
Asterisk configuration file in JSON format. This is intended to help
|
||||
improve the performance of AJAX applications using the manager interface
|
||||
over HTTP.
|
||||
* SIP and IAX manager events now use "ChannelType" in all cases where we
|
||||
indicate channel driver. Previously, we used a mixture of "Channel"
|
||||
and "ChannelDriver" headers.
|
||||
* Added a "Bridge" action which allows you to bridge any two channels that
|
||||
are currently active on the system.
|
||||
* Added a "ListAllVoicemailUsers" action that allows you to get a list of all
|
||||
the voicemail users setup.
|
||||
* Added 'DBDel' and 'DBDelTree' manager commands.
|
||||
* cdr_manager now reports events via the "cdr" level, separating it from
|
||||
the very verbose "call" level.
|
||||
* Manager users are now stored in memory. If you change the manager account
|
||||
list (delete or add accounts) you need to reload manager.
|
||||
* Added Masquerade manager event for when a masquerade happens between
|
||||
two channels.
|
||||
* Added "manager reload" command for the CLI
|
||||
* Lots of commands that only provided information are now allowed under the
|
||||
Reporting privilege, instead of only under Call or System.
|
||||
* The IAX* commands now require either System or Reporting privilege, to
|
||||
mirror the privileges of the SIP* commands.
|
||||
|
||||
Dialplan functions
|
||||
------------------
|
||||
* Added the DEVICE_STATE() dialplan function which allows retrieving any device
|
||||
state in the dialplan, as well as creating custom device states that are
|
||||
controllable from the dialplan.
|
||||
* Extend CALLERID() function with "pres" and "ton" parameters to
|
||||
fetch string representation of calling number presentation indicator
|
||||
and numeric representation of type of calling number value.
|
||||
* MailboxExists converted to dialplan function
|
||||
* A new option to Dial() for telling IP phones not to count the call
|
||||
as "missed" when dial times out and cancels.
|
||||
* Added LOCK(), TRYLOCK(), and UNLOCK(), which provide a single level dialplan
|
||||
mutex. No deadlocks are possible, as LOCK() only allows a single lock to be
|
||||
held for any given channel. Also, locks are automatically freed when a
|
||||
channel is hung up.
|
||||
* Added HINT() dialplan function that allows retrieving hint information.
|
||||
Hints are mappings between extensions and devices for the sake of
|
||||
determining the state of an extension. This function can retrieve the list
|
||||
of devices or the name associated with a hint.
|
||||
* Added EXTENSION_STATE() dialplan function which allows retrieving the state
|
||||
of any extension.
|
||||
* Added SYSINFO() dialplan function which allows retrieval of system information
|
||||
* Added a new dialplan function, DIALPLAN_EXISTS(), which allows you to check for
|
||||
the existence of a dialplan target.
|
||||
* Added two new dialplan functions, TOUPPER and TOLOWER, which convert a string to
|
||||
upper and lower case, respectively.
|
||||
|
||||
CLI Changes
|
||||
-----------
|
||||
* New CLI command "core show hint" (usage: core show hint <exten>)
|
||||
* New CLI command "core show settings"
|
||||
* Added 'core show channels count' CLI command.
|
||||
* Added the ability to set the core debug and verbose values on a per-file basis.
|
||||
* Added 'queue pause member' and 'queue unpause member' CLI commands
|
||||
* Ability to set process limits ("ulimit") without restarting Asterisk
|
||||
* Enhanced "agi debug" to print the channel name as a prefix to the debug
|
||||
output to make debugging on busy systems much easier.
|
||||
* New CLI commands "dialplan set extenpatternmatching true/false"
|
||||
* New CLI command: "core set chanvar" to set a channel variable from the CLI.
|
||||
* Added an easy way to execute Asterisk CLI commands at startup. Any commands
|
||||
listed in the startup_commands file in the Asterisk configuration directory
|
||||
will get executed.
|
||||
|
||||
SIP changes
|
||||
-----------
|
||||
* Improved NAT and STUN support.
|
||||
chan_sip now can use port numbers in bindaddr, externip and externhost
|
||||
options, as well as contact a STUN server to detect its external address
|
||||
for the SIP socket. See sip.conf.sample, 'NAT' section.
|
||||
* The default SIP useragent= identifier now includes the Asterisk version
|
||||
* A new option, match_auth_username in sip.conf changes the matching of incoming requests.
|
||||
If set, and the incoming request carries authentication info,
|
||||
the username to match in the users list is taken from the Digest header
|
||||
rather than from the From: field. This feature is considered experimental.
|
||||
* The "musiconhold" and "musicclass" settings in sip.conf are now removed,
|
||||
since they where replaced by "mohsuggest" and "mohinterpret" in version 1.4
|
||||
* The "localmask" setting was removed in version 1.2 and the reminder about it
|
||||
being removed is now also removed.
|
||||
* A new option "busylevel" for setting a level of calls where asterisk reports
|
||||
a device as busy, to separate it from call-limit. This value is also added
|
||||
to the SIP_PEER dialplan function.
|
||||
* A new realtime family called "sipregs" is now supported to store SIP registration
|
||||
data. If this family is defined, "sippeers" will be used for configuration and
|
||||
"sipregs" for registrations. If it's not defined, "sippeers" will be used for
|
||||
registration data, as before.
|
||||
* The SIPPEER function have new options for port address, call and pickup groups
|
||||
* Added support for T.140 realtime text in SIP/RTP
|
||||
* The "checkmwi" option has been removed from sip.conf, as it is no longer
|
||||
required due to the restructuring of how MWI is handled. See the descriptions
|
||||
in this file of the "pollmailboxes" and "pollfreq" options to voicemail.conf
|
||||
for more information.
|
||||
* Added rtpdest option to CHANNEL() dialplan function.
|
||||
* Added SIPREFERRINGCONTEXT and SIPREFERREDBYHDR variables which are set when a transfer takes place.
|
||||
* SIP now adds a header to the CANCEL if the call was answered by another phone
|
||||
in the same dial command, or if the new c option in dial() is used.
|
||||
* The new default is that 100 Trying is not sent on REGISTER attempts as the RFC specifically
|
||||
states it is not needed. For phones, however, that do require it the "registertrying" option
|
||||
has been added so it can be enabled.
|
||||
* A new option called "callcounter" (global/peer/user level) enables call counters needed
|
||||
for better status reports needed for queues and SIP subscriptions. (Call-Limit was previously
|
||||
used to enable this functionality).
|
||||
* New settings for timer T1 and timer B on a global level or per device. This makes it
|
||||
possible to force timeout faster on non-responsive SIP servers. These settings are
|
||||
considered advanced, so don't use them unless you have a problem.
|
||||
* Added a dial string option to be able to set the To: header in an INVITE to any
|
||||
SIP uri.
|
||||
* Added a new global and per-peer option, qualifyfreq, which allows you to configure
|
||||
the qualify frequency.
|
||||
* Added SIP Session Timers support (RFC 4028). This prevents stuck SIP sessions that
|
||||
were not properly torn down due to network or endpoint failures during an established
|
||||
SIP session.
|
||||
* Added TCP and TLS support for SIP. See doc/siptls.txt and configs/sip.conf.sample for
|
||||
more information on how it is used.
|
||||
|
||||
IAX2 changes
|
||||
------------
|
||||
* Added the trunkmaxsize configuration option to chan_iax2.
|
||||
* Added the srvlookup option to iax.conf
|
||||
* Added support for OSP. The token is set and retrieved through the CHANNEL()
|
||||
dialplan function.
|
||||
|
||||
XMPP Google Talk/Jingle changes
|
||||
-------------------------------
|
||||
* Added the bindaddr option to gtalk.conf.
|
||||
|
||||
Skinny changes
|
||||
-------------
|
||||
* Added skinny show device, skinny show line, and skinny show settings CLI commands.
|
||||
* Proper codec support in chan_skinny.
|
||||
* Added settings for IP and Ethernet QoS requests
|
||||
|
||||
MGCP changes
|
||||
------------
|
||||
* Added separate settings for media QoS in mgcp.conf
|
||||
|
||||
Console Channel Driver changes
|
||||
-------------------
|
||||
* Added experimental support for video send & receive to chan_oss.
|
||||
This requires SDL and ffmpeg/avcodec, plus Video4Linux or X11 to act as
|
||||
a video source.
|
||||
|
||||
Phone channel changes (chan_phone)
|
||||
----------------------------------
|
||||
* Added G729 passthrough support to chan_phone for Sigma Designs boards.
|
||||
|
||||
H.323 channel Changes
|
||||
---------------------
|
||||
* H323 remote hold notification support added (by NOTIFY message
|
||||
and/or H.450 supplementary service)
|
||||
|
||||
Local channel changes
|
||||
---------------------
|
||||
* The device state functionality in the Local channel driver has been updated
|
||||
to indicate INUSE or NOT_INUSE when a Local channel is being used as opposed
|
||||
to just UNKNOWN if the extension exists.
|
||||
* Added jitterbuffer support for chan_local. This allows you to use the
|
||||
generic jitterbuffer on incoming calls going to Asterisk applications.
|
||||
For example, this would allow you to use a jitterbuffer for an incoming
|
||||
SIP call to Voicemail by putting a Local channel in the middle. This
|
||||
feature is enabled by using the 'j' option in the Dial string to the Local
|
||||
channel in conjunction with the existing 'n' option for local channels.
|
||||
|
||||
Zaptel channel driver (chan_zap) Changes
|
||||
----------------------------------------
|
||||
* SS7 support in chan_zap (via libss7 library)
|
||||
* In India, some carriers transmit CID via dtmf. Some code has been added
|
||||
that will handle some situations. The cidstart=polarity_IN choice has been added for
|
||||
those carriers that transmit CID via dtmf after a polarity change.
|
||||
* CID matching information is now shown when doing 'dialplan show'.
|
||||
* Added zap show version CLI command to chan_zap.
|
||||
* Added setvar support to zapata.conf channel entries.
|
||||
* Added two new options: mwimonitor and mwimonitornotify. These options allow
|
||||
you to enable MWI monitoring on FXO lines. When the MWI state changes,
|
||||
the script specified in the mwimonitornotify option is executed. An internal
|
||||
event indicating the new state of the mailbox is also generated, so that
|
||||
the normal MWI facilities in Asterisk work as usual.
|
||||
* Added signalling type 'auto', which attempts to use the same signalling type
|
||||
for a channel as configured in Zaptel. This is primarily designed for analog
|
||||
ports, but will also work for digital ports that are configured for FXS or FXO
|
||||
signalling types. This mode is also the default now, so if your zapata.conf
|
||||
does not specify signalling for a channel (which is unlikely as the sample
|
||||
configuration file has always recommended specifying it for every channel) then
|
||||
the 'auto' mode will be used for that channel if possible.
|
||||
* Added a 'zap set dnd' command to allow CLI control of the Do-Not-Disturb
|
||||
state for a channel; also ensured that the DNDState Manager event is
|
||||
emitted no matter how the DND state is set or cleared.
|
||||
|
||||
New Channel Drivers
|
||||
-------------------
|
||||
* Added a new channel driver, chan_unistim. See doc/unistim.txt and
|
||||
configs/unistim.conf.sample for details. This new channel driver allows
|
||||
you to use Nortel i2002, i2004, and i2050 phones with Asterisk.
|
||||
* Added a new channel driver, chan_console, which uses portaudio as a cross
|
||||
platform audio interface. It was written as a channel driver that would
|
||||
work with Mac CoreAudio, but portaudio supports a number of other audio
|
||||
interfaces, as well. Note that this channel driver requires v19 or higher
|
||||
of portaudio; older versions have a different API.
|
||||
|
||||
DUNDi changes
|
||||
-------------
|
||||
* Added the ability to specify arguments to the Dial application when using
|
||||
the DUNDi switch in the dialplan.
|
||||
* Added the ability to set weights for responses dynamically. This can be
|
||||
done using a global variable or a dialplan function. Using the SHELL()
|
||||
function would allow you to have an external script set the weight for
|
||||
each response.
|
||||
* Added two new dialplan functions, DUNDIQUERY and DUNDIRESULT. These
|
||||
functions will allow you to initiate a DUNDi query from the dialplan,
|
||||
find out how many results there are, and access each one.
|
||||
|
||||
ENUM changes
|
||||
------------
|
||||
* Added two new dialplan functions, ENUMQUERY and ENUMRESULT. These
|
||||
functions will allow you to initiate an ENUM lookup from the dialplan,
|
||||
and Asterisk will cache the results. ENUMRESULT can be used to access
|
||||
the results without doing multiple DNS queries.
|
||||
|
||||
Voicemail Changes
|
||||
-----------------
|
||||
* Added the ability to customize which sound files are used for some of the
|
||||
prompts within the Voicemail application by changing them in voicemail.conf
|
||||
* Added the ability for the "voicemail show users" CLI command to show users
|
||||
configured by the dynamic realtime configuration method.
|
||||
* MWI (Message Waiting Indication) handling has been significantly
|
||||
restructured internally to Asterisk. It is now totally event based
|
||||
instead of polling based. The voicemail application will notify other
|
||||
modules that have subscribed to MWI events when something in the mailbox
|
||||
changes.
|
||||
This also means that if any other entity outside of Asterisk is changing
|
||||
the contents of mailboxes, then the voicemail application still needs to
|
||||
poll for changes. Examples of situations that would require this option
|
||||
are web interfaces to voicemail or an email client in the case of using
|
||||
IMAP storage. So, two new options have been added to voicemail.conf
|
||||
to account for this: "pollmailboxes" and "pollfreq". See the sample
|
||||
configuration file for details.
|
||||
* Added "tw" language support
|
||||
* Added support for storage of greetings using an IMAP server
|
||||
* Added ability to customize forward, reverse, stop, and pause keys for message playback
|
||||
* SMDI is now enabled in voicemail using the smdienable option.
|
||||
* A "lockmode" option has been added to asterisk.conf to configure the file
|
||||
locking method used for voicemail, and potentially other things in the
|
||||
future. The default is the old behavior, lockfile. However, there is a
|
||||
new method, "flock", that uses a different method for situations where the
|
||||
lockfile will not work, such as on SMB/CIFS mounts.
|
||||
* Added the ability to backup deleted messages, to ease recovery in the case
|
||||
that a user accidentally deletes a message, and discovers that they need it.
|
||||
|
||||
Queue changes
|
||||
-------------
|
||||
* Added the general option 'shared_lastcall' so that member's wrapuptime may be
|
||||
used across multiple queues.
|
||||
* Added QUEUE_VARIABLES function to set queue variables added setqueuevar and
|
||||
setqueueentryvar options for each queue, see queues.conf.sample for details.
|
||||
* Added keepstats option to queues.conf which will keep queue
|
||||
statistics during a reload.
|
||||
* setinterfacevar option in queues.conf also now sets a variable
|
||||
called MEMBERNAME which contains the member's name.
|
||||
* Added 'Strategy' field to manager event QueueParams which represents
|
||||
the queue strategy in use.
|
||||
* Added option to run macro when a queue member is connected to a caller,
|
||||
see queues.conf.sample for details.
|
||||
* app_queue now has a 'loose' option which is almost exactly like 'strict' except it
|
||||
does not count paused queue members as unavailable.
|
||||
* Added min-announce-frequency option to queues.conf which allows you to control the
|
||||
minimum amount of time between queue announcements for use when the caller's queue
|
||||
position changes frequently.
|
||||
* Added additional information to EXITWITHTIMEOUT and EXITWITHKEY events in the
|
||||
queue log.
|
||||
* Added ability for non-realtime queues to have realtime members
|
||||
* Added the "linear" strategy to queues.
|
||||
* Added the "wrandom" strategy to queues.
|
||||
* Added new channel variable QUEUE_MIN_PENALTY
|
||||
* QUEUE_MAX_PENALTY and QUEUE_MIN_PENALTY may be adjusted in mid-call by defining
|
||||
rules in queuerules.conf. See configs/queuerules.conf.sample for details
|
||||
* Added a new parameter for member definition, called state_interface. This may be
|
||||
used so that a member may be called via one interface but have a different interface's
|
||||
device state reported.
|
||||
|
||||
MeetMe Changes
|
||||
--------------
|
||||
* The 'o' option to provide an optimization has been removed and its functionality
|
||||
has been enabled by default.
|
||||
* When a conference is created, the UNIQUEID of the channel that caused it to be
|
||||
created is stored. Then, every channel that joins the conference will have the
|
||||
MEETMEUNIQUEID channel variable set with this ID. This can be used to relate
|
||||
callers that come and go from long standing conferences.
|
||||
* Added a new application, MeetMeChannelAdmin, which is similar to MeetMeAdmin,
|
||||
except it does operations on a channel by name, instead of number in a conference.
|
||||
This is a very useful feature in combination with the 'X' option to ChanSpy.
|
||||
* Added 'C' option to Meetme which causes a caller to continue in the dialplan
|
||||
when kicked out.
|
||||
* Added new RealTime functionality to provide support for scheduled conferencing.
|
||||
This includes optional messages to the caller if they attempt to join before
|
||||
the schedule start time, or to allow the caller to join the conference early.
|
||||
Also included is optional support for limiting the number of callers per
|
||||
RealTime conference.
|
||||
* Added the S() and L() options to the MeetMe application. These are pretty
|
||||
much identical to the S() and L() options to Dial(). They let you set
|
||||
timeouts for the conference, as well as have warning sounds played to
|
||||
let the caller know how much time is left, and when it is running out.
|
||||
* Added the ability to do "meetme concise" with the "meetme" CLI command.
|
||||
This extends the concise capabilities of this CLI command to include
|
||||
listing all conferences, instead of an addition to the other sub commands
|
||||
for the "meetme" command.
|
||||
* Added the ability to specify the music on hold class used to play into the
|
||||
conference when there is only one member and the M option is used.
|
||||
|
||||
Other Dialplan Application Changes
|
||||
----------------------------------
|
||||
* Argument support for Gosub application
|
||||
* From the to-do lists: straighten out the app timeout args:
|
||||
Wait() app now really does 0.3 seconds- was truncating arg to an int.
|
||||
WaitExten() same as Wait().
|
||||
Congestion() - Now takes floating pt. argument.
|
||||
Busy() - now takes floating pt. argument.
|
||||
Read() - timeout now can be floating pt.
|
||||
WaitForRing() now takes floating pt timeout arg.
|
||||
SpeechBackground() -- clarified in the docstrings that the timeout is an integer seconds.
|
||||
* Added 's' option to Page application.
|
||||
* Added 'E' and 'V' commands to ExternalIVR.
|
||||
* Added 'o' and 'X' options to Chanspy.
|
||||
* Added a new dialplan application, Bridge, which allows you to bridge the
|
||||
calling channel to any other active channel on the system.
|
||||
* Added the ability to specify a music on hold class to play instead of ringing
|
||||
for the SLATrunk application.
|
||||
* The Read application no longer exits the dialplan on error. Instead, it sets
|
||||
READSTATUS to ERROR, which you can catch and handle separately.
|
||||
* Added 'm' option to Directory, which lists out names, 8 at a time, instead
|
||||
of asking for verification of each name, one at a time.
|
||||
* Privacy() no longer uses privacy.conf, as all options are specifyable as
|
||||
direct options to the app.
|
||||
* AMD() has a new "maximum word length" option. "show application AMD" from the CLI
|
||||
for more details
|
||||
|
||||
Music On Hold Changes
|
||||
---------------------
|
||||
* A new option, "digit", has been added for music on hold classes in
|
||||
musiconhold.conf. If this is set for a music on hold class, a caller
|
||||
listening to music on hold can press this digit to switch to listening
|
||||
to this music on hold class.
|
||||
* Support for realtime music on hold has been added.
|
||||
* In conjunction with the realtime music on hold, a general section has
|
||||
been added to musiconhold.conf, its sole variable is cachertclasses. If this
|
||||
is set, then music on hold classes found in realtime will be cached in memory.
|
||||
|
||||
AEL Changes
|
||||
-----------
|
||||
* AEL upgraded to use the Gosub with Arguments instead
|
||||
of Macro application, to hopefully reduce the problems
|
||||
seen with the artificially low stack ceiling that
|
||||
Macro bumps into. Macros can only call other Macros
|
||||
to a depth of 7. Tests run using gosub, show depths
|
||||
limited only by virtual memory. A small test demonstrated
|
||||
recursive call depths of 100,000 without problems.
|
||||
-- in addition to this, all apps that allowed a macro
|
||||
to be called, as in Dial, queues, etc, are now allowing
|
||||
a gosub call in similar fashion.
|
||||
* AEL now generates LOCAL(argname) declarations when it
|
||||
Set()'s the each arg name to the value of ${ARG1}, ${ARG2),
|
||||
etc. That makes the arguments local in scope. The user
|
||||
can define their own local variables in macros, now,
|
||||
by saying "local myvar=someval;" or using Set() in this
|
||||
fashion: Set(LOCAL(myvar)=someval); ("local" is now
|
||||
an AEL keyword).
|
||||
* utils/conf2ael introduced. Will convert an extensions.conf
|
||||
file into extensions.ael. Very crude and unfinished, but
|
||||
will be improved as time goes by. Should be useful for a
|
||||
first pass at conversion.
|
||||
* aelparse will now read extensions.conf to see if a referenced
|
||||
macro or context is there before issueing a warning.
|
||||
|
||||
Call Features (res_features) Changes
|
||||
------------------------------------
|
||||
* Added the parkedcalltransfers option to features.conf
|
||||
* The built-in method for doing attended transfers has been updated to
|
||||
include some new options that allow you to have the transferee sent
|
||||
back to the person that did the transfer if the transfer is not successful.
|
||||
See the options "atxferdropcall", "atxferloopdelay", and "atxfercallbackretries"
|
||||
in features.conf.sample.
|
||||
* Added support for configuring named groups of custom call features in
|
||||
features.conf. This means that features can be written a single time, and
|
||||
then mapped into groups of features for different key mappings or easier
|
||||
access control.
|
||||
* Updated the ParkedCall application to allow you to not specify a parking
|
||||
extension. If you don't specify a parking space to pick up, it will grab
|
||||
the first one available.
|
||||
|
||||
Language Support Changes
|
||||
------------------------
|
||||
* Brazilian Portuguese (pt-BR) in VM, and say.c was added
|
||||
* Added support for the Hungarian language for saying numbers, dates, and times.
|
||||
|
||||
AGI Changes
|
||||
-----------
|
||||
* Added SPEECH commands for speech recognition. A complete listing can be found
|
||||
using agi show.
|
||||
|
||||
Logger changes
|
||||
--------------
|
||||
* Added rotatestrategy option to logger.conf, along with two new options:
|
||||
"timestamp" which will use the time to name the logger files instead of
|
||||
sequence number; and "rotate", which rotates the names of the logfiles,
|
||||
similar to the way syslog rotates files.
|
||||
* Added exec_after_rotate option to logger.conf, which allows a system
|
||||
command to be run after rotation. This is primarily useful with
|
||||
rotatestrategry=rotate, to allow a limit on the number of logfiles kept
|
||||
and to ensure that the oldest log file gets deleted.
|
||||
* Added realtime support for the queue log
|
||||
|
||||
Miscellaneous New Modules
|
||||
-------------------------
|
||||
* Added a new CDR module, cdr_sqlite3_custom.
|
||||
* Added a new realtime configuration module, res_config_sqlite
|
||||
* Added a new codec translation module, codec_resample, which re-samples
|
||||
signed linear audio between 8 kHz and 16 kHz to help support wideband
|
||||
codecs.
|
||||
* Added a new module, res_phoneprov, which allows auto-provisioning of phones
|
||||
based on configuration templates that use Asterisk dialplan function and
|
||||
variable substitution. It should be possible to create phone profiles and
|
||||
templates that work for the majority of phones provisioned over http. It
|
||||
is currently only intended to provision a single user account per phone.
|
||||
An example profile and set of templates for Polycom phones is provided.
|
||||
NOTE: Polycom firmware is not included, but should be placed in
|
||||
AST_DATA_DIR/phoneprov/configs to match up with the included templates.
|
||||
* Added a new module, app_jack, which provides interfaces to JACK, the Jack
|
||||
Audio Connection Kit (http://www.jackaudio.org/). Two interfaces are
|
||||
provided; there is a JACK() application, and a JACK_HOOK() function. Both
|
||||
interfaces create an input and output JACK port. The application makes
|
||||
these ports the endpoint of the call. The audio coming from the channel
|
||||
goes out the output port and whatever comes back in on the input port is
|
||||
what gets sent to the channel. The JACK_HOOK() function turns on a JACK
|
||||
audiohook on the channel. This lets you run the audio coming from a
|
||||
channel through JACK, and whatever comes back in is what gets forwarded
|
||||
on as the channel's audio. This is very useful for building custom
|
||||
vocoders or doing recording or analysis of the channel's audio in another
|
||||
application.
|
||||
* Added a new module, res_config_curl, which permits using a HTTP POST url
|
||||
to retrieve, create, update, and delete realtime information from a remote
|
||||
web server. Note that this module requires func_curl.so to be loaded for
|
||||
backend functionality.
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
* Ability to use libcap to set high ToS bits when non-root
|
||||
on Linux. If configure is unable to find libcap then you
|
||||
can use --with-cap to specify the path.
|
||||
* Added maxfiles option to options section of asterisk.conf which allows you to specify
|
||||
what Asterisk should set as the maximum number of open files when it loads.
|
||||
* Added the jittertargetextra configuration option.
|
||||
* The cdr_manager module has a [mappings] feature, like cdr_custom,
|
||||
to add fields to the manager event from the CDR variables.
|
||||
* Added support for setting the CoS for VLAN traffic (802.1p). See the sample
|
||||
configuration files for the IP channel drivers. The new option is "cos".
|
||||
This information is also documented in doc/qos.tex, or the IP Quality of Service
|
||||
section of asterisk.pdf.
|
||||
* When originating a call using AMI or pbx_spool that fails the reason for failure
|
||||
will now be available in the failed extension using the REASON dialplan variable.
|
||||
* Added support for reading the TOUCH_MONITOR_PREFIX channel variable.
|
||||
It allows you to configure a prefix for auto-monitor recordings.
|
||||
* Added support for writing and running your dialplan in lua. See
|
||||
configs/extensions.lua.sample for examples of how to do this.
|
||||
* A new extension pattern matching algorithm, based on a trie, is introduced
|
||||
here, that could noticeably speed up mid-sized to large dialplans.
|
||||
It is NOT used by default, as duplicating the behaviour of the old pattern
|
||||
matcher is still under development. A config file option, in extensions.conf,
|
||||
in the [general] section, called "extenpatternmatchingnew", is by default
|
||||
set to false; setting that to true will force the use of the new algorithm.
|
||||
Also, the cli commands "dialplan set extenpatternmatchingnew true/false" can
|
||||
be used to switch the algorithms at run time.
|
||||
* A new option when starting a remote asterisk (rasterisk, asterisk -r) for
|
||||
specifying which socket to use to connect to the running Asterisk daemon
|
||||
(-s)
|
||||
* Added logging to 'make update' command. See update.log
|
||||
|
||||
341
trunk/COPYING
Normal file
341
trunk/COPYING
Normal file
@@ -0,0 +1,341 @@
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) 19yy <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) 19yy name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
244
trunk/CREDITS
Normal file
244
trunk/CREDITS
Normal file
@@ -0,0 +1,244 @@
|
||||
|
||||
=== DEVELOPMENT SUPPORT ===
|
||||
We'd like to thank the following companies for helping fund development of
|
||||
Asterisk:
|
||||
|
||||
Pilosoft, Inc. - for supporting ADSI development in Asterisk
|
||||
|
||||
Asterlink, Inc. - for supporting broad Asterisk development
|
||||
|
||||
GFS - for supporting ALSA development
|
||||
|
||||
Telesthetic - for supporting SIP development
|
||||
|
||||
Christos Ricudis - for substantial code contributions
|
||||
|
||||
nic.at - ENUM support in Asterisk
|
||||
|
||||
Paul Bagyenda, Digital Solutions - for initial Voicetronix driver development
|
||||
|
||||
John Todd, TalkPlus, Inc. and JR Richardson, Ntegrated Solutions. - for funding
|
||||
the development of SIP Session Timers support.
|
||||
|
||||
=== WISHLIST CONTRIBUTERS ===
|
||||
Jeremy McNamara - SpeeX support
|
||||
Nick Seraphin - RDNIS support
|
||||
Gary - Phonejack ADSI (in progress)
|
||||
Wasim - Hangup detect
|
||||
|
||||
=== HARDWARE DONORS ===
|
||||
* Thanks to QuickNet Technologies for their donation of an Internet
|
||||
PhoneJack and Linejack card to the project. (http://www.quicknet.net)
|
||||
|
||||
* Thanks to VoipSupply for their donation of Sipura ATAs to the project for
|
||||
T.38 testing. (http://www.voipsupply.com)
|
||||
|
||||
* Thanks to Grandstream for their donation of ATAs to the project for
|
||||
T.38 testing. (http://www.grandstream.com)
|
||||
|
||||
=== MISCELLANEOUS PATCHES ===
|
||||
Jim Dixon - Zapata Telephony and app_rpt
|
||||
http://www.zapatatelephony.org/app_rpt.html
|
||||
|
||||
Russell Bryant - Asterisk release manager and countless enhancements and bug
|
||||
fixes.
|
||||
russell(AT)digium.com
|
||||
|
||||
Anthony Minessale II - Countless big and small fixes, and relentless forward
|
||||
push. ChanSpy, ForkCDR, ControlPlayback, While/EndWhile, DumpChan, Dictate,
|
||||
MacroIf, ExecIf, ExecIfTime, RetryDial, MixMonitor applications; many
|
||||
realtime concepts and implementation pieces, including res_config_odbc;
|
||||
format_slin; cdr_custom; several features in Dial including L(), G() and
|
||||
enhancements to M() and D(); several CDR enhancements including CDR
|
||||
variables; attended transfer; one touch record; native MOH; manager
|
||||
eventmask; command line '-t' flag to allow recording/voicemail on nfs
|
||||
shares; #exec command and multiline comments in config files; setvar in iax
|
||||
and sip configs.
|
||||
anthmct(AT)yahoo.com http://www.asterlink.com
|
||||
|
||||
James Golovich - Innumerable contributions, including SIP TCP and TLS support.
|
||||
You can find him and asterisk-perl at http://asterisk.gnuinter.net
|
||||
|
||||
Andre Bierwirth - Extension hints and status
|
||||
|
||||
Jean-Denis Girard - Various contributions from the South Pacific Islands
|
||||
jd-girard(AT)esoft.pf http://www.esoft.pf
|
||||
|
||||
William Jordan / Vonage - MySQL enhancements to Voicemail
|
||||
wjordan(AT)vonage.com
|
||||
|
||||
Jac Kersing - Various fixes
|
||||
|
||||
Steven Critchfield - Seek and Trunc functions for playback and recording
|
||||
critch(AT)basesys.com
|
||||
|
||||
Jefferson Noxon - app_lookupcidname, app_db, and various other contributions
|
||||
|
||||
Klaus-Peter Junghanns - in-band DTMF on SIP and MGCP
|
||||
|
||||
Ross Finlayson - Dynamic RTP payload support
|
||||
|
||||
Mahmut Fettahlioglu - Audio recording, music-on-hold changes, alaw file
|
||||
format, and various fixes. Can be contacted at mahmut(AT)oa.com.au
|
||||
|
||||
James Dennis - Cisco SIP compatibility patches to work with SIP service
|
||||
providers. Can be contacted at asterisk(AT)jdennis.net
|
||||
|
||||
Tilghman Lesher - ast_localtime(); ast_say_date_with_format();
|
||||
GotoIfTime, SayUnixTime, HasNewVoicemail applications;
|
||||
CUT, SORT, EVAL, CURL, FIELDQTY, STRFTIME, some QUEUE* functions;
|
||||
func_odbc, cdr_adaptive_odbc, and other innumerable bug fixes.
|
||||
tilghman(AT)digium.com http://asterisk.drunkcoder.com/
|
||||
|
||||
Jayson Vantuyl - Manager protocol changes, various other bugs.
|
||||
jvantuyl(AT)computingedge.net
|
||||
|
||||
Thorsten Lockert - OpenBSD, FreeBSD ports, making MacOS X port run on 10.3,
|
||||
dialplan include verification, route lookup on OpenBSD, SNMP agent
|
||||
support (res_snmp), various other bugs. tholo(AT)sigmasoft.com
|
||||
|
||||
Josh Roberson - chan_zap reload support, Advanced Voicemail Features, & other
|
||||
misc. patches. - josh(AT)asteriasgi.com, http://www.asteriasgi.com
|
||||
|
||||
William Waites - syslog support, SIP NAT traversal for SIP-UA. ww(AT)styx.org
|
||||
|
||||
Rich Murphey - Porting to FreeBSD, NetBSD, OpenBSD, and Darwin.
|
||||
rich(AT)whiteoaklabs.com http://whiteoaklabs.com
|
||||
|
||||
Simon Lockhart - Porting to Solaris (based on work of Logan ???)
|
||||
simon(AT)slimey.org
|
||||
|
||||
Olle E. Johansson - SIP RFC compliance, documentation and testing, testing,
|
||||
testing; MiniVM - the small voicemail system, many documentation
|
||||
updates/corrections, and many bug fixes.
|
||||
oej(AT)edvina.net, http://edvina.net
|
||||
|
||||
Steve Kann - new jitter buffer for IAX2
|
||||
stevek(AT)stevek.com
|
||||
|
||||
Constantine Filin - major contributions to the Asterisk Realtime Architecture
|
||||
|
||||
Steve Murphy - privacy support, $[ ] parser upgrade, AEL2 parser upgrade.
|
||||
murf(AT)digium.com
|
||||
|
||||
Claude Patry - bug fixes, feature enhancements, and bug marshalling
|
||||
cpatry(AT)gmail.com
|
||||
|
||||
Miroslav Nachev, miro(AT)space-comm.com COSMOS Software Enterprises, Ltd.
|
||||
- for Variable for No Answer Timeout for Attended Transfer
|
||||
|
||||
Slav Klenov & Vanheuverzwijn Joachim - development of the generic jitterbuffer
|
||||
Securax Ltd. info(AT)securax.be
|
||||
|
||||
Roy Sigurd Karlsbakk - providing funding for generic jitterbuffer development
|
||||
roy(AT)karlsbakk.net, Briiz Telecom AS
|
||||
|
||||
Voop AS, Nuvio Inc, Inotel S.A and Foniris Telecom A/S - funding for rewrite
|
||||
of SIP transfers
|
||||
|
||||
Philippe Sultan - RADIUS CDR module, many fixes to res_jabber and gtalk/jingle
|
||||
channel drivers.
|
||||
INRIA, http://www.inria.fr/
|
||||
|
||||
John Martin, Aupix - Improved video support in the SIP channel
|
||||
T.140 text support in RTP/SIP
|
||||
|
||||
Steve Underwood - Provided T.38 pass through support.
|
||||
|
||||
George Konstantoulakis - Support for Greek in voicemail added by InAccess
|
||||
Networks (work funded by HOL, www.hol.gr) gkon(AT)inaccessnetworks.com
|
||||
|
||||
Daniel Nylander - Support for Swedish and Norwegian languages in voicemail.
|
||||
http://www.danielnylander.se/
|
||||
|
||||
Stojan Sljivic - An option for maximum number of messsages per mailbox in
|
||||
voicemail. Also an issue with voicemail synchronization has been fixed.
|
||||
GDS Partners www.gdspartners.com . stojan.sljivic(AT)gdspartners.com
|
||||
|
||||
Bartosz Supczinski - Support for Polish added by DIR (www.dir.pl)
|
||||
Bartosz.Supczinski(AT)dir.pl
|
||||
|
||||
James Rothenberger - Support for IMAP storage integration added by
|
||||
OneBizTone LLC Work funded by University of Pennsylvania jar(AT)onebiztone.com
|
||||
|
||||
Paul Cadach - Bringing chan_h323 up to date, bug fixes, and more!
|
||||
|
||||
Voop AS - Financial support for a lot of work with the SIP driver and the IAX
|
||||
trunk MTU patch
|
||||
|
||||
Cedric Hans - Development of chan_unistim
|
||||
cedric.hans(AT)mlkj.net
|
||||
|
||||
Sergio Fadda - console_video: video support for chan_oss and chan_alsa
|
||||
|
||||
Marta Carbone - console_video and the astobj2 framework
|
||||
|
||||
Luigi Rizzo - astobj2, console_video, windows build, chan_oss cleanup,
|
||||
and a bunch of infrastructure work (loader, new_cli, ...)
|
||||
|
||||
Brett Bryant - digit option for musiconhold selection, ENUMQUERY and ENUMRESULT functions,
|
||||
feature group configuration for features.conf, per-file CLI debug and verbose settings,
|
||||
TCP and TLS support for SIP, and various bug fixes.
|
||||
brettbryant(AT)gmail.com
|
||||
|
||||
=== OTHER CONTRIBUTIONS ===
|
||||
John Todd - Monkey sounds and associated teletorture prompt
|
||||
Michael Jerris - bug marshaling
|
||||
Leif Madsen, Jared Smith and Jim van Meggelen - the Asterisk book
|
||||
available under a Creative Commons License at http://www.asteriskdocs.org
|
||||
Brian M. Clapper - poll.c emulation
|
||||
This product includes software developed by Brian M. Clapper <bmc(AT)clapper.org>
|
||||
|
||||
=== HOLD MUSIC ===
|
||||
Music provided by www.freeplaymusic.com
|
||||
|
||||
=== OTHER SOURCE CODE IN ASTERISK ===
|
||||
Asterisk uses libedit, the lightweight readline replacement from NetBSD.
|
||||
The cdr_radius module uses libradiusclient-ng, which is also from NetBSD.
|
||||
They are BSD-licensed and require the following statement:
|
||||
|
||||
This product includes software developed by the NetBSD
|
||||
Foundation, Inc. and its contributors.
|
||||
|
||||
Digium did not implement the codecs in Asterisk. Here is the copyright on the
|
||||
GSM source:
|
||||
|
||||
Copyright 1992, 1993, 1994 by Jutta Degener and Carsten Bormann,
|
||||
Technische Universitaet Berlin
|
||||
|
||||
Any use of this software is permitted provided that this notice is not
|
||||
removed and that neither the authors nor the Technische Universitaet Berlin
|
||||
are deemed to have made any representations as to the suitability of this
|
||||
software for any purpose nor are held responsible for any defects of
|
||||
this software. THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE.
|
||||
|
||||
As a matter of courtesy, the authors request to be informed about uses
|
||||
this software has found, about bugs in this software, and about any
|
||||
improvements that may be of general interest.
|
||||
|
||||
Berlin, 28.11.1994
|
||||
Jutta Degener
|
||||
Carsten Bormann
|
||||
|
||||
And the copyright on the ADPCM source:
|
||||
|
||||
Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The
|
||||
Netherlands.
|
||||
|
||||
All Rights Reserved
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation, and that the names of Stichting Mathematisch
|
||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||
distribution of the software without specific, written prior permission.
|
||||
|
||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
69
trunk/LICENSE
Normal file
69
trunk/LICENSE
Normal file
@@ -0,0 +1,69 @@
|
||||
Asterisk is distributed under the GNU General Public License version 2
|
||||
and is also available under alternative licenses negotiated directly
|
||||
with Digium, Inc. If you obtained Asterisk under the GPL, then the GPL
|
||||
applies to all loadable Asterisk modules used on your system as well,
|
||||
except as defined below. The GPL (version 2) is included in this
|
||||
source tree in the file COPYING.
|
||||
|
||||
This package also includes various components that are not part of
|
||||
Asterisk itself; these components are in the 'contrib' directory
|
||||
and its subdirectories. Most of these components are also
|
||||
distributed under the GPL version 2 as well, except for the following:
|
||||
|
||||
contrib/firmware/iax/iaxy.bin:
|
||||
This file is Copyright (C) Digium, Inc. and is licensed for
|
||||
use with Digium IAXy hardware devices only. It can be
|
||||
distributed freely as long as the distribution is in the
|
||||
original form present in this package (not reformatted or
|
||||
modified).
|
||||
|
||||
Digium, Inc. (formerly Linux Support Services) holds copyright
|
||||
and/or sufficient licenses to all components of the Asterisk
|
||||
package, and therefore can grant, at its sole discretion, the ability
|
||||
for companies, individuals, or organizations to create proprietary or
|
||||
Open Source (even if not GPL) modules which may be dynamically linked at
|
||||
runtime with the portions of Asterisk which fall under our
|
||||
copyright/license umbrella, or are distributed under more flexible
|
||||
licenses than GPL.
|
||||
|
||||
If you wish to use our code in other GPL programs, don't worry --
|
||||
there is no requirement that you provide the same exception in your
|
||||
GPL'd products (although if you've written a module for Asterisk we
|
||||
would strongly encourage you to make the same exception that we do).
|
||||
|
||||
Specific permission is also granted to link Asterisk with OpenSSL and
|
||||
OpenH323 and distribute the resulting binary files.
|
||||
|
||||
In addition, Asterisk implements two management/control protocols: the
|
||||
Asterisk Manager Interface (AMI) and the Asterisk Gateway Interface
|
||||
(AGI). It is our belief that applications using these protocols to
|
||||
manage or control an Asterisk instance do not have to be licensed
|
||||
under the GPL or a compatible license, as we believe these protocols
|
||||
do not create a 'derivative work' as referred to in the GPL. However,
|
||||
should any court or other judiciary body find that these protocols do
|
||||
fall under the terms of the GPL, then we hereby grant you a license to
|
||||
use these protocols in combination with Asterisk in external
|
||||
applications licensed under any license you wish.
|
||||
|
||||
The 'Asterisk' name and logos are trademarks owned by Digium, Inc.,
|
||||
and use of them is subject to our trademark licensing policies. If you
|
||||
wish to use these trademarks for purposes other than simple
|
||||
redistribution of Asterisk source code obtained from Digium, you
|
||||
should contact our licensing department to determine the necessary
|
||||
steps you must take. For more information on this policy, please read:
|
||||
|
||||
http://www.digium.com/en/company/profile/trademarkpolicy.php
|
||||
|
||||
If you have any questions regarding our licensing policy, please
|
||||
contact us:
|
||||
|
||||
+1.877.344.4861 (via telephone in the USA)
|
||||
+1.256.428.6000 (via telephone outside the USA)
|
||||
+1.256.864.0464 (via FAX inside or outside the USA)
|
||||
IAX2/misery.digium.com/6000 (via IAX2)
|
||||
licensing@digium.com (via email)
|
||||
|
||||
Digium, Inc.
|
||||
445 Jan Davis Drive
|
||||
Huntsville, AL 35806
|
||||
USA
|
||||
834
trunk/Makefile
Normal file
834
trunk/Makefile
Normal file
@@ -0,0 +1,834 @@
|
||||
#
|
||||
# Asterisk -- A telephony toolkit for Linux.
|
||||
#
|
||||
# Top level Makefile
|
||||
#
|
||||
# Copyright (C) 1999-2006, Digium, Inc.
|
||||
#
|
||||
# Mark Spencer <markster@digium.com>
|
||||
#
|
||||
# This program is free software, distributed under the terms of
|
||||
# the GNU General Public License
|
||||
#
|
||||
|
||||
# All Makefiles use the following variables:
|
||||
#
|
||||
# ASTCFLAGS - compiler options
|
||||
# ASTLDFLAGS - linker flags (not libraries)
|
||||
# LIBS - additional libraries, at top-level for all links,
|
||||
# on a single object just for that object
|
||||
# SOLINK - linker flags used only for creating shared objects (.so files),
|
||||
# used for all .so links
|
||||
#
|
||||
# Initial values for ASTCFLAGS and ASTLDFLAGS can be specified in the
|
||||
# environment when running make, as follows:
|
||||
#
|
||||
# $ ASTCFLAGS="-Werror" make ...
|
||||
#
|
||||
# note that this is different from
|
||||
#
|
||||
# $ make ASTCFLAGS="-Werror" ...
|
||||
#
|
||||
# If you need to pass compiler/linker flags as 'make' variables, please use
|
||||
#
|
||||
# $ make COPTS="..." LDOPTS="..." ...
|
||||
#
|
||||
#
|
||||
# You can add the path of local module subdirs from the command line with
|
||||
# make LOCAL_MOD_SUBDIRS= ....
|
||||
|
||||
export ASTTOPDIR # Top level dir, used in subdirs' Makefiles
|
||||
export ASTERISKVERSION
|
||||
export ASTERISKVERSIONNUM
|
||||
|
||||
#--- values used for default paths
|
||||
|
||||
# DESTDIR is the staging (or final) directory where files are copied
|
||||
# during the install process. Define it before 'export', otherwise
|
||||
# export will set it to the empty string making ?= fail.
|
||||
# WARNING: do not put spaces or comments after the value.
|
||||
DESTDIR?=$(INSTALL_PATH)
|
||||
export DESTDIR
|
||||
|
||||
export INSTALL_PATH # Additional prefix for the following paths
|
||||
export ASTETCDIR # Path for config files
|
||||
export ASTVARRUNDIR
|
||||
export MODULES_DIR
|
||||
export ASTSPOOLDIR
|
||||
export ASTVARLIBDIR
|
||||
export ASTDATADIR
|
||||
export ASTLOGDIR
|
||||
export ASTLIBDIR
|
||||
export ASTMANDIR
|
||||
export ASTHEADERDIR
|
||||
export ASTBINDIR
|
||||
export ASTSBINDIR
|
||||
export AGI_DIR
|
||||
export ASTCONFPATH
|
||||
|
||||
export OSARCH # Operating system
|
||||
export PROC # Processor type
|
||||
|
||||
export NOISY_BUILD # Used in Makefile.rules
|
||||
export MENUSELECT_CFLAGS # Options selected in menuselect.
|
||||
export AST_DEVMODE # Set to "yes" for additional compiler
|
||||
# and runtime checks
|
||||
|
||||
export SOLINK # linker flags for shared objects
|
||||
export STATIC_BUILD # Additional cflags, set to -static
|
||||
# for static builds. Probably
|
||||
# should go directly to ASTLDFLAGS
|
||||
|
||||
#--- paths to various commands
|
||||
export CC
|
||||
export CXX
|
||||
export AR
|
||||
export RANLIB
|
||||
export HOST_CC
|
||||
export INSTALL
|
||||
export STRIP
|
||||
export DOWNLOAD
|
||||
export AWK
|
||||
export GREP
|
||||
export ID
|
||||
|
||||
# even though we could use '-include makeopts' here, use a wildcard
|
||||
# lookup anyway, so that make won't try to build makeopts if it doesn't
|
||||
# exist (other rules will force it to be built if needed)
|
||||
ifneq ($(wildcard makeopts),)
|
||||
include makeopts
|
||||
endif
|
||||
|
||||
# Some build systems, such as the one in openwrt, like to pass custom target
|
||||
# CFLAGS and LDFLAGS in the COPTS and LDOPTS variables.
|
||||
ASTCFLAGS+=$(COPTS)
|
||||
ASTLDFLAGS+=$(LDOPTS)
|
||||
|
||||
#Uncomment this to see all build commands instead of 'quiet' output
|
||||
#NOISY_BUILD=yes
|
||||
|
||||
ASTTOPDIR:=$(CURDIR)
|
||||
|
||||
# Overwite config files on "make samples"
|
||||
OVERWRITE=y
|
||||
|
||||
# Include debug and macro symbols in the executables (-g) and profiling info (-pg)
|
||||
DEBUG=-g3
|
||||
|
||||
|
||||
# Define standard directories for various platforms
|
||||
# These apply if they are not redefined in asterisk.conf
|
||||
ifeq ($(OSARCH),SunOS)
|
||||
ASTETCDIR=/var/etc/asterisk
|
||||
ASTLIBDIR=/opt/asterisk/lib
|
||||
ASTVARLIBDIR=/var/opt/asterisk
|
||||
ASTDBDIR=$(ASTVARLIBDIR)
|
||||
ASTKEYDIR=$(ASTVARLIBDIR)
|
||||
ASTSPOOLDIR=/var/spool/asterisk
|
||||
ASTLOGDIR=/var/log/asterisk
|
||||
ASTHEADERDIR=/opt/asterisk/include
|
||||
ASTBINDIR=/opt/asterisk/bin
|
||||
ASTSBINDIR=/opt/asterisk/sbin
|
||||
ASTVARRUNDIR=/var/run/asterisk
|
||||
ASTMANDIR=/opt/asterisk/man
|
||||
else
|
||||
ASTETCDIR=$(sysconfdir)/asterisk
|
||||
ASTLIBDIR=$(libdir)/asterisk
|
||||
ASTHEADERDIR=$(includedir)/asterisk
|
||||
ASTBINDIR=$(bindir)
|
||||
ASTSBINDIR=$(sbindir)
|
||||
ASTSPOOLDIR=$(localstatedir)/spool/asterisk
|
||||
ASTLOGDIR=$(localstatedir)/log/asterisk
|
||||
ASTVARRUNDIR=$(localstatedir)/run
|
||||
ASTMANDIR=$(mandir)
|
||||
ifneq ($(findstring BSD,$(OSARCH)),)
|
||||
ASTVARLIBDIR=$(prefix)/share/asterisk
|
||||
ASTVARRUNDIR=$(localstatedir)/run/asterisk
|
||||
ASTDBDIR=$(localstatedir)/db/asterisk
|
||||
else
|
||||
ASTVARLIBDIR=$(localstatedir)/lib/asterisk
|
||||
ASTDBDIR=$(ASTVARLIBDIR)
|
||||
endif
|
||||
ASTKEYDIR=$(ASTVARLIBDIR)
|
||||
endif
|
||||
ifeq ($(ASTDATADIR),)
|
||||
ASTDATADIR:=$(ASTVARLIBDIR)
|
||||
endif
|
||||
|
||||
# Asterisk.conf is located in ASTETCDIR or by using the -C flag
|
||||
# when starting Asterisk
|
||||
ASTCONFPATH=$(ASTETCDIR)/asterisk.conf
|
||||
MODULES_DIR=$(ASTLIBDIR)/modules
|
||||
AGI_DIR=$(ASTDATADIR)/agi-bin
|
||||
|
||||
# If you use Apache, you may determine by a grep 'DocumentRoot' of your httpd.conf file
|
||||
HTTP_DOCSDIR=/var/www/html
|
||||
# Determine by a grep 'ScriptAlias' of your Apache httpd.conf file
|
||||
HTTP_CGIDIR=/var/www/cgi-bin
|
||||
|
||||
# Uncomment this to use the older DSP routines
|
||||
#ASTCFLAGS+=-DOLD_DSP_ROUTINES
|
||||
|
||||
# If the file .asterisk.makeopts is present in your home directory, you can
|
||||
# include all of your favorite menuselect options so that every time you download
|
||||
# a new version of Asterisk, you don't have to run menuselect to set them.
|
||||
# The file /etc/asterisk.makeopts will also be included but can be overridden
|
||||
# by the file in your home directory.
|
||||
|
||||
GLOBAL_MAKEOPTS=$(wildcard /etc/asterisk.makeopts)
|
||||
USER_MAKEOPTS=$(wildcard ~/.asterisk.makeopts)
|
||||
|
||||
MOD_SUBDIR_CFLAGS=-I$(ASTTOPDIR)/include
|
||||
OTHER_SUBDIR_CFLAGS=-I$(ASTTOPDIR)/include
|
||||
|
||||
# Create OPTIONS variable, but probably we can assign directly to ASTCFLAGS
|
||||
OPTIONS=
|
||||
|
||||
ifeq ($(OSARCH),linux-gnu)
|
||||
ifeq ($(PROC),x86_64)
|
||||
# You must have GCC 3.4 to use k8, otherwise use athlon
|
||||
PROC=k8
|
||||
#PROC=athlon
|
||||
endif
|
||||
|
||||
ifeq ($(PROC),sparc64)
|
||||
#The problem with sparc is the best stuff is in newer versions of gcc (post 3.0) only.
|
||||
#This works for even old (2.96) versions of gcc and provides a small boost either way.
|
||||
#A ultrasparc cpu is really v9 but the stock debian stable 3.0 gcc doesn't support it.
|
||||
#So we go lowest common available by gcc and go a step down, still a step up from
|
||||
#the default as we now have a better instruction set to work with. - Belgarath
|
||||
PROC=ultrasparc
|
||||
OPTIONS+=$(shell if $(CC) -mtune=$(PROC) -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-mtune=$(PROC)"; fi)
|
||||
OPTIONS+=$(shell if $(CC) -mcpu=v8 -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-mcpu=v8"; fi)
|
||||
OPTIONS+=-fomit-frame-pointer
|
||||
endif
|
||||
|
||||
ifeq ($(PROC),arm)
|
||||
# The Cirrus logic is the only heavily shipping arm processor with a real floating point unit
|
||||
ifeq ($(SUB_PROC),maverick)
|
||||
OPTIONS+=-fsigned-char -mcpu=ep9312
|
||||
else
|
||||
ifeq ($(SUB_PROC),xscale)
|
||||
OPTIONS+=-fsigned-char -mcpu=xscale
|
||||
else
|
||||
OPTIONS+=-fsigned-char
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(findstring -save-temps,$(ASTCFLAGS)),)
|
||||
ASTCFLAGS+=-pipe
|
||||
endif
|
||||
|
||||
ASTCFLAGS+=-Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations $(DEBUG)
|
||||
|
||||
ASTCFLAGS+=-include $(ASTTOPDIR)/include/asterisk/autoconfig.h
|
||||
|
||||
ifeq ($(AST_DEVMODE),yes)
|
||||
ASTCFLAGS+=-Werror -Wunused -Wundef $(AST_DECLARATION_AFTER_STATEMENT)
|
||||
endif
|
||||
|
||||
ifneq ($(findstring BSD,$(OSARCH)),)
|
||||
ASTCFLAGS+=-I/usr/local/include
|
||||
ASTLDFLAGS+=-L/usr/local/lib
|
||||
endif
|
||||
|
||||
ifneq ($(PROC),ultrasparc)
|
||||
ASTCFLAGS+=$(shell if $(CC) -march=$(PROC) -S -o /dev/null -xc /dev/null >/dev/null 2>&1; then echo "-march=$(PROC)"; fi)
|
||||
endif
|
||||
|
||||
ifeq ($(PROC),ppc)
|
||||
ASTCFLAGS+=-fsigned-char
|
||||
endif
|
||||
|
||||
ifeq ($(OSARCH),FreeBSD)
|
||||
# -V is understood by BSD Make, not by GNU make.
|
||||
BSDVERSION=$(shell make -V OSVERSION -f /usr/share/mk/bsd.port.subdir.mk)
|
||||
ASTCFLAGS+=$(shell if test $(BSDVERSION) -lt 500016 ; then echo "-D_THREAD_SAFE"; fi)
|
||||
endif
|
||||
|
||||
ifeq ($(OSARCH),NetBSD)
|
||||
ASTCFLAGS+=-pthread -I/usr/pkg/include
|
||||
endif
|
||||
|
||||
ifeq ($(OSARCH),OpenBSD)
|
||||
ASTCFLAGS+=-pthread
|
||||
endif
|
||||
|
||||
ifeq ($(OSARCH),SunOS)
|
||||
ASTCFLAGS+=-Wcast-align -DSOLARIS -I../include/solaris-compat -I/opt/ssl/include -I/usr/local/ssl/include -D_XPG4_2
|
||||
endif
|
||||
|
||||
ASTERISKVERSION:=$(shell GREP=$(GREP) AWK=$(AWK) build_tools/make_version .)
|
||||
|
||||
ifneq ($(wildcard .version),)
|
||||
ASTERISKVERSIONNUM:=$(shell $(AWK) -F. '{printf "%01d%02d%02d", $$1, $$2, $$3}' .version)
|
||||
RPMVERSION:=$(shell sed 's/[-\/:]/_/g' .version)
|
||||
else
|
||||
RPMVERSION=unknown
|
||||
endif
|
||||
|
||||
ifneq ($(wildcard .svn),)
|
||||
ASTERISKVERSIONNUM:=999999
|
||||
endif
|
||||
|
||||
# XXX MALLOC_DEBUG is probably unused, Makefile.moddir_rules adds the
|
||||
# value directly to ASTCFLAGS
|
||||
ASTCFLAGS+=$(MALLOC_DEBUG)$(OPTIONS)
|
||||
|
||||
MOD_SUBDIRS:=channels pbx apps codecs formats cdr funcs tests main res $(LOCAL_MOD_SUBDIRS)
|
||||
OTHER_SUBDIRS:=utils agi
|
||||
SUBDIRS:=$(OTHER_SUBDIRS) $(MOD_SUBDIRS)
|
||||
SUBDIRS_INSTALL:=$(SUBDIRS:%=%-install)
|
||||
SUBDIRS_CLEAN:=$(SUBDIRS:%=%-clean)
|
||||
SUBDIRS_DIST_CLEAN:=$(SUBDIRS:%=%-dist-clean)
|
||||
SUBDIRS_UNINSTALL:=$(SUBDIRS:%=%-uninstall)
|
||||
MOD_SUBDIRS_EMBED_LDSCRIPT:=$(MOD_SUBDIRS:%=%-embed-ldscript)
|
||||
MOD_SUBDIRS_EMBED_LDFLAGS:=$(MOD_SUBDIRS:%=%-embed-ldflags)
|
||||
MOD_SUBDIRS_EMBED_LIBS:=$(MOD_SUBDIRS:%=%-embed-libs)
|
||||
MOD_SUBDIRS_MENUSELECT_TREE:=$(MOD_SUBDIRS:%=%-menuselect-tree)
|
||||
|
||||
ifneq ($(findstring darwin,$(OSARCH)),)
|
||||
ASTCFLAGS+=-D__Darwin__
|
||||
SOLINK=-dynamic -bundle -undefined suppress -force_flat_namespace
|
||||
else
|
||||
# These are used for all but Darwin
|
||||
SOLINK=-shared -Xlinker -x
|
||||
ifneq ($(findstring BSD,$(OSARCH)),)
|
||||
LDFLAGS+=-L/usr/local/lib
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(OSARCH),SunOS)
|
||||
SOLINK=-shared -fpic -L/usr/local/ssl/lib
|
||||
endif
|
||||
|
||||
# comment to print directories during submakes
|
||||
#PRINT_DIR=yes
|
||||
|
||||
SILENTMAKE:=$(MAKE) --quiet --no-print-directory
|
||||
ifneq ($(PRINT_DIR)$(NOISY_BUILD),)
|
||||
SUBMAKE:=$(MAKE) --quiet
|
||||
else
|
||||
SUBMAKE:=$(MAKE) --quiet --no-print-directory
|
||||
endif
|
||||
|
||||
# This is used when generating the doxygen documentation
|
||||
ifneq ($(DOT),:)
|
||||
HAVEDOT=yes
|
||||
else
|
||||
HAVEDOT=no
|
||||
endif
|
||||
|
||||
# $(MAKE) is printed in several places, and we want it to be a
|
||||
# fixed size string. Define a variable whose name has also the
|
||||
# same size, so we can easily align text.
|
||||
ifeq ($(MAKE), gmake)
|
||||
mK="gmake"
|
||||
else
|
||||
mK=" make"
|
||||
endif
|
||||
|
||||
all: _all
|
||||
@echo " +--------- Asterisk Build Complete ---------+"
|
||||
@echo " + Asterisk has successfully been built, and +"
|
||||
@echo " + can be installed by running: +"
|
||||
@echo " + +"
|
||||
@echo " + $(mK) install +"
|
||||
@echo " +-------------------------------------------+"
|
||||
|
||||
_all: cleantest makeopts $(SUBDIRS)
|
||||
|
||||
makeopts: configure
|
||||
@echo "****"
|
||||
@echo "**** The configure script must be executed before running '$(MAKE)'."
|
||||
@echo "**** Please run \"./configure\"."
|
||||
@echo "****"
|
||||
@exit 1
|
||||
|
||||
menuselect.makeopts: menuselect/menuselect menuselect-tree
|
||||
menuselect/menuselect --check-deps $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) menuselect.makeopts
|
||||
|
||||
$(MOD_SUBDIRS_EMBED_LDSCRIPT):
|
||||
@echo "EMBED_LDSCRIPTS+="`$(SILENTMAKE) -C $(@:-embed-ldscript=) SUBDIR=$(@:-embed-ldscript=) __embed_ldscript` >> makeopts.embed_rules
|
||||
|
||||
$(MOD_SUBDIRS_EMBED_LDFLAGS):
|
||||
@echo "EMBED_LDFLAGS+="`$(SILENTMAKE) -C $(@:-embed-ldflags=) SUBDIR=$(@:-embed-ldflags=) __embed_ldflags` >> makeopts.embed_rules
|
||||
|
||||
$(MOD_SUBDIRS_EMBED_LIBS):
|
||||
@echo "EMBED_LIBS+="`$(SILENTMAKE) -C $(@:-embed-libs=) SUBDIR=$(@:-embed-libs=) __embed_libs` >> makeopts.embed_rules
|
||||
|
||||
$(MOD_SUBDIRS_MENUSELECT_TREE):
|
||||
@$(SUBMAKE) -C $(@:-menuselect-tree=) SUBDIR=$(@:-menuselect-tree=) moduleinfo
|
||||
@$(SUBMAKE) -C $(@:-menuselect-tree=) SUBDIR=$(@:-menuselect-tree=) makeopts
|
||||
|
||||
makeopts.embed_rules: menuselect.makeopts
|
||||
@echo "Generating embedded module rules ..."
|
||||
@rm -f $@
|
||||
@$(MAKE) $(PRINT_DIR) $(MOD_SUBDIRS_EMBED_LDSCRIPT)
|
||||
@$(MAKE) $(PRINT_DIR) $(MOD_SUBDIRS_EMBED_LDFLAGS)
|
||||
@$(MAKE) $(PRINT_DIR) $(MOD_SUBDIRS_EMBED_LIBS)
|
||||
|
||||
$(SUBDIRS): main/version.c include/asterisk/build.h include/asterisk/buildopts.h defaults.h makeopts.embed_rules
|
||||
|
||||
ifeq ($(findstring $(OSARCH), mingw32 cygwin ),)
|
||||
# Non-windows:
|
||||
# ensure that all module subdirectories are processed before 'main' during
|
||||
# a parallel build, since if there are modules selected to be embedded the
|
||||
# directories containing them must be completed before the main Asterisk
|
||||
# binary can be built
|
||||
main: $(filter-out main,$(MOD_SUBDIRS))
|
||||
else
|
||||
# Windows: we need to build main (i.e. the asterisk dll) first,
|
||||
# followed by res, followed by the other directories, because
|
||||
# dll symbols must be resolved during linking and not at runtime.
|
||||
D1:= $(filter-out main,$(MOD_SUBDIRS))
|
||||
D1:= $(filter-out res,$(D1))
|
||||
|
||||
$(D1): res
|
||||
res: main
|
||||
endif
|
||||
|
||||
$(MOD_SUBDIRS):
|
||||
@ASTCFLAGS="$(MOD_SUBDIR_CFLAGS) $(ASTCFLAGS)" ASTLDFLAGS="$(ASTLDFLAGS)" $(MAKE) $(PRINT_DIR) --no-builtin-rules -C $@ SUBDIR=$@ all
|
||||
|
||||
$(OTHER_SUBDIRS):
|
||||
@ASTCFLAGS="$(OTHER_SUBDIR_CFLAGS) $(ASTCFLAGS)" ASTLDFLAGS="$(ASTLDFLAGS)" $(MAKE) $(PRINT_DIR) --no-builtin-rules -C $@ SUBDIR=$@ all
|
||||
|
||||
defaults.h: makeopts
|
||||
@build_tools/make_defaults_h > $@.tmp
|
||||
@cmp -s $@.tmp $@ || mv $@.tmp $@
|
||||
@rm -f $@.tmp
|
||||
|
||||
main/version.c:
|
||||
@build_tools/make_version_c > $@.tmp
|
||||
@cmp -s $@.tmp $@ || mv $@.tmp $@
|
||||
@rm -f $@.tmp
|
||||
|
||||
include/asterisk/buildopts.h: menuselect.makeopts
|
||||
@build_tools/make_buildopts_h > $@.tmp
|
||||
@cmp -s $@.tmp $@ || mv $@.tmp $@
|
||||
@rm -f $@.tmp
|
||||
|
||||
include/asterisk/build.h:
|
||||
@build_tools/make_build_h > $@.tmp
|
||||
@cmp -s $@.tmp $@ || mv $@.tmp $@
|
||||
@rm -f $@.tmp
|
||||
|
||||
$(SUBDIRS_CLEAN):
|
||||
@$(MAKE) $(PRINT_DIR) -C $(@:-clean=) clean
|
||||
|
||||
$(SUBDIRS_DIST_CLEAN):
|
||||
@$(MAKE) $(PRINT_DIR) -C $(@:-dist-clean=) dist-clean
|
||||
|
||||
clean: $(SUBDIRS_CLEAN)
|
||||
rm -f defaults.h
|
||||
rm -f include/asterisk/build.h
|
||||
rm -f main/version.c
|
||||
@$(MAKE) -C menuselect clean
|
||||
cp -f .cleancount .lastclean
|
||||
|
||||
dist-clean: distclean
|
||||
|
||||
distclean: $(SUBDIRS_DIST_CLEAN) clean
|
||||
@$(MAKE) -C menuselect dist-clean
|
||||
@$(MAKE) -C sounds dist-clean
|
||||
rm -f menuselect.makeopts makeopts menuselect-tree menuselect.makedeps
|
||||
rm -f makeopts.embed_rules
|
||||
rm -f config.log config.status
|
||||
rm -rf autom4te.cache
|
||||
rm -f include/asterisk/autoconfig.h
|
||||
rm -f include/asterisk/buildopts.h
|
||||
rm -rf doc/api
|
||||
rm -f build_tools/menuselect-deps
|
||||
|
||||
datafiles: _all
|
||||
if [ x`$(ID) -un` = xroot ]; then CFLAGS="$(ASTCFLAGS)" sh build_tools/mkpkgconfig $(DESTDIR)/usr/lib/pkgconfig; fi
|
||||
# Should static HTTP be installed during make samples or even with its own target ala
|
||||
# webvoicemail? There are portions here that *could* be customized but might also be
|
||||
# improved a lot. I'll put it here for now.
|
||||
mkdir -p $(DESTDIR)$(ASTDATADIR)/phoneprov
|
||||
for x in phoneprov/*; do \
|
||||
$(INSTALL) -m 644 $$x $(DESTDIR)$(ASTDATADIR)/phoneprov ; \
|
||||
done
|
||||
mkdir -p $(DESTDIR)$(ASTDATADIR)/static-http
|
||||
for x in static-http/*; do \
|
||||
$(INSTALL) -m 644 $$x $(DESTDIR)$(ASTDATADIR)/static-http ; \
|
||||
done
|
||||
if [ -d doc/tex/asterisk ] ; then \
|
||||
mkdir -p $(DESTDIR)$(ASTDATADIR)/static-http/docs ; \
|
||||
for n in doc/tex/asterisk/* ; do \
|
||||
$(INSTALL) -m 644 $$n $(DESTDIR)$(ASTDATADIR)/static-http/docs ; \
|
||||
done \
|
||||
fi
|
||||
mkdir -p $(DESTDIR)$(ASTDATADIR)/images
|
||||
for x in images/*.jpg; do \
|
||||
$(INSTALL) -m 644 $$x $(DESTDIR)$(ASTDATADIR)/images ; \
|
||||
done
|
||||
mkdir -p $(DESTDIR)$(AGI_DIR)
|
||||
$(MAKE) -C sounds install
|
||||
|
||||
update:
|
||||
@if [ -d .svn ]; then \
|
||||
echo "Updating from Subversion..." ; \
|
||||
fromrev="`svn info | $(AWK) '/Revision: / {print $$2}'`"; \
|
||||
svn update | tee update.out; \
|
||||
torev="`svn info | $(AWK) '/Revision: / {print $$2}'`"; \
|
||||
echo "`date` Updated from revision $${fromrev} to $${torev}." >> update.log; \
|
||||
rm -f .version; \
|
||||
if [ `grep -c ^C update.out` -gt 0 ]; then \
|
||||
echo ; echo "The following files have conflicts:" ; \
|
||||
grep ^C update.out | cut -b4- ; \
|
||||
fi ; \
|
||||
rm -f update.out; \
|
||||
else \
|
||||
echo "Not under version control"; \
|
||||
fi
|
||||
|
||||
NEWHEADERS=$(notdir $(wildcard include/asterisk/*.h))
|
||||
OLDHEADERS=$(filter-out $(NEWHEADERS),$(notdir $(wildcard $(DESTDIR)$(ASTHEADERDIR)/*.h)))
|
||||
|
||||
bininstall: _all
|
||||
mkdir -p $(DESTDIR)$(MODULES_DIR)
|
||||
mkdir -p $(DESTDIR)$(ASTSBINDIR)
|
||||
mkdir -p $(DESTDIR)$(ASTETCDIR)
|
||||
mkdir -p $(DESTDIR)$(ASTBINDIR)
|
||||
mkdir -p $(DESTDIR)$(ASTVARRUNDIR)
|
||||
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/voicemail
|
||||
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/dictate
|
||||
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/system
|
||||
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/tmp
|
||||
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/meetme
|
||||
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/monitor
|
||||
$(INSTALL) -m 755 main/asterisk $(DESTDIR)$(ASTSBINDIR)/
|
||||
$(LN) -sf asterisk $(DESTDIR)$(ASTSBINDIR)/rasterisk
|
||||
$(INSTALL) -m 755 contrib/scripts/astgenkey $(DESTDIR)$(ASTSBINDIR)/
|
||||
$(INSTALL) -m 755 contrib/scripts/autosupport $(DESTDIR)$(ASTSBINDIR)/
|
||||
if [ ! -f $(DESTDIR)$(ASTSBINDIR)/safe_asterisk ]; then \
|
||||
cat contrib/scripts/safe_asterisk | sed 's|__ASTERISK_SBIN_DIR__|$(ASTSBINDIR)|;s|__ASTERISK_VARRUN_DIR__|$(ASTVARRUNDIR)|;' > $(DESTDIR)$(ASTSBINDIR)/safe_asterisk ;\
|
||||
chmod 755 $(DESTDIR)$(ASTSBINDIR)/safe_asterisk;\
|
||||
fi
|
||||
$(INSTALL) -d $(DESTDIR)$(ASTHEADERDIR)
|
||||
$(INSTALL) -m 644 include/asterisk.h $(DESTDIR)$(includedir)
|
||||
$(INSTALL) -m 644 include/asterisk/*.h $(DESTDIR)$(ASTHEADERDIR)
|
||||
if [ -n "$(OLDHEADERS)" ]; then \
|
||||
rm -f $(addprefix $(DESTDIR)$(ASTHEADERDIR)/,$(OLDHEADERS)) ;\
|
||||
fi
|
||||
mkdir -p $(DESTDIR)$(ASTLOGDIR)/cdr-csv
|
||||
mkdir -p $(DESTDIR)$(ASTLOGDIR)/cdr-custom
|
||||
mkdir -p $(DESTDIR)$(ASTDATADIR)/keys
|
||||
mkdir -p $(DESTDIR)$(ASTDATADIR)/firmware
|
||||
mkdir -p $(DESTDIR)$(ASTDATADIR)/firmware/iax
|
||||
mkdir -p $(DESTDIR)$(ASTMANDIR)/man8
|
||||
$(INSTALL) -m 644 keys/iaxtel.pub $(DESTDIR)$(ASTDATADIR)/keys
|
||||
$(INSTALL) -m 644 keys/freeworlddialup.pub $(DESTDIR)$(ASTDATADIR)/keys
|
||||
$(INSTALL) -m 644 doc/asterisk.8 $(DESTDIR)$(ASTMANDIR)/man8
|
||||
$(INSTALL) -m 644 contrib/scripts/astgenkey.8 $(DESTDIR)$(ASTMANDIR)/man8
|
||||
$(INSTALL) -m 644 contrib/scripts/autosupport.8 $(DESTDIR)$(ASTMANDIR)/man8
|
||||
$(INSTALL) -m 644 contrib/scripts/safe_asterisk.8 $(DESTDIR)$(ASTMANDIR)/man8
|
||||
if [ -f contrib/firmware/iax/iaxy.bin ] ; then \
|
||||
$(INSTALL) -m 644 contrib/firmware/iax/iaxy.bin $(DESTDIR)$(ASTDATADIR)/firmware/iax/iaxy.bin; \
|
||||
fi
|
||||
|
||||
$(SUBDIRS_INSTALL):
|
||||
@DESTDIR="$(DESTDIR)" ASTSBINDIR="$(ASTSBINDIR)" $(MAKE) --quiet $(PRINT_DIR) -C $(@:-install=) install
|
||||
|
||||
NEWMODS:=$(foreach d,$(MOD_SUBDIRS),$(notdir $(wildcard $(d)/*.so)))
|
||||
OLDMODS=$(filter-out $(NEWMODS),$(notdir $(wildcard $(DESTDIR)$(MODULES_DIR)/*.so)))
|
||||
|
||||
oldmodcheck:
|
||||
@if [ -n "$(OLDMODS)" ]; then \
|
||||
echo " WARNING WARNING WARNING" ;\
|
||||
echo "" ;\
|
||||
echo " Your Asterisk modules directory, located at" ;\
|
||||
echo " $(DESTDIR)$(MODULES_DIR)" ;\
|
||||
echo " contains modules that were not installed by this " ;\
|
||||
echo " version of Asterisk. Please ensure that these" ;\
|
||||
echo " modules are compatible with this version before" ;\
|
||||
echo " attempting to run Asterisk." ;\
|
||||
echo "" ;\
|
||||
for f in $(OLDMODS); do \
|
||||
echo " $$f" ;\
|
||||
done ;\
|
||||
echo "" ;\
|
||||
echo " WARNING WARNING WARNING" ;\
|
||||
fi
|
||||
|
||||
install: datafiles bininstall $(SUBDIRS_INSTALL)
|
||||
@if [ -x /usr/sbin/asterisk-post-install ]; then \
|
||||
/usr/sbin/asterisk-post-install $(DESTDIR) . ; \
|
||||
fi
|
||||
@echo " +---- Asterisk Installation Complete -------+"
|
||||
@echo " + +"
|
||||
@echo " + YOU MUST READ THE SECURITY DOCUMENT +"
|
||||
@echo " + +"
|
||||
@echo " + Asterisk has successfully been installed. +"
|
||||
@echo " + If you would like to install the sample +"
|
||||
@echo " + configuration files (overwriting any +"
|
||||
@echo " + existing config files), run: +"
|
||||
@echo " + +"
|
||||
@echo " + $(mK) samples +"
|
||||
@echo " + +"
|
||||
@echo " +----------------- or ---------------------+"
|
||||
@echo " + +"
|
||||
@echo " + You can go ahead and install the asterisk +"
|
||||
@echo " + program documentation now or later run: +"
|
||||
@echo " + +"
|
||||
@echo " + $(mK) progdocs +"
|
||||
@echo " + +"
|
||||
@echo " + **Note** This requires that you have +"
|
||||
@echo " + doxygen installed on your local system +"
|
||||
@echo " +-------------------------------------------+"
|
||||
@$(MAKE) -s oldmodcheck
|
||||
|
||||
upgrade: bininstall
|
||||
|
||||
# XXX why *.adsi is installed first ?
|
||||
adsi:
|
||||
@echo Installing adsi config files...
|
||||
@mkdir -p $(DESTDIR)$(ASTETCDIR)
|
||||
@for x in configs/*.adsi; do \
|
||||
dst="$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x`" ; \
|
||||
if [ -f $${dst} ] ; then \
|
||||
echo "Overwriting $$x" ; \
|
||||
else \
|
||||
echo "Installing $$x" ; \
|
||||
fi ; \
|
||||
$(INSTALL) -m 644 $$x $(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x` ; \
|
||||
done
|
||||
|
||||
samples: adsi
|
||||
@echo Installing other config files...
|
||||
@mkdir -p $(DESTDIR)$(ASTETCDIR)
|
||||
@for x in configs/*.sample; do \
|
||||
dst="$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x .sample`" ; \
|
||||
if [ -f $${dst} ]; then \
|
||||
if [ "$(OVERWRITE)" = "y" ]; then \
|
||||
if cmp -s $${dst} $$x ; then \
|
||||
echo "Config file $$x is unchanged"; \
|
||||
continue; \
|
||||
fi ; \
|
||||
mv -f $${dst} $${dst}.old ; \
|
||||
else \
|
||||
echo "Skipping config file $$x"; \
|
||||
continue; \
|
||||
fi ;\
|
||||
fi ; \
|
||||
echo "Installing file $$x"; \
|
||||
$(INSTALL) -m 644 $$x $${dst} ;\
|
||||
done
|
||||
@if [ "$(OVERWRITE)" = "y" ] || [ ! -f $(DESTDIR)$(ASTCONFPATH) ]; then \
|
||||
echo "Creating asterisk.conf"; \
|
||||
( \
|
||||
echo "[directories](!) ; remove the (!) to enable this" ; \
|
||||
echo "astetcdir => $(ASTETCDIR)" ; \
|
||||
echo "astmoddir => $(MODULES_DIR)" ; \
|
||||
echo "astvarlibdir => $(ASTVARLIBDIR)" ; \
|
||||
echo "astdbdir => $(ASTDBDIR)" ; \
|
||||
echo "astkeydir => $(ASTKEYDIR)" ; \
|
||||
echo "astdatadir => $(ASTDATADIR)" ; \
|
||||
echo "astagidir => $(AGI_DIR)" ; \
|
||||
echo "astspooldir => $(ASTSPOOLDIR)" ; \
|
||||
echo "astrundir => $(ASTVARRUNDIR)" ; \
|
||||
echo "astlogdir => $(ASTLOGDIR)" ; \
|
||||
echo "" ; \
|
||||
echo ";[options]" ; \
|
||||
echo ";verbose = 3" ; \
|
||||
echo ";debug = 3" ; \
|
||||
echo ";alwaysfork = yes ; same as -F at startup" ; \
|
||||
echo ";nofork = yes ; same as -f at startup" ; \
|
||||
echo ";quiet = yes ; same as -q at startup" ; \
|
||||
echo ";timestamp = yes ; same as -T at startup" ; \
|
||||
echo ";execincludes = yes ; support #exec in config files" ; \
|
||||
echo ";console = yes ; Run as console (same as -c at startup)" ; \
|
||||
echo ";highpriority = yes ; Run realtime priority (same as -p at startup)" ; \
|
||||
echo ";initcrypto = yes ; Initialize crypto keys (same as -i at startup)" ; \
|
||||
echo ";nocolor = yes ; Disable console colors" ; \
|
||||
echo ";dontwarn = yes ; Disable some warnings" ; \
|
||||
echo ";dumpcore = yes ; Dump core on crash (same as -g at startup)" ; \
|
||||
echo ";languageprefix = yes ; Use the new sound prefix path syntax" ; \
|
||||
echo ";internal_timing = yes" ; \
|
||||
echo ";systemname = my_system_name ; prefix uniqueid with a system name for global uniqueness issues" ; \
|
||||
echo ";autosystemname = yes ; automatically set systemname to hostname - uses 'localhost' on failure, or systemname if set" ; \
|
||||
echo ";maxcalls = 10 ; Maximum amount of calls allowed" ; \
|
||||
echo ";maxload = 0.9 ; Asterisk stops accepting new calls if the load average exceed this limit" ; \
|
||||
echo ";maxfiles = 1000 ; Maximum amount of openfiles" ; \
|
||||
echo ";minmemfree = 1 ; in MBs, Asterisk stops accepting new calls if the amount of free memory falls below this watermark" ; \
|
||||
echo ";cache_record_files = yes ; Cache recorded sound files to another directory during recording" ; \
|
||||
echo ";record_cache_dir = /tmp ; Specify cache directory (used in cnjunction with cache_record_files)" ; \
|
||||
echo ";transmit_silence_during_record = yes ; Transmit SLINEAR silence while a channel is being recorded" ; \
|
||||
echo ";transcode_via_sln = yes ; Build transcode paths via SLINEAR, instead of directly" ; \
|
||||
echo ";runuser = asterisk ; The user to run as" ; \
|
||||
echo ";rungroup = asterisk ; The group to run as" ; \
|
||||
echo "" ; \
|
||||
echo "; Changing the following lines may compromise your security." ; \
|
||||
echo ";[files]" ; \
|
||||
echo ";astctlpermissions = 0660" ; \
|
||||
echo ";astctlowner = root" ; \
|
||||
echo ";astctlgroup = apache" ; \
|
||||
echo ";astctl = asterisk.ctl" ; \
|
||||
) > $(DESTDIR)$(ASTCONFPATH) ; \
|
||||
else \
|
||||
echo "Skipping asterisk.conf creation"; \
|
||||
fi
|
||||
mkdir -p $(DESTDIR)$(ASTSPOOLDIR)/voicemail/default/1234/INBOX
|
||||
build_tools/make_sample_voicemail $(DESTDIR)/$(ASTDATADIR) $(DESTDIR)/$(ASTSPOOLDIR)
|
||||
|
||||
webvmail:
|
||||
@[ -d $(DESTDIR)$(HTTP_DOCSDIR)/ ] || ( printf "http docs directory not found.\nUpdate assignment of variable HTTP_DOCSDIR in Makefile!\n" && exit 1 )
|
||||
@[ -d $(DESTDIR)$(HTTP_CGIDIR) ] || ( printf "cgi-bin directory not found.\nUpdate assignment of variable HTTP_CGIDIR in Makefile!\n" && exit 1 )
|
||||
$(INSTALL) -m 4755 -o root -g root contrib/scripts/vmail.cgi $(DESTDIR)$(HTTP_CGIDIR)/vmail.cgi
|
||||
mkdir -p $(DESTDIR)$(HTTP_DOCSDIR)/_asterisk
|
||||
for x in images/*.gif; do \
|
||||
$(INSTALL) -m 644 $$x $(DESTDIR)$(HTTP_DOCSDIR)/_asterisk/; \
|
||||
done
|
||||
@echo " +--------- Asterisk Web Voicemail ----------+"
|
||||
@echo " + +"
|
||||
@echo " + Asterisk Web Voicemail is installed in +"
|
||||
@echo " + your cgi-bin directory: +"
|
||||
@echo " + $(DESTDIR)$(HTTP_CGIDIR)"
|
||||
@echo " + IT USES A SETUID ROOT PERL SCRIPT, SO +"
|
||||
@echo " + IF YOU DON'T LIKE THAT, UNINSTALL IT! +"
|
||||
@echo " + +"
|
||||
@echo " + Other static items have been stored in: +"
|
||||
@echo " + $(DESTDIR)$(HTTP_DOCSDIR)"
|
||||
@echo " + +"
|
||||
@echo " + If these paths do not match your httpd +"
|
||||
@echo " + installation, correct the definitions +"
|
||||
@echo " + in your Makefile of HTTP_CGIDIR and +"
|
||||
@echo " + HTTP_DOCSDIR +"
|
||||
@echo " + +"
|
||||
@echo " +-------------------------------------------+"
|
||||
|
||||
spec:
|
||||
sed "s/^Version:.*/Version: $(RPMVERSION)/g" redhat/asterisk.spec > asterisk.spec ; \
|
||||
|
||||
rpm: __rpm
|
||||
|
||||
__rpm: main/version.c include/asterisk/buildopts.h spec
|
||||
rm -rf /tmp/asterisk ; \
|
||||
mkdir -p /tmp/asterisk/redhat/RPMS/i386 ; \
|
||||
$(MAKE) DESTDIR=/tmp/asterisk install ; \
|
||||
$(MAKE) DESTDIR=/tmp/asterisk samples ; \
|
||||
mkdir -p /tmp/asterisk/etc/rc.d/init.d ; \
|
||||
cp -f contrib/init.d/rc.redhat.asterisk /tmp/asterisk/etc/rc.d/init.d/asterisk ; \
|
||||
rpmbuild --rcfile /usr/lib/rpm/rpmrc:redhat/rpmrc -bb asterisk.spec
|
||||
|
||||
progdocs:
|
||||
(cat contrib/asterisk-ng-doxygen; echo "HAVE_DOT=$(HAVEDOT)"; \
|
||||
echo "PROJECT_NUMBER=$(ASTERISKVERSION)") | doxygen -
|
||||
|
||||
config:
|
||||
@if [ "${OSARCH}" = "linux-gnu" ]; then \
|
||||
if [ -f /etc/redhat-release -o -f /etc/fedora-release ]; then \
|
||||
$(INSTALL) -m 755 contrib/init.d/rc.redhat.asterisk $(DESTDIR)/etc/rc.d/init.d/asterisk; \
|
||||
if [ -z "$(DESTDIR)" ]; then /sbin/chkconfig --add asterisk; fi; \
|
||||
elif [ -f /etc/debian_version ]; then \
|
||||
$(INSTALL) -m 755 contrib/init.d/rc.debian.asterisk $(DESTDIR)/etc/init.d/asterisk; \
|
||||
if [ -z "$(DESTDIR)" ]; then /usr/sbin/update-rc.d asterisk start 50 2 3 4 5 . stop 91 2 3 4 5 .; fi; \
|
||||
elif [ -f /etc/gentoo-release ]; then \
|
||||
$(INSTALL) -m 755 contrib/init.d/rc.gentoo.asterisk $(DESTDIR)/etc/init.d/asterisk; \
|
||||
if [ -z "$(DESTDIR)" ]; then /sbin/rc-update add asterisk default; fi; \
|
||||
elif [ -f /etc/mandrake-release -o -f /etc/mandriva-release ]; then \
|
||||
$(INSTALL) -m 755 contrib/init.d/rc.mandrake.asterisk $(DESTDIR)/etc/rc.d/init.d/asterisk; \
|
||||
if [ -z "$(DESTDIR)" ]; then /sbin/chkconfig --add asterisk; fi; \
|
||||
elif [ -f /etc/SuSE-release -o -f /etc/novell-release ]; then \
|
||||
$(INSTALL) -m 755 contrib/init.d/rc.suse.asterisk $(DESTDIR)/etc/init.d/asterisk; \
|
||||
if [ -z "$(DESTDIR)" ]; then /sbin/chkconfig --add asterisk; fi; \
|
||||
elif [ -f /etc/slackware-version ]; then \
|
||||
echo "Slackware is not currently supported, although an init script does exist for it." \
|
||||
else \
|
||||
echo "We could not install init scripts for your distribution."; \
|
||||
fi \
|
||||
else \
|
||||
echo "We could not install init scripts for your operating system."; \
|
||||
fi
|
||||
|
||||
sounds:
|
||||
$(MAKE) -C sounds all
|
||||
|
||||
# If the cleancount has been changed, force a make clean.
|
||||
# .cleancount is the global clean count, and .lastclean is the
|
||||
# last clean count we had
|
||||
|
||||
cleantest:
|
||||
@cmp -s .cleancount .lastclean || $(MAKE) clean
|
||||
|
||||
$(SUBDIRS_UNINSTALL):
|
||||
@$(MAKE) $(PRINT_DIR) -C $(@:-uninstall=) uninstall
|
||||
|
||||
_uninstall: $(SUBDIRS_UNINSTALL)
|
||||
rm -f $(DESTDIR)$(MODULES_DIR)/*
|
||||
rm -f $(DESTDIR)$(ASTSBINDIR)/*asterisk*
|
||||
rm -f $(DESTDIR)$(ASTSBINDIR)/astgenkey
|
||||
rm -f $(DESTDIR)$(ASTSBINDIR)/autosupport
|
||||
rm -rf $(DESTDIR)$(ASTHEADERDIR)
|
||||
rm -rf $(DESTDIR)$(ASTDATADIR)/firmware
|
||||
rm -f $(DESTDIR)$(ASTMANDIR)/man8/asterisk.8
|
||||
rm -f $(DESTDIR)$(ASTMANDIR)/man8/astgenkey.8
|
||||
rm -f $(DESTDIR)$(ASTMANDIR)/man8/autosupport.8
|
||||
rm -f $(DESTDIR)$(ASTMANDIR)/man8/safe_asterisk.8
|
||||
$(MAKE) -C sounds uninstall
|
||||
|
||||
uninstall: _uninstall
|
||||
@echo " +--------- Asterisk Uninstall Complete -----+"
|
||||
@echo " + Asterisk binaries, sounds, man pages, +"
|
||||
@echo " + headers, modules, and firmware builds, +"
|
||||
@echo " + have all been uninstalled. +"
|
||||
@echo " + +"
|
||||
@echo " + To remove ALL traces of Asterisk, +"
|
||||
@echo " + including configuration, spool +"
|
||||
@echo " + directories, and logs, run the following +"
|
||||
@echo " + command: +"
|
||||
@echo " + +"
|
||||
@echo " + $(mK) uninstall-all +"
|
||||
@echo " +-------------------------------------------+"
|
||||
|
||||
uninstall-all: _uninstall
|
||||
rm -rf $(DESTDIR)$(ASTLIBDIR)
|
||||
rm -rf $(DESTDIR)$(ASTVARLIBDIR)
|
||||
rm -rf $(DESTDIR)$(ASTDATADIR)
|
||||
rm -rf $(DESTDIR)$(ASTSPOOLDIR)
|
||||
rm -rf $(DESTDIR)$(ASTETCDIR)
|
||||
rm -rf $(DESTDIR)$(ASTLOGDIR)
|
||||
|
||||
menuconfig: menuselect
|
||||
|
||||
gmenuconfig: gmenuselect
|
||||
|
||||
menuselect: menuselect/menuselect menuselect-tree
|
||||
-@menuselect/menuselect $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) menuselect.makeopts && (echo "menuselect changes saved!"; rm -f channels/h323/Makefile.ast main/asterisk) || echo "menuselect changes NOT saved!"
|
||||
|
||||
gmenuselect: menuselect/gmenuselect menuselect-tree
|
||||
-@menuselect/gmenuselect $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS) menuselect.makeopts && (echo "menuselect changes saved!"; rm -f channels/h323/Makefile.ast main/asterisk) || echo "menuselect changes NOT saved!"
|
||||
|
||||
# options for make in menuselect/
|
||||
MAKE_MENUSELECT=CC="$(HOST_CC)" CXX="$(CXX)" LD="" AR="" RANLIB="" CFLAGS="" $(MAKE) -C menuselect CONFIGURE_SILENT="--silent"
|
||||
|
||||
menuselect/menuselect: menuselect/makeopts
|
||||
$(MAKE_MENUSELECT)
|
||||
|
||||
menuselect/gmenuselect: menuselect/makeopts
|
||||
$(MAKE_MENUSELECT) gmenuselect
|
||||
|
||||
menuselect/makeopts:
|
||||
$(MAKE_MENUSELECT) makeopts
|
||||
|
||||
menuselect-tree: $(foreach dir,$(filter-out main,$(MOD_SUBDIRS)),$(wildcard $(dir)/*.c) $(wildcard $(dir)/*.cc)) build_tools/cflags.xml sounds/sounds.xml build_tools/embed_modules.xml configure
|
||||
@echo "Generating input for menuselect ..."
|
||||
@echo "<?xml version=\"1.0\"?>" > $@
|
||||
@echo >> $@
|
||||
@echo "<menu name=\"Asterisk Module and Build Option Selection\">" >> $@
|
||||
@for dir in $(sort $(filter-out main,$(MOD_SUBDIRS))); do $(SUBMAKE) -C $${dir} SUBDIR=$${dir} moduleinfo >> $@; done
|
||||
@for dir in $(sort $(filter-out main,$(MOD_SUBDIRS))); do $(SUBMAKE) -C $${dir} SUBDIR=$${dir} makeopts >> $@; done
|
||||
@cat build_tools/cflags.xml >> $@
|
||||
@cat build_tools/embed_modules.xml >> $@
|
||||
@cat sounds/sounds.xml >> $@
|
||||
@echo "</menu>" >> $@
|
||||
|
||||
pdf: asterisk.pdf
|
||||
asterisk.pdf:
|
||||
$(MAKE) -C doc/tex asterisk.pdf
|
||||
|
||||
.PHONY: menuselect main sounds clean dist-clean distclean all prereqs cleantest uninstall _uninstall uninstall-all pdf dont-optimize $(SUBDIRS_INSTALL) $(SUBDIRS_DIST_CLEAN) $(SUBDIRS_CLEAN) $(SUBDIRS_UNINSTALL) $(SUBDIRS) $(MOD_SUBDIRS_EMBED_LDSCRIPT) $(MOD_SUBDIRS_EMBED_LDFLAGS) $(MOD_SUBDIRS_EMBED_LIBS) main/version.c
|
||||
166
trunk/Makefile.moddir_rules
Normal file
166
trunk/Makefile.moddir_rules
Normal file
@@ -0,0 +1,166 @@
|
||||
#
|
||||
# Asterisk -- A telephony toolkit for Linux.
|
||||
#
|
||||
# Makefile rules for subdirectories containing modules
|
||||
#
|
||||
# Copyright (C) 2006, Digium, Inc.
|
||||
#
|
||||
# Kevin P. Fleming <kpfleming@digium.com>
|
||||
#
|
||||
# This program is free software, distributed under the terms of
|
||||
# the GNU General Public License
|
||||
#
|
||||
|
||||
# Makefile rules for building modules.
|
||||
|
||||
# In most cases, we set target-specific variables for certain targets
|
||||
# (remember that they apply recursively to prerequisites).
|
||||
# Also note that we can only set one variable per rule, so we have to
|
||||
# repeat the left hand side to set multiple variables.
|
||||
|
||||
ifneq ($(findstring MALLOC_DEBUG,$(MENUSELECT_CFLAGS)),)
|
||||
ifeq ($(findstring astmm.h,$(ASTCFLAGS)),)
|
||||
ASTCFLAGS+=-include $(ASTTOPDIR)/include/asterisk/astmm.h
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(findstring LOADABLE_MODULES,$(MENUSELECT_CFLAGS)),)
|
||||
ASTCFLAGS+=${GC_CFLAGS}
|
||||
endif
|
||||
|
||||
ifneq ($(findstring STATIC_BUILD,$(MENUSELECT_CFLAGS)),)
|
||||
STATIC_BUILD=-static
|
||||
endif
|
||||
|
||||
include $(ASTTOPDIR)/Makefile.rules
|
||||
|
||||
# If MODULE_PREFIX is defined, use it to run the standard functions to set
|
||||
# C_MODS, CC_MODS, LOADABLE_MODS and EMBEDDED_MODS.
|
||||
# Each word of MODULE_PREFIX is a prefix for filenames that we consider
|
||||
# valid C or CC modules (eg. app, func ...). Note that the underscore
|
||||
# is added here, and does not need to be in MODULE_PREFIX
|
||||
#
|
||||
# Use MODULE_EXCLUDE to specify additional modules to exclude.
|
||||
|
||||
ifneq ($(MODULE_PREFIX),)
|
||||
ALL_C_MODS:=
|
||||
ALL_C_MODS+=$(foreach p,$(MODULE_PREFIX),$(patsubst %.c,%,$(wildcard $(p)_*.c)))
|
||||
ALL_CC_MODS:=
|
||||
ALL_CC_MODS+=$(foreach p,$(MODULE_PREFIX),$(patsubst %.cc,%,$(wildcard $(p)_*.cc)))
|
||||
|
||||
C_MODS:=$(filter-out $(MENUSELECT_$(MENUSELECT_CATEGORY)),$(ALL_C_MODS))
|
||||
CC_MODS:=$(filter-out $(MENUSELECT_$(MENUSELECT_CATEGORY)),$(ALL_CC_MODS))
|
||||
|
||||
# and store in the list of embedded or loadable modules
|
||||
ifneq ($(findstring $(MENUSELECT_CATEGORY),$(MENUSELECT_EMBED)),)
|
||||
EMBEDDED_MODS:=$(C_MODS) $(CC_MODS)
|
||||
else
|
||||
LOADABLE_MODS:=$(C_MODS) $(CC_MODS)
|
||||
endif
|
||||
endif
|
||||
|
||||
# Both C++ and C++ sources need their module name in AST_MODULE
|
||||
# We also pass whatever _INCLUDE list is generated by menuselect
|
||||
# (they are stored in file 'makeopts')
|
||||
|
||||
$(addsuffix .oo,$(CC_MODS)) $(addsuffix .o,$(C_MODS)): \
|
||||
ASTCFLAGS+= -DAST_MODULE=\"$*\" $(MENUSELECT_OPTS_$*:%=-D%) $(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_INCLUDE))
|
||||
|
||||
ifeq ($(findstring $(OSARCH), mingw32 cygwin ),)
|
||||
# don't define -fPIC on mingw32 and cygwin, it is the default
|
||||
$(LOADABLE_MODS:%=%.so): ASTCFLAGS+=-fPIC
|
||||
endif
|
||||
|
||||
# For loadable modules, pass _LIB and _LDFLAGS from menuselect.
|
||||
$(LOADABLE_MODS:%=%.so): LIBS+=$(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_LIB))
|
||||
$(LOADABLE_MODS:%=%.so): ASTLDFLAGS+=$(foreach dep,$(MENUSELECT_DEPENDS_$*),$(value $(dep)_LDFLAGS))
|
||||
|
||||
$(EMBEDDED_MODS:%=%.o): ASTCFLAGS+=-DEMBEDDED_MODULE=$*
|
||||
|
||||
$(addsuffix .so,$(filter $(LOADABLE_MODS),$(C_MODS))): %.so: %.o
|
||||
$(addsuffix .so,$(filter $(LOADABLE_MODS),$(CC_MODS))): %.so: %.oo
|
||||
|
||||
modules.link: $(addsuffix .eo,$(filter $(EMBEDDED_MODS),$(C_MODS)))
|
||||
|
||||
.PHONY: clean uninstall _all moduleinfo makeopts
|
||||
|
||||
ifneq ($(LOADABLE_MODS),)
|
||||
_all: $(LOADABLE_MODS:%=%.so)
|
||||
ifneq ($(findstring $(OSARCH), mingw32 cygwin ),)
|
||||
# linker options and extra libraries for cygwin
|
||||
SOLINK=-Wl,--out-implib=lib$@.a -shared
|
||||
LIBS+=-L$(ASTTOPDIR)/main -lasterisk -L$(ASTTOPDIR)/res $($@_LIBS)
|
||||
# additional libraries in res/
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(EMBEDDED_MODS),)
|
||||
_all: modules.link
|
||||
__embed_ldscript:
|
||||
@echo "../$(SUBDIR)/modules.link"
|
||||
__embed_ldflags:
|
||||
@echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(C_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LDFLAGS))"
|
||||
@echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(CC_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LDFLAGS))"
|
||||
__embed_libs:
|
||||
@echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(C_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LIB))"
|
||||
@echo "$(foreach mod,$(filter $(EMBEDDED_MODS),$(CC_MODS)),$(foreach dep,$(MENUSELECT_DEPENDS_$(mod)),$(dep)_LIB))"
|
||||
else
|
||||
__embed_ldscript:
|
||||
__embed_ldflags:
|
||||
__embed_libs:
|
||||
endif
|
||||
|
||||
modules.link:
|
||||
@rm -f $@
|
||||
@for file in $(patsubst %,$(SUBDIR)/%,$(filter %.eo,$^)); do echo "INPUT (../$${file})" >> $@; done
|
||||
@for file in $(patsubst %,$(SUBDIR)/%,$(filter-out %.eo,$^)); do echo "INPUT (../$${file})" >> $@; done
|
||||
|
||||
clean::
|
||||
rm -f *.so *.o *.oo *.eo
|
||||
rm -f .*.o.d .*.oo.d
|
||||
rm -f *.s *.i
|
||||
rm -f modules.link
|
||||
|
||||
install:: all
|
||||
@echo "Installing modules from `basename $(CURDIR)`..."
|
||||
@for x in $(LOADABLE_MODS:%=%.so); do $(INSTALL) -m 755 $$x $(DESTDIR)$(MODULES_DIR) ; done
|
||||
|
||||
uninstall::
|
||||
|
||||
dist-clean::
|
||||
rm -f .*.moduleinfo .moduleinfo
|
||||
rm -f .*.makeopts .makeopts
|
||||
|
||||
.%.moduleinfo: %.c
|
||||
@echo "<member name=\"$*\" displayname=\"$(shell $(GREP) -e AST_MODULE_INFO $< | head -n 1 | cut -d '"' -f 2)\" remove_on_change=\"$(SUBDIR)/$*.o $(SUBDIR)/$*.so\">" > $@
|
||||
$(AWK) -f $(ASTTOPDIR)/build_tools/get_moduleinfo $< >> $@
|
||||
echo "</member>" >> $@
|
||||
|
||||
.%.moduleinfo: %.cc
|
||||
@echo "<member name=\"$*\" displayname=\"$(shell $(GREP) -e AST_MODULE_INFO $< | head -n 1 | cut -d '"' -f 2)\" remove_on_change=\"$(SUBDIR)/$*.oo $(SUBDIR)/$*.so\">" > $@
|
||||
$(AWK) -f $(ASTTOPDIR)/build_tools/get_moduleinfo $< >> $@
|
||||
echo "</member>" >> $@
|
||||
|
||||
.moduleinfo:: $(addsuffix .moduleinfo,$(addprefix .,$(ALL_C_MODS) $(ALL_CC_MODS)))
|
||||
@echo "<category name=\"MENUSELECT_$(MENUSELECT_CATEGORY)\" displayname=\"$(MENUSELECT_DESCRIPTION)\" remove_on_change=\"$(SUBDIR)/modules.link\">" > $@
|
||||
@cat $^ >> $@
|
||||
@echo "</category>" >> $@
|
||||
|
||||
moduleinfo: .moduleinfo
|
||||
@cat $<
|
||||
|
||||
.%.makeopts: %.c
|
||||
@$(AWK) -f $(ASTTOPDIR)/build_tools/get_makeopts $< > $@
|
||||
|
||||
.%.makeopts: %.cc
|
||||
@$(AWK) -f $(ASTTOPDIR)/build_tools/get_makeopts $< > $@
|
||||
|
||||
.makeopts:: $(addsuffix .makeopts,$(addprefix .,$(ALL_C_MODS) $(ALL_CC_MODS)))
|
||||
@cat $^ > $@
|
||||
|
||||
makeopts: .makeopts
|
||||
@cat $<
|
||||
|
||||
ifneq ($(wildcard .*.d),)
|
||||
include .*.d
|
||||
endif
|
||||
98
trunk/Makefile.rules
Normal file
98
trunk/Makefile.rules
Normal file
@@ -0,0 +1,98 @@
|
||||
#
|
||||
# Asterisk -- A telephony toolkit for Linux.
|
||||
#
|
||||
# Makefile rules
|
||||
#
|
||||
# Copyright (C) 2006, Digium, Inc.
|
||||
#
|
||||
# Kevin P. Fleming <kpfleming@digium.com>
|
||||
#
|
||||
# This program is free software, distributed under the terms of
|
||||
# the GNU General Public License
|
||||
#
|
||||
|
||||
# Rules for various build phases.
|
||||
# Each command is preceded by a short comment on what to do.
|
||||
# Prefixing one or the other with @\# or @ or nothing makes the desired
|
||||
# behaviour. ECHO_PREFIX prefixes the comment, CMD_PREFIX prefixes the command.
|
||||
|
||||
-include $(ASTTOPDIR)/makeopts
|
||||
|
||||
.PHONY: dist-clean
|
||||
|
||||
# extra cflags to build dependencies. Recursively expanded.
|
||||
MAKE_DEPS= -MMD -MT $@ -MF .$(subst /,_,$@).d -MP
|
||||
|
||||
ifeq ($(NOISY_BUILD),)
|
||||
ECHO_PREFIX=@
|
||||
CMD_PREFIX=@
|
||||
else
|
||||
ECHO_PREFIX=@\#
|
||||
CMD_PREFIX=
|
||||
endif
|
||||
|
||||
ifeq ($(findstring DONT_OPTIMIZE,$(MENUSELECT_CFLAGS)),)
|
||||
# More GSM codec optimization
|
||||
# Uncomment to enable MMXTM optimizations for x86 architecture CPU's
|
||||
# which support MMX instructions. This should be newer pentiums,
|
||||
# ppro's, etc, as well as the AMD K6 and K7.
|
||||
#K6OPT=-DK6OPT
|
||||
|
||||
OPTIMIZE?=-O6
|
||||
ASTCFLAGS+=$(OPTIMIZE)
|
||||
endif
|
||||
|
||||
# build rules for various targets
|
||||
%.o: %.c
|
||||
$(ECHO_PREFIX) echo " [CC] $< -> $@"
|
||||
$(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) $(MAKE_DEPS)
|
||||
|
||||
%.o: %.i
|
||||
$(ECHO_PREFIX) echo " [CCi] $< -> $@"
|
||||
$(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) $(MAKE_DEPS)
|
||||
|
||||
%.i: %.c
|
||||
$(ECHO_PREFIX) echo " [CPP] $< -> $@"
|
||||
$(CMD_PREFIX) $(CC) -o $@ -E $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) $(MAKE_DEPS)
|
||||
|
||||
%.o: %.s
|
||||
$(ECHO_PREFIX) echo " [AS] $< -> $@"
|
||||
$(CMD_PREFIX) $(CC) -o $@ -c $< $(PTHREAD_CFLAGS) $(ASTCFLAGS) $(MAKE_DEPS)
|
||||
|
||||
%.oo: %.cc
|
||||
$(ECHO_PREFIX) echo " [CXX] $< -> $@"
|
||||
$(CMD_PREFIX) $(CXX) -o $@ -c $< $(PTHREAD_CFLAGS) $(filter-out -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations,$(ASTCFLAGS)) $(MAKE_DEPS)
|
||||
|
||||
%.c: %.y
|
||||
$(ECHO_PREFIX) echo " [BISON] $< -> $@"
|
||||
$(CMD_PREFIX) bison -o $@ -d --name-prefix=ast_yy $<
|
||||
|
||||
%.c: %.fl
|
||||
$(ECHO_PREFIX) echo " [FLEX] $< -> $@"
|
||||
$(CMD_PREFIX) flex -o $@ --full $<
|
||||
|
||||
%.so: %.o
|
||||
$(ECHO_PREFIX) echo " [LD] $^ -> $@"
|
||||
$(CMD_PREFIX) $(CC) $(STATIC_BUILD) -o $@ $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $(SOLINK) $^ $(PTHREAD_LIBS) $(LIBS)
|
||||
|
||||
%.so: %.oo
|
||||
$(ECHO_PREFIX) echo " [LDXX] $^ -> $@"
|
||||
$(CMD_PREFIX) $(CXX) $(STATIC_BUILD) -o $@ $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $(SOLINK) $^ $(PTHREAD_LIBS) $(LIBS)
|
||||
|
||||
%.eo: %.o
|
||||
$(ECHO_PREFIX) echo " [EMBED] $< -> $@"
|
||||
$(CMD_PREFIX) $(ASTTOPDIR)/build_tools/make_linker_eo_script $* > .$@.ld
|
||||
$(CMD_PREFIX) $(LD) -r -T .$@.ld -o $@ $<
|
||||
$(CMD_PREFIX) rm -f .$@.ld
|
||||
|
||||
%.eo: %.oo
|
||||
$(ECHO_PREFIX) echo " [EMBED] $< -> $@"
|
||||
$(CMD_PREFIX) $(ASTTOPDIR)/build_tools/make_linker_eo_script $* > .$@.ld
|
||||
$(CMD_PREFIX) $(LD) -r -T .$@.ld -o $@ $<
|
||||
$(CMD_PREFIX) rm -f .$@.ld
|
||||
|
||||
%: %.o
|
||||
$(ECHO_PREFIX) echo " [LD] $^ -> $@"
|
||||
$(CMD_PREFIX) $(CXX) $(STATIC_BUILD) -o $@ $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $^ $(PTHREAD_LIBS) $(LIBS)
|
||||
|
||||
dist-clean::
|
||||
262
trunk/README
Normal file
262
trunk/README
Normal file
@@ -0,0 +1,262 @@
|
||||
The Asterisk(R) Open Source PBX
|
||||
by Mark Spencer <markster@digium.com>
|
||||
and the Asterisk.org developer community
|
||||
|
||||
Copyright (C) 2001-2006 Digium, Inc.
|
||||
and other copyright holders.
|
||||
================================================================
|
||||
|
||||
* SECURITY
|
||||
It is imperative that you read and fully understand the contents of
|
||||
the security information file (doc/security.txt) before you attempt
|
||||
to configure and run an Asterisk server.
|
||||
|
||||
* WHAT IS ASTERISK ?
|
||||
Asterisk is an Open Source PBX and telephony toolkit. It is, in a
|
||||
sense, middleware between Internet and telephony channels on the bottom,
|
||||
and Internet and telephony applications at the top. For more information
|
||||
on the project itself, please visit the Asterisk home page at:
|
||||
|
||||
http://www.asterisk.org
|
||||
|
||||
In addition you'll find lots of information compiled by the Asterisk
|
||||
community on this Wiki:
|
||||
|
||||
http://www.voip-info.org/wiki-Asterisk
|
||||
|
||||
There is a book on Asterisk published by O'Reilly under the
|
||||
Creative Commons License. It is available in book stores as well
|
||||
as in a downloadable version on the http://www.asteriskdocs.org
|
||||
web site.
|
||||
|
||||
* SUPPORTED OPERATING SYSTEMS
|
||||
|
||||
== Linux ==
|
||||
The Asterisk Open Source PBX is developed and tested primarily on the
|
||||
GNU/Linux operating system, and is supported on every major GNU/Linux
|
||||
distribution.
|
||||
|
||||
== Others ==
|
||||
Asterisk has also been 'ported' and reportedly runs properly on other
|
||||
operating systems as well, including Sun Solaris, Apple's Mac OS X, and
|
||||
the BSD variants.
|
||||
|
||||
* GETTING STARTED
|
||||
|
||||
First, be sure you've got supported hardware (but note that you don't need
|
||||
ANY special hardware, not even a soundcard) to install and run Asterisk.
|
||||
|
||||
Supported telephony hardware includes:
|
||||
|
||||
* All Wildcard (tm) products from Digium (www.digium.com)
|
||||
* QuickNet Internet PhoneJack and LineJack (http://www.quicknet.net)
|
||||
* any full duplex sound card supported by ALSA or OSS
|
||||
* any ISDN card supported by mISDN on Linux (BRI)
|
||||
* The Xorcom AstriBank channel bank
|
||||
* VoiceTronix OpenLine products
|
||||
|
||||
The are several drivers for ISDN BRI cards available from third party sources.
|
||||
Check the voip-info.org wiki for more information on chan_capi and
|
||||
zaphfc.
|
||||
|
||||
* UPGRADING FROM AN EARLIER VERSION
|
||||
|
||||
If you are updating from a previous version of Asterisk, make sure you
|
||||
read the UPGRADE.txt file in the source directory. There are some files
|
||||
and configuration options that you will have to change, even though we
|
||||
made every effort possible to maintain backwards compatibility.
|
||||
|
||||
In order to discover new features to use, please check the configuration
|
||||
examples in the /configs directory of the source code distribution.
|
||||
To discover the major new features of Asterisk 1.2, please visit
|
||||
http://edvina.net/asterisk1-2/
|
||||
|
||||
* NEW INSTALLATIONS
|
||||
|
||||
Ensure that your system contains a compatible compiler and development
|
||||
libraries. Asterisk requires either the GNU Compiler Collection (GCC) version
|
||||
3.0 or higher, or a compiler that supports the C99 specification and some of
|
||||
the gcc language extensions. In addition, your system needs to have the C
|
||||
library headers available, and the headers and libraries for OpenSSL,
|
||||
ncurses and zlib.
|
||||
On many distributions, these files are installed by packages with names like
|
||||
'glibc-devel', 'ncurses-devel', 'openssl-devel' and 'zlib-devel' or similar.
|
||||
|
||||
So let's proceed:
|
||||
|
||||
1) Read this README file.
|
||||
|
||||
There are more documents than this one in the doc/ directory.
|
||||
You may also want to check the configuration files that contain
|
||||
examples and reference guides. They are all in the configs/
|
||||
directory.
|
||||
|
||||
2) Run "./configure"
|
||||
|
||||
Execute the configure script to guess values for system-dependent
|
||||
variables used during compilation.
|
||||
|
||||
3) Run "make menuselect" [optional]
|
||||
|
||||
This is needed if you want to select the modules that will be
|
||||
compiled and to check modules dependencies.
|
||||
|
||||
4) Run "make"
|
||||
|
||||
Assuming the build completes successfully:
|
||||
|
||||
5) Run "make install"
|
||||
|
||||
Each time you update or checkout from the repository, you are strongly
|
||||
encouraged to ensure all previous object files are removed to avoid internal
|
||||
inconsistency in Asterisk. Normally, this is automatically done with
|
||||
the presence of the file .cleancount, which increments each time a 'make clean'
|
||||
is required, and the file .lastclean, which contains the last .cleancount used.
|
||||
|
||||
If this is your first time working with Asterisk, you may wish to install
|
||||
the sample PBX, with demonstration extensions, etc. If so, run:
|
||||
|
||||
6) "make samples"
|
||||
|
||||
Doing so will overwrite any existing config files you have.
|
||||
|
||||
Finally, you can launch Asterisk in the foreground mode (not a daemon)
|
||||
with:
|
||||
|
||||
# asterisk -vvvc
|
||||
|
||||
You'll see a bunch of verbose messages fly by your screen as Asterisk
|
||||
initializes (that's the "very very verbose" mode). When it's ready, if
|
||||
you specified the "c" then you'll get a command line console, that looks
|
||||
like this:
|
||||
|
||||
*CLI>
|
||||
|
||||
You can type "help" at any time to get help with the system. For help
|
||||
with a specific command, type "help <command>". To start the PBX using
|
||||
your sound card, you can type "dial" to dial the PBX. Then you can use
|
||||
"answer", "hangup", and "dial" to simulate the actions of a telephone.
|
||||
Remember that if you don't have a full duplex sound card (and Asterisk
|
||||
will tell you somewhere in its verbose messages if you do/don't) then it
|
||||
won't work right (not yet).
|
||||
|
||||
"man asterisk" at the Unix/Linux command prompt will give you detailed
|
||||
information on how to start and stop Asterisk, as well as all the command
|
||||
line options for starting Asterisk.
|
||||
|
||||
Feel free to look over the configuration files in /etc/asterisk, where
|
||||
you'll find a lot of information about what you can do with Asterisk.
|
||||
|
||||
* ABOUT CONFIGURATION FILES
|
||||
|
||||
All Asterisk configuration files share a common format. Comments are
|
||||
delimited by ';' (since '#' of course, being a DTMF digit, may occur in
|
||||
many places). A configuration file is divided into sections whose names
|
||||
appear in []'s. Each section typically contains two types of statements,
|
||||
those of the form 'variable = value', and those of the form 'object =>
|
||||
parameters'. Internally the use of '=' and '=>' is exactly the same, so
|
||||
they're used only to help make the configuration file easier to
|
||||
understand, and do not affect how it is actually parsed.
|
||||
|
||||
Entries of the form 'variable=value' set the value of some parameter in
|
||||
asterisk. For example, in zapata.conf, one might specify:
|
||||
|
||||
switchtype=national
|
||||
|
||||
in order to indicate to Asterisk that the switch they are connecting to is
|
||||
of the type "national". In general, the parameter will apply to
|
||||
instantiations which occur below its specification. For example, if the
|
||||
configuration file read:
|
||||
|
||||
switchtype = national
|
||||
channel => 1-4
|
||||
channel => 10-12
|
||||
switchtype = dms100
|
||||
channel => 25-47
|
||||
|
||||
the "national" switchtype would be applied to channels one through
|
||||
four and channels 10 through 12, whereas the "dms100" switchtype would
|
||||
apply to channels 25 through 47.
|
||||
|
||||
The "object => parameters" instantiates an object with the given
|
||||
parameters. For example, the line "channel => 25-47" creates objects for
|
||||
the channels 25 through 47 of the card, obtaining the settings
|
||||
from the variables specified above.
|
||||
|
||||
* SPECIAL NOTE ON TIME
|
||||
|
||||
Those using SIP phones should be aware that Asterisk is sensitive to
|
||||
large jumps in time. Manually changing the system time using date(1)
|
||||
(or other similar commands) may cause SIP registrations and other
|
||||
internal processes to fail. If your system cannot keep accurate time
|
||||
by itself use NTP (http://www.ntp.org/) to keep the system clock
|
||||
synchronized to "real time". NTP is designed to keep the system clock
|
||||
synchronized by speeding up or slowing down the system clock until it
|
||||
is synchronized to "real time" rather than by jumping the time and
|
||||
causing discontinuities. Most Linux distributions include precompiled
|
||||
versions of NTP. Beware of some time synchronization methods that get
|
||||
the correct real time periodically and then manually set the system
|
||||
clock.
|
||||
|
||||
Apparent time changes due to daylight savings time are just that,
|
||||
apparent. The use of daylight savings time in a Linux system is
|
||||
purely a user interface issue and does not affect the operation of the
|
||||
Linux kernel or Asterisk. The system clock on Linux kernels operates
|
||||
on UTC. UTC does not use daylight savings time.
|
||||
|
||||
Also note that this issue is separate from the clocking of TDM
|
||||
channels, and is known to at least affect SIP registrations.
|
||||
|
||||
* FILE DESCRIPTORS
|
||||
|
||||
Depending on the size of your system and your configuration,
|
||||
Asterisk can consume a large number of file descriptors. In UNIX,
|
||||
file descriptors are used for more than just files on disk. File
|
||||
descriptors are also used for handling network communication
|
||||
(e.g. SIP, IAX2, or H.323 calls) and hardware access (e.g. analog and
|
||||
digital trunk hardware). Asterisk accesses many on-disk files for
|
||||
everything from configuration information to voicemail storage.
|
||||
|
||||
Most systems limit the number of file descriptors that Asterisk can
|
||||
have open at one time. This can limit the number of simultaneous
|
||||
calls that your system can handle. For example, if the limit is set
|
||||
at 1024 (a common default value) Asterisk can handle approxiately 150
|
||||
SIP calls simultaneously. To change the number of file descriptors
|
||||
follow the instructions for your system below:
|
||||
|
||||
== PAM-based Linux System ==
|
||||
|
||||
If your system uses PAM (Pluggable Authentication Modules) edit
|
||||
/etc/security/limits.conf. Add these lines to the bottom of the file:
|
||||
|
||||
root soft nofile 4096
|
||||
root hard nofile 8196
|
||||
asterisk soft nofile 4096
|
||||
asterisk hard nofile 8196
|
||||
|
||||
(adjust the numbers to taste). You may need to reboot the system for
|
||||
these changes to take effect.
|
||||
|
||||
== Generic UNIX System ==
|
||||
|
||||
If there are no instructions specifically adapted to your system
|
||||
above you can try adding the command "ulimit -n 8192" to the script
|
||||
that starts Asterisk.
|
||||
|
||||
* MORE INFORMATION
|
||||
|
||||
See the doc directory for more documentation on various features. Again,
|
||||
please read all the configuration samples that include documentation on
|
||||
the configuration options.
|
||||
|
||||
Finally, you may wish to visit the web site and join the mailing list if
|
||||
you're interested in getting more information.
|
||||
|
||||
http://www.asterisk.org/support
|
||||
|
||||
Welcome to the growing worldwide community of Asterisk users!
|
||||
|
||||
Mark Spencer
|
||||
|
||||
----
|
||||
Asterisk is a trademark belonging to Digium, inc
|
||||
163
trunk/UPGRADE.txt
Normal file
163
trunk/UPGRADE.txt
Normal file
@@ -0,0 +1,163 @@
|
||||
Information for Upgrading From Previous Asterisk Releases
|
||||
=========================================================
|
||||
|
||||
AEL:
|
||||
|
||||
* Macros are now implemented underneath with the Gosub() application.
|
||||
Heaven Help You if you wrote code depending on any aspect of this!
|
||||
Previous to 1.6, macros were implemented with the Macro() app, which
|
||||
provided a nice feature of auto-returning. The compiler will do its
|
||||
best to insert a Return() app call at the end of your macro if you did
|
||||
not include it, but really, you should make sure that all execution
|
||||
paths within your macros end in "return;".
|
||||
|
||||
* The conf2ael program is 'introduced' in this release; it is in a rather
|
||||
crude state, but deemed useful for making a first pass at converting
|
||||
extensions.conf code into AEL. More intelligence will come with time.
|
||||
|
||||
Core:
|
||||
|
||||
* The 'languageprefix' option in asterisk.conf is now deprecated, and
|
||||
the default sound file layout for non-English sounds is the 'new
|
||||
style' layout introduced in Asterisk 1.4 (and used by the automatic
|
||||
sound file installer in the Makefile).
|
||||
|
||||
* The ast_expr2 stuff has been modified to handle floating-point numbers.
|
||||
Numbers of the format D.D are now acceptable input for the expr parser,
|
||||
Where D is a string of base-10 digits. All math is now done in "long double",
|
||||
if it is available on your compiler/architecture. This was half-way between
|
||||
a bug-fix (because the MATH func returns fp by default), and an enhancement.
|
||||
Also, for those counting on, or needing, integer operations, a series of
|
||||
'functions' were also added to the expr language, to allow several styles
|
||||
of rounding/truncation, along with a set of common floating point operations,
|
||||
like sin, cos, tan, log, pow, etc. The ability to call external functions
|
||||
like CDR(), etc. was also added, without having to use the ${...} notation.
|
||||
|
||||
* The delimiter passed to applications has been changed to the comma (','), as
|
||||
that is what people are used to using within extensions.conf. If you are
|
||||
using realtime extensions, you will need to translate your existing dialplan
|
||||
to use this separator. To use a literal comma, you need merely to escape it
|
||||
with a backslash ('\'). Another possible side effect is that you may need to
|
||||
remove the obscene level of backslashing that was necessary for the dialplan
|
||||
to work correctly in 1.4 and previous versions. This should make writing
|
||||
dialplans less painful in the future, albeit with the pain of a one-time
|
||||
conversion.
|
||||
|
||||
* The logger.conf option 'rotatetimestamp' has been deprecated in favor of
|
||||
'rotatestrategy'. This new option supports a 'rotate' strategy that more
|
||||
closely mimics the system logger in terms of file rotation.
|
||||
|
||||
* The concise versions of various CLI commands are now deprecated. We recommend
|
||||
using the manager interface (AMI) for application integration with Asterisk.
|
||||
|
||||
Voicemail:
|
||||
|
||||
* The voicemail configuration values 'maxmessage' and 'minmessage' have
|
||||
been changed to 'maxsecs' and 'minsecs' to clarify their purpose and
|
||||
to make them more distinguishable from 'maxmsgs', which sets folder
|
||||
size. The old variables will continue to work in this version, albeit
|
||||
with a deprecation warning.
|
||||
* If you use any interface for modifying voicemail aside from the built in
|
||||
dialplan applications, then the option "pollmailboxes" *must* be set in
|
||||
voicemail.conf for message waiting indication (MWI) to work properly. This
|
||||
is because Voicemail notification is now event based instead of polling
|
||||
based. The channel drivers are no longer responsible for constantly manually
|
||||
checking mailboxes for changes so that they can send MWI information to users.
|
||||
Examples of situations that would require this option are web interfaces to
|
||||
voicemail or an email client in the case of using IMAP storage.
|
||||
|
||||
Applications:
|
||||
|
||||
* ChanIsAvail() now has a 't' option, which allows the specified device
|
||||
to be queried for state without consulting the channel drivers. This
|
||||
performs mostly a 'ChanExists' sort of function.
|
||||
* SetCallerPres() has been replaced with the CALLERPRES() dialplan function
|
||||
and is now deprecated.
|
||||
* DISA()'s fifth argument is now an options argument. If you have previously
|
||||
used 'NOANSWER' in this argument, you'll need to convert that to the new
|
||||
option 'n'.
|
||||
* Macro() is now deprecated. If you need subroutines, you should use the
|
||||
Gosub()/Return() applications. To replace MacroExclusive(), we have
|
||||
introduced dialplan functions LOCK(), TRYLOCK(), and UNLOCK(). You may use
|
||||
these functions in any location where you desire to ensure that only one
|
||||
channel is executing that path at any one time.
|
||||
* Read() now sets a READSTATUS variable on exit. It does NOT automatically
|
||||
return -1 (and hangup) anymore on error. If you want to hangup on error,
|
||||
you need to do so explicitly in your dialplan.
|
||||
* Privacy() no longer uses privacy.conf, so any options must be specified
|
||||
directly in the application arguments.
|
||||
|
||||
Dialplan Functions:
|
||||
|
||||
* QUEUE_MEMBER_COUNT() has been deprecated in favor of the QUEUE_MEMBER() function. For
|
||||
more information, issue a "show function QUEUE_MEMBER" from the CLI.
|
||||
|
||||
CDR:
|
||||
|
||||
* The cdr_sqlite module has been marked as deprecated in favor of
|
||||
cdr_sqlite3_custom. It will potentially be removed from the tree
|
||||
after Asterisk 1.6 is released.
|
||||
|
||||
* The cdr_odbc module now uses res_odbc to manage its connections. The
|
||||
username and password parameters in cdr_odbc.conf, therefore, are no
|
||||
longer used. The dsn parameter now points to an entry in res_odbc.conf.
|
||||
|
||||
Formats:
|
||||
|
||||
* format_wav: The GAIN preprocessor definition and source code that used it
|
||||
is removed. This change was made in response to user complaints of
|
||||
choppiness or the clipping of loud signal peaks. To increase the volume
|
||||
of voicemail messages, use the 'volgain' option in voicemail.conf
|
||||
|
||||
Channel Drivers:
|
||||
|
||||
* SIP: a small upgrade to support the "Record" button on the SNOM360,
|
||||
which sends a sip INFO message with a "Record: on" or "Record: off"
|
||||
header. If Asterisk is set up (via features.conf) to accept "One Touch Monitor"
|
||||
requests (by default, via '*1'), then the user-configured dialpad sequence
|
||||
is generated, and recording can be started and stopped via this button. The
|
||||
file names and formats are all controlled via the normal mechanisms. If the
|
||||
user has not configured the automon feature, the normal "415 Unsupported media type"
|
||||
is returned, and nothing is done.
|
||||
* SIP: The "call-limit" option is marked as deprecated. It still works in this version of
|
||||
Asterisk, but will be removed in the following version. Please use the groupcount functions
|
||||
in the dialplan to enforce call limits. The "limitonpeer" configuration option is
|
||||
now renamed to "counteronpeer".
|
||||
* SIP: The "username" option is now renamed to "defaultuser" to match "defaultip".
|
||||
These are used only before registration to call a peer with the uri
|
||||
sip:defaultuser@defaultip
|
||||
The "username" setting still work, but is deprecated and will not work in
|
||||
the next version of Asterisk.
|
||||
|
||||
* chan_local.c: the comma delimiter inside the channel name has been changed to a
|
||||
semicolon, in order to make the Local channel driver compatible with the comma
|
||||
delimiter change in applications.
|
||||
* H323: The "tos" setting has changed name to "tos_audio" and "cos" to "cos_audio"
|
||||
to be compatible with settings in sip.conf. The "tos" and "cos" configuration
|
||||
is deprecated and will stop working in the next release of Asterisk.
|
||||
|
||||
* Console: A new console channel driver, chan_console, has been added to Asterisk.
|
||||
This new module can not be loaded at the same time as chan_alsa or chan_oss. The
|
||||
default modules.conf only loads one of them (chan_oss by default). So, unless you
|
||||
have modified your modules.conf to not use the autoload option, then you will need
|
||||
to modify modules.conf to add another "noload" line to ensure that only one of
|
||||
these three modules gets loaded.
|
||||
|
||||
Configuration:
|
||||
|
||||
* pbx_dundi.c: tos parameter changed to use new values. Old values like lowdelay,
|
||||
lowcost and other is not acceptable now. Look into qos.tex for description of
|
||||
this parameter.
|
||||
|
||||
Manager:
|
||||
|
||||
* Manager has been upgraded to version 1.1 with a lot of changes.
|
||||
Please check doc/manager_1_1.txt for information
|
||||
|
||||
* The IAXpeers command output has been changed to more closely resemble the
|
||||
output of the SIPpeers command.
|
||||
|
||||
* cdr_manager now reports at the "cdr" level, not at "call" You may need to
|
||||
change your manager.conf to add the level to existing AMI users, if they
|
||||
want to see the CDR events generated.
|
||||
|
||||
1106
trunk/acinclude.m4
Normal file
1106
trunk/acinclude.m4
Normal file
File diff suppressed because it is too large
Load Diff
82
trunk/agi/DialAnMp3.agi
Normal file
82
trunk/agi/DialAnMp3.agi
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Simple AGI application to play mp3's selected by a user both using
|
||||
# xmms and over the phone itself.
|
||||
#
|
||||
$|=1;
|
||||
while(<STDIN>) {
|
||||
chomp;
|
||||
last unless length($_);
|
||||
if (/^agi_(\w+)\:\s+(.*)$/) {
|
||||
$AGI{$1} = $2;
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "AGI Environment Dump:\n";
|
||||
foreach $i (sort keys %AGI) {
|
||||
print STDERR " -- $i = $AGI{$i}\n";
|
||||
}
|
||||
|
||||
dbmopen(%DIGITS, "/var/lib/asterisk/mp3list", 0644) || die("Unable to open mp3list");;
|
||||
|
||||
sub checkresult {
|
||||
my ($res) = @_;
|
||||
my $retval;
|
||||
$tests++;
|
||||
chomp $res;
|
||||
if ($res =~ /^200/) {
|
||||
$res =~ /result=(-?[\w\*\#]+)/;
|
||||
return $1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#print STDERR "1. Playing beep...\n";
|
||||
#print "STREAM FILE beep \"\"\n";
|
||||
#$result = <STDIN>;
|
||||
#checkresult($result);
|
||||
|
||||
print STDERR "2. Getting song name...\n";
|
||||
print "GET DATA demo-enterkeywords\n";
|
||||
$result = <STDIN>;
|
||||
$digitstr = checkresult($result);
|
||||
if ($digitstr < 0) {
|
||||
exit(1);
|
||||
}
|
||||
$digitstr =~ s/\*/ /g;
|
||||
|
||||
print STDERR "Resulting songname is $digitstr\n";
|
||||
@searchwords = split (/\s+/, $digitstr);
|
||||
print STDERR "Searchwords: " . join(':', @searchwords) . "\n";
|
||||
|
||||
foreach $key (sort keys %DIGITS) {
|
||||
@words = split(/\s+/, $DIGITS{$key});
|
||||
$match = 1;
|
||||
foreach $search (@searchwords) {
|
||||
$match = 0 unless grep(/$search/, @words);
|
||||
}
|
||||
if ($match > 0) {
|
||||
print STDERR "File $key matches\n";
|
||||
# Play a beep
|
||||
print "STREAM FILE beep \"\"\n";
|
||||
system("xmms", $key);
|
||||
$result = <STDIN>;
|
||||
if (&checkresult($result) < 0) {
|
||||
exit 0;
|
||||
}
|
||||
print "EXEC MP3Player \"$key\"\n";
|
||||
# print "WAIT FOR DIGIT 60000\n";
|
||||
$result = <STDIN>;
|
||||
if (&checkresult($result) < 0) {
|
||||
exit 0;
|
||||
}
|
||||
print STDERR "Got here...\n";
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "4. Testing 'saynumber' of $digitstr...\n";
|
||||
print "STREAM FILE demo-nomatch\"\"\n";
|
||||
$result = <STDIN>;
|
||||
checkresult($result);
|
||||
|
||||
52
trunk/agi/Makefile
Normal file
52
trunk/agi/Makefile
Normal file
@@ -0,0 +1,52 @@
|
||||
#
|
||||
# Asterisk -- A telephony toolkit for Linux.
|
||||
#
|
||||
# Makefile for AGI-related stuff
|
||||
#
|
||||
# Copyright (C) 1999-2006, Digium
|
||||
#
|
||||
# Mark Spencer <markster@digium.com>
|
||||
#
|
||||
# This program is free software, distributed under the terms of
|
||||
# the GNU General Public License
|
||||
#
|
||||
|
||||
.PHONY: clean all uninstall
|
||||
|
||||
AGIS=agi-test.agi eagi-test eagi-sphinx-test jukebox.agi
|
||||
|
||||
ifeq ($(OSARCH),SunOS)
|
||||
LIBS+=-lsocket -lnsl
|
||||
endif
|
||||
|
||||
ifeq ($(OSARCH),mingw32)
|
||||
AGIS:=
|
||||
endif
|
||||
|
||||
include $(ASTTOPDIR)/Makefile.rules
|
||||
|
||||
all: $(AGIS)
|
||||
|
||||
strcompat.c: ../main/strcompat.c
|
||||
@cp $< $@
|
||||
|
||||
eagi-test: eagi-test.o strcompat.o
|
||||
|
||||
eagi-sphinx-test: eagi-sphinx-test.o
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(AGI_DIR)
|
||||
for x in $(AGIS); do $(INSTALL) -m 755 $$x $(DESTDIR)$(AGI_DIR) ; done
|
||||
|
||||
uninstall:
|
||||
for x in $(AGIS); do rm -f $(DESTDIR)$(AGI_DIR)/$$x ; done
|
||||
|
||||
clean:
|
||||
rm -f *.so *.o look eagi-test eagi-sphinx-test
|
||||
rm -f .*.o.d .*.oo.d
|
||||
rm -f *.s *.i
|
||||
rm -f strcompat.c
|
||||
|
||||
ifneq ($(wildcard .*.d),)
|
||||
include .*.d
|
||||
endif
|
||||
79
trunk/agi/agi-test.agi
Normal file
79
trunk/agi/agi-test.agi
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
|
||||
$|=1;
|
||||
|
||||
# Setup some variables
|
||||
my %AGI; my $tests = 0; my $fail = 0; my $pass = 0;
|
||||
|
||||
while(<STDIN>) {
|
||||
chomp;
|
||||
last unless length($_);
|
||||
if (/^agi_(\w+)\:\s+(.*)$/) {
|
||||
$AGI{$1} = $2;
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "AGI Environment Dump:\n";
|
||||
foreach my $i (sort keys %AGI) {
|
||||
print STDERR " -- $i = $AGI{$i}\n";
|
||||
}
|
||||
|
||||
sub checkresult {
|
||||
my ($res) = @_;
|
||||
my $retval;
|
||||
$tests++;
|
||||
chomp $res;
|
||||
if ($res =~ /^200/) {
|
||||
$res =~ /result=(-?\d+)/;
|
||||
if (!length($1)) {
|
||||
print STDERR "FAIL ($res)\n";
|
||||
$fail++;
|
||||
} else {
|
||||
print STDERR "PASS ($1)\n";
|
||||
$pass++;
|
||||
}
|
||||
} else {
|
||||
print STDERR "FAIL (unexpected result '$res')\n";
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "1. Testing 'sendfile'...";
|
||||
print "STREAM FILE beep \"\"\n";
|
||||
my $result = <STDIN>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "2. Testing 'sendtext'...";
|
||||
print "SEND TEXT \"hello world\"\n";
|
||||
my $result = <STDIN>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "3. Testing 'sendimage'...";
|
||||
print "SEND IMAGE asterisk-image\n";
|
||||
my $result = <STDIN>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "4. Testing 'saynumber'...";
|
||||
print "SAY NUMBER 192837465 \"\"\n";
|
||||
my $result = <STDIN>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "5. Testing 'waitdtmf'...";
|
||||
print "WAIT FOR DIGIT 1000\n";
|
||||
my $result = <STDIN>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "6. Testing 'record'...";
|
||||
print "RECORD FILE testagi gsm 1234 3000\n";
|
||||
my $result = <STDIN>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "6a. Testing 'record' playback...";
|
||||
print "STREAM FILE testagi \"\"\n";
|
||||
my $result = <STDIN>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "================== Complete ======================\n";
|
||||
print STDERR "$tests tests completed, $pass passed, $fail failed\n";
|
||||
print STDERR "==================================================\n";
|
||||
231
trunk/agi/eagi-sphinx-test.c
Normal file
231
trunk/agi/eagi-sphinx-test.c
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Extended AGI test application
|
||||
*
|
||||
* This code is released into public domain
|
||||
* without any warranty of any kind.
|
||||
*
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
* Extended AGI test application
|
||||
*
|
||||
* This code is released into public domain
|
||||
* without any warranty of any kind.
|
||||
*
|
||||
* \ingroup agi
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
#include "asterisk/compat.h"
|
||||
|
||||
#define AUDIO_FILENO (STDERR_FILENO + 1)
|
||||
|
||||
#define SPHINX_HOST "192.168.1.108"
|
||||
#define SPHINX_PORT 3460
|
||||
|
||||
static int sphinx_sock = -1;
|
||||
|
||||
static int connect_sphinx(void)
|
||||
{
|
||||
struct hostent *hp;
|
||||
struct sockaddr_in sin;
|
||||
int res;
|
||||
hp = gethostbyname(SPHINX_HOST);
|
||||
if (!hp) {
|
||||
fprintf(stderr, "Unable to resolve '%s'\n", SPHINX_HOST);
|
||||
return -1;
|
||||
}
|
||||
sphinx_sock = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (sphinx_sock < 0) {
|
||||
fprintf(stderr, "Unable to allocate socket: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(SPHINX_PORT);
|
||||
memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
|
||||
if (connect(sphinx_sock, (struct sockaddr *)&sin, sizeof(sin))) {
|
||||
fprintf(stderr, "Unable to connect on socket: %s\n", strerror(errno));
|
||||
close(sphinx_sock);
|
||||
sphinx_sock = -1;
|
||||
return -1;
|
||||
}
|
||||
res = fcntl(sphinx_sock, F_GETFL);
|
||||
if ((res < 0) || (fcntl(sphinx_sock, F_SETFL, res | O_NONBLOCK) < 0)) {
|
||||
fprintf(stderr, "Unable to set flags on socket: %s\n", strerror(errno));
|
||||
close(sphinx_sock);
|
||||
sphinx_sock = -1;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_environment(void)
|
||||
{
|
||||
char buf[256];
|
||||
char *val;
|
||||
/* Read environment */
|
||||
for(;;) {
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
if (feof(stdin))
|
||||
return -1;
|
||||
buf[strlen(buf) - 1] = '\0';
|
||||
/* Check for end of environment */
|
||||
if (!strlen(buf))
|
||||
return 0;
|
||||
val = strchr(buf, ':');
|
||||
if (!val) {
|
||||
fprintf(stderr, "Invalid environment: '%s'\n", buf);
|
||||
return -1;
|
||||
}
|
||||
*val = '\0';
|
||||
val++;
|
||||
val++;
|
||||
/* Skip space */
|
||||
fprintf(stderr, "Environment: '%s' is '%s'\n", buf, val);
|
||||
|
||||
/* Load into normal environment */
|
||||
setenv(buf, val, 1);
|
||||
|
||||
}
|
||||
/* Never reached */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *wait_result(void)
|
||||
{
|
||||
fd_set fds;
|
||||
int res;
|
||||
int max;
|
||||
static char astresp[256];
|
||||
static char sphinxresp[256];
|
||||
char audiobuf[4096];
|
||||
for (;;) {
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
FD_SET(AUDIO_FILENO, &fds);
|
||||
max = AUDIO_FILENO;
|
||||
if (sphinx_sock > -1) {
|
||||
FD_SET(sphinx_sock, &fds);
|
||||
if (sphinx_sock > max)
|
||||
max = sphinx_sock;
|
||||
}
|
||||
/* Wait for *some* sort of I/O */
|
||||
res = select(max + 1, &fds, NULL, NULL, NULL);
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "Error in select: %s\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
if (FD_ISSET(STDIN_FILENO, &fds)) {
|
||||
fgets(astresp, sizeof(astresp), stdin);
|
||||
if (feof(stdin)) {
|
||||
fprintf(stderr, "Got hungup on apparently\n");
|
||||
return NULL;
|
||||
}
|
||||
astresp[strlen(astresp) - 1] = '\0';
|
||||
fprintf(stderr, "Ooh, got a response from Asterisk: '%s'\n", astresp);
|
||||
return astresp;
|
||||
}
|
||||
if (FD_ISSET(AUDIO_FILENO, &fds)) {
|
||||
res = read(AUDIO_FILENO, audiobuf, sizeof(audiobuf));
|
||||
if (res > 0) {
|
||||
if (sphinx_sock > -1)
|
||||
write(sphinx_sock, audiobuf, res);
|
||||
}
|
||||
}
|
||||
if ((sphinx_sock > -1) && FD_ISSET(sphinx_sock, &fds)) {
|
||||
res = read(sphinx_sock, sphinxresp, sizeof(sphinxresp));
|
||||
if (res > 0) {
|
||||
fprintf(stderr, "Oooh, Sphinx found a token: '%s'\n", sphinxresp);
|
||||
return sphinxresp;
|
||||
} else if (res == 0) {
|
||||
fprintf(stderr, "Hrm, lost sphinx, guess we're on our own\n");
|
||||
close(sphinx_sock);
|
||||
sphinx_sock = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static char *run_command(char *command)
|
||||
{
|
||||
fprintf(stdout, "%s\n", command);
|
||||
return wait_result();
|
||||
}
|
||||
|
||||
static int run_script(void)
|
||||
{
|
||||
char *res;
|
||||
res = run_command("STREAM FILE demo-enterkeywords 0123456789*#");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "1. Result is '%s'\n", res);
|
||||
res = run_command("STREAM FILE demo-nomatch 0123456789*#");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "2. Result is '%s'\n", res);
|
||||
res = run_command("SAY NUMBER 23452345 0123456789*#");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "3. Result is '%s'\n", res);
|
||||
res = run_command("GET DATA demo-enterkeywords");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "4. Result is '%s'\n", res);
|
||||
res = run_command("STREAM FILE auth-thankyou \"\"");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "5. Result is '%s'\n", res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *tmp;
|
||||
int ver = 0;
|
||||
int subver = 0;
|
||||
/* Setup stdin/stdout for line buffering */
|
||||
setlinebuf(stdin);
|
||||
setlinebuf(stdout);
|
||||
if (read_environment()) {
|
||||
fprintf(stderr, "Failed to read environment: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
connect_sphinx();
|
||||
tmp = getenv("agi_enhanced");
|
||||
if (tmp) {
|
||||
if (sscanf(tmp, "%d.%d", &ver, &subver) != 2)
|
||||
ver = 0;
|
||||
}
|
||||
if (ver < 1) {
|
||||
fprintf(stderr, "No enhanced AGI services available. Use EAGI, not AGI\n");
|
||||
exit(1);
|
||||
}
|
||||
if (run_script())
|
||||
return -1;
|
||||
exit(0);
|
||||
}
|
||||
165
trunk/agi/eagi-test.c
Normal file
165
trunk/agi/eagi-test.c
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Extended AGI test application
|
||||
*
|
||||
* This code is released into the public domain
|
||||
* with no warranty of any kind
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
#define AUDIO_FILENO (STDERR_FILENO + 1)
|
||||
|
||||
/*! \file
|
||||
* Extended AGI test application
|
||||
*
|
||||
* This code is released into the public domain
|
||||
* with no warranty of any kind
|
||||
*
|
||||
* \ingroup agi
|
||||
*/
|
||||
|
||||
static int read_environment(void)
|
||||
{
|
||||
char buf[256];
|
||||
char *val;
|
||||
/* Read environment */
|
||||
for(;;) {
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
if (feof(stdin))
|
||||
return -1;
|
||||
buf[strlen(buf) - 1] = '\0';
|
||||
/* Check for end of environment */
|
||||
if (!strlen(buf))
|
||||
return 0;
|
||||
val = strchr(buf, ':');
|
||||
if (!val) {
|
||||
fprintf(stderr, "Invalid environment: '%s'\n", buf);
|
||||
return -1;
|
||||
}
|
||||
*val = '\0';
|
||||
val++;
|
||||
val++;
|
||||
/* Skip space */
|
||||
fprintf(stderr, "Environment: '%s' is '%s'\n", buf, val);
|
||||
|
||||
/* Load into normal environment */
|
||||
setenv(buf, val, 1);
|
||||
|
||||
}
|
||||
/* Never reached */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *wait_result(void)
|
||||
{
|
||||
fd_set fds;
|
||||
int res;
|
||||
int bytes = 0;
|
||||
static char astresp[256];
|
||||
char audiobuf[4096];
|
||||
for (;;) {
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
FD_SET(AUDIO_FILENO, &fds);
|
||||
/* Wait for *some* sort of I/O */
|
||||
res = select(AUDIO_FILENO + 1, &fds, NULL, NULL, NULL);
|
||||
if (res < 0) {
|
||||
fprintf(stderr, "Error in select: %s\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
if (FD_ISSET(STDIN_FILENO, &fds)) {
|
||||
fgets(astresp, sizeof(astresp), stdin);
|
||||
if (feof(stdin)) {
|
||||
fprintf(stderr, "Got hungup on apparently\n");
|
||||
return NULL;
|
||||
}
|
||||
astresp[strlen(astresp) - 1] = '\0';
|
||||
fprintf(stderr, "Ooh, got a response from Asterisk: '%s'\n", astresp);
|
||||
return astresp;
|
||||
}
|
||||
if (FD_ISSET(AUDIO_FILENO, &fds)) {
|
||||
res = read(AUDIO_FILENO, audiobuf, sizeof(audiobuf));
|
||||
if (res > 0) {
|
||||
/* XXX Process the audio with sphinx here XXX */
|
||||
#if 0
|
||||
fprintf(stderr, "Got %d/%d bytes of audio\n", res, bytes);
|
||||
#endif
|
||||
bytes += res;
|
||||
/* Prentend we detected some audio after 3 seconds */
|
||||
if (bytes > 16000 * 3) {
|
||||
return "Sample Message";
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static char *run_command(char *command)
|
||||
{
|
||||
fprintf(stdout, "%s\n", command);
|
||||
return wait_result();
|
||||
}
|
||||
|
||||
static int run_script(void)
|
||||
{
|
||||
char *res;
|
||||
res = run_command("STREAM FILE demo-enterkeywords 0123456789*#");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "1. Result is '%s'\n", res);
|
||||
res = run_command("STREAM FILE demo-nomatch 0123456789*#");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "2. Result is '%s'\n", res);
|
||||
res = run_command("SAY NUMBER 23452345 0123456789*#");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "3. Result is '%s'\n", res);
|
||||
res = run_command("GET DATA demo-enterkeywords");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "4. Result is '%s'\n", res);
|
||||
res = run_command("STREAM FILE auth-thankyou \"\"");
|
||||
if (!res) {
|
||||
fprintf(stderr, "Failed to execute command\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "5. Result is '%s'\n", res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *tmp;
|
||||
int ver = 0;
|
||||
int subver = 0;
|
||||
/* Setup stdin/stdout for line buffering */
|
||||
setlinebuf(stdin);
|
||||
setlinebuf(stdout);
|
||||
if (read_environment()) {
|
||||
fprintf(stderr, "Failed to read environment: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
tmp = getenv("agi_enhanced");
|
||||
if (tmp) {
|
||||
if (sscanf(tmp, "%d.%d", &ver, &subver) != 2)
|
||||
ver = 0;
|
||||
}
|
||||
if (ver < 1) {
|
||||
fprintf(stderr, "No enhanced AGI services available. Use EAGI, not AGI\n");
|
||||
exit(1);
|
||||
}
|
||||
if (run_script())
|
||||
return -1;
|
||||
exit(0);
|
||||
}
|
||||
94
trunk/agi/fastagi-test
Normal file
94
trunk/agi/fastagi-test
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
use Socket;
|
||||
use Carp;
|
||||
use IO::Handle;
|
||||
|
||||
my $port = 4573;
|
||||
|
||||
$|=1;
|
||||
|
||||
# Setup some variables
|
||||
my %AGI; my $tests = 0; my $fail = 0; my $pass = 0;
|
||||
|
||||
sub checkresult {
|
||||
my ($res) = @_;
|
||||
my $retval;
|
||||
$tests++;
|
||||
chomp $res;
|
||||
if ($res =~ /^200/) {
|
||||
$res =~ /result=(-?\d+)/;
|
||||
if (!length($1)) {
|
||||
print STDERR "FAIL ($res)\n";
|
||||
$fail++;
|
||||
} else {
|
||||
print STDERR "PASS ($1)\n";
|
||||
$pass++;
|
||||
}
|
||||
} else {
|
||||
print STDERR "FAIL (unexpected result '$res')\n";
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
|
||||
socket(SERVER, PF_INET, SOCK_STREAM, 0);
|
||||
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));
|
||||
bind(SERVER, sockaddr_in($port, INADDR_ANY)) || die("can't bind\n");
|
||||
listen(SERVER, SOMAXCONN);
|
||||
|
||||
for(;;) {
|
||||
my $raddr = accept(CLIENT, SERVER);
|
||||
my ($s, $p) = sockaddr_in($raddr);
|
||||
CLIENT->autoflush(1);
|
||||
while(<CLIENT>) {
|
||||
chomp;
|
||||
last unless length($_);
|
||||
if (/^agi_(\w+)\:\s+(.*)$/) {
|
||||
$AGI{$1} = $2;
|
||||
}
|
||||
}
|
||||
print STDERR "AGI Environment Dump from $s:$p --\n";
|
||||
foreach my $i (sort keys %AGI) {
|
||||
print STDERR " -- $i = $AGI{$i}\n";
|
||||
}
|
||||
|
||||
print STDERR "1. Testing 'sendfile'...";
|
||||
print CLIENT "STREAM FILE beep \"\"\n";
|
||||
my $result = <CLIENT>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "2. Testing 'sendtext'...";
|
||||
print CLIENT "SEND TEXT \"hello world\"\n";
|
||||
my $result = <CLIENT>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "3. Testing 'sendimage'...";
|
||||
print CLIENT "SEND IMAGE asterisk-image\n";
|
||||
my $result = <CLIENT>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "4. Testing 'saynumber'...";
|
||||
print CLIENT "SAY NUMBER 192837465 \"\"\n";
|
||||
my $result = <CLIENT>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "5. Testing 'waitdtmf'...";
|
||||
print CLIENT "WAIT FOR DIGIT 1000\n";
|
||||
my $result = <CLIENT>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "6. Testing 'record'...";
|
||||
print CLIENT "RECORD FILE testagi gsm 1234 3000\n";
|
||||
my $result = <CLIENT>;
|
||||
&checkresult($result);
|
||||
|
||||
print STDERR "6a. Testing 'record' playback...";
|
||||
print CLIENT "STREAM FILE testagi \"\"\n";
|
||||
my $result = <CLIENT>;
|
||||
&checkresult($result);
|
||||
close(CLIENT);
|
||||
print STDERR "================== Complete ======================\n";
|
||||
print STDERR "$tests tests completed, $pass passed, $fail failed\n";
|
||||
print STDERR "==================================================\n";
|
||||
}
|
||||
|
||||
488
trunk/agi/jukebox.agi
Executable file
488
trunk/agi/jukebox.agi
Executable file
@@ -0,0 +1,488 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Jukebox 0.2
|
||||
#
|
||||
# A music manager for Asterisk.
|
||||
#
|
||||
# Copyright (C) 2005-2006, Justin Tunney
|
||||
#
|
||||
# Justin Tunney <jesuscyborg@gmail.com>
|
||||
#
|
||||
# This program is free software, distributed under the terms of the
|
||||
# GNU General Public License v2.
|
||||
#
|
||||
# Keep it open source pigs
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
#
|
||||
# Uses festival to list off all your MP3 music files over a channel in
|
||||
# a hierarchical fashion. Put this file in your agi-bin folder which
|
||||
# is located at: /var/lib/asterisk/agi-bin Be sure to chmod +x it!
|
||||
#
|
||||
# Invocation Example:
|
||||
# exten => 68742,1,Answer()
|
||||
# exten => 68742,2,agi,jukebox.agi|/home/justin/Music
|
||||
# exten => 68742,3,Hangup()
|
||||
#
|
||||
# exten => 68742,1,Answer()
|
||||
# exten => 68742,2,agi,jukebox.agi|/home/justin/Music|pm
|
||||
# exten => 68742,3,Hangup()
|
||||
#
|
||||
# Options:
|
||||
# p - Precache text2wave outputs for every possible filename.
|
||||
# It is much better to set this option because if a caller
|
||||
# presses a key during a cache operation, it will be ignored.
|
||||
# m - Go back to menu after playing song
|
||||
# g - Do not play the greeting message
|
||||
#
|
||||
# Usage Instructions:
|
||||
# - Press '*' to go up a directory. If you are in the root music
|
||||
# folder you will be exitted from the script.
|
||||
# - If you have a really long list of files, you can filter the list
|
||||
# at any time by pressing '#' and spelling out a few letters you
|
||||
# expect the files to start with. For example, if you wanted to
|
||||
# know what extension 'Requiem For A Dream' was, you'd type:
|
||||
# '#737'. Note, phone keypads don't include Q and Z. Q is 7 and
|
||||
# Z is 9.
|
||||
#
|
||||
# Notes:
|
||||
# - This AGI script uses the MP3Player command which uses the
|
||||
# mpg123 Program. Grab yourself a copy of this program by
|
||||
# going to http://www.mpg123.de/cgi-bin/sitexplorer.cgi?/mpg123/
|
||||
# Be sure to download mpg123-0.59r.tar.gz because it is known to
|
||||
# work with Asterisk and hopefully isn't the release with that
|
||||
# awful security problem. If you're using Fedora Core 3 with
|
||||
# Alsa like me, make linux-alsa isn't going to work. Do make
|
||||
# linux-devel and you're peachy keen.
|
||||
#
|
||||
# - You won't get nifty STDERR debug messages if you're using a
|
||||
# remote asterisk shell.
|
||||
#
|
||||
# - For some reason, caching certain files will generate the
|
||||
# error: 'using default diphone ax-ax for y-pau'. Example:
|
||||
# # echo "Depeche Mode - CUW - 05 - The Meaning of Love" | text2wave -o /var/jukeboxcache/jukeboxcache/Depeche_Mode/Depeche_Mode_-_CUW_-_05_-_The_Meaning_of_Love.mp3.ul -otype ulaw -
|
||||
# The temporary work around is to just touch these files.
|
||||
#
|
||||
# - The background app doesn't like to get more than 2031 chars
|
||||
# of input.
|
||||
#
|
||||
|
||||
use strict;
|
||||
|
||||
$|=1;
|
||||
|
||||
# Setup some variables
|
||||
my %AGI; my $tests = 0; my $fail = 0; my $pass = 0;
|
||||
my @masterCacheList = ();
|
||||
my $maxNumber = 10;
|
||||
|
||||
while (<STDIN>) {
|
||||
chomp;
|
||||
last unless length($_);
|
||||
if (/^agi_(\w+)\:\s+(.*)$/) {
|
||||
$AGI{$1} = $2;
|
||||
}
|
||||
}
|
||||
|
||||
# setup options
|
||||
my $SHOWGREET = 1;
|
||||
my $PRECACHE = 0;
|
||||
my $MENUAFTERSONG = 0;
|
||||
|
||||
$PRECACHE = 1 if $ARGV[1] =~ /p/;
|
||||
$MENUAFTERSONG = 1 if $ARGV[1] =~ /m/;
|
||||
$SHOWGREET = 0 if $ARGV[1] =~ /g/;
|
||||
|
||||
# setup folders
|
||||
my $MUSIC = $ARGV[0];
|
||||
$MUSIC = &rmts($MUSIC);
|
||||
my $FESTIVALCACHE = "/var/jukeboxcache";
|
||||
if (! -e $FESTIVALCACHE) {
|
||||
`mkdir -p -m0776 $FESTIVALCACHE`;
|
||||
}
|
||||
|
||||
# make sure we have some essential files
|
||||
if (! -e "$FESTIVALCACHE/jukebox_greet.ul") {
|
||||
`echo "Welcome to the Asterisk Jukebox" | text2wave -o $FESTIVALCACHE/jukebox_greet.ul -otype ulaw -`;
|
||||
}
|
||||
if (! -e "$FESTIVALCACHE/jukebox_press.ul") {
|
||||
`echo "Press" | text2wave -o $FESTIVALCACHE/jukebox_press.ul -otype ulaw -`;
|
||||
}
|
||||
if (! -e "$FESTIVALCACHE/jukebox_for.ul") {
|
||||
`echo "For" | text2wave -o $FESTIVALCACHE/jukebox_for.ul -otype ulaw -`;
|
||||
}
|
||||
if (! -e "$FESTIVALCACHE/jukebox_toplay.ul") {
|
||||
`echo "To play" | text2wave -o $FESTIVALCACHE/jukebox_toplay.ul -otype ulaw -`;
|
||||
}
|
||||
if (! -e "$FESTIVALCACHE/jukebox_nonefound.ul") {
|
||||
`echo "There were no music files found in this folder" | text2wave -o $FESTIVALCACHE/jukebox_nonefound.ul -otype ulaw -`;
|
||||
}
|
||||
if (! -e "$FESTIVALCACHE/jukebox_percent.ul") {
|
||||
`echo "Percent" | text2wave -o $FESTIVALCACHE/jukebox_percent.ul -otype ulaw -`;
|
||||
}
|
||||
if (! -e "$FESTIVALCACHE/jukebox_generate.ul") {
|
||||
`echo "Please wait while Astrisk Jukebox cashes the files of your music collection" | text2wave -o $FESTIVALCACHE/jukebox_generate.ul -otype ulaw -`;
|
||||
}
|
||||
if (! -e "$FESTIVALCACHE/jukebox_invalid.ul") {
|
||||
`echo "You have entered an invalid selection" | text2wave -o $FESTIVALCACHE/jukebox_invalid.ul -otype ulaw -`;
|
||||
}
|
||||
if (! -e "$FESTIVALCACHE/jukebox_thankyou.ul") {
|
||||
`echo "Thank you for using Astrisk Jukebox, Goodbye" | text2wave -o $FESTIVALCACHE/jukebox_thankyou.ul -otype ulaw -`;
|
||||
}
|
||||
|
||||
# greet the user
|
||||
if ($SHOWGREET) {
|
||||
print "EXEC Playback \"$FESTIVALCACHE/jukebox_greet\"\n";
|
||||
my $result = <STDIN>; &check_result($result);
|
||||
}
|
||||
|
||||
# go through the directories
|
||||
music_dir_cache() if $PRECACHE;
|
||||
music_dir_menu('/');
|
||||
|
||||
exit 0;
|
||||
|
||||
##########################################################################
|
||||
|
||||
sub music_dir_menu {
|
||||
my $dir = shift;
|
||||
|
||||
# generate a list of mp3's and directories and assign each one it's
|
||||
# own selection number. Then make sure that we've got a sound clip
|
||||
# for the file name
|
||||
if (!opendir(THEDIR, rmts($MUSIC.$dir))) {
|
||||
print STDERR "Failed to open music directory: $dir\n";
|
||||
exit 1;
|
||||
}
|
||||
my @files = sort readdir THEDIR;
|
||||
my $cnt = 1;
|
||||
my @masterBgList = ();
|
||||
|
||||
foreach my $file (@files) {
|
||||
chomp($file);
|
||||
if ($file ne '.' && $file ne '..' && $file ne 'festivalcache') { # ignore special files
|
||||
my $real_version = &rmts($MUSIC.$dir).'/'.$file;
|
||||
my $cache_version = &rmts($FESTIVALCACHE.$dir).'/'.$file.'.ul';
|
||||
my $cache_version2 = &rmts($FESTIVALCACHE.$dir).'/'.$file;
|
||||
my $cache_version_esc = &clean_file($cache_version);
|
||||
my $cache_version2_esc = &clean_file($cache_version2);
|
||||
|
||||
if (-d $real_version) {
|
||||
# 0:id 1:type 2:text2wav-file 3:for-filtering 4:the-directory 5:text2wav echo
|
||||
push(@masterBgList, [$cnt++, 1, $cache_version2_esc, &remove_special_chars($file), $file, "for the $file folder"]);
|
||||
} elsif ($real_version =~ /\.mp3$/) {
|
||||
# 0:id 1:type 2:text2wav-file 3:for-filtering 4:the-mp3
|
||||
push(@masterBgList, [$cnt++, 2, $cache_version2_esc, &remove_special_chars($file), $real_version, "to play $file"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
close(THEDIR);
|
||||
|
||||
my @filterList = @masterBgList;
|
||||
|
||||
if (@filterList == 0) {
|
||||
print "EXEC Playback \"$FESTIVALCACHE/jukebox_nonefound\"\n";
|
||||
my $result = <STDIN>; &check_result($result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
MYCONTINUE:
|
||||
|
||||
# play bg selections and figure out their selection
|
||||
my $digit = '';
|
||||
my $digitstr = '';
|
||||
for (my $n=0; $n<@filterList; $n++) {
|
||||
&cache_speech(&remove_file_extension($filterList[$n][5]), "$filterList[$n][2].ul") if ! -e "$filterList[$n][2].ul";
|
||||
&cache_speech("Press $filterList[$n][0]", "$FESTIVALCACHE/jukebox_$filterList[$n][0].ul") if ! -e "$FESTIVALCACHE/jukebox_$filterList[$n][0].ul";
|
||||
print "EXEC Background \"$filterList[$n][2]&$FESTIVALCACHE/jukebox_$filterList[$n][0]\"\n";
|
||||
my $result = <STDIN>;
|
||||
$digit = &check_result($result);
|
||||
if ($digit > 0) {
|
||||
$digitstr .= chr($digit);
|
||||
last;
|
||||
}
|
||||
}
|
||||
for (;;) {
|
||||
print "WAIT FOR DIGIT 3000\n";
|
||||
my $result = <STDIN>;
|
||||
$digit = &check_result($result);
|
||||
last if $digit <= 0;
|
||||
$digitstr .= chr($digit);
|
||||
}
|
||||
|
||||
# see if it's a valid selection
|
||||
print STDERR "Digits Entered: '$digitstr'\n";
|
||||
exit 0 if $digitstr eq '';
|
||||
my $found = 0;
|
||||
goto EXITSUB if $digitstr =~ /\*/;
|
||||
|
||||
# filter the list
|
||||
if ($digitstr =~ /^\#\d+/) {
|
||||
my $regexp = '';
|
||||
for (my $n=1; $n<length($digitstr); $n++) {
|
||||
my $d = substr($digitstr, $n, 1);
|
||||
if ($d == 2) {
|
||||
$regexp .= '[abc]';
|
||||
} elsif ($d == 3) {
|
||||
$regexp .= '[def]';
|
||||
} elsif ($d == 4) {
|
||||
$regexp .= '[ghi]';
|
||||
} elsif ($d == 5) {
|
||||
$regexp .= '[jkl]';
|
||||
} elsif ($d == 6) {
|
||||
$regexp .= '[mno]';
|
||||
} elsif ($d == 7) {
|
||||
$regexp .= '[pqrs]';
|
||||
} elsif ($d == 8) {
|
||||
$regexp .= '[tuv]';
|
||||
} elsif ($d == 9) {
|
||||
$regexp .= '[wxyz]';
|
||||
}
|
||||
}
|
||||
@filterList = ();
|
||||
for (my $n=1; $n<@masterBgList; $n++) {
|
||||
push(@filterList, $masterBgList[$n]) if $masterBgList[$n][3] =~ /^$regexp/i;
|
||||
}
|
||||
goto MYCONTINUE;
|
||||
}
|
||||
|
||||
for (my $n=0; $n<@masterBgList; $n++) {
|
||||
if ($digitstr == $masterBgList[$n][0]) {
|
||||
if ($masterBgList[$n][1] == 1) { # a folder
|
||||
&music_dir_menu(rmts($dir).'/'.$masterBgList[$n][4]);
|
||||
@filterList = @masterBgList;
|
||||
goto MYCONTINUE;
|
||||
} elsif ($masterBgList[$n][1] == 2) { # a file
|
||||
# because *'s scripting language is crunk and won't allow us to escape
|
||||
# funny filenames, we need to create a temporary symlink to the mp3
|
||||
# file
|
||||
my $mp3 = &escape_file($masterBgList[$n][4]);
|
||||
my $link = `mktemp`;
|
||||
chomp($link);
|
||||
$link .= '.mp3';
|
||||
print STDERR "ln -s $mp3 $link\n";
|
||||
my $cmdr = `ln -s $mp3 $link`;
|
||||
chomp($cmdr);
|
||||
print "Failed to create symlink to mp3: $cmdr\n" if $cmdr ne '';
|
||||
|
||||
print "EXEC MP3Player \"$link\"\n";
|
||||
my $result = <STDIN>; &check_result($result);
|
||||
|
||||
`rm $link`;
|
||||
|
||||
if (!$MENUAFTERSONG) {
|
||||
print "EXEC Playback \"$FESTIVALCACHE/jukebox_thankyou\"\n";
|
||||
my $result = <STDIN>; &check_result($result);
|
||||
exit 0;
|
||||
} else {
|
||||
goto MYCONTINUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
print "EXEC Playback \"$FESTIVALCACHE/jukebox_invalid\"\n";
|
||||
my $result = <STDIN>; &check_result($result);
|
||||
}
|
||||
EXITSUB:
|
||||
}
|
||||
|
||||
sub cache_speech {
|
||||
my $speech = shift;
|
||||
my $file = shift;
|
||||
|
||||
my $theDir = extract_file_dir($file);
|
||||
`mkdir -p -m0776 $theDir`;
|
||||
|
||||
print STDERR "echo \"$speech\" | text2wave -o $file -otype ulaw -\n";
|
||||
my $cmdr = `echo "$speech" | text2wave -o $file -otype ulaw -`;
|
||||
chomp($cmdr);
|
||||
if ($cmdr =~ /using default diphone/) {
|
||||
# temporary bug work around....
|
||||
`touch $file`;
|
||||
} elsif ($cmdr ne '') {
|
||||
print STDERR "Command Failed\n";
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
|
||||
sub music_dir_cache {
|
||||
# generate list of text2speech files to generate
|
||||
if (!music_dir_cache_genlist('/')) {
|
||||
print STDERR "Horrible Dreadful Error: No Music Found in $MUSIC!";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
# add to list how many 'number' files we have to generate. We can't
|
||||
# use the SayNumber app in Asterisk because we want to chain all
|
||||
# talking in one Background command. We also want a consistent
|
||||
# voice...
|
||||
for (my $n=1; $n<=$maxNumber; $n++) {
|
||||
push(@masterCacheList, [3, "Press $n", "$FESTIVALCACHE/jukebox_$n.ul"]) if ! -e "$FESTIVALCACHE/jukebox_$n.ul";
|
||||
}
|
||||
|
||||
# now generate all these darn text2speech files
|
||||
if (@masterCacheList > 5) {
|
||||
print "EXEC Playback \"$FESTIVALCACHE/jukebox_generate\"\n";
|
||||
my $result = <STDIN>; &check_result($result);
|
||||
}
|
||||
my $theTime = time();
|
||||
for (my $n=0; $n < @masterCacheList; $n++) {
|
||||
my $cmdr = '';
|
||||
if ($masterCacheList[$n][0] == 1) { # directory
|
||||
&cache_speech("for folder $masterCacheList[$n][1]", $masterCacheList[$n][2]);
|
||||
} elsif ($masterCacheList[$n][0] == 2) { # file
|
||||
&cache_speech("to play $masterCacheList[$n][1]", $masterCacheList[$n][2]);
|
||||
} elsif ($masterCacheList[$n][0] == 3) { # number
|
||||
&cache_speech($masterCacheList[$n][1], $masterCacheList[$n][2]);
|
||||
}
|
||||
if (time() >= $theTime + 30) {
|
||||
my $percent = int($n / @masterCacheList * 100);
|
||||
print "SAY NUMBER $percent \"\"\n";
|
||||
my $result = <STDIN>; &check_result($result);
|
||||
print "EXEC Playback \"$FESTIVALCACHE/jukebox_percent\"\n";
|
||||
my $result = <STDIN>; &check_result($result);
|
||||
$theTime = time();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# this function will fill the @masterCacheList of all the files that
|
||||
# need to have text2speeced ulaw files of their names generated
|
||||
sub music_dir_cache_genlist {
|
||||
my $dir = shift;
|
||||
if (!opendir(THEDIR, rmts($MUSIC.$dir))) {
|
||||
print STDERR "Failed to open music directory: $dir\n";
|
||||
exit 1;
|
||||
}
|
||||
my @files = sort readdir THEDIR;
|
||||
my $foundFiles = 0;
|
||||
my $tmpMaxNum = 0;
|
||||
foreach my $file (@files) {
|
||||
chomp;
|
||||
if ($file ne '.' && $file ne '..' && $file ne 'festivalcache') { # ignore special files
|
||||
my $real_version = &rmts($MUSIC.$dir).'/'.$file;
|
||||
my $cache_version = &rmts($FESTIVALCACHE.$dir).'/'.$file.'.ul';
|
||||
my $cache_version2 = &rmts($FESTIVALCACHE.$dir).'/'.$file;
|
||||
my $cache_version_esc = &clean_file($cache_version);
|
||||
my $cache_version2_esc = &clean_file($cache_version2);
|
||||
|
||||
if (-d $real_version) {
|
||||
if (music_dir_cache_genlist(rmts($dir).'/'.$file)) {
|
||||
$tmpMaxNum++;
|
||||
$maxNumber = $tmpMaxNum if $tmpMaxNum > $maxNumber;
|
||||
push(@masterCacheList, [1, $file, $cache_version_esc]) if ! -e $cache_version_esc;
|
||||
$foundFiles = 1;
|
||||
}
|
||||
} elsif ($real_version =~ /\.mp3$/) {
|
||||
$tmpMaxNum++;
|
||||
$maxNumber = $tmpMaxNum if $tmpMaxNum > $maxNumber;
|
||||
push(@masterCacheList, [2, &remove_file_extension($file), $cache_version_esc]) if ! -e $cache_version_esc;
|
||||
$foundFiles = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(THEDIR);
|
||||
return $foundFiles;
|
||||
}
|
||||
|
||||
sub rmts { # remove trailing slash
|
||||
my $hog = shift;
|
||||
$hog =~ s/\/$//;
|
||||
return $hog;
|
||||
}
|
||||
|
||||
sub extract_file_name {
|
||||
my $hog = shift;
|
||||
$hog =~ /\/?([^\/]+)$/;
|
||||
return $1;
|
||||
}
|
||||
|
||||
sub extract_file_dir {
|
||||
my $hog = shift;
|
||||
return $hog if ! ($hog =~ /\//);
|
||||
$hog =~ /(.*)\/[^\/]*$/;
|
||||
return $1;
|
||||
}
|
||||
|
||||
sub remove_file_extension {
|
||||
my $hog = shift;
|
||||
return $hog if ! ($hog =~ /\./);
|
||||
$hog =~ /(.*)\.[^.]*$/;
|
||||
return $1;
|
||||
}
|
||||
|
||||
sub clean_file {
|
||||
my $hog = shift;
|
||||
$hog =~ s/\\/_/g;
|
||||
$hog =~ s/ /_/g;
|
||||
$hog =~ s/\t/_/g;
|
||||
$hog =~ s/\'/_/g;
|
||||
$hog =~ s/\"/_/g;
|
||||
$hog =~ s/\(/_/g;
|
||||
$hog =~ s/\)/_/g;
|
||||
$hog =~ s/&/_/g;
|
||||
$hog =~ s/\[/_/g;
|
||||
$hog =~ s/\]/_/g;
|
||||
$hog =~ s/\$/_/g;
|
||||
$hog =~ s/\|/_/g;
|
||||
$hog =~ s/\^/_/g;
|
||||
return $hog;
|
||||
}
|
||||
|
||||
sub remove_special_chars {
|
||||
my $hog = shift;
|
||||
$hog =~ s/\\//g;
|
||||
$hog =~ s/ //g;
|
||||
$hog =~ s/\t//g;
|
||||
$hog =~ s/\'//g;
|
||||
$hog =~ s/\"//g;
|
||||
$hog =~ s/\(//g;
|
||||
$hog =~ s/\)//g;
|
||||
$hog =~ s/&//g;
|
||||
$hog =~ s/\[//g;
|
||||
$hog =~ s/\]//g;
|
||||
$hog =~ s/\$//g;
|
||||
$hog =~ s/\|//g;
|
||||
$hog =~ s/\^//g;
|
||||
return $hog;
|
||||
}
|
||||
|
||||
sub escape_file {
|
||||
my $hog = shift;
|
||||
$hog =~ s/\\/\\\\/g;
|
||||
$hog =~ s/ /\\ /g;
|
||||
$hog =~ s/\t/\\\t/g;
|
||||
$hog =~ s/\'/\\\'/g;
|
||||
$hog =~ s/\"/\\\"/g;
|
||||
$hog =~ s/\(/\\\(/g;
|
||||
$hog =~ s/\)/\\\)/g;
|
||||
$hog =~ s/&/\\&/g;
|
||||
$hog =~ s/\[/\\\[/g;
|
||||
$hog =~ s/\]/\\\]/g;
|
||||
$hog =~ s/\$/\\\$/g;
|
||||
$hog =~ s/\|/\\\|/g;
|
||||
$hog =~ s/\^/\\\^/g;
|
||||
return $hog;
|
||||
}
|
||||
|
||||
sub check_result {
|
||||
my ($res) = @_;
|
||||
my $retval;
|
||||
$tests++;
|
||||
chomp $res;
|
||||
if ($res =~ /^200/) {
|
||||
$res =~ /result=(-?\d+)/;
|
||||
if (!length($1)) {
|
||||
print STDERR "FAIL ($res)\n";
|
||||
$fail++;
|
||||
exit 1;
|
||||
} else {
|
||||
print STDERR "PASS ($1)\n";
|
||||
return $1;
|
||||
}
|
||||
} else {
|
||||
print STDERR "FAIL (unexpected result '$res')\n";
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
44
trunk/agi/numeralize
Normal file
44
trunk/agi/numeralize
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Build a database linking filenames to their numerical representations
|
||||
# using a keypad for the DialAnMp3 application
|
||||
#
|
||||
|
||||
$mp3dir="/usr/media/mpeg3";
|
||||
|
||||
dbmopen(%DIGITS, "/var/lib/asterisk/mp3list", 0644) || die("Unable to open mp3list");;
|
||||
sub process_dir {
|
||||
my ($dir) = @_;
|
||||
my $file;
|
||||
my $digits;
|
||||
my @entries;
|
||||
opendir(DIR, $dir);
|
||||
@entries = readdir(DIR);
|
||||
closedir(DIR);
|
||||
foreach $_ (@entries) {
|
||||
if (!/^\./) {
|
||||
$file = "$dir/$_";
|
||||
if (-d "$file") {
|
||||
process_dir("$file");
|
||||
} else {
|
||||
$digits = $_;
|
||||
$digits =~ s/[^ \w]+//g;
|
||||
$digits =~ s/\_/ /g;
|
||||
$digits =~ tr/[a-z]/[A-Z]/;
|
||||
$digits =~ tr/[A-C]/2/;
|
||||
$digits =~ tr/[D-F]/3/;
|
||||
$digits =~ tr/[G-I]/4/;
|
||||
$digits =~ tr/[J-L]/5/;
|
||||
$digits =~ tr/[M-O]/6/;
|
||||
$digits =~ tr/[P-S]/7/;
|
||||
$digits =~ tr/[T-V]/8/;
|
||||
$digits =~ tr/[W-Z]/9/;
|
||||
$digits =~ s/\s+/ /;
|
||||
print "File: $file, digits: $digits\n";
|
||||
$DIGITS{$file} = $digits;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process_dir($mp3dir);
|
||||
41
trunk/apps/Makefile
Normal file
41
trunk/apps/Makefile
Normal file
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# Asterisk -- A telephony toolkit for Linux.
|
||||
#
|
||||
# Makefile for PBX applications
|
||||
#
|
||||
# Copyright (C) 1999-2006, Digium, Inc.
|
||||
#
|
||||
# This program is free software, distributed under the terms of
|
||||
# the GNU General Public License
|
||||
#
|
||||
|
||||
-include $(ASTTOPDIR)/menuselect.makeopts $(ASTTOPDIR)/menuselect.makedeps
|
||||
|
||||
MODULE_PREFIX=app
|
||||
MENUSELECT_CATEGORY=APPS
|
||||
MENUSELECT_DESCRIPTION=Applications
|
||||
|
||||
MENUSELECT_OPTS_app_directory:=$(MENUSELECT_OPTS_app_voicemail)
|
||||
ifneq ($(findstring ODBC_STORAGE,$(MENUSELECT_OPTS_app_voicemail)),)
|
||||
MENUSELECT_DEPENDS_app_voicemail+=$(MENUSELECT_DEPENDS_ODBC_STORAGE)
|
||||
MENUSELECT_DEPENDS_app_directory+=$(MENUSELECT_DEPENDS_ODBC_STORAGE)
|
||||
endif
|
||||
ifneq ($(findstring IMAP_STORAGE,$(MENUSELECT_OPTS_app_voicemail)),)
|
||||
MENUSELECT_DEPENDS_app_voicemail+=$(MENUSELECT_DEPENDS_IMAP_STORAGE)
|
||||
MENUSELECT_DEPENDS_app_directory+=$(MENUSELECT_DEPENDS_IMAP_STORAGE)
|
||||
endif
|
||||
|
||||
ifeq (SunOS,$(shell uname))
|
||||
MENUSELECT_DEPENDS_app_chanspy+=RT
|
||||
RT_LIB=-lrt
|
||||
endif
|
||||
|
||||
all: _all
|
||||
|
||||
include $(ASTTOPDIR)/Makefile.moddir_rules
|
||||
|
||||
ifneq ($(findstring $(OSARCH), mingw32 cygwin ),)
|
||||
LIBS+= -lres_features.so -lres_ael_share.so -lres_monitor.so -lres_speech.so
|
||||
LIBS+= -lres_smdi.so
|
||||
endif
|
||||
|
||||
1581
trunk/apps/app_adsiprog.c
Normal file
1581
trunk/apps/app_adsiprog.c
Normal file
File diff suppressed because it is too large
Load Diff
813
trunk/apps/app_alarmreceiver.c
Normal file
813
trunk/apps/app_alarmreceiver.c
Normal file
@@ -0,0 +1,813 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2004 - 2005 Steve Rodgers
|
||||
*
|
||||
* Steve Rodgers <hwstar@rodgers.sdcoxmail.com>
|
||||
*
|
||||
* 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 Central Station Alarm receiver for Ademco Contact ID
|
||||
* \author Steve Rodgers <hwstar@rodgers.sdcoxmail.com>
|
||||
*
|
||||
* *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
|
||||
*
|
||||
* Use at your own risk. Please consult the GNU GPL license document included with Asterisk. *
|
||||
*
|
||||
* *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <math.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
#include "asterisk/ulaw.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/dsp.h"
|
||||
#include "asterisk/config.h"
|
||||
#include "asterisk/localtime.h"
|
||||
#include "asterisk/callerid.h"
|
||||
#include "asterisk/astdb.h"
|
||||
#include "asterisk/utils.h"
|
||||
|
||||
#define ALMRCV_CONFIG "alarmreceiver.conf"
|
||||
#define ADEMCO_CONTACT_ID "ADEMCO_CONTACT_ID"
|
||||
|
||||
struct event_node{
|
||||
char data[17];
|
||||
struct event_node *next;
|
||||
};
|
||||
|
||||
typedef struct event_node event_node_t;
|
||||
|
||||
static char *app = "AlarmReceiver";
|
||||
|
||||
static char *synopsis = "Provide support for receiving alarm reports from a burglar or fire alarm panel";
|
||||
static char *descrip =
|
||||
" AlarmReceiver(): Only 1 signalling format is supported at this time: Ademco\n"
|
||||
"Contact ID. This application should be called whenever there is an alarm\n"
|
||||
"panel calling in to dump its events. The application will handshake with the\n"
|
||||
"alarm panel, and receive events, validate them, handshake them, and store them\n"
|
||||
"until the panel hangs up. Once the panel hangs up, the application will run the\n"
|
||||
"system command specified by the eventcmd setting in alarmreceiver.conf and pipe\n"
|
||||
"the events to the standard input of the application. The configuration file also\n"
|
||||
"contains settings for DTMF timing, and for the loudness of the acknowledgement\n"
|
||||
"tones.\n";
|
||||
|
||||
/* Config Variables */
|
||||
|
||||
static int fdtimeout = 2000;
|
||||
static int sdtimeout = 200;
|
||||
static int toneloudness = 4096;
|
||||
static int log_individual_events = 0;
|
||||
static char event_spool_dir[128] = {'\0'};
|
||||
static char event_app[128] = {'\0'};
|
||||
static char db_family[128] = {'\0'};
|
||||
static char time_stamp_format[128] = {"%a %b %d, %Y @ %H:%M:%S %Z"};
|
||||
|
||||
/* Misc variables */
|
||||
|
||||
static char event_file[14] = "/event-XXXXXX";
|
||||
|
||||
/*
|
||||
* Attempt to access a database variable and increment it,
|
||||
* provided that the user defined db-family in alarmreceiver.conf
|
||||
* The alarmreceiver app will write statistics to a few variables
|
||||
* in this family if it is defined. If the new key doesn't exist in the
|
||||
* family, then create it and set its value to 1.
|
||||
*/
|
||||
|
||||
static void database_increment( char *key )
|
||||
{
|
||||
int res = 0;
|
||||
unsigned v;
|
||||
char value[16];
|
||||
|
||||
|
||||
if (ast_strlen_zero(db_family))
|
||||
return; /* If not defined, don't do anything */
|
||||
|
||||
res = ast_db_get(db_family, key, value, sizeof(value) - 1);
|
||||
|
||||
if(res){
|
||||
ast_verb(4, "AlarmReceiver: Creating database entry %s and setting to 1\n", key);
|
||||
/* Guess we have to create it */
|
||||
res = ast_db_put(db_family, key, "1");
|
||||
return;
|
||||
}
|
||||
|
||||
sscanf(value, "%u", &v);
|
||||
v++;
|
||||
|
||||
if(option_verbose >= 4)
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: New value for %s: %u\n", key, v);
|
||||
|
||||
snprintf(value, sizeof(value), "%u", v);
|
||||
|
||||
res = ast_db_put(db_family, key, value);
|
||||
|
||||
if((res)&&(option_verbose >= 4))
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: database_increment write error\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Build a MuLaw data block for a single frequency tone
|
||||
*/
|
||||
|
||||
static void make_tone_burst(unsigned char *data, float freq, float loudness, int len, int *x)
|
||||
{
|
||||
int i;
|
||||
float val;
|
||||
|
||||
for(i = 0; i < len; i++){
|
||||
val = loudness * sin((freq * 2.0 * M_PI * (*x)++)/8000.0);
|
||||
data[i] = AST_LIN2MU((int)val);
|
||||
}
|
||||
|
||||
/* wrap back around from 8000 */
|
||||
|
||||
if (*x >= 8000) *x = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a single tone burst for a specifed duration and frequency.
|
||||
* Returns 0 if successful
|
||||
*/
|
||||
|
||||
static int send_tone_burst(struct ast_channel *chan, float freq, int duration, int tldn)
|
||||
{
|
||||
int res = 0;
|
||||
int i = 0;
|
||||
int x = 0;
|
||||
struct ast_frame *f, wf;
|
||||
|
||||
struct {
|
||||
unsigned char offset[AST_FRIENDLY_OFFSET];
|
||||
unsigned char buf[640];
|
||||
} tone_block;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
|
||||
if (ast_waitfor(chan, -1) < 0){
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
f = ast_read(chan);
|
||||
if (!f){
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (f->frametype == AST_FRAME_VOICE) {
|
||||
wf.frametype = AST_FRAME_VOICE;
|
||||
wf.subclass = AST_FORMAT_ULAW;
|
||||
wf.offset = AST_FRIENDLY_OFFSET;
|
||||
wf.mallocd = 0;
|
||||
wf.data = tone_block.buf;
|
||||
wf.datalen = f->datalen;
|
||||
wf.samples = wf.datalen;
|
||||
|
||||
make_tone_burst(tone_block.buf, freq, (float) tldn, wf.datalen, &x);
|
||||
|
||||
i += wf.datalen / 8;
|
||||
if (i > duration) {
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
if (ast_write(chan, &wf)){
|
||||
ast_verb(4, "AlarmReceiver: Failed to write frame on %s\n", chan->name);
|
||||
ast_log(LOG_WARNING, "AlarmReceiver Failed to write frame on %s\n",chan->name);
|
||||
res = -1;
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ast_frfree(f);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive a string of DTMF digits where the length of the digit string is known in advance. Do not give preferential
|
||||
* treatment to any digit value, and allow separate time out values to be specified for the first digit and all subsequent
|
||||
* digits.
|
||||
*
|
||||
* Returns 0 if all digits successfully received.
|
||||
* Returns 1 if a digit time out occurred
|
||||
* Returns -1 if the caller hung up or there was a channel error.
|
||||
*
|
||||
*/
|
||||
|
||||
static int receive_dtmf_digits(struct ast_channel *chan, char *digit_string, int length, int fdto, int sdto)
|
||||
{
|
||||
int res = 0;
|
||||
int i = 0;
|
||||
int r;
|
||||
struct ast_frame *f;
|
||||
struct timeval lastdigittime;
|
||||
|
||||
lastdigittime = ast_tvnow();
|
||||
for(;;){
|
||||
/* if outa time, leave */
|
||||
if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) >
|
||||
((i > 0) ? sdto : fdto)){
|
||||
if(option_verbose >= 4)
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: DTMF Digit Timeout on %s\n", chan->name);
|
||||
|
||||
ast_debug(1,"AlarmReceiver: DTMF timeout on chan %s\n",chan->name);
|
||||
|
||||
res = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((r = ast_waitfor(chan, -1) < 0)) {
|
||||
ast_debug(1, "Waitfor returned %d\n", r);
|
||||
continue;
|
||||
}
|
||||
|
||||
f = ast_read(chan);
|
||||
|
||||
if (f == NULL){
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If they hung up, leave */
|
||||
if ((f->frametype == AST_FRAME_CONTROL) &&
|
||||
(f->subclass == AST_CONTROL_HANGUP)){
|
||||
ast_frfree(f);
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* if not DTMF, just do it again */
|
||||
if (f->frametype != AST_FRAME_DTMF){
|
||||
ast_frfree(f);
|
||||
continue;
|
||||
}
|
||||
|
||||
digit_string[i++] = f->subclass; /* save digit */
|
||||
|
||||
ast_frfree(f);
|
||||
|
||||
/* If we have all the digits we expect, leave */
|
||||
if(i >= length)
|
||||
break;
|
||||
|
||||
lastdigittime = ast_tvnow();
|
||||
}
|
||||
|
||||
digit_string[i] = '\0'; /* Nul terminate the end of the digit string */
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the metadata to the log file
|
||||
*/
|
||||
|
||||
static int write_metadata( FILE *logfile, char *signalling_type, struct ast_channel *chan)
|
||||
{
|
||||
int res = 0;
|
||||
struct timeval t;
|
||||
struct ast_tm now;
|
||||
char *cl,*cn;
|
||||
char workstring[80];
|
||||
char timestamp[80];
|
||||
|
||||
/* Extract the caller ID location */
|
||||
if (chan->cid.cid_num)
|
||||
ast_copy_string(workstring, chan->cid.cid_num, sizeof(workstring));
|
||||
workstring[sizeof(workstring) - 1] = '\0';
|
||||
|
||||
ast_callerid_parse(workstring, &cn, &cl);
|
||||
if (cl)
|
||||
ast_shrink_phone_number(cl);
|
||||
|
||||
|
||||
/* Get the current time */
|
||||
|
||||
t = ast_tvnow();
|
||||
ast_localtime(&t, &now, NULL);
|
||||
|
||||
/* Format the time */
|
||||
|
||||
ast_strftime(timestamp, sizeof(timestamp), time_stamp_format, &now);
|
||||
|
||||
|
||||
res = fprintf(logfile, "\n\n[metadata]\n\n");
|
||||
|
||||
if(res >= 0)
|
||||
res = fprintf(logfile, "PROTOCOL=%s\n", signalling_type);
|
||||
|
||||
if(res >= 0)
|
||||
res = fprintf(logfile, "CALLINGFROM=%s\n", (!cl) ? "<unknown>" : cl);
|
||||
|
||||
if(res >- 0)
|
||||
res = fprintf(logfile, "CALLERNAME=%s\n", (!cn) ? "<unknown>" : cn);
|
||||
|
||||
if(res >= 0)
|
||||
res = fprintf(logfile, "TIMESTAMP=%s\n\n", timestamp);
|
||||
|
||||
if(res >= 0)
|
||||
res = fprintf(logfile, "[events]\n\n");
|
||||
|
||||
if(res < 0){
|
||||
if (option_verbose >= 3 )
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't write metadata\n");
|
||||
|
||||
ast_debug(1,"AlarmReceiver: can't write metadata\n");
|
||||
}
|
||||
else
|
||||
res = 0;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a single event to the log file
|
||||
*/
|
||||
|
||||
static int write_event( FILE *logfile, event_node_t *event)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if( fprintf(logfile, "%s\n", event->data) < 0)
|
||||
res = -1;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* If we are configured to log events, do so here.
|
||||
*
|
||||
*/
|
||||
|
||||
static int log_events(struct ast_channel *chan, char *signalling_type, event_node_t *event)
|
||||
{
|
||||
|
||||
int res = 0;
|
||||
char workstring[sizeof(event_spool_dir)+sizeof(event_file)] = "";
|
||||
int fd;
|
||||
FILE *logfile;
|
||||
event_node_t *elp = event;
|
||||
|
||||
if (!ast_strlen_zero(event_spool_dir)) {
|
||||
|
||||
/* Make a template */
|
||||
|
||||
ast_copy_string(workstring, event_spool_dir, sizeof(workstring));
|
||||
strncat(workstring, event_file, sizeof(workstring) - strlen(workstring) - 1);
|
||||
|
||||
/* Make the temporary file */
|
||||
|
||||
fd = mkstemp(workstring);
|
||||
|
||||
if(fd == -1) {
|
||||
if (option_verbose >= 3)
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't make temporary file\n");
|
||||
ast_debug(1,"AlarmReceiver: can't make temporary file\n");
|
||||
res = -1;
|
||||
}
|
||||
|
||||
if(!res){
|
||||
logfile = fdopen(fd, "w");
|
||||
if(logfile){
|
||||
/* Write the file */
|
||||
res = write_metadata(logfile, signalling_type, chan);
|
||||
if(!res)
|
||||
while((!res) && (elp != NULL)){
|
||||
res = write_event(logfile, elp);
|
||||
elp = elp->next;
|
||||
}
|
||||
if(!res){
|
||||
if(fflush(logfile) == EOF)
|
||||
res = -1;
|
||||
if(!res){
|
||||
if(fclose(logfile) == EOF)
|
||||
res = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
res = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function implements the logic to receive the Ademco contact ID format.
|
||||
*
|
||||
* The function will return 0 when the caller hangs up, else a -1 if there was a problem.
|
||||
*/
|
||||
|
||||
static int receive_ademco_contact_id( struct ast_channel *chan, void *data, int fdto, int sdto, int tldn, event_node_t **ehead)
|
||||
{
|
||||
int i,j;
|
||||
int res = 0;
|
||||
int checksum;
|
||||
char event[17];
|
||||
event_node_t *enew, *elp;
|
||||
int got_some_digits = 0;
|
||||
int events_received = 0;
|
||||
int ack_retries = 0;
|
||||
|
||||
static char digit_map[15] = "0123456789*#ABC";
|
||||
static unsigned char digit_weights[15] = {10,1,2,3,4,5,6,7,8,9,11,12,13,14,15};
|
||||
|
||||
database_increment("calls-received");
|
||||
|
||||
/* Wait for first event */
|
||||
|
||||
if(option_verbose >= 4)
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Waiting for first event from panel\n");
|
||||
|
||||
while(res >= 0){
|
||||
|
||||
if(got_some_digits == 0){
|
||||
|
||||
/* Send ACK tone sequence */
|
||||
|
||||
|
||||
if(option_verbose >= 4)
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Sending 1400Hz 100ms burst (ACK)\n");
|
||||
|
||||
|
||||
res = send_tone_burst(chan, 1400.0, 100, tldn);
|
||||
|
||||
if(!res)
|
||||
res = ast_safe_sleep(chan, 100);
|
||||
|
||||
if(!res){
|
||||
if(option_verbose >= 4)
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Sending 2300Hz 100ms burst (ACK)\n");
|
||||
|
||||
res = send_tone_burst(chan, 2300.0, 100, tldn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if( res >= 0)
|
||||
res = receive_dtmf_digits(chan, event, sizeof(event) - 1, fdto, sdto);
|
||||
|
||||
if (res < 0){
|
||||
|
||||
if(events_received == 0)
|
||||
/* Hangup with no events received should be logged in the DB */
|
||||
database_increment("no-events-received");
|
||||
else{
|
||||
if(ack_retries){
|
||||
if(option_verbose >= 4)
|
||||
ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: ACK retries during this call: %d\n", ack_retries);
|
||||
|
||||
database_increment("ack-retries");
|
||||
}
|
||||
}
|
||||
if(option_verbose >= 4)
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: App exiting...\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if(res != 0){
|
||||
/* Didn't get all of the digits */
|
||||
if(option_verbose >= 2)
|
||||
ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Incomplete string: %s, trying again...\n", event);
|
||||
|
||||
if(!got_some_digits){
|
||||
got_some_digits = (!ast_strlen_zero(event)) ? 1 : 0;
|
||||
ack_retries++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
got_some_digits = 1;
|
||||
|
||||
ast_verb(2, "AlarmReceiver: Received Event %s\n", event);
|
||||
ast_debug(1, "AlarmReceiver: Received event: %s\n", event);
|
||||
|
||||
/* Calculate checksum */
|
||||
|
||||
for(j = 0, checksum = 0; j < 16; j++){
|
||||
for(i = 0 ; i < sizeof(digit_map) ; i++){
|
||||
if(digit_map[i] == event[j])
|
||||
break;
|
||||
}
|
||||
|
||||
if(i == 16)
|
||||
break;
|
||||
|
||||
checksum += digit_weights[i];
|
||||
}
|
||||
|
||||
if(i == 16){
|
||||
ast_verb(2, "AlarmReceiver: Bad DTMF character %c, trying again\n", event[j]);
|
||||
continue; /* Bad character */
|
||||
}
|
||||
|
||||
/* Checksum is mod(15) of the total */
|
||||
|
||||
checksum = checksum % 15;
|
||||
|
||||
if (checksum) {
|
||||
database_increment("checksum-errors");
|
||||
ast_verb(2, "AlarmReceiver: Nonzero checksum\n");
|
||||
ast_debug(1, "AlarmReceiver: Nonzero checksum\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check the message type for correctness */
|
||||
|
||||
if(strncmp(event + 4, "18", 2)){
|
||||
if(strncmp(event + 4, "98", 2)){
|
||||
database_increment("format-errors");
|
||||
ast_verb(2, "AlarmReceiver: Wrong message type\n");
|
||||
ast_debug(1, "AlarmReceiver: Wrong message type\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
events_received++;
|
||||
|
||||
/* Queue the Event */
|
||||
if (!(enew = ast_calloc(1, sizeof(*enew)))) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
enew->next = NULL;
|
||||
ast_copy_string(enew->data, event, sizeof(enew->data));
|
||||
|
||||
/*
|
||||
* Insert event onto end of list
|
||||
*/
|
||||
|
||||
if(*ehead == NULL){
|
||||
*ehead = enew;
|
||||
}
|
||||
else{
|
||||
for(elp = *ehead; elp->next != NULL; elp = elp->next)
|
||||
;
|
||||
|
||||
elp->next = enew;
|
||||
}
|
||||
|
||||
if(res > 0)
|
||||
res = 0;
|
||||
|
||||
/* Let the user have the option of logging the single event before sending the kissoff tone */
|
||||
|
||||
if((res == 0) && (log_individual_events))
|
||||
res = log_events(chan, ADEMCO_CONTACT_ID, enew);
|
||||
|
||||
/* Wait 200 msec before sending kissoff */
|
||||
|
||||
if(res == 0)
|
||||
res = ast_safe_sleep(chan, 200);
|
||||
|
||||
/* Send the kissoff tone */
|
||||
|
||||
if(res == 0)
|
||||
res = send_tone_burst(chan, 1400.0, 900, tldn);
|
||||
}
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This is the main function called by Asterisk Core whenever the App is invoked in the extension logic.
|
||||
* This function will always return 0.
|
||||
*/
|
||||
|
||||
static int alarmreceiver_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
event_node_t *elp, *efree;
|
||||
char signalling_type[64] = "";
|
||||
|
||||
event_node_t *event_head = NULL;
|
||||
|
||||
/* Set write and read formats to ULAW */
|
||||
|
||||
ast_verb(4, "AlarmReceiver: Setting read and write formats to ULAW\n");
|
||||
|
||||
if (ast_set_write_format(chan,AST_FORMAT_ULAW)){
|
||||
ast_log(LOG_WARNING, "AlarmReceiver: Unable to set write format to Mu-law on %s\n",chan->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_set_read_format(chan,AST_FORMAT_ULAW)){
|
||||
ast_log(LOG_WARNING, "AlarmReceiver: Unable to set read format to Mu-law on %s\n",chan->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set default values for this invocation of the application */
|
||||
|
||||
ast_copy_string(signalling_type, ADEMCO_CONTACT_ID, sizeof(signalling_type));
|
||||
|
||||
|
||||
/* Answer the channel if it is not already */
|
||||
|
||||
ast_verb(4, "AlarmReceiver: Answering channel\n");
|
||||
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if ((res = ast_answer(chan)))
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Wait for the connection to settle post-answer */
|
||||
|
||||
ast_verb(4, "AlarmReceiver: Waiting for connection to stabilize\n");
|
||||
|
||||
res = ast_safe_sleep(chan, 1250);
|
||||
|
||||
/* Attempt to receive the events */
|
||||
|
||||
if(!res){
|
||||
|
||||
/* Determine the protocol to receive in advance */
|
||||
/* Note: Ademco contact is the only one supported at this time */
|
||||
/* Others may be added later */
|
||||
|
||||
if(!strcmp(signalling_type, ADEMCO_CONTACT_ID))
|
||||
receive_ademco_contact_id(chan, data, fdtimeout, sdtimeout, toneloudness, &event_head);
|
||||
else
|
||||
res = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Events queued by receiver, write them all out here if so configured */
|
||||
|
||||
if((!res) && (log_individual_events == 0)){
|
||||
res = log_events(chan, signalling_type, event_head);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Do we exec a command line at the end?
|
||||
*/
|
||||
|
||||
if((!res) && (!ast_strlen_zero(event_app)) && (event_head)){
|
||||
ast_debug(1,"Alarmreceiver: executing: %s\n", event_app);
|
||||
ast_safe_system(event_app);
|
||||
}
|
||||
|
||||
/*
|
||||
* Free up the data allocated in our linked list
|
||||
*/
|
||||
|
||||
for(elp = event_head; (elp != NULL);){
|
||||
efree = elp;
|
||||
elp = elp->next;
|
||||
ast_free(efree);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load the configuration from the configuration file
|
||||
*/
|
||||
|
||||
static int load_config(void)
|
||||
{
|
||||
struct ast_config *cfg;
|
||||
const char *p;
|
||||
struct ast_flags config_flags = { 0 };
|
||||
|
||||
/* Read in the config file */
|
||||
|
||||
cfg = ast_config_load(ALMRCV_CONFIG, config_flags);
|
||||
|
||||
if(!cfg){
|
||||
|
||||
if(option_verbose >= 4)
|
||||
ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: No config file\n");
|
||||
return 0;
|
||||
}
|
||||
else{
|
||||
|
||||
|
||||
p = ast_variable_retrieve(cfg, "general", "eventcmd");
|
||||
|
||||
if(p){
|
||||
ast_copy_string(event_app, p, sizeof(event_app));
|
||||
event_app[sizeof(event_app) - 1] = '\0';
|
||||
}
|
||||
|
||||
p = ast_variable_retrieve(cfg, "general", "loudness");
|
||||
if(p){
|
||||
toneloudness = atoi(p);
|
||||
if(toneloudness < 100)
|
||||
toneloudness = 100;
|
||||
if(toneloudness > 8192)
|
||||
toneloudness = 8192;
|
||||
}
|
||||
p = ast_variable_retrieve(cfg, "general", "fdtimeout");
|
||||
if(p){
|
||||
fdtimeout = atoi(p);
|
||||
if(fdtimeout < 1000)
|
||||
fdtimeout = 1000;
|
||||
if(fdtimeout > 10000)
|
||||
fdtimeout = 10000;
|
||||
}
|
||||
|
||||
p = ast_variable_retrieve(cfg, "general", "sdtimeout");
|
||||
if(p){
|
||||
sdtimeout = atoi(p);
|
||||
if(sdtimeout < 110)
|
||||
sdtimeout = 110;
|
||||
if(sdtimeout > 4000)
|
||||
sdtimeout = 4000;
|
||||
|
||||
}
|
||||
|
||||
p = ast_variable_retrieve(cfg, "general", "logindividualevents");
|
||||
if(p){
|
||||
log_individual_events = ast_true(p);
|
||||
|
||||
}
|
||||
|
||||
p = ast_variable_retrieve(cfg, "general", "eventspooldir");
|
||||
|
||||
if(p){
|
||||
ast_copy_string(event_spool_dir, p, sizeof(event_spool_dir));
|
||||
event_spool_dir[sizeof(event_spool_dir) - 1] = '\0';
|
||||
}
|
||||
|
||||
p = ast_variable_retrieve(cfg, "general", "timestampformat");
|
||||
|
||||
if(p){
|
||||
ast_copy_string(time_stamp_format, p, sizeof(time_stamp_format));
|
||||
time_stamp_format[sizeof(time_stamp_format) - 1] = '\0';
|
||||
}
|
||||
|
||||
p = ast_variable_retrieve(cfg, "general", "db-family");
|
||||
|
||||
if(p){
|
||||
ast_copy_string(db_family, p, sizeof(db_family));
|
||||
db_family[sizeof(db_family) - 1] = '\0';
|
||||
}
|
||||
ast_config_destroy(cfg);
|
||||
}
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* These functions are required to implement an Asterisk App.
|
||||
*/
|
||||
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
if(load_config()) {
|
||||
if (ast_register_application(app, alarmreceiver_exec, synopsis, descrip))
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
else
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Alarm Receiver for Asterisk");
|
||||
418
trunk/apps/app_amd.c
Normal file
418
trunk/apps/app_amd.c
Normal file
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2003 - 2006, Aheeva Technology.
|
||||
*
|
||||
* Claude Klimos (claude.klimos@aheeva.com)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* A license has been granted to Digium (via disclaimer) for the use of
|
||||
* this code.
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Answering machine detection
|
||||
*
|
||||
* \author Claude Klimos (claude.klimos@aheeva.com)
|
||||
*/
|
||||
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/dsp.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/config.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
|
||||
static char *app = "AMD";
|
||||
static char *synopsis = "Attempts to detect answering machines";
|
||||
static char *descrip =
|
||||
" AMD([initialSilence],[greeting],[afterGreetingSilence],[totalAnalysisTime]\n"
|
||||
" ,[minimumWordLength],[betweenWordsSilence],[maximumNumberOfWords]\n"
|
||||
" ,[silenceThreshold],[|maximumWordLength])\n"
|
||||
" This application attempts to detect answering machines at the beginning\n"
|
||||
" of outbound calls. Simply call this application after the call\n"
|
||||
" has been answered (outbound only, of course).\n"
|
||||
" When loaded, AMD reads amd.conf and uses the parameters specified as\n"
|
||||
" default values. Those default values get overwritten when calling AMD\n"
|
||||
" with parameters.\n"
|
||||
"- 'initialSilence' is the maximum silence duration before the greeting. If\n"
|
||||
" exceeded then MACHINE.\n"
|
||||
"- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"
|
||||
"- 'afterGreetingSilence' is the silence after detecting a greeting.\n"
|
||||
" If exceeded then HUMAN.\n"
|
||||
"- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"
|
||||
" on a HUMAN or MACHINE.\n"
|
||||
"- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"
|
||||
"- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"
|
||||
" consider the audio that follows as a new word.\n"
|
||||
"- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"
|
||||
" If exceeded then MACHINE.\n"
|
||||
"- 'silenceThreshold' is the silence threshold.\n"
|
||||
"- 'maximumWordLength' is the maximum duration of a word to accept. If exceeded then MACHINE\n"
|
||||
"This application sets the following channel variables upon completion:\n"
|
||||
" AMDSTATUS - This is the status of the answering machine detection.\n"
|
||||
" Possible values are:\n"
|
||||
" MACHINE | HUMAN | NOTSURE | HANGUP\n"
|
||||
" AMDCAUSE - Indicates the cause that led to the conclusion.\n"
|
||||
" Possible values are:\n"
|
||||
" TOOLONG-<%d total_time>\n"
|
||||
" INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
|
||||
" HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
|
||||
" MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
|
||||
" LONGGREETING-<%d voiceDuration>-<%d greeting>\n"
|
||||
" MAXWORDLENGTH-<%d consecutiveVoiceDuration>\n";
|
||||
|
||||
#define STATE_IN_WORD 1
|
||||
#define STATE_IN_SILENCE 2
|
||||
|
||||
/* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
|
||||
static int dfltInitialSilence = 2500;
|
||||
static int dfltGreeting = 1500;
|
||||
static int dfltAfterGreetingSilence = 800;
|
||||
static int dfltTotalAnalysisTime = 5000;
|
||||
static int dfltMinimumWordLength = 100;
|
||||
static int dfltBetweenWordsSilence = 50;
|
||||
static int dfltMaximumNumberOfWords = 3;
|
||||
static int dfltSilenceThreshold = 256;
|
||||
static int dfltMaximumWordLength = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
|
||||
|
||||
static void isAnsweringMachine(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_frame *f = NULL;
|
||||
struct ast_dsp *silenceDetector = NULL;
|
||||
int dspsilence = 0, readFormat, framelength;
|
||||
int inInitialSilence = 1;
|
||||
int inGreeting = 0;
|
||||
int voiceDuration = 0;
|
||||
int silenceDuration = 0;
|
||||
int iTotalTime = 0;
|
||||
int iWordsCount = 0;
|
||||
int currentState = STATE_IN_SILENCE;
|
||||
int previousState = STATE_IN_SILENCE;
|
||||
int consecutiveVoiceDuration = 0;
|
||||
char amdCause[256] = "", amdStatus[256] = "";
|
||||
char *parse = ast_strdupa(data);
|
||||
|
||||
/* Lets set the initial values of the variables that will control the algorithm.
|
||||
The initial values are the default ones. If they are passed as arguments
|
||||
when invoking the application, then the default values will be overwritten
|
||||
by the ones passed as parameters. */
|
||||
int initialSilence = dfltInitialSilence;
|
||||
int greeting = dfltGreeting;
|
||||
int afterGreetingSilence = dfltAfterGreetingSilence;
|
||||
int totalAnalysisTime = dfltTotalAnalysisTime;
|
||||
int minimumWordLength = dfltMinimumWordLength;
|
||||
int betweenWordsSilence = dfltBetweenWordsSilence;
|
||||
int maximumNumberOfWords = dfltMaximumNumberOfWords;
|
||||
int silenceThreshold = dfltSilenceThreshold;
|
||||
int maximumWordLength = dfltMaximumWordLength;
|
||||
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(argInitialSilence);
|
||||
AST_APP_ARG(argGreeting);
|
||||
AST_APP_ARG(argAfterGreetingSilence);
|
||||
AST_APP_ARG(argTotalAnalysisTime);
|
||||
AST_APP_ARG(argMinimumWordLength);
|
||||
AST_APP_ARG(argBetweenWordsSilence);
|
||||
AST_APP_ARG(argMaximumNumberOfWords);
|
||||
AST_APP_ARG(argSilenceThreshold);
|
||||
AST_APP_ARG(argMaximumWordLength);
|
||||
);
|
||||
|
||||
ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
|
||||
|
||||
/* Lets parse the arguments. */
|
||||
if (!ast_strlen_zero(parse)) {
|
||||
/* Some arguments have been passed. Lets parse them and overwrite the defaults. */
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
if (!ast_strlen_zero(args.argInitialSilence))
|
||||
initialSilence = atoi(args.argInitialSilence);
|
||||
if (!ast_strlen_zero(args.argGreeting))
|
||||
greeting = atoi(args.argGreeting);
|
||||
if (!ast_strlen_zero(args.argAfterGreetingSilence))
|
||||
afterGreetingSilence = atoi(args.argAfterGreetingSilence);
|
||||
if (!ast_strlen_zero(args.argTotalAnalysisTime))
|
||||
totalAnalysisTime = atoi(args.argTotalAnalysisTime);
|
||||
if (!ast_strlen_zero(args.argMinimumWordLength))
|
||||
minimumWordLength = atoi(args.argMinimumWordLength);
|
||||
if (!ast_strlen_zero(args.argBetweenWordsSilence))
|
||||
betweenWordsSilence = atoi(args.argBetweenWordsSilence);
|
||||
if (!ast_strlen_zero(args.argMaximumNumberOfWords))
|
||||
maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
|
||||
if (!ast_strlen_zero(args.argSilenceThreshold))
|
||||
silenceThreshold = atoi(args.argSilenceThreshold);
|
||||
if (!ast_strlen_zero(args.argMaximumWordLength))
|
||||
maximumWordLength = atoi(args.argMaximumWordLength);
|
||||
} else {
|
||||
ast_debug(1, "AMD using the default parameters.\n");
|
||||
}
|
||||
|
||||
/* Now we're ready to roll! */
|
||||
ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
|
||||
"totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
|
||||
initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
|
||||
minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
|
||||
|
||||
/* Set read format to signed linear so we get signed linear frames in */
|
||||
readFormat = chan->readformat;
|
||||
if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
|
||||
ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
|
||||
pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
|
||||
pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a new DSP that will detect the silence */
|
||||
if (!(silenceDetector = ast_dsp_new())) {
|
||||
ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
|
||||
pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
|
||||
pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set silence threshold to specified value */
|
||||
ast_dsp_set_threshold(silenceDetector, silenceThreshold);
|
||||
|
||||
/* Now we go into a loop waiting for frames from the channel */
|
||||
while ((res = ast_waitfor(chan, totalAnalysisTime)) > -1) {
|
||||
/* If we fail to read in a frame, that means they hung up */
|
||||
if (!(f = ast_read(chan))) {
|
||||
ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name);
|
||||
ast_debug(1, "Got hangup\n");
|
||||
strcpy(amdStatus, "HANGUP");
|
||||
break;
|
||||
}
|
||||
|
||||
if (f->frametype == AST_FRAME_VOICE) {
|
||||
/* If the total time exceeds the analysis time then give up as we are not too sure */
|
||||
framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
|
||||
iTotalTime += framelength;
|
||||
if (iTotalTime >= totalAnalysisTime) {
|
||||
ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name );
|
||||
ast_frfree(f);
|
||||
strcpy(amdStatus , "NOTSURE");
|
||||
sprintf(amdCause , "TOOLONG-%d", iTotalTime);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Feed the frame of audio into the silence detector and see if we get a result */
|
||||
dspsilence = 0;
|
||||
ast_dsp_silence(silenceDetector, f, &dspsilence);
|
||||
if (dspsilence) {
|
||||
silenceDuration = dspsilence;
|
||||
|
||||
if (silenceDuration >= betweenWordsSilence) {
|
||||
if (currentState != STATE_IN_SILENCE ) {
|
||||
previousState = currentState;
|
||||
ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name);
|
||||
}
|
||||
/* Find words less than word duration */
|
||||
if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
|
||||
ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration);
|
||||
}
|
||||
currentState = STATE_IN_SILENCE;
|
||||
consecutiveVoiceDuration = 0;
|
||||
}
|
||||
|
||||
if (inInitialSilence == 1 && silenceDuration >= initialSilence) {
|
||||
ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
|
||||
chan->name, silenceDuration, initialSilence);
|
||||
ast_frfree(f);
|
||||
strcpy(amdStatus , "MACHINE");
|
||||
sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
|
||||
break;
|
||||
}
|
||||
|
||||
if (silenceDuration >= afterGreetingSilence && inGreeting == 1) {
|
||||
ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
|
||||
chan->name, silenceDuration, afterGreetingSilence);
|
||||
ast_frfree(f);
|
||||
strcpy(amdStatus , "HUMAN");
|
||||
sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
consecutiveVoiceDuration += framelength;
|
||||
voiceDuration += framelength;
|
||||
|
||||
/* If I have enough consecutive voice to say that I am in a Word, I can only increment the
|
||||
number of words if my previous state was Silence, which means that I moved into a word. */
|
||||
if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
|
||||
iWordsCount++;
|
||||
ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount);
|
||||
previousState = currentState;
|
||||
currentState = STATE_IN_WORD;
|
||||
}
|
||||
if (consecutiveVoiceDuration >= maximumWordLength){
|
||||
ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration);
|
||||
ast_frfree(f);
|
||||
strcpy(amdStatus , "MACHINE");
|
||||
sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
|
||||
break;
|
||||
}
|
||||
if (iWordsCount >= maximumNumberOfWords) {
|
||||
ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount);
|
||||
ast_frfree(f);
|
||||
strcpy(amdStatus , "MACHINE");
|
||||
sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
|
||||
break;
|
||||
}
|
||||
|
||||
if (inGreeting == 1 && voiceDuration >= greeting) {
|
||||
ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting);
|
||||
ast_frfree(f);
|
||||
strcpy(amdStatus , "MACHINE");
|
||||
sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
|
||||
break;
|
||||
}
|
||||
|
||||
if (voiceDuration >= minimumWordLength ) {
|
||||
if (silenceDuration > 0)
|
||||
ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration);
|
||||
silenceDuration = 0;
|
||||
}
|
||||
if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0){
|
||||
/* Only go in here once to change the greeting flag when we detect the 1st word */
|
||||
if (silenceDuration > 0)
|
||||
ast_verb(3, "AMD: Channel [%s]. Before Greeting Time: silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration);
|
||||
inInitialSilence = 0;
|
||||
inGreeting = 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
/* It took too long to get a frame back. Giving up. */
|
||||
ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
|
||||
strcpy(amdStatus , "NOTSURE");
|
||||
sprintf(amdCause , "TOOLONG-%d", iTotalTime);
|
||||
}
|
||||
|
||||
/* Set the status and cause on the channel */
|
||||
pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
|
||||
pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
|
||||
|
||||
/* Restore channel read format */
|
||||
if (readFormat && ast_set_read_format(chan, readFormat))
|
||||
ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
|
||||
|
||||
/* Free the DSP used to detect silence */
|
||||
ast_dsp_free(silenceDetector);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static int amd_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
isAnsweringMachine(chan, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_config(int reload)
|
||||
{
|
||||
struct ast_config *cfg = NULL;
|
||||
char *cat = NULL;
|
||||
struct ast_variable *var = NULL;
|
||||
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
|
||||
|
||||
if (!(cfg = ast_config_load("amd.conf", config_flags))) {
|
||||
ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
|
||||
return -1;
|
||||
} else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
|
||||
return 0;
|
||||
|
||||
cat = ast_category_browse(cfg, NULL);
|
||||
|
||||
while (cat) {
|
||||
if (!strcasecmp(cat, "general") ) {
|
||||
var = ast_variable_browse(cfg, cat);
|
||||
while (var) {
|
||||
if (!strcasecmp(var->name, "initial_silence")) {
|
||||
dfltInitialSilence = atoi(var->value);
|
||||
} else if (!strcasecmp(var->name, "greeting")) {
|
||||
dfltGreeting = atoi(var->value);
|
||||
} else if (!strcasecmp(var->name, "after_greeting_silence")) {
|
||||
dfltAfterGreetingSilence = atoi(var->value);
|
||||
} else if (!strcasecmp(var->name, "silence_threshold")) {
|
||||
dfltSilenceThreshold = atoi(var->value);
|
||||
} else if (!strcasecmp(var->name, "total_analysis_time")) {
|
||||
dfltTotalAnalysisTime = atoi(var->value);
|
||||
} else if (!strcasecmp(var->name, "min_word_length")) {
|
||||
dfltMinimumWordLength = atoi(var->value);
|
||||
} else if (!strcasecmp(var->name, "between_words_silence")) {
|
||||
dfltBetweenWordsSilence = atoi(var->value);
|
||||
} else if (!strcasecmp(var->name, "maximum_number_of_words")) {
|
||||
dfltMaximumNumberOfWords = atoi(var->value);
|
||||
} else if (!strcasecmp(var->name, "maximum_word_length")) {
|
||||
dfltMaximumWordLength = atoi(var->value);
|
||||
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
|
||||
app, cat, var->name, var->lineno);
|
||||
}
|
||||
var = var->next;
|
||||
}
|
||||
}
|
||||
cat = ast_category_browse(cfg, cat);
|
||||
}
|
||||
|
||||
ast_config_destroy(cfg);
|
||||
|
||||
ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
|
||||
"totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
|
||||
dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
|
||||
dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
if (load_config(0))
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
if (ast_register_application(app, amd_exec, synopsis, descrip))
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
static int reload(void)
|
||||
{
|
||||
if (load_config(1))
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
|
||||
.load = load_module,
|
||||
.unload = unload_module,
|
||||
.reload = reload,
|
||||
);
|
||||
212
trunk/apps/app_authenticate.c
Normal file
212
trunk/apps/app_authenticate.c
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Execute arbitrary authenticate commands
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/astdb.h"
|
||||
#include "asterisk/utils.h"
|
||||
|
||||
enum {
|
||||
OPT_ACCOUNT = (1 << 0),
|
||||
OPT_DATABASE = (1 << 1),
|
||||
OPT_MULTIPLE = (1 << 3),
|
||||
OPT_REMOVE = (1 << 4),
|
||||
} auth_option_flags;
|
||||
|
||||
AST_APP_OPTIONS(auth_app_options, {
|
||||
AST_APP_OPTION('a', OPT_ACCOUNT),
|
||||
AST_APP_OPTION('d', OPT_DATABASE),
|
||||
AST_APP_OPTION('m', OPT_MULTIPLE),
|
||||
AST_APP_OPTION('r', OPT_REMOVE),
|
||||
});
|
||||
|
||||
|
||||
static char *app = "Authenticate";
|
||||
|
||||
static char *synopsis = "Authenticate a user";
|
||||
|
||||
static char *descrip =
|
||||
" Authenticate(password[,options[,maxdigits]]): This application asks the caller\n"
|
||||
"to enter a given password in order to continue dialplan execution. If the password\n"
|
||||
"begins with the '/' character, it is interpreted as a file which contains a list of\n"
|
||||
"valid passwords, listed 1 password per line in the file.\n"
|
||||
" When using a database key, the value associated with the key can be anything.\n"
|
||||
"Users have three attempts to authenticate before the channel is hung up.\n"
|
||||
" Options:\n"
|
||||
" a - Set the channels' account code to the password that is entered\n"
|
||||
" d - Interpret the given path as database key, not a literal file\n"
|
||||
" m - Interpret the given path as a file which contains a list of account\n"
|
||||
" codes and password hashes delimited with ':', listed one per line in\n"
|
||||
" the file. When one of the passwords is matched, the channel will have\n"
|
||||
" its account code set to the corresponding account code in the file.\n"
|
||||
" r - Remove the database key upon successful entry (valid with 'd' only)\n"
|
||||
" maxdigits - maximum acceptable number of digits. Stops reading after\n"
|
||||
" maxdigits have been entered (without requiring the user to\n"
|
||||
" press the '#' key).\n"
|
||||
" Defaults to 0 - no limit - wait for the user press the '#' key.\n"
|
||||
;
|
||||
|
||||
static int auth_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0, retries, maxdigits;
|
||||
char passwd[256], *prompt = "agent-pass", *argcopy = NULL;
|
||||
struct ast_flags flags = {0};
|
||||
|
||||
AST_DECLARE_APP_ARGS(arglist,
|
||||
AST_APP_ARG(password);
|
||||
AST_APP_ARG(options);
|
||||
AST_APP_ARG(maxdigits);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "Authenticate requires an argument(password)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if ((res = ast_answer(chan)))
|
||||
return -1;
|
||||
}
|
||||
|
||||
argcopy = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(arglist, argcopy);
|
||||
|
||||
if (!ast_strlen_zero(arglist.options))
|
||||
ast_app_parse_options(auth_app_options, &flags, NULL, arglist.options);
|
||||
|
||||
if (!ast_strlen_zero(arglist.maxdigits)) {
|
||||
maxdigits = atoi(arglist.maxdigits);
|
||||
if ((maxdigits<1) || (maxdigits>sizeof(passwd)-2))
|
||||
maxdigits = sizeof(passwd) - 2;
|
||||
} else {
|
||||
maxdigits = sizeof(passwd) - 2;
|
||||
}
|
||||
|
||||
/* Start asking for password */
|
||||
for (retries = 0; retries < 3; retries++) {
|
||||
if ((res = ast_app_getdata(chan, prompt, passwd, maxdigits, 0)) < 0)
|
||||
break;
|
||||
res = 0;
|
||||
if (arglist.password[0] == '/') {
|
||||
if (ast_test_flag(&flags,OPT_DATABASE)) {
|
||||
char tmp[256];
|
||||
/* Compare against a database key */
|
||||
if (!ast_db_get(arglist.password + 1, passwd, tmp, sizeof(tmp))) {
|
||||
/* It's a good password */
|
||||
if (ast_test_flag(&flags,OPT_REMOVE))
|
||||
ast_db_del(arglist.password + 1, passwd);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Compare against a file */
|
||||
FILE *f;
|
||||
char buf[256] = "", md5passwd[33] = "", *md5secret = NULL;
|
||||
|
||||
if (!(f = fopen(arglist.password, "r"))) {
|
||||
ast_log(LOG_WARNING, "Unable to open file '%s' for authentication: %s\n", arglist.password, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
while (!feof(f)) {
|
||||
fgets(buf, sizeof(buf), f);
|
||||
if (!feof(f) && !ast_strlen_zero(buf)) {
|
||||
buf[strlen(buf) - 1] = '\0';
|
||||
if (ast_test_flag(&flags,OPT_MULTIPLE)) {
|
||||
md5secret = strchr(buf, ':');
|
||||
if (md5secret == NULL)
|
||||
continue;
|
||||
*md5secret = '\0';
|
||||
md5secret++;
|
||||
ast_md5_hash(md5passwd, passwd);
|
||||
if (!strcmp(md5passwd, md5secret)) {
|
||||
if (ast_test_flag(&flags,OPT_ACCOUNT))
|
||||
ast_cdr_setaccount(chan, buf);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!strcmp(passwd, buf)) {
|
||||
if (ast_test_flag(&flags,OPT_ACCOUNT))
|
||||
ast_cdr_setaccount(chan, buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
if (!ast_strlen_zero(buf)) {
|
||||
if (ast_test_flag(&flags,OPT_MULTIPLE)) {
|
||||
if (md5secret && !strcmp(md5passwd, md5secret))
|
||||
break;
|
||||
} else {
|
||||
if (!strcmp(passwd, buf))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Compare against a fixed password */
|
||||
if (!strcmp(passwd, arglist.password))
|
||||
break;
|
||||
}
|
||||
prompt = "auth-incorrect";
|
||||
}
|
||||
if ((retries < 3) && !res) {
|
||||
if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE))
|
||||
ast_cdr_setaccount(chan, passwd);
|
||||
if (!(res = ast_streamfile(chan, "auth-thankyou", chan->language)))
|
||||
res = ast_waitstream(chan, "");
|
||||
} else {
|
||||
if (!ast_streamfile(chan, "vm-goodbye", chan->language))
|
||||
res = ast_waitstream(chan, "");
|
||||
res = -1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
if (ast_register_application(app, auth_exec, synopsis, descrip))
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Authentication Application");
|
||||
63
trunk/apps/app_cdr.c
Normal file
63
trunk/apps/app_cdr.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Martin Pycko <martinp@digium.com>
|
||||
*
|
||||
* 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 Applications connected with CDR engine
|
||||
*
|
||||
* \author Martin Pycko <martinp@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
static char *nocdr_descrip =
|
||||
" NoCDR(): This application will tell Asterisk not to maintain a CDR for the\n"
|
||||
"current call.\n";
|
||||
|
||||
static char *nocdr_app = "NoCDR";
|
||||
static char *nocdr_synopsis = "Tell Asterisk to not maintain a CDR for the current call";
|
||||
|
||||
|
||||
static int nocdr_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
if (chan->cdr)
|
||||
ast_set_flag(chan->cdr, AST_CDR_FLAG_POST_DISABLED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(nocdr_app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
if (ast_register_application(nocdr_app, nocdr_exec, nocdr_synopsis, nocdr_descrip))
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Tell Asterisk to not maintain a CDR for the current call");
|
||||
157
trunk/apps/app_chanisavail.c
Normal file
157
trunk/apps/app_chanisavail.c
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
* James Golovich <james@gnuinter.net>
|
||||
*
|
||||
* 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 Check if Channel is Available
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
* \author James Golovich <james@gnuinter.net>
|
||||
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/devicestate.h"
|
||||
|
||||
static char *app = "ChanIsAvail";
|
||||
|
||||
static char *synopsis = "Check channel availability";
|
||||
|
||||
static char *descrip =
|
||||
" ChanIsAvail(Technology/resource[&Technology2/resource2...][,options]): \n"
|
||||
"This application will check to see if any of the specified channels are\n"
|
||||
"available.\n"
|
||||
" Options:\n"
|
||||
" s - Consider the channel unavailable if the channel is in use at all.\n"
|
||||
" t - Simply checks if specified channels exist in the channel list\n"
|
||||
" (implies option s).\n"
|
||||
"This application sets the following channel variable upon completion:\n"
|
||||
" AVAILCHAN - the name of the available channel, if one exists\n"
|
||||
" AVAILORIGCHAN - the canonical channel name that was used to create the channel\n"
|
||||
" AVAILSTATUS - the status code for the available channel\n";
|
||||
|
||||
|
||||
static int chanavail_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=-1, inuse=-1, option_state=0, string_compare=0;
|
||||
int status;
|
||||
char *info, tmp[512], trychan[512], *peers, *tech, *number, *rest, *cur;
|
||||
struct ast_channel *tempchan;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(reqchans);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "ChanIsAvail requires an argument (Zap/1&Zap/2)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
info = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, info);
|
||||
|
||||
if (args.options) {
|
||||
if (strchr(args.options, 's'))
|
||||
option_state = 1;
|
||||
if (strchr(args.options, 't'))
|
||||
string_compare = 1;
|
||||
}
|
||||
peers = args.reqchans;
|
||||
if (peers) {
|
||||
cur = peers;
|
||||
do {
|
||||
/* remember where to start next time */
|
||||
rest = strchr(cur, '&');
|
||||
if (rest) {
|
||||
*rest = 0;
|
||||
rest++;
|
||||
}
|
||||
tech = cur;
|
||||
number = strchr(tech, '/');
|
||||
if (!number) {
|
||||
ast_log(LOG_WARNING, "ChanIsAvail argument takes format ([technology]/[device])\n");
|
||||
return -1;
|
||||
}
|
||||
*number = '\0';
|
||||
number++;
|
||||
|
||||
if (string_compare) {
|
||||
/* ast_parse_device_state checks for "SIP/1234" as a channel name.
|
||||
ast_device_state will ask the SIP driver for the channel state. */
|
||||
|
||||
snprintf(trychan, sizeof(trychan), "%s/%s",cur,number);
|
||||
status = inuse = ast_parse_device_state(trychan);
|
||||
} else if (option_state) {
|
||||
/* If the pbx says in use then don't bother trying further.
|
||||
This is to permit testing if someone's on a call, even if the
|
||||
channel can permit more calls (ie callwaiting, sip calls, etc). */
|
||||
|
||||
snprintf(trychan, sizeof(trychan), "%s/%s",cur,number);
|
||||
status = inuse = ast_device_state(trychan);
|
||||
}
|
||||
if ((inuse <= 1) && (tempchan = ast_request(tech, chan->nativeformats, number, &status))) {
|
||||
pbx_builtin_setvar_helper(chan, "AVAILCHAN", tempchan->name);
|
||||
/* Store the originally used channel too */
|
||||
snprintf(tmp, sizeof(tmp), "%s/%s", tech, number);
|
||||
pbx_builtin_setvar_helper(chan, "AVAILORIGCHAN", tmp);
|
||||
snprintf(tmp, sizeof(tmp), "%d", status);
|
||||
pbx_builtin_setvar_helper(chan, "AVAILSTATUS", tmp);
|
||||
ast_hangup(tempchan);
|
||||
tempchan = NULL;
|
||||
res = 1;
|
||||
break;
|
||||
} else {
|
||||
snprintf(tmp, sizeof(tmp), "%d", status);
|
||||
pbx_builtin_setvar_helper(chan, "AVAILSTATUS", tmp);
|
||||
}
|
||||
cur = rest;
|
||||
} while (cur);
|
||||
}
|
||||
if (res < 1) {
|
||||
pbx_builtin_setvar_helper(chan, "AVAILCHAN", "");
|
||||
pbx_builtin_setvar_helper(chan, "AVAILORIGCHAN", "");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, chanavail_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Check channel availability");
|
||||
93
trunk/apps/app_channelredirect.c
Normal file
93
trunk/apps/app_channelredirect.c
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2006, Sergey Basmanov
|
||||
*
|
||||
* 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 ChannelRedirect application
|
||||
*
|
||||
* \author Sergey Basmanov <sergey_basmanov@mail.ru>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/features.h"
|
||||
|
||||
static char *app = "ChannelRedirect";
|
||||
static char *synopsis = "Redirects given channel to a dialplan target.";
|
||||
static char *descrip =
|
||||
"ChannelRedirect(channel,[[context,]extension,]priority)\n"
|
||||
" Sends the specified channel to the specified extension priority\n";
|
||||
|
||||
|
||||
static int asyncgoto_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = -1;
|
||||
char *info;
|
||||
struct ast_channel *chan2 = NULL;
|
||||
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(channel);
|
||||
AST_APP_ARG(label);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "%s requires an argument (channel,[[context,]exten,]priority)\n", app);
|
||||
return -1;
|
||||
}
|
||||
|
||||
info = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, info);
|
||||
|
||||
if (ast_strlen_zero(args.channel) || ast_strlen_zero(args.label)) {
|
||||
ast_log(LOG_WARNING, "%s requires an argument (channel,[[context,]exten,]priority)\n", app);
|
||||
goto quit;
|
||||
}
|
||||
|
||||
chan2 = ast_get_channel_by_name_locked(args.channel);
|
||||
if (!chan2) {
|
||||
ast_log(LOG_WARNING, "No such channel: %s\n", args.channel);
|
||||
goto quit;
|
||||
}
|
||||
|
||||
res = ast_parseable_goto(chan2, args.label);
|
||||
|
||||
ast_channel_unlock(chan2);
|
||||
quit:
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, asyncgoto_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Redirects a given channel to a dialplan target");
|
||||
730
trunk/apps/app_chanspy.c
Normal file
730
trunk/apps/app_chanspy.c
Normal file
@@ -0,0 +1,730 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2005 Anthony Minessale II (anthmct@yahoo.com)
|
||||
* Copyright (C) 2005 - 2006, Digium, Inc.
|
||||
*
|
||||
* A license has been granted to Digium (via disclaimer) for the use of
|
||||
* this code.
|
||||
*
|
||||
* 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 ChanSpy: Listen in on any channel.
|
||||
*
|
||||
* \author Anthony Minessale II <anthmct@yahoo.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/audiohook.h"
|
||||
#include "asterisk/features.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/say.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/translate.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
|
||||
#define AST_NAME_STRLEN 256
|
||||
|
||||
static const char *tdesc = "Listen to a channel, and optionally whisper into it";
|
||||
static const char *app_chan = "ChanSpy";
|
||||
static const char *desc_chan =
|
||||
" ChanSpy([chanprefix][,options]): This application is used to listen to the\n"
|
||||
"audio from an Asterisk channel. This includes the audio coming in and\n"
|
||||
"out of the channel being spied on. If the 'chanprefix' parameter is specified,\n"
|
||||
"only channels beginning with this string will be spied upon.\n"
|
||||
" While spying, the following actions may be performed:\n"
|
||||
" - Dialing # cycles the volume level.\n"
|
||||
" - Dialing * will stop spying and look for another channel to spy on.\n"
|
||||
" - Dialing a series of digits followed by # builds a channel name to append\n"
|
||||
" to 'chanprefix'. For example, executing ChanSpy(Agent) and then dialing\n"
|
||||
" the digits '1234#' while spying will begin spying on the channel\n"
|
||||
" 'Agent/1234'.\n"
|
||||
" Note: The X option supersedes the three features above in that if a valid\n"
|
||||
" single digit extension exists in the correct context ChanSpy will\n"
|
||||
" exit to it. This also disables choosing a channel based on 'chanprefix'\n"
|
||||
" and a digit sequence.\n"
|
||||
" Options:\n"
|
||||
" b - Only spy on channels involved in a bridged call.\n"
|
||||
" g(grp) - Match only channels where their SPYGROUP variable is set to\n"
|
||||
" contain 'grp' in an optional : delimited list.\n"
|
||||
" q - Don't play a beep when beginning to spy on a channel, or speak the\n"
|
||||
" selected channel name.\n"
|
||||
" r[(basename)] - Record the session to the monitor spool directory. An\n"
|
||||
" optional base for the filename may be specified. The\n"
|
||||
" default is 'chanspy'.\n"
|
||||
" v([value]) - Adjust the initial volume in the range from -4 to 4. A\n"
|
||||
" negative value refers to a quieter setting.\n"
|
||||
" w - Enable 'whisper' mode, so the spying channel can talk to\n"
|
||||
" the spied-on channel.\n"
|
||||
" W - Enable 'private whisper' mode, so the spying channel can\n"
|
||||
" talk to the spied-on channel but cannot listen to that\n"
|
||||
" channel.\n"
|
||||
" o - Only listen to audio coming from this channel.\n"
|
||||
" X - Allow the user to exit ChanSpy to a valid single digit\n"
|
||||
" numeric extension in the current context or the context\n"
|
||||
" specified by the SPY_EXIT_CONTEXT channel variable. The\n"
|
||||
" name of the last channel that was spied on will be stored\n"
|
||||
" in the SPY_CHANNEL variable.\n"
|
||||
;
|
||||
|
||||
static const char *app_ext = "ExtenSpy";
|
||||
static const char *desc_ext =
|
||||
" ExtenSpy(exten[@context][,options]): This application is used to listen to the\n"
|
||||
"audio from an Asterisk channel. This includes the audio coming in and\n"
|
||||
"out of the channel being spied on. Only channels created by outgoing calls for the\n"
|
||||
"specified extension will be selected for spying. If the optional context is not\n"
|
||||
"supplied, the current channel's context will be used.\n"
|
||||
" While spying, the following actions may be performed:\n"
|
||||
" - Dialing # cycles the volume level.\n"
|
||||
" - Dialing * will stop spying and look for another channel to spy on.\n"
|
||||
" Note: The X option superseeds the two features above in that if a valid\n"
|
||||
" single digit extension exists in the correct context it ChanSpy will\n"
|
||||
" exit to it.\n"
|
||||
" Options:\n"
|
||||
" b - Only spy on channels involved in a bridged call.\n"
|
||||
" g(grp) - Match only channels where their ${SPYGROUP} variable is set to\n"
|
||||
" contain 'grp' in an optional : delimited list.\n"
|
||||
" q - Don't play a beep when beginning to spy on a channel, or speak the\n"
|
||||
" selected channel name.\n"
|
||||
" r[(basename)] - Record the session to the monitor spool directory. An\n"
|
||||
" optional base for the filename may be specified. The\n"
|
||||
" default is 'chanspy'.\n"
|
||||
" v([value]) - Adjust the initial volume in the range from -4 to 4. A\n"
|
||||
" negative value refers to a quieter setting.\n"
|
||||
" w - Enable 'whisper' mode, so the spying channel can talk to\n"
|
||||
" the spied-on channel.\n"
|
||||
" W - Enable 'private whisper' mode, so the spying channel can\n"
|
||||
" talk to the spied-on channel but cannot listen to that\n"
|
||||
" channel.\n"
|
||||
" o - Only listen to audio coming from this channel.\n"
|
||||
" X - Allow the user to exit ChanSpy to a valid single digit\n"
|
||||
" numeric extension in the current context or the context\n"
|
||||
" specified by the SPY_EXIT_CONTEXT channel variable. The\n"
|
||||
" name of the last channel that was spied on will be stored\n"
|
||||
" in the SPY_CHANNEL variable.\n"
|
||||
;
|
||||
|
||||
enum {
|
||||
OPTION_QUIET = (1 << 0), /* Quiet, no announcement */
|
||||
OPTION_BRIDGED = (1 << 1), /* Only look at bridged calls */
|
||||
OPTION_VOLUME = (1 << 2), /* Specify initial volume */
|
||||
OPTION_GROUP = (1 << 3), /* Only look at channels in group */
|
||||
OPTION_RECORD = (1 << 4),
|
||||
OPTION_WHISPER = (1 << 5),
|
||||
OPTION_PRIVATE = (1 << 6), /* Private Whisper mode */
|
||||
OPTION_READONLY = (1 << 7), /* Don't mix the two channels */
|
||||
OPTION_EXIT = (1 << 8), /* Exit to a valid single digit extension */
|
||||
} chanspy_opt_flags;
|
||||
|
||||
enum {
|
||||
OPT_ARG_VOLUME = 0,
|
||||
OPT_ARG_GROUP,
|
||||
OPT_ARG_RECORD,
|
||||
OPT_ARG_ARRAY_SIZE,
|
||||
} chanspy_opt_args;
|
||||
|
||||
AST_APP_OPTIONS(spy_opts, {
|
||||
AST_APP_OPTION('q', OPTION_QUIET),
|
||||
AST_APP_OPTION('b', OPTION_BRIDGED),
|
||||
AST_APP_OPTION('w', OPTION_WHISPER),
|
||||
AST_APP_OPTION('W', OPTION_PRIVATE),
|
||||
AST_APP_OPTION_ARG('v', OPTION_VOLUME, OPT_ARG_VOLUME),
|
||||
AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
|
||||
AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD),
|
||||
AST_APP_OPTION('o', OPTION_READONLY),
|
||||
AST_APP_OPTION('X', OPTION_EXIT),
|
||||
});
|
||||
|
||||
|
||||
struct chanspy_translation_helper {
|
||||
/* spy data */
|
||||
struct ast_audiohook spy_audiohook;
|
||||
struct ast_audiohook whisper_audiohook;
|
||||
int fd;
|
||||
int volfactor;
|
||||
};
|
||||
|
||||
static void *spy_alloc(struct ast_channel *chan, void *data)
|
||||
{
|
||||
/* just store the data pointer in the channel structure */
|
||||
return data;
|
||||
}
|
||||
|
||||
static void spy_release(struct ast_channel *chan, void *data)
|
||||
{
|
||||
/* nothing to do */
|
||||
}
|
||||
|
||||
static int spy_generate(struct ast_channel *chan, void *data, int len, int samples)
|
||||
{
|
||||
struct chanspy_translation_helper *csth = data;
|
||||
struct ast_frame *f = NULL;
|
||||
|
||||
ast_audiohook_lock(&csth->spy_audiohook);
|
||||
if (csth->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
|
||||
/* Channel is already gone more than likely */
|
||||
ast_audiohook_unlock(&csth->spy_audiohook);
|
||||
return -1;
|
||||
}
|
||||
|
||||
f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR);
|
||||
|
||||
ast_audiohook_unlock(&csth->spy_audiohook);
|
||||
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
if (ast_write(chan, f)) {
|
||||
ast_frfree(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (csth->fd)
|
||||
write(csth->fd, f->data, f->datalen);
|
||||
|
||||
ast_frfree(f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_generator spygen = {
|
||||
.alloc = spy_alloc,
|
||||
.release = spy_release,
|
||||
.generate = spy_generate,
|
||||
};
|
||||
|
||||
static int start_spying(struct ast_channel *chan, struct ast_channel *spychan, struct ast_audiohook *audiohook)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_channel *peer = NULL;
|
||||
|
||||
ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan->name, chan->name);
|
||||
|
||||
res = ast_audiohook_attach(chan, audiohook);
|
||||
|
||||
if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
|
||||
ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int channel_spy(struct ast_channel *chan, struct ast_channel *spyee, int *volfactor, int fd,
|
||||
const struct ast_flags *flags, char *exitcontext)
|
||||
{
|
||||
struct chanspy_translation_helper csth;
|
||||
int running = 0, res, x = 0;
|
||||
char inp[24] = {0};
|
||||
char *name;
|
||||
struct ast_frame *f;
|
||||
struct ast_silence_generator *silgen = NULL;
|
||||
|
||||
if (ast_check_hangup(chan) || ast_check_hangup(spyee))
|
||||
return 0;
|
||||
|
||||
name = ast_strdupa(spyee->name);
|
||||
ast_verb(2, "Spying on channel %s\n", name);
|
||||
|
||||
memset(&csth, 0, sizeof(csth));
|
||||
|
||||
ast_audiohook_init(&csth.spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "ChanSpy");
|
||||
|
||||
if (start_spying(spyee, chan, &csth.spy_audiohook)) {
|
||||
ast_audiohook_destroy(&csth.spy_audiohook);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ast_test_flag(flags, OPTION_WHISPER)) {
|
||||
ast_audiohook_init(&csth.whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "ChanSpy");
|
||||
start_spying(spyee, chan, &csth.whisper_audiohook);
|
||||
}
|
||||
|
||||
csth.volfactor = *volfactor;
|
||||
|
||||
if (csth.volfactor) {
|
||||
csth.spy_audiohook.options.read_volume = csth.volfactor;
|
||||
csth.spy_audiohook.options.write_volume = csth.volfactor;
|
||||
}
|
||||
|
||||
csth.fd = fd;
|
||||
|
||||
if (ast_test_flag(flags, OPTION_PRIVATE))
|
||||
silgen = ast_channel_start_silence_generator(chan);
|
||||
else
|
||||
ast_activate_generator(chan, &spygen, &csth);
|
||||
|
||||
/* We can no longer rely on 'spyee' being an actual channel;
|
||||
it can be hung up and freed out from under us. However, the
|
||||
channel destructor will put NULL into our csth.spy.chan
|
||||
field when that happens, so that is our signal that the spyee
|
||||
channel has gone away.
|
||||
*/
|
||||
|
||||
/* Note: it is very important that the ast_waitfor() be the first
|
||||
condition in this expression, so that if we wait for some period
|
||||
of time before receiving a frame from our spying channel, we check
|
||||
for hangup on the spied-on channel _after_ knowing that a frame
|
||||
has arrived, since the spied-on channel could have gone away while
|
||||
we were waiting
|
||||
*/
|
||||
while ((res = ast_waitfor(chan, -1) > -1) && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
|
||||
if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
|
||||
running = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ast_test_flag(flags, OPTION_WHISPER) && f->frametype == AST_FRAME_VOICE) {
|
||||
ast_audiohook_lock(&csth.whisper_audiohook);
|
||||
ast_audiohook_write_frame(&csth.whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
|
||||
ast_audiohook_unlock(&csth.whisper_audiohook);
|
||||
ast_frfree(f);
|
||||
continue;
|
||||
}
|
||||
|
||||
res = (f->frametype == AST_FRAME_DTMF) ? f->subclass : 0;
|
||||
ast_frfree(f);
|
||||
if (!res)
|
||||
continue;
|
||||
|
||||
if (x == sizeof(inp))
|
||||
x = 0;
|
||||
|
||||
if (res < 0) {
|
||||
running = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ast_test_flag(flags, OPTION_EXIT)) {
|
||||
char tmp[2];
|
||||
tmp[0] = res;
|
||||
tmp[1] = '\0';
|
||||
if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
|
||||
ast_debug(1, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext);
|
||||
pbx_builtin_setvar_helper(chan, "SPY_CHANNEL", name);
|
||||
running = -2;
|
||||
break;
|
||||
} else {
|
||||
ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
|
||||
}
|
||||
} else if (res >= '0' && res <= '9') {
|
||||
inp[x++] = res;
|
||||
}
|
||||
|
||||
if (res == '*') {
|
||||
running = 0;
|
||||
break;
|
||||
} else if (res == '#') {
|
||||
if (!ast_strlen_zero(inp)) {
|
||||
running = atoi(inp);
|
||||
break;
|
||||
}
|
||||
|
||||
(*volfactor)++;
|
||||
if (*volfactor > 4)
|
||||
*volfactor = -4;
|
||||
ast_verb(3, "Setting spy volume on %s to %d\n", chan->name, *volfactor);
|
||||
|
||||
csth.volfactor = *volfactor;
|
||||
csth.spy_audiohook.options.read_volume = csth.volfactor;
|
||||
csth.spy_audiohook.options.write_volume = csth.volfactor;
|
||||
}
|
||||
}
|
||||
|
||||
if (ast_test_flag(flags, OPTION_PRIVATE))
|
||||
ast_channel_stop_silence_generator(chan, silgen);
|
||||
else
|
||||
ast_deactivate_generator(chan);
|
||||
|
||||
if (ast_test_flag(flags, OPTION_WHISPER)) {
|
||||
ast_audiohook_lock(&csth.whisper_audiohook);
|
||||
ast_audiohook_detach(&csth.whisper_audiohook);
|
||||
ast_audiohook_unlock(&csth.whisper_audiohook);
|
||||
ast_audiohook_destroy(&csth.whisper_audiohook);
|
||||
}
|
||||
|
||||
ast_audiohook_lock(&csth.spy_audiohook);
|
||||
ast_audiohook_detach(&csth.spy_audiohook);
|
||||
ast_audiohook_unlock(&csth.spy_audiohook);
|
||||
ast_audiohook_destroy(&csth.spy_audiohook);
|
||||
|
||||
ast_verb(2, "Done Spying on channel %s\n", name);
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
static struct ast_channel *next_channel(const struct ast_channel *last, const char *spec,
|
||||
const char *exten, const char *context)
|
||||
{
|
||||
struct ast_channel *this;
|
||||
|
||||
redo:
|
||||
if (spec)
|
||||
this = ast_walk_channel_by_name_prefix_locked(last, spec, strlen(spec));
|
||||
else if (exten)
|
||||
this = ast_walk_channel_by_exten_locked(last, exten, context);
|
||||
else
|
||||
this = ast_channel_walk_locked(last);
|
||||
|
||||
if (this) {
|
||||
ast_channel_unlock(this);
|
||||
if (!strncmp(this->name, "Zap/pseudo", 10))
|
||||
goto redo;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
static int common_exec(struct ast_channel *chan, const struct ast_flags *flags,
|
||||
int volfactor, const int fd, const char *mygroup, const char *spec,
|
||||
const char *exten, const char *context)
|
||||
{
|
||||
struct ast_channel *peer, *prev, *next;
|
||||
char nameprefix[AST_NAME_STRLEN];
|
||||
char peer_name[AST_NAME_STRLEN + 5];
|
||||
char exitcontext[AST_MAX_CONTEXT] = "";
|
||||
signed char zero_volume = 0;
|
||||
int waitms;
|
||||
int res;
|
||||
char *ptr;
|
||||
int num;
|
||||
|
||||
if (ast_test_flag(flags, OPTION_EXIT)) {
|
||||
const char *c;
|
||||
if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT")))
|
||||
ast_copy_string(exitcontext, c, sizeof(exitcontext));
|
||||
else if (!ast_strlen_zero(chan->macrocontext))
|
||||
ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
|
||||
else
|
||||
ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
|
||||
}
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
ast_answer(chan);
|
||||
|
||||
ast_set_flag(chan, AST_FLAG_SPYING); /* so nobody can spy on us while we are spying */
|
||||
|
||||
waitms = 100;
|
||||
|
||||
for (;;) {
|
||||
if (!ast_test_flag(flags, OPTION_QUIET)) {
|
||||
res = ast_streamfile(chan, "beep", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, "");
|
||||
else if (res < 0) {
|
||||
ast_clear_flag(chan, AST_FLAG_SPYING);
|
||||
break;
|
||||
}
|
||||
if (!ast_strlen_zero(exitcontext)) {
|
||||
char tmp[2];
|
||||
tmp[0] = res;
|
||||
tmp[1] = '\0';
|
||||
if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
|
||||
goto exit;
|
||||
else
|
||||
ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
|
||||
}
|
||||
}
|
||||
|
||||
res = ast_waitfordigit(chan, waitms);
|
||||
if (res < 0) {
|
||||
ast_clear_flag(chan, AST_FLAG_SPYING);
|
||||
break;
|
||||
}
|
||||
if (!ast_strlen_zero(exitcontext)) {
|
||||
char tmp[2];
|
||||
tmp[0] = res;
|
||||
tmp[1] = '\0';
|
||||
if (!ast_goto_if_exists(chan, exitcontext, tmp, 1))
|
||||
goto exit;
|
||||
else
|
||||
ast_debug(2, "Exit by single digit did not work in chanspy. Extension %s does not exist in context %s\n", tmp, exitcontext);
|
||||
}
|
||||
|
||||
/* reset for the next loop around, unless overridden later */
|
||||
waitms = 100;
|
||||
peer = prev = next = NULL;
|
||||
|
||||
for (peer = next_channel(peer, spec, exten, context);
|
||||
peer;
|
||||
prev = peer, peer = next ? next : next_channel(peer, spec, exten, context), next = NULL) {
|
||||
const char *group;
|
||||
int igrp = !mygroup;
|
||||
char *groups[25];
|
||||
int num_groups = 0;
|
||||
char *dup_group;
|
||||
int x;
|
||||
char *s;
|
||||
|
||||
if (peer == prev)
|
||||
break;
|
||||
|
||||
if (peer == chan)
|
||||
continue;
|
||||
|
||||
if (ast_test_flag(flags, OPTION_BRIDGED) && !ast_bridged_channel(peer))
|
||||
continue;
|
||||
|
||||
if (ast_check_hangup(peer) || ast_test_flag(peer, AST_FLAG_SPYING))
|
||||
continue;
|
||||
|
||||
if (mygroup) {
|
||||
if ((group = pbx_builtin_getvar_helper(peer, "SPYGROUP"))) {
|
||||
dup_group = ast_strdupa(group);
|
||||
num_groups = ast_app_separate_args(dup_group, ':', groups,
|
||||
sizeof(groups) / sizeof(groups[0]));
|
||||
}
|
||||
|
||||
for (x = 0; x < num_groups; x++) {
|
||||
if (!strcmp(mygroup, groups[x])) {
|
||||
igrp = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!igrp)
|
||||
continue;
|
||||
|
||||
strcpy(peer_name, "spy-");
|
||||
strncat(peer_name, peer->name, AST_NAME_STRLEN);
|
||||
ptr = strchr(peer_name, '/');
|
||||
*ptr++ = '\0';
|
||||
|
||||
for (s = peer_name; s < ptr; s++)
|
||||
*s = tolower(*s);
|
||||
|
||||
if (!ast_test_flag(flags, OPTION_QUIET)) {
|
||||
if (ast_fileexists(peer_name, NULL, NULL) != -1) {
|
||||
res = ast_streamfile(chan, peer_name, chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, "");
|
||||
if (res)
|
||||
break;
|
||||
} else
|
||||
res = ast_say_character_str(chan, peer_name, "", chan->language);
|
||||
if ((num = atoi(ptr)))
|
||||
ast_say_digits(chan, atoi(ptr), "", chan->language);
|
||||
}
|
||||
|
||||
waitms = 5000;
|
||||
res = channel_spy(chan, peer, &volfactor, fd, flags, exitcontext);
|
||||
|
||||
if (res == -1) {
|
||||
goto exit;
|
||||
} else if (res == -2) {
|
||||
res = 0;
|
||||
goto exit;
|
||||
} else if (res > 1 && spec) {
|
||||
snprintf(nameprefix, AST_NAME_STRLEN, "%s/%d", spec, res);
|
||||
if ((next = ast_get_channel_by_name_prefix_locked(nameprefix, strlen(nameprefix)))) {
|
||||
ast_channel_unlock(next);
|
||||
} else {
|
||||
/* stay on this channel */
|
||||
next = peer;
|
||||
}
|
||||
peer = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
exit:
|
||||
|
||||
ast_clear_flag(chan, AST_FLAG_SPYING);
|
||||
|
||||
ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int chanspy_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *mygroup = NULL;
|
||||
char *recbase = NULL;
|
||||
int fd = 0;
|
||||
struct ast_flags flags;
|
||||
int oldwf = 0;
|
||||
int volfactor = 0;
|
||||
int res;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(spec);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
char *opts[OPT_ARG_ARRAY_SIZE];
|
||||
|
||||
data = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, data);
|
||||
|
||||
if (args.spec && !strcmp(args.spec, "all"))
|
||||
args.spec = NULL;
|
||||
|
||||
if (args.options) {
|
||||
ast_app_parse_options(spy_opts, &flags, opts, args.options);
|
||||
if (ast_test_flag(&flags, OPTION_GROUP))
|
||||
mygroup = opts[OPT_ARG_GROUP];
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_RECORD) &&
|
||||
!(recbase = opts[OPT_ARG_RECORD]))
|
||||
recbase = "chanspy";
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
|
||||
int vol;
|
||||
|
||||
if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
|
||||
ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
|
||||
else
|
||||
volfactor = vol;
|
||||
}
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_PRIVATE))
|
||||
ast_set_flag(&flags, OPTION_WHISPER);
|
||||
} else
|
||||
ast_clear_flag(&flags, AST_FLAGS_ALL);
|
||||
|
||||
oldwf = chan->writeformat;
|
||||
if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
|
||||
ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (recbase) {
|
||||
char filename[512];
|
||||
|
||||
snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
|
||||
if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
|
||||
ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
|
||||
fd = 0;
|
||||
}
|
||||
}
|
||||
|
||||
res = common_exec(chan, &flags, volfactor, fd, mygroup, args.spec, NULL, NULL);
|
||||
|
||||
if (fd)
|
||||
close(fd);
|
||||
|
||||
if (oldwf && ast_set_write_format(chan, oldwf) < 0)
|
||||
ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int extenspy_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *ptr, *exten = NULL;
|
||||
char *mygroup = NULL;
|
||||
char *recbase = NULL;
|
||||
int fd = 0;
|
||||
struct ast_flags flags;
|
||||
int oldwf = 0;
|
||||
int volfactor = 0;
|
||||
int res;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(context);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
data = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, data);
|
||||
if (!ast_strlen_zero(args.context) && (ptr = strchr(args.context, '@'))) {
|
||||
exten = args.context;
|
||||
*ptr++ = '\0';
|
||||
args.context = ptr;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(args.context))
|
||||
args.context = ast_strdupa(chan->context);
|
||||
|
||||
if (args.options) {
|
||||
char *opts[OPT_ARG_ARRAY_SIZE];
|
||||
|
||||
ast_app_parse_options(spy_opts, &flags, opts, args.options);
|
||||
if (ast_test_flag(&flags, OPTION_GROUP))
|
||||
mygroup = opts[OPT_ARG_GROUP];
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_RECORD) &&
|
||||
!(recbase = opts[OPT_ARG_RECORD]))
|
||||
recbase = "chanspy";
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_VOLUME) && opts[OPT_ARG_VOLUME]) {
|
||||
int vol;
|
||||
|
||||
if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &vol) != 1) || (vol > 4) || (vol < -4))
|
||||
ast_log(LOG_NOTICE, "Volume factor must be a number between -4 and 4\n");
|
||||
else
|
||||
volfactor = vol;
|
||||
}
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_PRIVATE))
|
||||
ast_set_flag(&flags, OPTION_WHISPER);
|
||||
} else
|
||||
ast_clear_flag(&flags, AST_FLAGS_ALL);
|
||||
|
||||
oldwf = chan->writeformat;
|
||||
if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
|
||||
ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (recbase) {
|
||||
char filename[512];
|
||||
|
||||
snprintf(filename, sizeof(filename), "%s/%s.%d.raw", ast_config_AST_MONITOR_DIR, recbase, (int) time(NULL));
|
||||
if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, AST_FILE_MODE)) <= 0) {
|
||||
ast_log(LOG_WARNING, "Cannot open '%s' for recording\n", filename);
|
||||
fd = 0;
|
||||
}
|
||||
}
|
||||
|
||||
res = common_exec(chan, &flags, volfactor, fd, mygroup, NULL, exten, args.context);
|
||||
|
||||
if (fd)
|
||||
close(fd);
|
||||
|
||||
if (oldwf && ast_set_write_format(chan, oldwf) < 0)
|
||||
ast_log(LOG_ERROR, "Could Not Set Write Format.\n");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
res |= ast_unregister_application(app_chan);
|
||||
res |= ast_unregister_application(app_ext);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
res |= ast_register_application(app_chan, chanspy_exec, tdesc, desc_chan);
|
||||
res |= ast_register_application(app_ext, extenspy_exec, tdesc, desc_ext);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Listen to the audio of an active channel");
|
||||
168
trunk/apps/app_controlplayback.c
Normal file
168
trunk/apps/app_controlplayback.c
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Trivial application to control playback of a sound file
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
static const char *app = "ControlPlayback";
|
||||
|
||||
static const char *synopsis = "Play a file with fast forward and rewind";
|
||||
|
||||
static const char *descrip =
|
||||
" ControlPlayback(file[,skipms[,ff[,rew[,stop[,pause[,restart,options]]]]]]]):\n"
|
||||
"This application will play back the given filename. By default, the '*' key\n"
|
||||
"can be used to rewind, and the '#' key can be used to fast-forward.\n"
|
||||
"Parameters:\n"
|
||||
" skipms - This is number of milliseconds to skip when rewinding or\n"
|
||||
" fast-forwarding.\n"
|
||||
" ff - Fast-forward when this DTMF digit is received.\n"
|
||||
" rew - Rewind when this DTMF digit is received.\n"
|
||||
" stop - Stop playback when this DTMF digit is received.\n"
|
||||
" pause - Pause playback when this DTMF digit is received.\n"
|
||||
" restart - Restart playback when this DTMF digit is received.\n"
|
||||
"Options:\n"
|
||||
" o(#) - Start at # ms from the beginning of the file.\n"
|
||||
"This application sets the following channel variables upon completion:\n"
|
||||
" CPLAYBACKSTATUS - This variable contains the status of the attempt as a text\n"
|
||||
" string, one of: SUCCESS | USERSTOPPED | ERROR\n"
|
||||
" CPLAYBACKOFFSET - This contains the offset in ms into the file where\n"
|
||||
" playback was at when it stopped. -1 is end of file.\n"
|
||||
" CPLAYBACKSTOPKEY - If the playback is stopped by the user this variable contains\n"
|
||||
" the key that was pressed.\n";
|
||||
|
||||
enum {
|
||||
OPT_OFFSET = (1 << 1),
|
||||
};
|
||||
|
||||
enum {
|
||||
OPT_ARG_OFFSET = 0,
|
||||
/* must stay as the last entry ... */
|
||||
OPT_ARG_ARRAY_LEN,
|
||||
};
|
||||
|
||||
AST_APP_OPTIONS(cpb_opts, BEGIN_OPTIONS
|
||||
AST_APP_OPTION_ARG('o', OPT_OFFSET, OPT_ARG_OFFSET),
|
||||
END_OPTIONS );
|
||||
|
||||
static int is_on_phonepad(char key)
|
||||
{
|
||||
return key == 35 || key == 42 || (key >= 48 && key <= 57);
|
||||
}
|
||||
|
||||
static int controlplayback_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
int skipms = 0;
|
||||
long offsetms = 0;
|
||||
char offsetbuf[20];
|
||||
char stopkeybuf[2];
|
||||
char *tmp;
|
||||
struct ast_flags opts = { 0, };
|
||||
char *opt_args[OPT_ARG_ARRAY_LEN];
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(filename);
|
||||
AST_APP_ARG(skip);
|
||||
AST_APP_ARG(fwd);
|
||||
AST_APP_ARG(rev);
|
||||
AST_APP_ARG(stop);
|
||||
AST_APP_ARG(pause);
|
||||
AST_APP_ARG(restart);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, tmp);
|
||||
|
||||
if (args.argc < 1) {
|
||||
ast_log(LOG_WARNING, "ControlPlayback requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
skipms = args.skip ? (atoi(args.skip) ? atoi(args.skip) : 3000) : 3000;
|
||||
|
||||
if (!args.fwd || !is_on_phonepad(*args.fwd))
|
||||
args.fwd = "#";
|
||||
if (!args.rev || !is_on_phonepad(*args.rev))
|
||||
args.rev = "*";
|
||||
if (args.stop && !is_on_phonepad(*args.stop))
|
||||
args.stop = NULL;
|
||||
if (args.pause && !is_on_phonepad(*args.pause))
|
||||
args.pause = NULL;
|
||||
if (args.restart && !is_on_phonepad(*args.restart))
|
||||
args.restart = NULL;
|
||||
|
||||
if (args.options) {
|
||||
ast_app_parse_options(cpb_opts, &opts, opt_args, args.options);
|
||||
if (ast_test_flag(&opts, OPT_OFFSET))
|
||||
offsetms = atol(opt_args[OPT_ARG_OFFSET]);
|
||||
}
|
||||
|
||||
res = ast_control_streamfile(chan, args.filename, args.fwd, args.rev, args.stop, args.pause, args.restart, skipms, &offsetms);
|
||||
|
||||
/* If we stopped on one of our stop keys, return 0 */
|
||||
if (res > 0 && args.stop && strchr(args.stop, res)) {
|
||||
pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "USERSTOPPED");
|
||||
snprintf(stopkeybuf, sizeof(stopkeybuf), "%c", res);
|
||||
pbx_builtin_setvar_helper(chan, "CPLAYBACKSTOPKEY", stopkeybuf);
|
||||
res = 0;
|
||||
} else {
|
||||
if (res < 0) {
|
||||
res = 0;
|
||||
pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "ERROR");
|
||||
} else
|
||||
pbx_builtin_setvar_helper(chan, "CPLAYBACKSTATUS", "SUCCESS");
|
||||
}
|
||||
|
||||
snprintf(offsetbuf, sizeof(offsetbuf), "%ld", offsetms);
|
||||
pbx_builtin_setvar_helper(chan, "CPLAYBACKOFFSET", offsetbuf);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
res = ast_unregister_application(app);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, controlplayback_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Control Playback Application");
|
||||
139
trunk/apps/app_db.c
Normal file
139
trunk/apps/app_db.c
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
* Copyright (C) 2003, Jefferson Noxon
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
* Jefferson Noxon <jeff@debian.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 Database access functions
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
* \author Jefferson Noxon <jeff@debian.org>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/astdb.h"
|
||||
#include "asterisk/lock.h"
|
||||
|
||||
/*! \todo XXX Remove this application after 1.4 is relased */
|
||||
static char *d_descrip =
|
||||
" DBdel(family/key): This application will delete a key from the Asterisk\n"
|
||||
"database.\n"
|
||||
" This application has been DEPRECATED in favor of the DB_DELETE function.\n";
|
||||
|
||||
static char *dt_descrip =
|
||||
" DBdeltree(family[/keytree]): This application will delete a family or keytree\n"
|
||||
"from the Asterisk database\n";
|
||||
|
||||
static char *d_app = "DBdel";
|
||||
static char *dt_app = "DBdeltree";
|
||||
|
||||
static char *d_synopsis = "Delete a key from the database";
|
||||
static char *dt_synopsis = "Delete a family or keytree from the database";
|
||||
|
||||
|
||||
static int deltree_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *argv, *family, *keytree;
|
||||
|
||||
argv = ast_strdupa(data);
|
||||
|
||||
if (strchr(argv, '/')) {
|
||||
family = strsep(&argv, "/");
|
||||
keytree = strsep(&argv, "\0");
|
||||
if (!family || !keytree) {
|
||||
ast_debug(1, "Ignoring; Syntax error in argument\n");
|
||||
return 0;
|
||||
}
|
||||
if (ast_strlen_zero(keytree))
|
||||
keytree = 0;
|
||||
} else {
|
||||
family = argv;
|
||||
keytree = 0;
|
||||
}
|
||||
|
||||
if (keytree)
|
||||
ast_verb(3, "DBdeltree: family=%s, keytree=%s\n", family, keytree);
|
||||
else
|
||||
ast_verb(3, "DBdeltree: family=%s\n", family);
|
||||
|
||||
if (ast_db_deltree(family, keytree))
|
||||
ast_verb(3, "DBdeltree: Error deleting key from database.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int del_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *argv, *family, *key;
|
||||
static int deprecation_warning = 0;
|
||||
|
||||
if (!deprecation_warning) {
|
||||
deprecation_warning = 1;
|
||||
ast_log(LOG_WARNING, "The DBdel application has been deprecated in favor of the DB_DELETE dialplan function!\n");
|
||||
}
|
||||
|
||||
argv = ast_strdupa(data);
|
||||
|
||||
if (strchr(argv, '/')) {
|
||||
family = strsep(&argv, "/");
|
||||
key = strsep(&argv, "\0");
|
||||
if (!family || !key) {
|
||||
ast_debug(1, "Ignoring; Syntax error in argument\n");
|
||||
return 0;
|
||||
}
|
||||
ast_verb(3, "DBdel: family=%s, key=%s\n", family, key);
|
||||
if (ast_db_del(family, key))
|
||||
ast_verb(3, "DBdel: Error deleting key from database.\n");
|
||||
} else {
|
||||
ast_debug(1, "Ignoring, no parameters\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = ast_unregister_application(dt_app);
|
||||
retval |= ast_unregister_application(d_app);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = ast_register_application(d_app, del_exec, d_synopsis, d_descrip);
|
||||
retval |= ast_register_application(dt_app, deltree_exec, dt_synopsis, dt_descrip);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Database Access Functions");
|
||||
2047
trunk/apps/app_dial.c
Normal file
2047
trunk/apps/app_dial.c
Normal file
File diff suppressed because it is too large
Load Diff
338
trunk/apps/app_dictate.c
Normal file
338
trunk/apps/app_dictate.c
Normal file
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2005, Anthony Minessale II
|
||||
*
|
||||
* Anthony Minessale II <anthmct@yahoo.com>
|
||||
*
|
||||
* Donated by Sangoma Technologies <http://www.samgoma.com>
|
||||
*
|
||||
* 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 Virtual Dictation Machine Application For Asterisk
|
||||
*
|
||||
* \author Anthony Minessale II <anthmct@yahoo.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/say.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *app = "Dictate";
|
||||
static char *synopsis = "Virtual Dictation Machine";
|
||||
static char *desc = " Dictate([<base_dir>[,<filename>]])\n"
|
||||
"Start dictation machine using optional base dir for files.\n";
|
||||
|
||||
|
||||
typedef enum {
|
||||
DFLAG_RECORD = (1 << 0),
|
||||
DFLAG_PLAY = (1 << 1),
|
||||
DFLAG_TRUNC = (1 << 2),
|
||||
DFLAG_PAUSE = (1 << 3),
|
||||
} dflags;
|
||||
|
||||
typedef enum {
|
||||
DMODE_INIT,
|
||||
DMODE_RECORD,
|
||||
DMODE_PLAY
|
||||
} dmodes;
|
||||
|
||||
#define ast_toggle_flag(it,flag) if(ast_test_flag(it, flag)) ast_clear_flag(it, flag); else ast_set_flag(it, flag)
|
||||
|
||||
static int play_and_wait(struct ast_channel *chan, char *file, char *digits)
|
||||
{
|
||||
int res = -1;
|
||||
if (!ast_streamfile(chan, file, chan->language)) {
|
||||
res = ast_waitstream(chan, digits);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int dictate_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *path = NULL, filein[256], *filename = "";
|
||||
char *parse;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(base);
|
||||
AST_APP_ARG(filename);
|
||||
);
|
||||
char dftbase[256];
|
||||
char *base;
|
||||
struct ast_flags flags = {0};
|
||||
struct ast_filestream *fs;
|
||||
struct ast_frame *f = NULL;
|
||||
int ffactor = 320 * 80,
|
||||
res = 0,
|
||||
done = 0,
|
||||
oldr = 0,
|
||||
lastop = 0,
|
||||
samples = 0,
|
||||
speed = 1,
|
||||
digit = 0,
|
||||
len = 0,
|
||||
maxlen = 0,
|
||||
mode = 0;
|
||||
|
||||
snprintf(dftbase, sizeof(dftbase), "%s/dictate", ast_config_AST_SPOOL_DIR);
|
||||
if (!ast_strlen_zero(data)) {
|
||||
parse = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
} else
|
||||
args.argc = 0;
|
||||
|
||||
if (args.argc && !ast_strlen_zero(args.base)) {
|
||||
base = args.base;
|
||||
} else {
|
||||
base = dftbase;
|
||||
}
|
||||
if (args.argc > 1 && args.filename) {
|
||||
filename = args.filename;
|
||||
}
|
||||
oldr = chan->readformat;
|
||||
if ((res = ast_set_read_format(chan, AST_FORMAT_SLINEAR)) < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set to linear mode.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_answer(chan);
|
||||
ast_safe_sleep(chan, 200);
|
||||
for (res = 0; !res;) {
|
||||
if (ast_strlen_zero(filename)) {
|
||||
if (ast_app_getdata(chan, "dictate/enter_filename", filein, sizeof(filein), 0) ||
|
||||
ast_strlen_zero(filein)) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ast_copy_string(filein, filename, sizeof(filein));
|
||||
filename = "";
|
||||
}
|
||||
ast_mkdir(base, 0755);
|
||||
len = strlen(base) + strlen(filein) + 2;
|
||||
if (!path || len > maxlen) {
|
||||
path = alloca(len);
|
||||
memset(path, 0, len);
|
||||
maxlen = len;
|
||||
} else {
|
||||
memset(path, 0, maxlen);
|
||||
}
|
||||
|
||||
snprintf(path, len, "%s/%s", base, filein);
|
||||
fs = ast_writefile(path, "raw", NULL, O_CREAT|O_APPEND, 0, AST_FILE_MODE);
|
||||
mode = DMODE_PLAY;
|
||||
memset(&flags, 0, sizeof(flags));
|
||||
ast_set_flag(&flags, DFLAG_PAUSE);
|
||||
digit = play_and_wait(chan, "dictate/forhelp", AST_DIGIT_ANY);
|
||||
done = 0;
|
||||
speed = 1;
|
||||
res = 0;
|
||||
lastop = 0;
|
||||
samples = 0;
|
||||
while (!done && ((res = ast_waitfor(chan, -1)) > -1) && fs && (f = ast_read(chan))) {
|
||||
if (digit) {
|
||||
struct ast_frame fr = {AST_FRAME_DTMF, digit};
|
||||
ast_queue_frame(chan, &fr);
|
||||
digit = 0;
|
||||
}
|
||||
if ((f->frametype == AST_FRAME_DTMF)) {
|
||||
int got = 1;
|
||||
switch(mode) {
|
||||
case DMODE_PLAY:
|
||||
switch(f->subclass) {
|
||||
case '1':
|
||||
ast_set_flag(&flags, DFLAG_PAUSE);
|
||||
mode = DMODE_RECORD;
|
||||
break;
|
||||
case '2':
|
||||
speed++;
|
||||
if (speed > 4) {
|
||||
speed = 1;
|
||||
}
|
||||
res = ast_say_number(chan, speed, AST_DIGIT_ANY, chan->language, NULL);
|
||||
break;
|
||||
case '7':
|
||||
samples -= ffactor;
|
||||
if(samples < 0) {
|
||||
samples = 0;
|
||||
}
|
||||
ast_seekstream(fs, samples, SEEK_SET);
|
||||
break;
|
||||
case '8':
|
||||
samples += ffactor;
|
||||
ast_seekstream(fs, samples, SEEK_SET);
|
||||
break;
|
||||
|
||||
default:
|
||||
got = 0;
|
||||
}
|
||||
break;
|
||||
case DMODE_RECORD:
|
||||
switch(f->subclass) {
|
||||
case '1':
|
||||
ast_set_flag(&flags, DFLAG_PAUSE);
|
||||
mode = DMODE_PLAY;
|
||||
break;
|
||||
case '8':
|
||||
ast_toggle_flag(&flags, DFLAG_TRUNC);
|
||||
lastop = 0;
|
||||
break;
|
||||
default:
|
||||
got = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
got = 0;
|
||||
}
|
||||
if (!got) {
|
||||
switch(f->subclass) {
|
||||
case '#':
|
||||
done = 1;
|
||||
continue;
|
||||
break;
|
||||
case '*':
|
||||
ast_toggle_flag(&flags, DFLAG_PAUSE);
|
||||
if (ast_test_flag(&flags, DFLAG_PAUSE)) {
|
||||
digit = play_and_wait(chan, "dictate/pause", AST_DIGIT_ANY);
|
||||
} else {
|
||||
digit = play_and_wait(chan, mode == DMODE_PLAY ? "dictate/playback" : "dictate/record", AST_DIGIT_ANY);
|
||||
}
|
||||
break;
|
||||
case '0':
|
||||
ast_set_flag(&flags, DFLAG_PAUSE);
|
||||
digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY);
|
||||
switch(mode) {
|
||||
case DMODE_PLAY:
|
||||
digit = play_and_wait(chan, "dictate/play_help", AST_DIGIT_ANY);
|
||||
break;
|
||||
case DMODE_RECORD:
|
||||
digit = play_and_wait(chan, "dictate/record_help", AST_DIGIT_ANY);
|
||||
break;
|
||||
}
|
||||
if (digit == 0) {
|
||||
digit = play_and_wait(chan, "dictate/both_help", AST_DIGIT_ANY);
|
||||
} else if (digit < 0) {
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (f->frametype == AST_FRAME_VOICE) {
|
||||
switch(mode) {
|
||||
struct ast_frame *fr;
|
||||
int x;
|
||||
case DMODE_PLAY:
|
||||
if (lastop != DMODE_PLAY) {
|
||||
if (ast_test_flag(&flags, DFLAG_PAUSE)) {
|
||||
digit = play_and_wait(chan, "dictate/playback_mode", AST_DIGIT_ANY);
|
||||
if (digit == 0) {
|
||||
digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY);
|
||||
} else if (digit < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastop != DFLAG_PLAY) {
|
||||
lastop = DFLAG_PLAY;
|
||||
ast_closestream(fs);
|
||||
if (!(fs = ast_openstream(chan, path, chan->language)))
|
||||
break;
|
||||
ast_seekstream(fs, samples, SEEK_SET);
|
||||
chan->stream = NULL;
|
||||
}
|
||||
lastop = DMODE_PLAY;
|
||||
}
|
||||
|
||||
if (!ast_test_flag(&flags, DFLAG_PAUSE)) {
|
||||
for (x = 0; x < speed; x++) {
|
||||
if ((fr = ast_readframe(fs))) {
|
||||
ast_write(chan, fr);
|
||||
samples += fr->samples;
|
||||
ast_frfree(fr);
|
||||
fr = NULL;
|
||||
} else {
|
||||
samples = 0;
|
||||
ast_seekstream(fs, 0, SEEK_SET);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DMODE_RECORD:
|
||||
if (lastop != DMODE_RECORD) {
|
||||
int oflags = O_CREAT | O_WRONLY;
|
||||
if (ast_test_flag(&flags, DFLAG_PAUSE)) {
|
||||
digit = play_and_wait(chan, "dictate/record_mode", AST_DIGIT_ANY);
|
||||
if (digit == 0) {
|
||||
digit = play_and_wait(chan, "dictate/paused", AST_DIGIT_ANY);
|
||||
} else if (digit < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lastop = DMODE_RECORD;
|
||||
ast_closestream(fs);
|
||||
if ( ast_test_flag(&flags, DFLAG_TRUNC)) {
|
||||
oflags |= O_TRUNC;
|
||||
digit = play_and_wait(chan, "dictate/truncating_audio", AST_DIGIT_ANY);
|
||||
} else {
|
||||
oflags |= O_APPEND;
|
||||
}
|
||||
fs = ast_writefile(path, "raw", NULL, oflags, 0, AST_FILE_MODE);
|
||||
if (ast_test_flag(&flags, DFLAG_TRUNC)) {
|
||||
ast_seekstream(fs, 0, SEEK_SET);
|
||||
ast_clear_flag(&flags, DFLAG_TRUNC);
|
||||
} else {
|
||||
ast_seekstream(fs, 0, SEEK_END);
|
||||
}
|
||||
}
|
||||
if (!ast_test_flag(&flags, DFLAG_PAUSE)) {
|
||||
res = ast_writestream(fs, f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
if (oldr) {
|
||||
ast_set_read_format(chan, oldr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
res = ast_unregister_application(app);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, dictate_exec, synopsis, desc);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Virtual Dictation Machine");
|
||||
172
trunk/apps/app_directed_pickup.c
Normal file
172
trunk/apps/app_directed_pickup.c
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2005, Joshua Colp
|
||||
*
|
||||
* Joshua Colp <jcolp@digium.com>
|
||||
*
|
||||
* 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 Directed Call Pickup Support
|
||||
*
|
||||
* \author Joshua Colp <jcolp@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/features.h"
|
||||
|
||||
#define PICKUPMARK "PICKUPMARK"
|
||||
|
||||
static const char *app = "Pickup";
|
||||
static const char *synopsis = "Directed Call Pickup";
|
||||
static const char *descrip =
|
||||
" Pickup([extension[@context][&extension2@[context]...]]): This application can\n"
|
||||
"pickup any ringing channel that is calling the specified extension. If no\n"
|
||||
"context is specified, the current context will be used. If you use the special\n"
|
||||
"string \"PICKUPMARK\" for the context parameter, for example 10@PICKUPMARK,\n"
|
||||
"this application tries to find a channel which has defined a ${PICKUPMARK}\n"
|
||||
"channel variable with the same value as \"extension\" (in this example, \"10\").\n"
|
||||
"When no parameter is specified, the application will pickup a channel matching\n"
|
||||
"the pickup group of the active channel.";
|
||||
|
||||
/* Perform actual pickup between two channels */
|
||||
static int pickup_do(struct ast_channel *chan, struct ast_channel *target)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
ast_debug(1, "Call pickup on '%s' by '%s'\n", target->name, chan->name);
|
||||
|
||||
if ((res = ast_answer(chan))) {
|
||||
ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((res = ast_queue_control(chan, AST_CONTROL_ANSWER))) {
|
||||
ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((res = ast_channel_masquerade(target, chan))) {
|
||||
ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, target->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Helper function that determines whether a channel is capable of being picked up */
|
||||
static int can_pickup(struct ast_channel *chan)
|
||||
{
|
||||
if (!chan->pbx && (chan->_state == AST_STATE_RINGING || chan->_state == AST_STATE_RING))
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Attempt to pick up specified extension with context */
|
||||
static int pickup_by_exten(struct ast_channel *chan, const char *exten, const char *context)
|
||||
{
|
||||
int res = -1;
|
||||
struct ast_channel *target = NULL;
|
||||
|
||||
while ((target = ast_channel_walk_locked(target))) {
|
||||
if ((!strcasecmp(target->macroexten, exten) || !strcasecmp(target->exten, exten)) &&
|
||||
!strcasecmp(target->dialcontext, context) &&
|
||||
can_pickup(target)) {
|
||||
res = pickup_do(chan, target);
|
||||
ast_channel_unlock(target);
|
||||
break;
|
||||
}
|
||||
ast_channel_unlock(target);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Attempt to pick up specified mark */
|
||||
static int pickup_by_mark(struct ast_channel *chan, const char *mark)
|
||||
{
|
||||
int res = -1;
|
||||
const char *tmp = NULL;
|
||||
struct ast_channel *target = NULL;
|
||||
|
||||
while ((target = ast_channel_walk_locked(target))) {
|
||||
if ((tmp = pbx_builtin_getvar_helper(target, PICKUPMARK)) &&
|
||||
!strcasecmp(tmp, mark) &&
|
||||
can_pickup(target)) {
|
||||
res = pickup_do(chan, target);
|
||||
ast_channel_unlock(target);
|
||||
break;
|
||||
}
|
||||
ast_channel_unlock(target);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Main application entry point */
|
||||
static int pickup_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char *tmp = ast_strdupa(data);
|
||||
char *exten = NULL, *context = NULL;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
res = ast_pickup_call(chan);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Parse extension (and context if there) */
|
||||
while (!ast_strlen_zero(tmp) && (exten = strsep(&tmp, "&"))) {
|
||||
if ((context = strchr(exten, '@')))
|
||||
*context++ = '\0';
|
||||
if (!ast_strlen_zero(context) && !strcasecmp(context, PICKUPMARK)) {
|
||||
if (!pickup_by_mark(chan, exten))
|
||||
break;
|
||||
} else {
|
||||
if (!pickup_by_exten(chan, exten, !ast_strlen_zero(context) ? context : chan->context))
|
||||
break;
|
||||
}
|
||||
ast_log(LOG_NOTICE, "No target channel found for %s.\n", exten);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(app);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, pickup_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Directed Call Pickup Application");
|
||||
842
trunk/apps/app_directory.c
Normal file
842
trunk/apps/app_directory.c
Normal file
@@ -0,0 +1,842 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Provide a directory of extensions
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/say.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
#ifdef ODBC_STORAGE
|
||||
#include <sys/mman.h>
|
||||
#include "asterisk/res_odbc.h"
|
||||
|
||||
static char odbc_database[80] = "asterisk";
|
||||
static char odbc_table[80] = "voicemessages";
|
||||
static char vmfmts[80] = "wav";
|
||||
#endif
|
||||
|
||||
static char *app = "Directory";
|
||||
|
||||
static char *synopsis = "Provide directory of voicemail extensions";
|
||||
static char *descrip =
|
||||
" Directory(vm-context[,dial-context[,options]]): This application will present\n"
|
||||
"the calling channel with a directory of extensions from which they can search\n"
|
||||
"by name. The list of names and corresponding extensions is retrieved from the\n"
|
||||
"voicemail configuration file, voicemail.conf.\n"
|
||||
" This application will immediately exit if one of the following DTMF digits are\n"
|
||||
"received and the extension to jump to exists:\n"
|
||||
" 0 - Jump to the 'o' extension, if it exists.\n"
|
||||
" * - Jump to the 'a' extension, if it exists.\n\n"
|
||||
" Parameters:\n"
|
||||
" vm-context - This is the context within voicemail.conf to use for the\n"
|
||||
" Directory.\n"
|
||||
" dial-context - This is the dialplan context to use when looking for an\n"
|
||||
" extension that the user has selected, or when jumping to the\n"
|
||||
" 'o' or 'a' extension.\n\n"
|
||||
" Options:\n"
|
||||
" e - In addition to the name, also read the extension number to the\n"
|
||||
" caller before presenting dialing options.\n"
|
||||
" f - Allow the caller to enter the first name of a user in the directory\n"
|
||||
" instead of using the last name.\n"
|
||||
" m - Instead of reading each name sequentially and asking for confirmation,\n"
|
||||
" create a menu of up to 8 names.\n";
|
||||
|
||||
/* For simplicity, I'm keeping the format compatible with the voicemail config,
|
||||
but i'm open to suggestions for isolating it */
|
||||
|
||||
#define VOICEMAIL_CONFIG "voicemail.conf"
|
||||
|
||||
/* How many digits to read in */
|
||||
#define NUMDIGITS 3
|
||||
|
||||
enum {
|
||||
OPT_LISTBYFIRSTNAME = (1 << 0),
|
||||
OPT_SAYEXTENSION = (1 << 1),
|
||||
OPT_FROMVOICEMAIL = (1 << 2),
|
||||
OPT_SELECTFROMMENU = (1 << 3),
|
||||
} directory_option_flags;
|
||||
|
||||
struct directory_item {
|
||||
char exten[AST_MAX_EXTENSION + 1];
|
||||
char name[AST_MAX_EXTENSION + 1];
|
||||
char key[50]; /* Text to order items. Either lastname+firstname or firstname+lastname */
|
||||
|
||||
AST_LIST_ENTRY(directory_item) entry;
|
||||
};
|
||||
|
||||
AST_APP_OPTIONS(directory_app_options, {
|
||||
AST_APP_OPTION('f', OPT_LISTBYFIRSTNAME),
|
||||
AST_APP_OPTION('e', OPT_SAYEXTENSION),
|
||||
AST_APP_OPTION('v', OPT_FROMVOICEMAIL),
|
||||
AST_APP_OPTION('m', OPT_SELECTFROMMENU),
|
||||
});
|
||||
|
||||
#ifdef ODBC_STORAGE
|
||||
struct generic_prepare_struct {
|
||||
const char *sql;
|
||||
const char *param;
|
||||
};
|
||||
|
||||
static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
|
||||
{
|
||||
struct generic_prepare_struct *gps = data;
|
||||
SQLHSTMT stmt;
|
||||
int res;
|
||||
|
||||
res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
|
||||
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
||||
ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
|
||||
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
||||
ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", (char *)gps->sql);
|
||||
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(gps->param))
|
||||
SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->param), 0, (void *)gps->param, 0, NULL);
|
||||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
static void retrieve_file(char *dir)
|
||||
{
|
||||
int x = 0;
|
||||
int res;
|
||||
int fd=-1;
|
||||
size_t fdlen = 0;
|
||||
void *fdm = MAP_FAILED;
|
||||
SQLHSTMT stmt;
|
||||
char sql[256];
|
||||
char fmt[80]="", empty[10] = "";
|
||||
char *c;
|
||||
SQLLEN colsize;
|
||||
char full_fn[256];
|
||||
struct odbc_obj *obj;
|
||||
struct generic_prepare_struct gps = { .sql = sql, .param = dir };
|
||||
|
||||
obj = ast_odbc_request_obj(odbc_database, 1);
|
||||
if (obj) {
|
||||
do {
|
||||
ast_copy_string(fmt, vmfmts, sizeof(fmt));
|
||||
c = strchr(fmt, '|');
|
||||
if (c)
|
||||
*c = '\0';
|
||||
if (!strcasecmp(fmt, "wav49"))
|
||||
strcpy(fmt, "WAV");
|
||||
snprintf(full_fn, sizeof(full_fn), "%s.%s", dir, fmt);
|
||||
snprintf(sql, sizeof(sql), "SELECT recording FROM %s WHERE dir=? AND msgnum=-1", odbc_table);
|
||||
stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
|
||||
|
||||
if (!stmt) {
|
||||
ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
|
||||
break;
|
||||
}
|
||||
res = SQLFetch(stmt);
|
||||
if (res == SQL_NO_DATA) {
|
||||
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
||||
break;
|
||||
} else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
||||
ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
|
||||
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
||||
break;
|
||||
}
|
||||
fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, AST_FILE_MODE);
|
||||
if (fd < 0) {
|
||||
ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
|
||||
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
||||
break;
|
||||
}
|
||||
|
||||
res = SQLGetData(stmt, 1, SQL_BINARY, empty, 0, &colsize);
|
||||
fdlen = colsize;
|
||||
if (fd > -1) {
|
||||
char tmp[1]="";
|
||||
lseek(fd, fdlen - 1, SEEK_SET);
|
||||
if (write(fd, tmp, 1) != 1) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
break;
|
||||
}
|
||||
if (fd > -1)
|
||||
fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
}
|
||||
if (fdm != MAP_FAILED) {
|
||||
memset(fdm, 0, fdlen);
|
||||
res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
|
||||
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
|
||||
ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
|
||||
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
|
||||
} while (0);
|
||||
ast_odbc_release_obj(obj);
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
|
||||
if (fdm != MAP_FAILED)
|
||||
munmap(fdm, fdlen);
|
||||
if (fd > -1)
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int compare(const char *text, const char *template)
|
||||
{
|
||||
char digit;
|
||||
|
||||
while (*template) {
|
||||
digit = toupper(*text++);
|
||||
switch (digit) {
|
||||
case 0:
|
||||
return -1;
|
||||
case '1':
|
||||
digit = '1';
|
||||
break;
|
||||
case '2':
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
digit = '2';
|
||||
break;
|
||||
case '3':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
digit = '3';
|
||||
break;
|
||||
case '4':
|
||||
case 'G':
|
||||
case 'H':
|
||||
case 'I':
|
||||
digit = '4';
|
||||
break;
|
||||
case '5':
|
||||
case 'J':
|
||||
case 'K':
|
||||
case 'L':
|
||||
digit = '5';
|
||||
break;
|
||||
case '6':
|
||||
case 'M':
|
||||
case 'N':
|
||||
case 'O':
|
||||
digit = '6';
|
||||
break;
|
||||
case '7':
|
||||
case 'P':
|
||||
case 'Q':
|
||||
case 'R':
|
||||
case 'S':
|
||||
digit = '7';
|
||||
break;
|
||||
case '8':
|
||||
case 'T':
|
||||
case 'U':
|
||||
case 'V':
|
||||
digit = '8';
|
||||
break;
|
||||
case '9':
|
||||
case 'W':
|
||||
case 'X':
|
||||
case 'Y':
|
||||
case 'Z':
|
||||
digit = '9';
|
||||
break;
|
||||
|
||||
default:
|
||||
if (digit > ' ')
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*template++ != digit)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* play name of mailbox owner.
|
||||
* returns: -1 for bad or missing extension
|
||||
* '1' for selected entry from directory
|
||||
* '*' for skipped entry from directory
|
||||
*/
|
||||
static int play_mailbox_owner(struct ast_channel *chan, const char *context,
|
||||
const char *ext, const char *name, struct ast_flags *flags)
|
||||
{
|
||||
int res = 0;
|
||||
char fn[256];
|
||||
|
||||
/* Check for the VoiceMail2 greeting first */
|
||||
snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
|
||||
ast_config_AST_SPOOL_DIR, context, ext);
|
||||
#ifdef ODBC_STORAGE
|
||||
retrieve_file(fn);
|
||||
#endif
|
||||
|
||||
if (ast_fileexists(fn, NULL, chan->language) <= 0) {
|
||||
/* no file, check for an old-style Voicemail greeting */
|
||||
snprintf(fn, sizeof(fn), "%s/vm/%s/greet",
|
||||
ast_config_AST_SPOOL_DIR, ext);
|
||||
}
|
||||
#ifdef ODBC_STORAGE
|
||||
retrieve_file(fn);
|
||||
#endif
|
||||
|
||||
if (ast_fileexists(fn, NULL, chan->language) > 0) {
|
||||
res = ast_stream_and_wait(chan, fn, AST_DIGIT_ANY);
|
||||
ast_stopstream(chan);
|
||||
/* If Option 'e' was specified, also read the extension number with the name */
|
||||
if (ast_test_flag(flags, OPT_SAYEXTENSION)) {
|
||||
ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
|
||||
res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
|
||||
}
|
||||
} else {
|
||||
res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
|
||||
if (!ast_strlen_zero(name) && ast_test_flag(flags, OPT_SAYEXTENSION)) {
|
||||
ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
|
||||
res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
|
||||
}
|
||||
}
|
||||
#ifdef ODBC_STORAGE
|
||||
ast_filedelete(fn, NULL);
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int select_entry(struct ast_channel *chan, const char *context, const char *dialcontext, const struct directory_item *item, struct ast_flags *flags)
|
||||
{
|
||||
ast_debug(1, "Selecting '%s' - %s@%s\n", item->name, item->exten, dialcontext);
|
||||
|
||||
if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
|
||||
/* We still want to set the exten though */
|
||||
ast_copy_string(chan->exten, item->exten, sizeof(chan->exten));
|
||||
} else if (ast_goto_if_exists(chan, dialcontext, item->exten, 1)) {
|
||||
ast_log(LOG_WARNING,
|
||||
"Can't find extension '%s' in context '%s'. "
|
||||
"Did you pass the wrong context to Directory?\n",
|
||||
item->exten, dialcontext);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int select_item_seq(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags)
|
||||
{
|
||||
struct directory_item *item, **ptr;
|
||||
int i, res, loop;
|
||||
|
||||
for (ptr = items, i = 0; i < count; i++, ptr++) {
|
||||
item = *ptr;
|
||||
|
||||
for (loop = 3 ; loop > 0; loop--) {
|
||||
res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
|
||||
|
||||
if (!res)
|
||||
res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
|
||||
if (!res)
|
||||
res = ast_waitfordigit(chan, 3000);
|
||||
ast_stopstream(chan);
|
||||
|
||||
if (res == '1') { /* Name selected */
|
||||
return select_entry(chan, context, dialcontext, item, flags) ? -1 : 1;
|
||||
} else if (res == '*') {
|
||||
/* Skip to next match in list */
|
||||
break;
|
||||
}
|
||||
|
||||
if (res < 0)
|
||||
return -1;
|
||||
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing was selected */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int select_item_menu(struct ast_channel *chan, struct directory_item **items, int count, const char *context, const char *dialcontext, struct ast_flags *flags)
|
||||
{
|
||||
struct directory_item **block, *item;
|
||||
int i, limit, res = 0;
|
||||
char buf[9];
|
||||
|
||||
for (block = items; count; block += limit, count -= limit) {
|
||||
limit = count;
|
||||
if (limit > 8)
|
||||
limit = 8;
|
||||
|
||||
for (i = 0; i < limit && !res; i++) {
|
||||
item = block[i];
|
||||
|
||||
snprintf(buf, sizeof(buf), "digits/%d", i + 1);
|
||||
/* Press <num> for <name>, [ extension <ext> ] */
|
||||
res = ast_streamfile(chan, "dir-multi1", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, AST_DIGIT_ANY);
|
||||
if (!res)
|
||||
res = ast_streamfile(chan, buf, chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, AST_DIGIT_ANY);
|
||||
if (!res)
|
||||
res = ast_streamfile(chan, "dir-multi2", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, AST_DIGIT_ANY);
|
||||
if (!res)
|
||||
res = play_mailbox_owner(chan, context, item->exten, item->name, flags);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, AST_DIGIT_ANY);
|
||||
if (!res)
|
||||
res = ast_waitfordigit(chan, 800);
|
||||
}
|
||||
|
||||
/* Press "9" for more names. */
|
||||
if (!res && count > limit) {
|
||||
res = ast_streamfile(chan, "dir-multi9", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, AST_DIGIT_ANY);
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
res = ast_waitfordigit(chan, 3000);
|
||||
}
|
||||
|
||||
if (res && res > '0' && res < '1' + limit) {
|
||||
return select_entry(chan, context, dialcontext, block[res - '1'], flags) ? -1 : 1;
|
||||
}
|
||||
|
||||
if (res < 0)
|
||||
return -1;
|
||||
|
||||
res = 0;
|
||||
}
|
||||
|
||||
/* Nothing was selected */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_config *realtime_directory(char *context)
|
||||
{
|
||||
struct ast_config *cfg;
|
||||
struct ast_config *rtdata;
|
||||
struct ast_category *cat;
|
||||
struct ast_variable *var;
|
||||
char *mailbox;
|
||||
const char *fullname;
|
||||
const char *hidefromdir;
|
||||
char tmp[100];
|
||||
struct ast_flags config_flags = { 0 };
|
||||
|
||||
/* Load flat file config. */
|
||||
cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
|
||||
|
||||
if (!cfg) {
|
||||
/* Loading config failed. */
|
||||
ast_log(LOG_WARNING, "Loading config failed.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get realtime entries, categorized by their mailbox number
|
||||
and present in the requested context */
|
||||
rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL);
|
||||
|
||||
/* if there are no results, just return the entries from the config file */
|
||||
if (!rtdata)
|
||||
return cfg;
|
||||
|
||||
/* Does the context exist within the config file? If not, make one */
|
||||
cat = ast_category_get(cfg, context);
|
||||
if (!cat) {
|
||||
cat = ast_category_new(context, "", 99999);
|
||||
if (!cat) {
|
||||
ast_log(LOG_WARNING, "Out of memory\n");
|
||||
ast_config_destroy(cfg);
|
||||
return NULL;
|
||||
}
|
||||
ast_category_append(cfg, cat);
|
||||
}
|
||||
|
||||
mailbox = NULL;
|
||||
while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
|
||||
fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
|
||||
hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
|
||||
snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
|
||||
fullname ? fullname : "",
|
||||
hidefromdir ? hidefromdir : "no");
|
||||
var = ast_variable_new(mailbox, tmp, "");
|
||||
if (var)
|
||||
ast_variable_append(cat, var);
|
||||
else
|
||||
ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
|
||||
}
|
||||
ast_config_destroy(rtdata);
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static int check_match(struct directory_item **result, const char *item_fullname, const char *item_ext, const char *pattern_ext, int use_first_name)
|
||||
{
|
||||
struct directory_item *item;
|
||||
const char *key = NULL;
|
||||
int namelen;
|
||||
|
||||
|
||||
/* Set key to last name or first name depending on search mode */
|
||||
if (!use_first_name)
|
||||
key = strchr(item_fullname, ' ');
|
||||
|
||||
if (key)
|
||||
key++;
|
||||
else
|
||||
key = item_fullname;
|
||||
|
||||
if (compare(key, pattern_ext))
|
||||
return 0;
|
||||
|
||||
/* Match */
|
||||
item = ast_calloc(1, sizeof(*item));
|
||||
if (!item)
|
||||
return -1;
|
||||
ast_copy_string(item->name, item_fullname, sizeof(item->name));
|
||||
ast_copy_string(item->exten, item_ext, sizeof(item->exten));
|
||||
|
||||
ast_copy_string(item->key, key, sizeof(item->key));
|
||||
if (key != item_fullname) {
|
||||
/* Key is the last name. Append first name to key in order to sort Last,First */
|
||||
namelen = key - item_fullname - 1;
|
||||
if (namelen > sizeof(item->key) - strlen(item->key) - 1)
|
||||
namelen = sizeof(item->key) - strlen(item->key) - 1;
|
||||
strncat(item->key, item_fullname, namelen);
|
||||
}
|
||||
|
||||
*result = item;
|
||||
return 1;
|
||||
}
|
||||
|
||||
typedef AST_LIST_HEAD_NOLOCK(, directory_item) itemlist;
|
||||
|
||||
static int search_directory(const char *context, struct ast_config *vmcfg, struct ast_config *ucfg, const char *ext, int use_first_name, itemlist *alist)
|
||||
{
|
||||
struct ast_variable *v;
|
||||
char buf[AST_MAX_EXTENSION + 1], *pos, *bufptr, *cat;
|
||||
struct directory_item *item;
|
||||
int res;
|
||||
|
||||
ast_debug(2, "Pattern: %s\n", ext);
|
||||
|
||||
for (v = ast_variable_browse(vmcfg, context); v; v = v->next) {
|
||||
|
||||
/* Ignore hidden */
|
||||
if (strcasestr(v->value, "hidefromdir=yes"))
|
||||
continue;
|
||||
|
||||
ast_copy_string(buf, v->value, sizeof(buf));
|
||||
bufptr = buf;
|
||||
|
||||
/* password,Full Name,email,pager,options */
|
||||
strsep(&bufptr, ",");
|
||||
pos = strsep(&bufptr, ",");
|
||||
|
||||
res = check_match(&item, pos, v->name, ext, use_first_name);
|
||||
if (!res)
|
||||
continue;
|
||||
else if (res < 0)
|
||||
return -1;
|
||||
|
||||
AST_LIST_INSERT_TAIL(alist, item, entry);
|
||||
}
|
||||
|
||||
|
||||
if (ucfg) {
|
||||
for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) {
|
||||
const char *pos;
|
||||
if (!strcasecmp(cat, "general"))
|
||||
continue;
|
||||
if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
|
||||
continue;
|
||||
|
||||
/* Find all candidate extensions */
|
||||
pos = ast_variable_retrieve(ucfg, cat, "fullname");
|
||||
if (!pos)
|
||||
continue;
|
||||
|
||||
res = check_match(&item, pos, cat, ext, use_first_name);
|
||||
if (!res)
|
||||
continue;
|
||||
else if (res < 0)
|
||||
return -1;
|
||||
|
||||
AST_LIST_INSERT_TAIL(alist, item, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sort_items(struct directory_item **sorted, int count)
|
||||
{
|
||||
int reordered, i;
|
||||
struct directory_item **ptr, *tmp;
|
||||
|
||||
if (count < 2)
|
||||
return;
|
||||
|
||||
/* Bubble-sort items by the key */
|
||||
do {
|
||||
reordered = 0;
|
||||
for (ptr = sorted, i = 0; i < count - 1; i++, ptr++) {
|
||||
if (strcasecmp(ptr[0]->key, ptr[1]->key) > 0) {
|
||||
tmp = ptr[0];
|
||||
ptr[0] = ptr[1];
|
||||
ptr[1] = tmp;
|
||||
reordered++;
|
||||
}
|
||||
}
|
||||
} while (reordered);
|
||||
}
|
||||
|
||||
static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
|
||||
{
|
||||
if (!ast_goto_if_exists(chan, dialcontext, ext, 1) ||
|
||||
(!ast_strlen_zero(chan->macrocontext) &&
|
||||
!ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
|
||||
return 0;
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Can't find extension '%s' in current context. "
|
||||
"Not Exiting the Directory!\n", ext);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int do_directory(struct ast_channel *chan, struct ast_config *vmcfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, struct ast_flags *flags)
|
||||
{
|
||||
/* Read in the first three digits.. "digit" is the first digit, already read */
|
||||
int res = 0;
|
||||
itemlist alist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
|
||||
struct directory_item *item, **ptr, **sorted = NULL;
|
||||
int count, i;
|
||||
char ext[NUMDIGITS + 1] = "";
|
||||
|
||||
if (ast_strlen_zero(context)) {
|
||||
ast_log(LOG_WARNING,
|
||||
"Directory must be called with an argument "
|
||||
"(context in which to interpret extensions)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (digit == '0' && !goto_exten(chan, dialcontext, "o")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (digit == '*' && !goto_exten(chan, dialcontext, "a")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ext[0] = digit;
|
||||
if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0)
|
||||
return -1;
|
||||
|
||||
res = search_directory(context, vmcfg, ucfg, ext, ast_test_flag(flags, OPT_LISTBYFIRSTNAME), &alist);
|
||||
if (res)
|
||||
goto exit;
|
||||
|
||||
/* Count items in the list */
|
||||
count = 0;
|
||||
AST_LIST_TRAVERSE(&alist, item, entry) {
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
res = ast_streamfile(chan, "dir-nomatch", chan->language);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
||||
/* Create plain array of pointers to items (for sorting) */
|
||||
sorted = ast_calloc(count, sizeof(*sorted));
|
||||
|
||||
ptr = sorted;
|
||||
AST_LIST_TRAVERSE(&alist, item, entry) {
|
||||
*ptr++ = item;
|
||||
}
|
||||
|
||||
/* Sort items */
|
||||
sort_items(sorted, count);
|
||||
|
||||
if (option_debug) {
|
||||
ast_debug(2, "Listing matching entries:\n");
|
||||
for (ptr = sorted, i = 0; i < count; i++, ptr++) {
|
||||
ast_log(LOG_DEBUG, "%s: %s\n", ptr[0]->exten, ptr[0]->name);
|
||||
}
|
||||
}
|
||||
|
||||
if (ast_test_flag(flags, OPT_SELECTFROMMENU)) {
|
||||
/* Offer multiple entries at the same time */
|
||||
res = select_item_menu(chan, sorted, count, context, dialcontext, flags);
|
||||
} else {
|
||||
/* Offer entries one by one */
|
||||
res = select_item_seq(chan, sorted, count, context, dialcontext, flags);
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
res = ast_streamfile(chan, "dir-nomore", chan->language);
|
||||
}
|
||||
|
||||
exit:
|
||||
if (sorted)
|
||||
ast_free(sorted);
|
||||
|
||||
while ((item = AST_LIST_REMOVE_HEAD(&alist, entry)))
|
||||
ast_free(item);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int directory_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_config *cfg, *ucfg;
|
||||
const char *dirintro;
|
||||
char *parse, *opts[0];
|
||||
struct ast_flags flags = { 0 };
|
||||
struct ast_flags config_flags = { 0 };
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(vmcontext);
|
||||
AST_APP_ARG(dialcontext);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
|
||||
return -1;
|
||||
|
||||
if (ast_strlen_zero(args.dialcontext))
|
||||
args.dialcontext = args.vmcontext;
|
||||
|
||||
cfg = realtime_directory(args.vmcontext);
|
||||
if (!cfg) {
|
||||
ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ucfg = ast_config_load("users.conf", config_flags);
|
||||
|
||||
dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
|
||||
if (ast_strlen_zero(dirintro))
|
||||
dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
|
||||
if (ast_strlen_zero(dirintro))
|
||||
dirintro = ast_test_flag(&flags, OPT_LISTBYFIRSTNAME) ? "dir-intro-fn" : "dir-intro";
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
res = ast_answer(chan);
|
||||
|
||||
for (;;) {
|
||||
if (!res)
|
||||
res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
|
||||
ast_stopstream(chan);
|
||||
if (!res)
|
||||
res = ast_waitfordigit(chan, 5000);
|
||||
|
||||
if (res <= 0)
|
||||
break;
|
||||
|
||||
res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, &flags);
|
||||
if (res)
|
||||
break;
|
||||
|
||||
res = ast_waitstream(chan, AST_DIGIT_ANY);
|
||||
ast_stopstream(chan);
|
||||
|
||||
if (res)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ucfg)
|
||||
ast_config_destroy(ucfg);
|
||||
ast_config_destroy(cfg);
|
||||
|
||||
return res < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
res = ast_unregister_application(app);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
#ifdef ODBC_STORAGE
|
||||
struct ast_flags config_flags = { 0 };
|
||||
struct ast_config *cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
|
||||
const char *tmp;
|
||||
|
||||
if (cfg) {
|
||||
if ((tmp = ast_variable_retrieve(cfg, "general", "odbcstorage"))) {
|
||||
ast_copy_string(odbc_database, tmp, sizeof(odbc_database));
|
||||
}
|
||||
if ((tmp = ast_variable_retrieve(cfg, "general", "odbctable"))) {
|
||||
ast_copy_string(odbc_table, tmp, sizeof(odbc_table));
|
||||
}
|
||||
if ((tmp = ast_variable_retrieve(cfg, "general", "format"))) {
|
||||
ast_copy_string(vmfmts, tmp, sizeof(vmfmts));
|
||||
}
|
||||
ast_config_destroy(cfg);
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Unable to load " VOICEMAIL_CONFIG " - ODBC defaults will be used\n");
|
||||
#endif
|
||||
|
||||
return ast_register_application(app, directory_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");
|
||||
367
trunk/apps/app_disa.c
Normal file
367
trunk/apps/app_disa.c
Normal file
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
*
|
||||
* Made only slightly more sane by Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 DISA -- Direct Inward System Access Application
|
||||
*
|
||||
* \author Jim Dixon <jim@lambdatel.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <math.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/indications.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
#include "asterisk/ulaw.h"
|
||||
#include "asterisk/callerid.h"
|
||||
#include "asterisk/stringfields.h"
|
||||
|
||||
static char *app = "DISA";
|
||||
|
||||
static char *synopsis = "DISA (Direct Inward System Access)";
|
||||
|
||||
static char *descrip =
|
||||
"DISA(<numeric passcode>[,<context>[,<cid>[,mailbox[,options]]]]) or\n"
|
||||
"DISA(<filename>[,,,,options])\n"
|
||||
"The DISA, Direct Inward System Access, application allows someone from \n"
|
||||
"outside the telephone switch (PBX) to obtain an \"internal\" system \n"
|
||||
"dialtone and to place calls from it as if they were placing a call from \n"
|
||||
"within the switch.\n"
|
||||
"DISA plays a dialtone. The user enters their numeric passcode, followed by\n"
|
||||
"the pound sign (#). If the passcode is correct, the user is then given\n"
|
||||
"system dialtone within <context> on which a call may be placed. If the user\n"
|
||||
"enters an invalid extension and extension \"i\" exists in the specified\n"
|
||||
"context, it will be used.\n"
|
||||
"\n"
|
||||
"If you need to present a DISA dialtone without entering a password, simply\n"
|
||||
"set <passcode> to \"no-password\".\n"
|
||||
"\n"
|
||||
"Be aware that using this may compromise the security of your PBX.\n"
|
||||
"\n"
|
||||
"The arguments to this application (in extensions.conf) allow either\n"
|
||||
"specification of a single global passcode (that everyone uses), or\n"
|
||||
"individual passcodes contained in a file.\n"
|
||||
"\n"
|
||||
"The file that contains the passcodes (if used) allows a complete\n"
|
||||
"specification of all of the same arguments available on the command\n"
|
||||
"line, with the sole exception of the options. The file may contain blank\n"
|
||||
"lines, or comments starting with \"#\" or \";\".\n"
|
||||
"\n"
|
||||
"<context> specifies the dialplan context in which the user-entered extension\n"
|
||||
"will be matched. If no context is specified, the DISA application defaults\n"
|
||||
"the context to \"disa\". Presumably a normal system will have a special\n"
|
||||
"context set up for DISA use with some or a lot of restrictions.\n"
|
||||
"\n"
|
||||
"<cid> specifies a new (different) callerid to be used for this call.\n"
|
||||
"\n"
|
||||
"<mailbox[@context]> will cause a stutter-dialtone (indication \"dialrecall\")\n"
|
||||
"to be used, if the specified mailbox contains any new messages.\n"
|
||||
"\n"
|
||||
"The following options are available:\n"
|
||||
" n - the DISA application will not answer initially.\n"
|
||||
" p - the extension entered will be considered complete when a '#' is entered.\n";
|
||||
|
||||
enum {
|
||||
NOANSWER_FLAG = (1 << 0),
|
||||
POUND_TO_END_FLAG = (1 << 1),
|
||||
} option_flags;
|
||||
|
||||
AST_APP_OPTIONS(app_opts, {
|
||||
AST_APP_OPTION('n', NOANSWER_FLAG),
|
||||
AST_APP_OPTION('p', POUND_TO_END_FLAG),
|
||||
});
|
||||
|
||||
static void play_dialtone(struct ast_channel *chan, char *mailbox)
|
||||
{
|
||||
const struct ind_tone_zone_sound *ts = NULL;
|
||||
if(ast_app_has_voicemail(mailbox, NULL))
|
||||
ts = ast_get_indication_tone(chan->zone, "dialrecall");
|
||||
else
|
||||
ts = ast_get_indication_tone(chan->zone, "dial");
|
||||
if (ts)
|
||||
ast_playtones_start(chan, 0, ts->data, 0);
|
||||
else
|
||||
ast_tonepair_start(chan, 350, 440, 0, 0);
|
||||
}
|
||||
|
||||
static int disa_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int i = 0, j, k = 0, did_ignore = 0, special_noanswer = 0;
|
||||
int firstdigittimeout = (chan->pbx ? chan->pbx->rtimeout * 1000 : 20000);
|
||||
int digittimeout = (chan->pbx ? chan->pbx->dtimeout * 1000 : 10000);
|
||||
struct ast_flags flags;
|
||||
char *tmp, exten[AST_MAX_EXTENSION] = "", acctcode[20]="";
|
||||
char pwline[256];
|
||||
char ourcidname[256],ourcidnum[256];
|
||||
struct ast_frame *f;
|
||||
struct timeval lastdigittime;
|
||||
int res;
|
||||
FILE *fp;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(passcode);
|
||||
AST_APP_ARG(context);
|
||||
AST_APP_ARG(cid);
|
||||
AST_APP_ARG(mailbox);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_debug(1, "Digittimeout: %d\n", digittimeout);
|
||||
ast_debug(1, "Responsetimeout: %d\n", firstdigittimeout);
|
||||
|
||||
tmp = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, tmp);
|
||||
|
||||
if (ast_strlen_zero(args.context))
|
||||
args.context = "disa";
|
||||
if (ast_strlen_zero(args.mailbox))
|
||||
args.mailbox = "";
|
||||
if (!ast_strlen_zero(args.options))
|
||||
ast_app_parse_options(app_opts, &flags, NULL, args.options);
|
||||
|
||||
ast_debug(1, "Mailbox: %s\n",args.mailbox);
|
||||
|
||||
if (!ast_test_flag(&flags, NOANSWER_FLAG)) {
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
/* answer */
|
||||
ast_answer(chan);
|
||||
}
|
||||
} else
|
||||
special_noanswer = 1;
|
||||
|
||||
ast_debug(1, "Context: %s\n",args.context);
|
||||
|
||||
if (!strcasecmp(args.passcode, "no-password")) {
|
||||
k |= 1; /* We have the password */
|
||||
ast_debug(1, "DISA no-password login success\n");
|
||||
}
|
||||
|
||||
lastdigittime = ast_tvnow();
|
||||
|
||||
play_dialtone(chan, args.mailbox);
|
||||
|
||||
ast_set_flag(chan, AST_FLAG_END_DTMF_ONLY);
|
||||
|
||||
for (;;) {
|
||||
/* if outa time, give em reorder */
|
||||
if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > ((k&2) ? digittimeout : firstdigittimeout)) {
|
||||
ast_debug(1,"DISA %s entry timeout on chan %s\n",
|
||||
((k&1) ? "extension" : "password"),chan->name);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((res = ast_waitfor(chan, -1) < 0)) {
|
||||
ast_debug(1, "Waitfor returned %d\n", res);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(f = ast_read(chan))) {
|
||||
ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
|
||||
ast_frfree(f);
|
||||
ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If the frame coming in is not DTMF, just drop it and continue */
|
||||
if (f->frametype != AST_FRAME_DTMF) {
|
||||
ast_frfree(f);
|
||||
continue;
|
||||
}
|
||||
|
||||
j = f->subclass; /* save digit */
|
||||
ast_frfree(f);
|
||||
|
||||
if (!i) {
|
||||
k |= 2; /* We have the first digit */
|
||||
ast_playtones_stop(chan);
|
||||
}
|
||||
|
||||
lastdigittime = ast_tvnow();
|
||||
|
||||
/* got a DTMF tone */
|
||||
if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
|
||||
if (!(k&1)) { /* if in password state */
|
||||
if (j == '#') { /* end of password */
|
||||
/* see if this is an integer */
|
||||
if (sscanf(args.passcode,"%d",&j) < 1) { /* nope, it must be a filename */
|
||||
fp = fopen(args.passcode,"r");
|
||||
if (!fp) {
|
||||
ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,chan->name);
|
||||
ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
|
||||
return -1;
|
||||
}
|
||||
pwline[0] = 0;
|
||||
while(fgets(pwline,sizeof(pwline) - 1,fp)) {
|
||||
if (!pwline[0])
|
||||
continue;
|
||||
if (pwline[strlen(pwline) - 1] == '\n')
|
||||
pwline[strlen(pwline) - 1] = 0;
|
||||
if (!pwline[0])
|
||||
continue;
|
||||
/* skip comments */
|
||||
if (pwline[0] == '#')
|
||||
continue;
|
||||
if (pwline[0] == ';')
|
||||
continue;
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, pwline);
|
||||
|
||||
ast_debug(1, "Mailbox: %s\n",args.mailbox);
|
||||
|
||||
/* password must be in valid format (numeric) */
|
||||
if (sscanf(args.passcode,"%d", &j) < 1)
|
||||
continue;
|
||||
/* if we got it */
|
||||
if (!strcmp(exten,args.passcode)) {
|
||||
if (ast_strlen_zero(args.context))
|
||||
args.context = "disa";
|
||||
if (ast_strlen_zero(args.mailbox))
|
||||
args.mailbox = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
/* compare the two */
|
||||
if (strcmp(exten,args.passcode)) {
|
||||
ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
|
||||
goto reorder;
|
||||
|
||||
}
|
||||
/* password good, set to dial state */
|
||||
ast_debug(1,"DISA on chan %s password is good\n",chan->name);
|
||||
play_dialtone(chan, args.mailbox);
|
||||
|
||||
k|=1; /* In number mode */
|
||||
i = 0; /* re-set buffer pointer */
|
||||
exten[sizeof(acctcode)] = 0;
|
||||
ast_copy_string(acctcode, exten, sizeof(acctcode));
|
||||
exten[0] = 0;
|
||||
ast_debug(1,"Successful DISA log-in on chan %s\n", chan->name);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (j == '#') { /* end of extension */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
exten[i++] = j; /* save digit */
|
||||
exten[i] = 0;
|
||||
if (!(k&1))
|
||||
continue; /* if getting password, continue doing it */
|
||||
/* if this exists */
|
||||
|
||||
/* user wants end of number, remove # */
|
||||
if (ast_test_flag(&flags, POUND_TO_END_FLAG) && j == '#') {
|
||||
exten[--i] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ast_ignore_pattern(args.context, exten)) {
|
||||
play_dialtone(chan, "");
|
||||
did_ignore = 1;
|
||||
} else
|
||||
if (did_ignore) {
|
||||
ast_playtones_stop(chan);
|
||||
did_ignore = 0;
|
||||
}
|
||||
|
||||
/* if can do some more, do it */
|
||||
if (!ast_matchmore_extension(chan,args.context,exten,1, chan->cid.cid_num)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
|
||||
|
||||
if (k == 3) {
|
||||
int recheck = 0;
|
||||
struct ast_flags flags = { AST_CDR_FLAG_POSTED };
|
||||
|
||||
if (!ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
|
||||
pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
|
||||
exten[0] = 'i';
|
||||
exten[1] = '\0';
|
||||
recheck = 1;
|
||||
}
|
||||
if (!recheck || ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
|
||||
ast_playtones_stop(chan);
|
||||
/* We're authenticated and have a target extension */
|
||||
if (!ast_strlen_zero(args.cid)) {
|
||||
ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
|
||||
ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(acctcode))
|
||||
ast_string_field_set(chan, accountcode, acctcode);
|
||||
|
||||
if (special_noanswer) flags.flags = 0;
|
||||
ast_cdr_reset(chan->cdr, &flags);
|
||||
ast_explicit_goto(chan, args.context, exten, 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Received invalid, but no "i" extension exists in the given context */
|
||||
|
||||
reorder:
|
||||
/* Play congestion for a bit */
|
||||
ast_indicate(chan, AST_CONTROL_CONGESTION);
|
||||
ast_safe_sleep(chan, 10*1000);
|
||||
|
||||
ast_playtones_stop(chan);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, disa_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");
|
||||
160
trunk/apps/app_dumpchan.c
Normal file
160
trunk/apps/app_dumpchan.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2004 - 2005, Anthony Minessale II.
|
||||
*
|
||||
* Anthony Minessale <anthmct@yahoo.com>
|
||||
*
|
||||
* A license has been granted to Digium (via disclaimer) for the use of
|
||||
* this code.
|
||||
*
|
||||
* 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 Application to dump channel variables
|
||||
*
|
||||
* \author Anthony Minessale <anthmct@yahoo.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
static char *app = "DumpChan";
|
||||
static char *synopsis = "Dump Info About The Calling Channel";
|
||||
static char *desc =
|
||||
" DumpChan([<min_verbose_level>])\n"
|
||||
"Displays information on channel and listing of all channel\n"
|
||||
"variables. If min_verbose_level is specified, output is only\n"
|
||||
"displayed when the verbose level is currently set to that number\n"
|
||||
"or greater. \n";
|
||||
|
||||
|
||||
static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
|
||||
{
|
||||
struct timeval now;
|
||||
long elapsed_seconds = 0;
|
||||
int hour = 0, min = 0, sec = 0;
|
||||
char cgrp[BUFSIZ/2];
|
||||
char pgrp[BUFSIZ/2];
|
||||
char formatbuf[BUFSIZ/2];
|
||||
|
||||
now = ast_tvnow();
|
||||
memset(buf, 0, size);
|
||||
if (!c)
|
||||
return 0;
|
||||
|
||||
if (c->cdr) {
|
||||
elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
|
||||
hour = elapsed_seconds / 3600;
|
||||
min = (elapsed_seconds % 3600) / 60;
|
||||
sec = elapsed_seconds % 60;
|
||||
}
|
||||
|
||||
snprintf(buf,size,
|
||||
"Name= %s\n"
|
||||
"Type= %s\n"
|
||||
"UniqueID= %s\n"
|
||||
"CallerIDNum= %s\n"
|
||||
"CallerIDName= %s\n"
|
||||
"DNIDDigits= %s\n"
|
||||
"RDNIS= %s\n"
|
||||
"Language= %s\n"
|
||||
"State= %s (%d)\n"
|
||||
"Rings= %d\n"
|
||||
"NativeFormat= %s\n"
|
||||
"WriteFormat= %s\n"
|
||||
"ReadFormat= %s\n"
|
||||
"RawWriteFormat= %s\n"
|
||||
"RawReadFormat= %s\n"
|
||||
"1stFileDescriptor= %d\n"
|
||||
"Framesin= %d %s\n"
|
||||
"Framesout= %d %s\n"
|
||||
"TimetoHangup= %ld\n"
|
||||
"ElapsedTime= %dh%dm%ds\n"
|
||||
"Context= %s\n"
|
||||
"Extension= %s\n"
|
||||
"Priority= %d\n"
|
||||
"CallGroup= %s\n"
|
||||
"PickupGroup= %s\n"
|
||||
"Application= %s\n"
|
||||
"Data= %s\n"
|
||||
"Blocking_in= %s\n",
|
||||
c->name,
|
||||
c->tech->type,
|
||||
c->uniqueid,
|
||||
S_OR(c->cid.cid_num, "(N/A)"),
|
||||
S_OR(c->cid.cid_name, "(N/A)"),
|
||||
S_OR(c->cid.cid_dnid, "(N/A)"),
|
||||
S_OR(c->cid.cid_rdnis, "(N/A)"),
|
||||
c->language,
|
||||
ast_state2str(c->_state),
|
||||
c->_state,
|
||||
c->rings,
|
||||
ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->nativeformats),
|
||||
ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->writeformat),
|
||||
ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->readformat),
|
||||
ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->rawwriteformat),
|
||||
ast_getformatname_multiple(formatbuf, sizeof(formatbuf), c->rawreadformat),
|
||||
c->fds[0], c->fin & ~DEBUGCHAN_FLAG, (c->fin & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
|
||||
c->fout & ~DEBUGCHAN_FLAG, (c->fout & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "", (long)c->whentohangup,
|
||||
hour,
|
||||
min,
|
||||
sec,
|
||||
c->context,
|
||||
c->exten,
|
||||
c->priority,
|
||||
ast_print_group(cgrp, sizeof(cgrp), c->callgroup),
|
||||
ast_print_group(pgrp, sizeof(pgrp), c->pickupgroup),
|
||||
( c->appl ? c->appl : "(N/A)" ),
|
||||
( c-> data ? S_OR(c->data, "(Empty)") : "(None)"),
|
||||
(ast_test_flag(c, AST_FLAG_BLOCKING) ? c->blockproc : "(Not Blocking)"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dumpchan_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct ast_str *vars = ast_str_alloca(BUFSIZ * 4); /* XXX very large! */
|
||||
char info[1024];
|
||||
int level = 0;
|
||||
static char *line = "================================================================================";
|
||||
|
||||
if (!ast_strlen_zero(data))
|
||||
level = atoi(data);
|
||||
|
||||
pbx_builtin_serialize_variables(chan, &vars);
|
||||
serialize_showchan(chan, info, sizeof(info));
|
||||
if (option_verbose >= level)
|
||||
ast_verbose("\nDumping Info For Channel: %s:\n%s\nInfo:\n%s\nVariables:\n%s%s\n", chan->name, line, info, vars->str, line);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, dumpchan_exec, synopsis, desc);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dump Info About The Calling Channel");
|
||||
86
trunk/apps/app_echo.c
Normal file
86
trunk/apps/app_echo.c
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Echo application -- play back what you hear to evaluate latency
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
static char *app = "Echo";
|
||||
|
||||
static char *synopsis = "Echo audio, video, or DTMF back to the calling party";
|
||||
|
||||
static char *descrip =
|
||||
" Echo(): This application will echo any audio, video, or DTMF frames read from\n"
|
||||
"the calling channel back to itself. If the DTMF digit '#' is received, the\n"
|
||||
"application will exit.\n";
|
||||
|
||||
|
||||
static int echo_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = -1;
|
||||
int format;
|
||||
|
||||
format = ast_best_codec(chan->nativeformats);
|
||||
ast_set_write_format(chan, format);
|
||||
ast_set_read_format(chan, format);
|
||||
|
||||
while (ast_waitfor(chan, -1) > -1) {
|
||||
struct ast_frame *f = ast_read(chan);
|
||||
if (!f)
|
||||
break;
|
||||
f->delivery.tv_sec = 0;
|
||||
f->delivery.tv_usec = 0;
|
||||
if (ast_write(chan, f)) {
|
||||
ast_frfree(f);
|
||||
goto end;
|
||||
}
|
||||
if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) {
|
||||
res = 0;
|
||||
ast_frfree(f);
|
||||
goto end;
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
end:
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, echo_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Echo Application");
|
||||
221
trunk/apps/app_exec.c
Normal file
221
trunk/apps/app_exec.c
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (c) 2004 - 2005, Tilghman Lesher. All rights reserved.
|
||||
* Portions copyright (c) 2006, Philipp Dunkel.
|
||||
*
|
||||
* Tilghman Lesher <app_exec__v002@the-tilghman.com>
|
||||
*
|
||||
* This code is released by the author with no restrictions on usage.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Exec application
|
||||
*
|
||||
* \author Tilghman Lesher <app_exec__v002@the-tilghman.com>
|
||||
* \author Philipp Dunkel <philipp.dunkel@ebox.at>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
/* Maximum length of any variable */
|
||||
#define MAXRESULT 1024
|
||||
|
||||
/*! Note
|
||||
*
|
||||
* The key difference between these two apps is exit status. In a
|
||||
* nutshell, Exec tries to be transparent as possible, behaving
|
||||
* in exactly the same way as if the application it calls was
|
||||
* directly invoked from the dialplan.
|
||||
*
|
||||
* TryExec, on the other hand, provides a way to execute applications
|
||||
* and catch any possible fatal error without actually fatally
|
||||
* affecting the dialplan.
|
||||
*/
|
||||
|
||||
static char *app_exec = "Exec";
|
||||
static char *exec_synopsis = "Executes dialplan application";
|
||||
static char *exec_descrip =
|
||||
" Exec(appname(arguments)):\n"
|
||||
"Allows an arbitrary application to be invoked even when not\n"
|
||||
"hardcoded into the dialplan. If the underlying application\n"
|
||||
"terminates the dialplan, or if the application cannot be found,\n"
|
||||
"Exec will terminate the dialplan.\n"
|
||||
" To invoke external applications, see the application System.\n"
|
||||
" If you would like to catch any error instead, see TryExec.\n";
|
||||
|
||||
static char *app_tryexec = "TryExec";
|
||||
static char *tryexec_synopsis = "Executes dialplan application, always returning";
|
||||
static char *tryexec_descrip =
|
||||
" TryExec(appname(arguments)):\n"
|
||||
"Allows an arbitrary application to be invoked even when not\n"
|
||||
"hardcoded into the dialplan. To invoke external applications\n"
|
||||
"see the application System. Always returns to the dialplan.\n"
|
||||
"The channel variable TRYSTATUS will be set to one of:\n"
|
||||
" SUCCESS if the application returned zero\n"
|
||||
" FAILED if the application returned non-zero\n"
|
||||
" NOAPP if the application was not found or was not specified\n";
|
||||
|
||||
static char *app_execif = "ExecIf";
|
||||
static char *execif_synopsis = "Executes dialplan application, conditionally";
|
||||
static char *execif_descrip =
|
||||
" ExecIF (<expr>?<appiftrue>(<args>)[:<appiffalse>(<args>)])\n"
|
||||
"If <expr> is true, execute and return the result of <appiftrue>(<args>).\n"
|
||||
"If <expr> is true, but <appiftrue> is not found, then the application\n"
|
||||
"will return a non-zero value.\n";
|
||||
|
||||
static int exec_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char *s, *appname, *endargs, args[MAXRESULT];
|
||||
struct ast_app *app;
|
||||
|
||||
if (ast_strlen_zero(data))
|
||||
return 0;
|
||||
|
||||
s = ast_strdupa(data);
|
||||
args[0] = 0;
|
||||
appname = strsep(&s, "(");
|
||||
if (s) {
|
||||
endargs = strrchr(s, ')');
|
||||
if (endargs)
|
||||
*endargs = '\0';
|
||||
pbx_substitute_variables_helper(chan, s, args, MAXRESULT - 1);
|
||||
}
|
||||
if (appname) {
|
||||
app = pbx_findapp(appname);
|
||||
if (app) {
|
||||
res = pbx_exec(chan, app, args);
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Could not find application (%s)\n", appname);
|
||||
res = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int tryexec_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char *s, *appname, *endargs, args[MAXRESULT];
|
||||
struct ast_app *app;
|
||||
|
||||
if (ast_strlen_zero(data))
|
||||
return 0;
|
||||
|
||||
s = ast_strdupa(data);
|
||||
args[0] = 0;
|
||||
appname = strsep(&s, "(");
|
||||
if (s) {
|
||||
endargs = strrchr(s, ')');
|
||||
if (endargs)
|
||||
*endargs = '\0';
|
||||
pbx_substitute_variables_helper(chan, s, args, MAXRESULT - 1);
|
||||
}
|
||||
if (appname) {
|
||||
app = pbx_findapp(appname);
|
||||
if (app) {
|
||||
res = pbx_exec(chan, app, args);
|
||||
pbx_builtin_setvar_helper(chan, "TRYSTATUS", res ? "FAILED" : "SUCCESS");
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Could not find application (%s)\n", appname);
|
||||
pbx_builtin_setvar_helper(chan, "TRYSTATUS", "NOAPP");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int execif_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char *truedata = NULL, *falsedata = NULL, *end;
|
||||
struct ast_app *app = NULL;
|
||||
AST_DECLARE_APP_ARGS(expr,
|
||||
AST_APP_ARG(expr);
|
||||
AST_APP_ARG(remainder);
|
||||
);
|
||||
AST_DECLARE_APP_ARGS(apps,
|
||||
AST_APP_ARG(t);
|
||||
AST_APP_ARG(f);
|
||||
);
|
||||
char *parse = ast_strdupa(data);
|
||||
|
||||
AST_NONSTANDARD_APP_ARGS(expr, parse, '?');
|
||||
if (ast_strlen_zero(expr.remainder)) {
|
||||
ast_log(LOG_ERROR, "Usage: ExecIf(<expr>?<appiftrue>(<args>)[:<appiffalse>(<args)])\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
AST_NONSTANDARD_APP_ARGS(apps, expr.remainder, ':');
|
||||
|
||||
if (apps.t && (truedata = strchr(apps.t, '('))) {
|
||||
*truedata++ = '\0';
|
||||
if ((end = strrchr(truedata, ')')))
|
||||
*end = '\0';
|
||||
}
|
||||
|
||||
if (apps.f && (falsedata = strchr(apps.f, '('))) {
|
||||
*falsedata++ = '\0';
|
||||
if ((end = strrchr(falsedata, ')')))
|
||||
*end = '\0';
|
||||
}
|
||||
|
||||
if (pbx_checkcondition(expr.expr)) {
|
||||
if (!ast_strlen_zero(apps.t) && (app = pbx_findapp(apps.t))) {
|
||||
res = pbx_exec(chan, app, S_OR(truedata, ""));
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Could not find application! (%s)\n", apps.t);
|
||||
res = -1;
|
||||
}
|
||||
} else if (!ast_strlen_zero(apps.f)) {
|
||||
if ((app = pbx_findapp(apps.f))) {
|
||||
res = pbx_exec(chan, app, S_OR(falsedata, ""));
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Could not find application! (%s)\n", apps.f);
|
||||
res = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(app_exec);
|
||||
res |= ast_unregister_application(app_tryexec);
|
||||
res |= ast_unregister_application(app_execif);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = ast_register_application(app_exec, exec_exec, exec_synopsis, exec_descrip);
|
||||
res |= ast_register_application(app_tryexec, tryexec_exec, tryexec_synopsis, tryexec_descrip);
|
||||
res |= ast_register_application(app_execif, execif_exec, execif_synopsis, execif_descrip);
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Executes dialplan applications");
|
||||
575
trunk/apps/app_externalivr.c
Normal file
575
trunk/apps/app_externalivr.c
Normal file
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Kevin P. Fleming <kpfleming@digium.com>
|
||||
*
|
||||
* Portions taken from the file-based music-on-hold work
|
||||
* created by Anthony Minessale II in res_musiconhold.c
|
||||
*
|
||||
* 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 External IVR application interface
|
||||
*
|
||||
* \author Kevin P. Fleming <kpfleming@digium.com>
|
||||
*
|
||||
* \note Portions taken from the file-based music-on-hold work
|
||||
* created by Anthony Minessale II in res_musiconhold.c
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/linkedlists.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/utils.h"
|
||||
|
||||
static const char *app = "ExternalIVR";
|
||||
|
||||
static const char *synopsis = "Interfaces with an external IVR application";
|
||||
|
||||
static const char *descrip =
|
||||
" ExternalIVR(command[,arg[,arg...]]): Forks an process to run the supplied command,\n"
|
||||
"and starts a generator on the channel. The generator's play list is\n"
|
||||
"controlled by the external application, which can add and clear entries\n"
|
||||
"via simple commands issued over its stdout. The external application\n"
|
||||
"will receive all DTMF events received on the channel, and notification\n"
|
||||
"if the channel is hung up. The application will not be forcibly terminated\n"
|
||||
"when the channel is hung up.\n"
|
||||
"See doc/externalivr.txt for a protocol specification.\n";
|
||||
|
||||
/* XXX the parser in gcc 2.95 gets confused if you don't put a space between 'name' and the comma */
|
||||
#define ast_chan_log(level, channel, format, ...) ast_log(level, "%s: " format, channel->name , ## __VA_ARGS__)
|
||||
|
||||
struct playlist_entry {
|
||||
AST_LIST_ENTRY(playlist_entry) list;
|
||||
char filename[1];
|
||||
};
|
||||
|
||||
struct ivr_localuser {
|
||||
struct ast_channel *chan;
|
||||
AST_LIST_HEAD(playlist, playlist_entry) playlist;
|
||||
AST_LIST_HEAD(finishlist, playlist_entry) finishlist;
|
||||
int abort_current_sound;
|
||||
int playing_silence;
|
||||
int option_autoclear;
|
||||
};
|
||||
|
||||
|
||||
struct gen_state {
|
||||
struct ivr_localuser *u;
|
||||
struct ast_filestream *stream;
|
||||
struct playlist_entry *current;
|
||||
int sample_queue;
|
||||
};
|
||||
|
||||
static void send_child_event(FILE *handle, const char event, const char *data,
|
||||
const struct ast_channel *chan)
|
||||
{
|
||||
char tmp[256];
|
||||
|
||||
if (!data) {
|
||||
snprintf(tmp, sizeof(tmp), "%c,%10d", event, (int)time(NULL));
|
||||
} else {
|
||||
snprintf(tmp, sizeof(tmp), "%c,%10d,%s", event, (int)time(NULL), data);
|
||||
}
|
||||
|
||||
fprintf(handle, "%s\n", tmp);
|
||||
if (option_debug)
|
||||
ast_chan_log(LOG_DEBUG, chan, "sent '%s'\n", tmp);
|
||||
}
|
||||
|
||||
static void *gen_alloc(struct ast_channel *chan, void *params)
|
||||
{
|
||||
struct ivr_localuser *u = params;
|
||||
struct gen_state *state;
|
||||
|
||||
if (!(state = ast_calloc(1, sizeof(*state))))
|
||||
return NULL;
|
||||
|
||||
state->u = u;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void gen_closestream(struct gen_state *state)
|
||||
{
|
||||
if (!state->stream)
|
||||
return;
|
||||
|
||||
ast_closestream(state->stream);
|
||||
state->u->chan->stream = NULL;
|
||||
state->stream = NULL;
|
||||
}
|
||||
|
||||
static void gen_release(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct gen_state *state = data;
|
||||
|
||||
gen_closestream(state);
|
||||
ast_free(data);
|
||||
}
|
||||
|
||||
/* caller has the playlist locked */
|
||||
static int gen_nextfile(struct gen_state *state)
|
||||
{
|
||||
struct ivr_localuser *u = state->u;
|
||||
char *file_to_stream;
|
||||
|
||||
u->abort_current_sound = 0;
|
||||
u->playing_silence = 0;
|
||||
gen_closestream(state);
|
||||
|
||||
while (!state->stream) {
|
||||
state->current = AST_LIST_REMOVE_HEAD(&u->playlist, list);
|
||||
if (state->current) {
|
||||
file_to_stream = state->current->filename;
|
||||
} else {
|
||||
file_to_stream = "silence/10";
|
||||
u->playing_silence = 1;
|
||||
}
|
||||
|
||||
if (!(state->stream = ast_openstream_full(u->chan, file_to_stream, u->chan->language, 1))) {
|
||||
ast_chan_log(LOG_WARNING, u->chan, "File '%s' could not be opened: %s\n", file_to_stream, strerror(errno));
|
||||
if (!u->playing_silence) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (!state->stream);
|
||||
}
|
||||
|
||||
static struct ast_frame *gen_readframe(struct gen_state *state)
|
||||
{
|
||||
struct ast_frame *f = NULL;
|
||||
struct ivr_localuser *u = state->u;
|
||||
|
||||
if (u->abort_current_sound ||
|
||||
(u->playing_silence && AST_LIST_FIRST(&u->playlist))) {
|
||||
gen_closestream(state);
|
||||
AST_LIST_LOCK(&u->playlist);
|
||||
gen_nextfile(state);
|
||||
AST_LIST_UNLOCK(&u->playlist);
|
||||
}
|
||||
|
||||
if (!(state->stream && (f = ast_readframe(state->stream)))) {
|
||||
if (state->current) {
|
||||
AST_LIST_LOCK(&u->finishlist);
|
||||
AST_LIST_INSERT_TAIL(&u->finishlist, state->current, list);
|
||||
AST_LIST_UNLOCK(&u->finishlist);
|
||||
state->current = NULL;
|
||||
}
|
||||
if (!gen_nextfile(state))
|
||||
f = ast_readframe(state->stream);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
static int gen_generate(struct ast_channel *chan, void *data, int len, int samples)
|
||||
{
|
||||
struct gen_state *state = data;
|
||||
struct ast_frame *f = NULL;
|
||||
int res = 0;
|
||||
|
||||
state->sample_queue += samples;
|
||||
|
||||
while (state->sample_queue > 0) {
|
||||
if (!(f = gen_readframe(state)))
|
||||
return -1;
|
||||
|
||||
res = ast_write(chan, f);
|
||||
ast_frfree(f);
|
||||
if (res < 0) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Failed to write frame: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
state->sample_queue -= f->samples;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static struct ast_generator gen =
|
||||
{
|
||||
alloc: gen_alloc,
|
||||
release: gen_release,
|
||||
generate: gen_generate,
|
||||
};
|
||||
|
||||
static struct playlist_entry *make_entry(const char *filename)
|
||||
{
|
||||
struct playlist_entry *entry;
|
||||
|
||||
if (!(entry = ast_calloc(1, sizeof(*entry) + strlen(filename) + 10))) /* XXX why 10 ? */
|
||||
return NULL;
|
||||
|
||||
strcpy(entry->filename, filename);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
static int app_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct playlist_entry *entry;
|
||||
int child_stdin[2] = { 0,0 };
|
||||
int child_stdout[2] = { 0,0 };
|
||||
int child_stderr[2] = { 0,0 };
|
||||
int res = -1;
|
||||
int gen_active = 0;
|
||||
int pid;
|
||||
char *buf, *command;
|
||||
FILE *child_commands = NULL;
|
||||
FILE *child_errors = NULL;
|
||||
FILE *child_events = NULL;
|
||||
struct ivr_localuser foo = {
|
||||
.playlist = AST_LIST_HEAD_INIT_VALUE,
|
||||
.finishlist = AST_LIST_HEAD_INIT_VALUE,
|
||||
};
|
||||
struct ivr_localuser *u = &foo;
|
||||
sigset_t fullset, oldset;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(cmd)[32];
|
||||
);
|
||||
|
||||
sigfillset(&fullset);
|
||||
pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
|
||||
|
||||
u->abort_current_sound = 0;
|
||||
u->chan = chan;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "ExternalIVR requires a command to execute\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, buf);
|
||||
|
||||
if (pipe(child_stdin)) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (pipe(child_stdout)) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (pipe(child_stderr)) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
ast_answer(chan);
|
||||
}
|
||||
|
||||
if (ast_activate_generator(chan, &gen, u) < 0) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
|
||||
goto exit;
|
||||
} else
|
||||
gen_active = 1;
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!pid) {
|
||||
/* child process */
|
||||
int i;
|
||||
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
|
||||
|
||||
if (ast_opt_high_priority)
|
||||
ast_set_priority(0);
|
||||
|
||||
dup2(child_stdin[0], STDIN_FILENO);
|
||||
dup2(child_stdout[1], STDOUT_FILENO);
|
||||
dup2(child_stderr[1], STDERR_FILENO);
|
||||
for (i = STDERR_FILENO + 1; i < 1024; i++)
|
||||
close(i);
|
||||
execv(args.cmd[0], args.cmd);
|
||||
fprintf(stderr, "Failed to execute '%s': %s\n", args.cmd[0], strerror(errno));
|
||||
_exit(1);
|
||||
} else {
|
||||
/* parent process */
|
||||
int child_events_fd = child_stdin[1];
|
||||
int child_commands_fd = child_stdout[0];
|
||||
int child_errors_fd = child_stderr[0];
|
||||
struct ast_frame *f;
|
||||
int ms;
|
||||
int exception;
|
||||
int ready_fd;
|
||||
int waitfds[2] = { child_errors_fd, child_commands_fd };
|
||||
struct ast_channel *rchan;
|
||||
|
||||
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
|
||||
|
||||
close(child_stdin[0]);
|
||||
child_stdin[0] = 0;
|
||||
close(child_stdout[1]);
|
||||
child_stdout[1] = 0;
|
||||
close(child_stderr[1]);
|
||||
child_stderr[1] = 0;
|
||||
|
||||
if (!(child_events = fdopen(child_events_fd, "w"))) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(child_commands = fdopen(child_commands_fd, "r"))) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(child_errors = fdopen(child_errors_fd, "r"))) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
setvbuf(child_events, NULL, _IONBF, 0);
|
||||
setvbuf(child_commands, NULL, _IONBF, 0);
|
||||
setvbuf(child_errors, NULL, _IONBF, 0);
|
||||
|
||||
res = 0;
|
||||
|
||||
while (1) {
|
||||
if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
|
||||
ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ast_check_hangup(chan)) {
|
||||
ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
|
||||
send_child_event(child_events, 'H', NULL, chan);
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
ready_fd = 0;
|
||||
ms = 100;
|
||||
errno = 0;
|
||||
exception = 0;
|
||||
|
||||
rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
|
||||
|
||||
if (!AST_LIST_EMPTY(&u->finishlist)) {
|
||||
AST_LIST_LOCK(&u->finishlist);
|
||||
while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
|
||||
send_child_event(child_events, 'F', entry->filename, chan);
|
||||
ast_free(entry);
|
||||
}
|
||||
AST_LIST_UNLOCK(&u->finishlist);
|
||||
}
|
||||
|
||||
if (rchan) {
|
||||
/* the channel has something */
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
|
||||
send_child_event(child_events, 'H', NULL, chan);
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (f->frametype == AST_FRAME_DTMF) {
|
||||
send_child_event(child_events, f->subclass, NULL, chan);
|
||||
if (u->option_autoclear) {
|
||||
if (!u->abort_current_sound && !u->playing_silence)
|
||||
send_child_event(child_events, 'T', NULL, chan);
|
||||
AST_LIST_LOCK(&u->playlist);
|
||||
while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
|
||||
send_child_event(child_events, 'D', entry->filename, chan);
|
||||
ast_free(entry);
|
||||
}
|
||||
if (!u->playing_silence)
|
||||
u->abort_current_sound = 1;
|
||||
AST_LIST_UNLOCK(&u->playlist);
|
||||
}
|
||||
} else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
|
||||
ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
|
||||
send_child_event(child_events, 'H', NULL, chan);
|
||||
ast_frfree(f);
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
ast_frfree(f);
|
||||
} else if (ready_fd == child_commands_fd) {
|
||||
char input[1024];
|
||||
|
||||
if (exception || feof(child_commands)) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!fgets(input, sizeof(input), child_commands))
|
||||
continue;
|
||||
|
||||
command = ast_strip(input);
|
||||
|
||||
if (option_debug)
|
||||
ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
|
||||
|
||||
if (strlen(input) < 4)
|
||||
continue;
|
||||
|
||||
if (input[0] == 'S') {
|
||||
if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
|
||||
send_child_event(child_events, 'Z', NULL, chan);
|
||||
strcpy(&input[2], "exception");
|
||||
}
|
||||
if (!u->abort_current_sound && !u->playing_silence)
|
||||
send_child_event(child_events, 'T', NULL, chan);
|
||||
AST_LIST_LOCK(&u->playlist);
|
||||
while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
|
||||
send_child_event(child_events, 'D', entry->filename, chan);
|
||||
ast_free(entry);
|
||||
}
|
||||
if (!u->playing_silence)
|
||||
u->abort_current_sound = 1;
|
||||
entry = make_entry(&input[2]);
|
||||
if (entry)
|
||||
AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
|
||||
AST_LIST_UNLOCK(&u->playlist);
|
||||
} else if (input[0] == 'A') {
|
||||
if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
|
||||
send_child_event(child_events, 'Z', NULL, chan);
|
||||
strcpy(&input[2], "exception");
|
||||
}
|
||||
entry = make_entry(&input[2]);
|
||||
if (entry) {
|
||||
AST_LIST_LOCK(&u->playlist);
|
||||
AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
|
||||
AST_LIST_UNLOCK(&u->playlist);
|
||||
}
|
||||
} else if (input[0] == 'E') {
|
||||
ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
|
||||
send_child_event(child_events, 'E', NULL, chan);
|
||||
res = 0;
|
||||
break;
|
||||
} else if (input[0] == 'H') {
|
||||
ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
|
||||
send_child_event(child_events, 'H', NULL, chan);
|
||||
res = -1;
|
||||
break;
|
||||
} else if (input[0] == 'O') {
|
||||
if (!strcasecmp(&input[2], "autoclear"))
|
||||
u->option_autoclear = 1;
|
||||
else if (!strcasecmp(&input[2], "noautoclear"))
|
||||
u->option_autoclear = 0;
|
||||
else
|
||||
ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
|
||||
} else if (input[0] == 'V') {
|
||||
char *c;
|
||||
c = strchr(&input[2], '=');
|
||||
if (!c) {
|
||||
send_child_event(child_events, 'Z', NULL, chan);
|
||||
} else {
|
||||
*c++ = '\0';
|
||||
pbx_builtin_setvar_helper(chan, &input[2], c);
|
||||
}
|
||||
}
|
||||
} else if (ready_fd == child_errors_fd) {
|
||||
char input[1024];
|
||||
|
||||
if (exception || feof(child_errors)) {
|
||||
ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fgets(input, sizeof(input), child_errors)) {
|
||||
command = ast_strip(input);
|
||||
ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
|
||||
}
|
||||
} else if ((ready_fd < 0) && ms) {
|
||||
if (errno == 0 || errno == EINTR)
|
||||
continue;
|
||||
|
||||
ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
if (gen_active)
|
||||
ast_deactivate_generator(chan);
|
||||
|
||||
if (child_events)
|
||||
fclose(child_events);
|
||||
|
||||
if (child_commands)
|
||||
fclose(child_commands);
|
||||
|
||||
if (child_errors)
|
||||
fclose(child_errors);
|
||||
|
||||
if (child_stdin[0])
|
||||
close(child_stdin[0]);
|
||||
|
||||
if (child_stdin[1])
|
||||
close(child_stdin[1]);
|
||||
|
||||
if (child_stdout[0])
|
||||
close(child_stdout[0]);
|
||||
|
||||
if (child_stdout[1])
|
||||
close(child_stdout[1]);
|
||||
|
||||
if (child_stderr[0])
|
||||
close(child_stderr[0]);
|
||||
|
||||
if (child_stderr[1])
|
||||
close(child_stderr[1]);
|
||||
|
||||
while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
|
||||
ast_free(entry);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, app_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "External IVR Interface Application");
|
||||
523
trunk/apps/app_festival.c
Normal file
523
trunk/apps/app_festival.c
Normal file
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2002, Christos Ricudis
|
||||
*
|
||||
* Christos Ricudis <ricudis@itc.auth.gr>
|
||||
*
|
||||
* 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 Connect to festival
|
||||
*
|
||||
* \author Christos Ricudis <ricudis@itc.auth.gr>
|
||||
*
|
||||
* \extref The Festival Speech Synthesis System - http://www.cstr.ed.ac.uk/projects/festival/
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/md5.h"
|
||||
#include "asterisk/config.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
#define FESTIVAL_CONFIG "festival.conf"
|
||||
#define MAXLEN 180
|
||||
#define MAXFESTLEN 2048
|
||||
|
||||
static char *app = "Festival";
|
||||
|
||||
static char *synopsis = "Say text to the user";
|
||||
|
||||
static char *descrip =
|
||||
" Festival(text[,intkeys]): Connect to Festival, send the argument, get back the waveform,\n"
|
||||
"play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
|
||||
"the value, or 'any' to allow any number back (useful in dialplan)\n";
|
||||
|
||||
|
||||
static char *socket_receive_file_to_buff(int fd, int *size)
|
||||
{
|
||||
/* Receive file (probably a waveform file) from socket using
|
||||
* Festival key stuff technique, but long winded I know, sorry
|
||||
* but will receive any file without closing the stream or
|
||||
* using OOB data
|
||||
*/
|
||||
static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
|
||||
char *buff, *tmp;
|
||||
int bufflen;
|
||||
int n,k,i;
|
||||
char c;
|
||||
|
||||
bufflen = 1024;
|
||||
if (!(buff = ast_malloc(bufflen)))
|
||||
return NULL;
|
||||
*size = 0;
|
||||
|
||||
for (k = 0; file_stuff_key[k] != '\0';) {
|
||||
n = read(fd, &c, 1);
|
||||
if (n == 0)
|
||||
break; /* hit stream eof before end of file */
|
||||
if ((*size) + k + 1 >= bufflen) {
|
||||
/* +1 so you can add a terminating NULL if you want */
|
||||
bufflen += bufflen / 4;
|
||||
if (!(tmp = ast_realloc(buff, bufflen))) {
|
||||
ast_free(buff);
|
||||
return NULL;
|
||||
}
|
||||
buff = tmp;
|
||||
}
|
||||
if (file_stuff_key[k] == c)
|
||||
k++;
|
||||
else if ((c == 'X') && (file_stuff_key[k+1] == '\0')) {
|
||||
/* It looked like the key but wasn't */
|
||||
for (i = 0; i < k; i++, (*size)++)
|
||||
buff[*size] = file_stuff_key[i];
|
||||
k = 0;
|
||||
/* omit the stuffed 'X' */
|
||||
} else {
|
||||
for (i = 0; i < k; i++, (*size)++)
|
||||
buff[*size] = file_stuff_key[i];
|
||||
k = 0;
|
||||
buff[*size] = c;
|
||||
(*size)++;
|
||||
}
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
static int send_waveform_to_fd(char *waveform, int length, int fd)
|
||||
{
|
||||
int res;
|
||||
int x;
|
||||
#ifdef __PPC__
|
||||
char c;
|
||||
#endif
|
||||
sigset_t fullset, oldset;
|
||||
|
||||
sigfillset(&fullset);
|
||||
pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
|
||||
|
||||
res = fork();
|
||||
if (res < 0)
|
||||
ast_log(LOG_WARNING, "Fork failed\n");
|
||||
if (res) {
|
||||
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
|
||||
return res;
|
||||
}
|
||||
for (x = 0; x < 256; x++) {
|
||||
if (x != fd)
|
||||
close(x);
|
||||
}
|
||||
if (ast_opt_high_priority)
|
||||
ast_set_priority(0);
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
|
||||
#ifdef __PPC__
|
||||
for (x = 0; x < length; x += 2) {
|
||||
c = *(waveform + x + 1);
|
||||
*(waveform + x + 1) = *(waveform + x);
|
||||
*(waveform + x) = c;
|
||||
}
|
||||
#endif
|
||||
write(fd, waveform, length);
|
||||
close(fd);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys)
|
||||
{
|
||||
int res = 0;
|
||||
int fds[2];
|
||||
int pid = -1;
|
||||
int needed = 0;
|
||||
int owriteformat;
|
||||
struct ast_frame *f;
|
||||
struct myframe {
|
||||
struct ast_frame f;
|
||||
char offset[AST_FRIENDLY_OFFSET];
|
||||
char frdata[2048];
|
||||
} myf = {
|
||||
.f = { 0, },
|
||||
};
|
||||
|
||||
if (pipe(fds)) {
|
||||
ast_log(LOG_WARNING, "Unable to create pipe\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Answer if it's not already going */
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
ast_answer(chan);
|
||||
ast_stopstream(chan);
|
||||
ast_indicate(chan, -1);
|
||||
|
||||
owriteformat = chan->writeformat;
|
||||
res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
|
||||
if (res < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = send_waveform_to_fd(waveform, length, fds[1]);
|
||||
if (res >= 0) {
|
||||
pid = res;
|
||||
/* Order is important -- there's almost always going to be mp3... we want to prioritize the
|
||||
user */
|
||||
for (;;) {
|
||||
res = ast_waitfor(chan, 1000);
|
||||
if (res < 1) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if (f->frametype == AST_FRAME_DTMF) {
|
||||
ast_debug(1, "User pressed a key\n");
|
||||
if (intkeys && strchr(intkeys, f->subclass)) {
|
||||
res = f->subclass;
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (f->frametype == AST_FRAME_VOICE) {
|
||||
/* Treat as a generator */
|
||||
needed = f->samples * 2;
|
||||
if (needed > sizeof(myf.frdata)) {
|
||||
ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
|
||||
(int)sizeof(myf.frdata) / 2, needed/2);
|
||||
needed = sizeof(myf.frdata);
|
||||
}
|
||||
res = read(fds[0], myf.frdata, needed);
|
||||
if (res > 0) {
|
||||
myf.f.frametype = AST_FRAME_VOICE;
|
||||
myf.f.subclass = AST_FORMAT_SLINEAR;
|
||||
myf.f.datalen = res;
|
||||
myf.f.samples = res / 2;
|
||||
myf.f.offset = AST_FRIENDLY_OFFSET;
|
||||
myf.f.src = __PRETTY_FUNCTION__;
|
||||
myf.f.data = myf.frdata;
|
||||
if (ast_write(chan, &myf.f) < 0) {
|
||||
res = -1;
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
if (res < needed) { /* last frame */
|
||||
ast_debug(1, "Last frame\n");
|
||||
res = 0;
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ast_debug(1, "No more waveform\n");
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
|
||||
#if 0
|
||||
if (pid > -1)
|
||||
kill(pid, SIGKILL);
|
||||
#endif
|
||||
if (!res && owriteformat)
|
||||
ast_set_write_format(chan, owriteformat);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int festival_exec(struct ast_channel *chan, void *vdata)
|
||||
{
|
||||
int usecache;
|
||||
int res = 0;
|
||||
struct sockaddr_in serv_addr;
|
||||
struct hostent *serverhost;
|
||||
struct ast_hostent ahp;
|
||||
int fd;
|
||||
FILE *fs;
|
||||
const char *host;
|
||||
const char *cachedir;
|
||||
const char *temp;
|
||||
const char *festivalcommand;
|
||||
int port = 1314;
|
||||
int n;
|
||||
char ack[4];
|
||||
char *waveform;
|
||||
int filesize;
|
||||
int wave;
|
||||
char bigstring[MAXFESTLEN];
|
||||
int i;
|
||||
struct MD5Context md5ctx;
|
||||
unsigned char MD5Res[16];
|
||||
char MD5Hex[33] = "";
|
||||
char koko[4] = "";
|
||||
char cachefile[MAXFESTLEN]="";
|
||||
int readcache = 0;
|
||||
int writecache = 0;
|
||||
int strln;
|
||||
int fdesc = -1;
|
||||
char buffer[16384];
|
||||
int seekpos = 0;
|
||||
char *data;
|
||||
struct ast_config *cfg;
|
||||
char *newfestivalcommand;
|
||||
struct ast_flags config_flags = { 0 };
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(text);
|
||||
AST_APP_ARG(interrupt);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(vdata)) {
|
||||
ast_log(LOG_WARNING, "festival requires an argument (text)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cfg = ast_config_load(FESTIVAL_CONFIG, config_flags);
|
||||
if (!cfg) {
|
||||
ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
|
||||
return -1;
|
||||
}
|
||||
if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
|
||||
host = "localhost";
|
||||
}
|
||||
if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
|
||||
port = 1314;
|
||||
} else {
|
||||
port = atoi(temp);
|
||||
}
|
||||
if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
|
||||
usecache = 0;
|
||||
} else {
|
||||
usecache = ast_true(temp);
|
||||
}
|
||||
if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
|
||||
cachedir = "/tmp/";
|
||||
}
|
||||
if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
|
||||
festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
|
||||
} else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
|
||||
int i, j;
|
||||
newfestivalcommand = alloca(strlen(festivalcommand) + 1);
|
||||
|
||||
for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
|
||||
if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
|
||||
newfestivalcommand[j++] = '\n';
|
||||
i++;
|
||||
} else if (festivalcommand[i] == '\\') {
|
||||
newfestivalcommand[j++] = festivalcommand[i + 1];
|
||||
i++;
|
||||
} else
|
||||
newfestivalcommand[j++] = festivalcommand[i];
|
||||
}
|
||||
newfestivalcommand[j] = '\0';
|
||||
festivalcommand = newfestivalcommand;
|
||||
}
|
||||
|
||||
data = ast_strdupa(vdata);
|
||||
AST_STANDARD_APP_ARGS(args, data);
|
||||
|
||||
if (args.interrupt && !strcasecmp(args.interrupt, "any"))
|
||||
args.interrupt = AST_DIGIT_ANY;
|
||||
|
||||
ast_debug(1, "Text passed to festival server : %s\n", args.text);
|
||||
/* Connect to local festival server */
|
||||
|
||||
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
if (fd < 0) {
|
||||
ast_log(LOG_WARNING, "festival_client: can't get socket\n");
|
||||
ast_config_destroy(cfg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||
|
||||
if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
|
||||
/* its a name rather than an ipnum */
|
||||
serverhost = ast_gethostbyname(host, &ahp);
|
||||
|
||||
if (serverhost == NULL) {
|
||||
ast_log(LOG_WARNING, "festival_client: gethostbyname failed\n");
|
||||
ast_config_destroy(cfg);
|
||||
return -1;
|
||||
}
|
||||
memmove(&serv_addr.sin_addr, serverhost->h_addr, serverhost->h_length);
|
||||
}
|
||||
|
||||
serv_addr.sin_family = AF_INET;
|
||||
serv_addr.sin_port = htons(port);
|
||||
|
||||
if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
|
||||
ast_log(LOG_WARNING, "festival_client: connect to server failed\n");
|
||||
ast_config_destroy(cfg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Compute MD5 sum of string */
|
||||
MD5Init(&md5ctx);
|
||||
MD5Update(&md5ctx, (unsigned char *)args.text, strlen(args.text));
|
||||
MD5Final(MD5Res, &md5ctx);
|
||||
MD5Hex[0] = '\0';
|
||||
|
||||
/* Convert to HEX and look if there is any matching file in the cache
|
||||
directory */
|
||||
for (i = 0; i < 16; i++) {
|
||||
snprintf(koko, sizeof(koko), "%X", MD5Res[i]);
|
||||
strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
|
||||
}
|
||||
readcache = 0;
|
||||
writecache = 0;
|
||||
if (strlen(cachedir) + strlen(MD5Hex) + 1 <= MAXFESTLEN && (usecache == -1)) {
|
||||
snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
|
||||
fdesc = open(cachefile, O_RDWR);
|
||||
if (fdesc == -1) {
|
||||
fdesc = open(cachefile, O_CREAT | O_RDWR, AST_FILE_MODE);
|
||||
if (fdesc != -1) {
|
||||
writecache = 1;
|
||||
strln = strlen(args.text);
|
||||
ast_debug(1, "line length : %d\n", strln);
|
||||
write(fdesc, &strln, sizeof(strln));
|
||||
write(fdesc, args.text, strln);
|
||||
seekpos = lseek(fdesc, 0, SEEK_CUR);
|
||||
ast_debug(1, "Seek position : %d\n", seekpos);
|
||||
}
|
||||
} else {
|
||||
read(fdesc, &strln, sizeof(strln));
|
||||
ast_debug(1, "Cache file exists, strln=%d, strlen=%d\n", strln, (int)strlen(args.text));
|
||||
if (strlen(args.text) == strln) {
|
||||
ast_debug(1, "Size OK\n");
|
||||
read(fdesc, &bigstring, strln);
|
||||
bigstring[strln] = 0;
|
||||
if (strcmp(bigstring, args.text) == 0) {
|
||||
readcache = 1;
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Strings do not match\n");
|
||||
}
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Size mismatch\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readcache == 1) {
|
||||
close(fd);
|
||||
fd = fdesc;
|
||||
ast_debug(1, "Reading from cache...\n");
|
||||
} else {
|
||||
ast_debug(1, "Passing text to festival...\n");
|
||||
fs = fdopen(dup(fd), "wb");
|
||||
fprintf(fs, festivalcommand, args.text);
|
||||
fflush(fs);
|
||||
fclose(fs);
|
||||
}
|
||||
|
||||
/* Write to cache and then pass it down */
|
||||
if (writecache == 1) {
|
||||
ast_debug(1, "Writing result to cache...\n");
|
||||
while ((strln = read(fd, buffer, 16384)) != 0) {
|
||||
write(fdesc, buffer, strln);
|
||||
}
|
||||
close(fd);
|
||||
close(fdesc);
|
||||
fd = open(cachefile, O_RDWR);
|
||||
lseek(fd, seekpos, SEEK_SET);
|
||||
}
|
||||
|
||||
ast_debug(1, "Passing data to channel...\n");
|
||||
|
||||
/* Read back info from server */
|
||||
/* This assumes only one waveform will come back, also LP is unlikely */
|
||||
wave = 0;
|
||||
do {
|
||||
int read_data;
|
||||
for (n = 0; n < 3; ) {
|
||||
read_data = read(fd, ack + n, 3 - n);
|
||||
/* this avoids falling in infinite loop
|
||||
* in case that festival server goes down
|
||||
*/
|
||||
if (read_data == -1) {
|
||||
ast_log(LOG_WARNING, "Unable to read from cache/festival fd\n");
|
||||
close(fd);
|
||||
ast_config_destroy(cfg);
|
||||
return -1;
|
||||
}
|
||||
n += read_data;
|
||||
}
|
||||
ack[3] = '\0';
|
||||
if (strcmp(ack, "WV\n") == 0) { /* receive a waveform */
|
||||
ast_debug(1, "Festival WV command\n");
|
||||
if ((waveform = socket_receive_file_to_buff(fd, &filesize))) {
|
||||
res = send_waveform_to_channel(chan, waveform, filesize, args.interrupt);
|
||||
ast_free(waveform);
|
||||
}
|
||||
break;
|
||||
} else if (strcmp(ack, "LP\n") == 0) { /* receive an s-expr */
|
||||
ast_debug(1, "Festival LP command\n");
|
||||
if ((waveform = socket_receive_file_to_buff(fd, &filesize))) {
|
||||
waveform[filesize] = '\0';
|
||||
ast_log(LOG_WARNING, "Festival returned LP : %s\n", waveform);
|
||||
ast_free(waveform);
|
||||
}
|
||||
} else if (strcmp(ack, "ER\n") == 0) { /* server got an error */
|
||||
ast_log(LOG_WARNING, "Festival returned ER\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
} while (strcmp(ack, "OK\n") != 0);
|
||||
close(fd);
|
||||
ast_config_destroy(cfg);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
struct ast_flags config_flags = { 0 };
|
||||
struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG, config_flags);
|
||||
if (!cfg) {
|
||||
ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
ast_config_destroy(cfg);
|
||||
return ast_register_application(app, festival_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");
|
||||
112
trunk/apps/app_flash.c
Normal file
112
trunk/apps/app_flash.c
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 App to flash a zap trunk
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend>zaptel</depend>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/zapata.h"
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
#include "asterisk/image.h"
|
||||
|
||||
static char *app = "Flash";
|
||||
|
||||
static char *synopsis = "Flashes a Zap Trunk";
|
||||
|
||||
static char *descrip =
|
||||
"Performs a flash on a zap trunk. This can be used\n"
|
||||
"to access features provided on an incoming analogue circuit\n"
|
||||
"such as conference and call waiting. Use with SendDTMF() to\n"
|
||||
"perform external transfers\n";
|
||||
|
||||
|
||||
static inline int zt_wait_event(int fd)
|
||||
{
|
||||
/* Avoid the silly zt_waitevent which ignores a bunch of events */
|
||||
int i,j=0;
|
||||
i = ZT_IOMUX_SIGEVENT;
|
||||
if (ioctl(fd, ZT_IOMUX, &i) == -1) return -1;
|
||||
if (ioctl(fd, ZT_GETEVENT, &j) == -1) return -1;
|
||||
return j;
|
||||
}
|
||||
|
||||
static int flash_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = -1;
|
||||
int x;
|
||||
struct zt_params ztp;
|
||||
|
||||
if (strcasecmp(chan->tech->type, "Zap")) {
|
||||
ast_log(LOG_WARNING, "%s is not a Zap channel\n", chan->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&ztp, 0, sizeof(ztp));
|
||||
res = ioctl(chan->fds[0], ZT_GET_PARAMS, &ztp);
|
||||
if (!res) {
|
||||
if (ztp.sigtype & __ZT_SIG_FXS) {
|
||||
x = ZT_FLASH;
|
||||
res = ioctl(chan->fds[0], ZT_HOOK, &x);
|
||||
if (!res || (errno == EINPROGRESS)) {
|
||||
if (res) {
|
||||
/* Wait for the event to finish */
|
||||
zt_wait_event(chan->fds[0]);
|
||||
}
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
ast_verb(3, "Flashed channel %s\n", chan->name);
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Unable to flash channel %s: %s\n", chan->name, strerror(errno));
|
||||
} else
|
||||
ast_log(LOG_WARNING, "%s is not an FXO Channel\n", chan->name);
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Unable to get parameters of %s: %s\n", chan->name, strerror(errno));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, flash_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Flash channel application");
|
||||
|
||||
1059
trunk/apps/app_followme.c
Normal file
1059
trunk/apps/app_followme.c
Normal file
File diff suppressed because it is too large
Load Diff
99
trunk/apps/app_forkcdr.c
Normal file
99
trunk/apps/app_forkcdr.c
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Anthony Minessale anthmct@yahoo.com
|
||||
* Development of this app Sponsered/Funded by TAAN Softworks Corp
|
||||
*
|
||||
* 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 Fork CDR application
|
||||
*
|
||||
* \author Anthony Minessale anthmct@yahoo.com
|
||||
*
|
||||
* \note Development of this app Sponsored/Funded by TAAN Softworks Corp
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/cdr.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
static char *app = "ForkCDR";
|
||||
static char *synopsis =
|
||||
"Forks the Call Data Record";
|
||||
static char *descrip =
|
||||
" ForkCDR([options]): Causes the Call Data Record to fork an additional\n"
|
||||
"cdr record starting from the time of the fork call\n"
|
||||
" Options:\n"
|
||||
" v - If the option is passed all cdr variables will be passed along also.\n";
|
||||
|
||||
|
||||
static void ast_cdr_fork(struct ast_channel *chan)
|
||||
{
|
||||
struct ast_cdr *cdr;
|
||||
struct ast_cdr *newcdr;
|
||||
struct ast_flags flags = { AST_CDR_FLAG_KEEP_VARS };
|
||||
|
||||
cdr = chan->cdr;
|
||||
|
||||
while (cdr->next)
|
||||
cdr = cdr->next;
|
||||
|
||||
if (!(newcdr = ast_cdr_dup(cdr)))
|
||||
return;
|
||||
|
||||
ast_cdr_append(cdr, newcdr);
|
||||
ast_cdr_reset(newcdr, &flags);
|
||||
|
||||
if (!ast_test_flag(cdr, AST_CDR_FLAG_KEEP_VARS))
|
||||
ast_cdr_free_vars(cdr, 0);
|
||||
|
||||
ast_set_flag(cdr, AST_CDR_FLAG_CHILD | AST_CDR_FLAG_LOCKED);
|
||||
}
|
||||
|
||||
static int forkcdr_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if (!chan->cdr) {
|
||||
ast_log(LOG_WARNING, "Channel does not have a CDR\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(data))
|
||||
ast_set2_flag(chan->cdr, strchr(data, 'v'), AST_CDR_FLAG_KEEP_VARS);
|
||||
|
||||
ast_cdr_fork(chan);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, forkcdr_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Fork The CDR into 2 separate entities");
|
||||
130
trunk/apps/app_getcpeid.c
Normal file
130
trunk/apps/app_getcpeid.c
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Get ADSI CPE ID
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/adsi.h"
|
||||
|
||||
static char *app = "GetCPEID";
|
||||
|
||||
static char *synopsis = "Get ADSI CPE ID";
|
||||
|
||||
static char *descrip =
|
||||
" GetCPEID(): Obtains and displays ADSI CPE ID and other information in order\n"
|
||||
"to properly setup zapata.conf for on-hook operations.\n";
|
||||
|
||||
|
||||
static int cpeid_setstatus(struct ast_channel *chan, char *stuff[], int voice)
|
||||
{
|
||||
int justify[5] = { ADSI_JUST_CENT, ADSI_JUST_LEFT, ADSI_JUST_LEFT, ADSI_JUST_LEFT };
|
||||
char *tmp[5];
|
||||
int x;
|
||||
for (x=0;x<4;x++)
|
||||
tmp[x] = stuff[x];
|
||||
tmp[4] = NULL;
|
||||
return ast_adsi_print(chan, tmp, justify, voice);
|
||||
}
|
||||
|
||||
static int cpeid_exec(struct ast_channel *chan, void *idata)
|
||||
{
|
||||
int res=0;
|
||||
unsigned char cpeid[4];
|
||||
int gotgeometry = 0;
|
||||
int gotcpeid = 0;
|
||||
int width, height, buttons;
|
||||
char *data[4];
|
||||
unsigned int x;
|
||||
|
||||
for (x = 0; x < 4; x++)
|
||||
data[x] = alloca(80);
|
||||
|
||||
strcpy(data[0], "** CPE Info **");
|
||||
strcpy(data[1], "Identifying CPE...");
|
||||
strcpy(data[2], "Please wait...");
|
||||
res = ast_adsi_load_session(chan, NULL, 0, 1);
|
||||
if (res > 0) {
|
||||
cpeid_setstatus(chan, data, 0);
|
||||
res = ast_adsi_get_cpeid(chan, cpeid, 0);
|
||||
if (res > 0) {
|
||||
gotcpeid = 1;
|
||||
ast_verb(3, "Got CPEID of '%02x:%02x:%02x:%02x' on '%s'\n", cpeid[0], cpeid[1], cpeid[2], cpeid[3], chan->name);
|
||||
}
|
||||
if (res > -1) {
|
||||
strcpy(data[1], "Measuring CPE...");
|
||||
strcpy(data[2], "Please wait...");
|
||||
cpeid_setstatus(chan, data, 0);
|
||||
res = ast_adsi_get_cpeinfo(chan, &width, &height, &buttons, 0);
|
||||
if (res > -1) {
|
||||
ast_verb(3, "CPE has %d lines, %d columns, and %d buttons on '%s'\n", height, width, buttons, chan->name);
|
||||
gotgeometry = 1;
|
||||
}
|
||||
}
|
||||
if (res > -1) {
|
||||
if (gotcpeid)
|
||||
snprintf(data[1], 80, "CPEID: %02x:%02x:%02x:%02x", cpeid[0], cpeid[1], cpeid[2], cpeid[3]);
|
||||
else
|
||||
strcpy(data[1], "CPEID Unknown");
|
||||
if (gotgeometry)
|
||||
snprintf(data[2], 80, "Geom: %dx%d, %d buttons", width, height, buttons);
|
||||
else
|
||||
strcpy(data[2], "Geometry unknown");
|
||||
strcpy(data[3], "Press # to exit");
|
||||
cpeid_setstatus(chan, data, 1);
|
||||
for(;;) {
|
||||
res = ast_waitfordigit(chan, 1000);
|
||||
if (res < 0)
|
||||
break;
|
||||
if (res == '#') {
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ast_adsi_unload_session(chan);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, cpeid_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Get ADSI CPE ID");
|
||||
205
trunk/apps/app_ices.c
Normal file
205
trunk/apps/app_ices.c
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Stream to an icecast server via ICES (see contrib/asterisk-ices.xml)
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \extref ICES - http://www.icecast.org/ices.php
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "asterisk/paths.h" /* use ast_config_AST_CONFIG_DIR */
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/frame.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
|
||||
#define ICES "/usr/bin/ices"
|
||||
#define LOCAL_ICES "/usr/local/bin/ices"
|
||||
|
||||
static char *app = "ICES";
|
||||
|
||||
static char *synopsis = "Encode and stream using 'ices'";
|
||||
|
||||
static char *descrip =
|
||||
" ICES(config.xml) Streams to an icecast server using ices\n"
|
||||
"(available separately). A configuration file must be supplied\n"
|
||||
"for ices (see examples/asterisk-ices.conf). \n";
|
||||
|
||||
|
||||
static int icesencode(char *filename, int fd)
|
||||
{
|
||||
int res;
|
||||
int x;
|
||||
sigset_t fullset, oldset;
|
||||
|
||||
sigfillset(&fullset);
|
||||
pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
|
||||
|
||||
res = fork();
|
||||
if (res < 0)
|
||||
ast_log(LOG_WARNING, "Fork failed\n");
|
||||
if (res) {
|
||||
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Stop ignoring PIPE */
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
|
||||
|
||||
if (ast_opt_high_priority)
|
||||
ast_set_priority(0);
|
||||
dup2(fd, STDIN_FILENO);
|
||||
for (x=STDERR_FILENO + 1;x<1024;x++) {
|
||||
if ((x != STDIN_FILENO) && (x != STDOUT_FILENO))
|
||||
close(x);
|
||||
}
|
||||
/* Most commonly installed in /usr/local/bin */
|
||||
execl(ICES, "ices", filename, (char *)NULL);
|
||||
/* But many places has it in /usr/bin */
|
||||
execl(LOCAL_ICES, "ices", filename, (char *)NULL);
|
||||
/* As a last-ditch effort, try to use PATH */
|
||||
execlp("ices", "ices", filename, (char *)NULL);
|
||||
ast_log(LOG_WARNING, "Execute of ices failed\n");
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
static int ices_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
int fds[2];
|
||||
int ms = -1;
|
||||
int pid = -1;
|
||||
int flags;
|
||||
int oreadformat;
|
||||
struct timeval last;
|
||||
struct ast_frame *f;
|
||||
char filename[256]="";
|
||||
char *c;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "ICES requires an argument (configfile.xml)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
last = ast_tv(0, 0);
|
||||
|
||||
if (pipe(fds)) {
|
||||
ast_log(LOG_WARNING, "Unable to create pipe\n");
|
||||
return -1;
|
||||
}
|
||||
flags = fcntl(fds[1], F_GETFL);
|
||||
fcntl(fds[1], F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
ast_stopstream(chan);
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
res = ast_answer(chan);
|
||||
|
||||
if (res) {
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
ast_log(LOG_WARNING, "Answer failed!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
oreadformat = chan->readformat;
|
||||
res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
|
||||
if (res < 0) {
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
|
||||
return -1;
|
||||
}
|
||||
if (((char *)data)[0] == '/')
|
||||
ast_copy_string(filename, (char *) data, sizeof(filename));
|
||||
else
|
||||
snprintf(filename, sizeof(filename), "%s/%s", ast_config_AST_CONFIG_DIR, (char *)data);
|
||||
/* Placeholder for options */
|
||||
c = strchr(filename, '|');
|
||||
if (c)
|
||||
*c = '\0';
|
||||
res = icesencode(filename, fds[0]);
|
||||
close(fds[0]);
|
||||
if (res >= 0) {
|
||||
pid = res;
|
||||
for (;;) {
|
||||
/* Wait for audio, and stream */
|
||||
ms = ast_waitfor(chan, -1);
|
||||
if (ms < 0) {
|
||||
ast_debug(1, "Hangup detected\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
ast_debug(1, "Null frame == hangup() detected\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if (f->frametype == AST_FRAME_VOICE) {
|
||||
res = write(fds[1], f->data, f->datalen);
|
||||
if (res < 0) {
|
||||
if (errno != EAGAIN) {
|
||||
ast_log(LOG_WARNING, "Write failed to pipe: %s\n", strerror(errno));
|
||||
res = -1;
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
if (pid > -1)
|
||||
kill(pid, SIGKILL);
|
||||
if (!res && oreadformat)
|
||||
ast_set_read_format(chan, oreadformat);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, ices_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Encode and Stream via icecast and ices");
|
||||
80
trunk/apps/app_image.c
Normal file
80
trunk/apps/app_image.c
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 App to transmit an image
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/image.h"
|
||||
|
||||
static char *app = "SendImage";
|
||||
|
||||
static char *synopsis = "Send an image file";
|
||||
|
||||
static char *descrip =
|
||||
" SendImage(filename): Sends an image on a channel.\n"
|
||||
"If the channel supports image transport but the image send fails, the channel\n"
|
||||
"will be hung up. Otherwise, the dialplan continues execution. This\n"
|
||||
"application sets the following channel variable upon completion:\n"
|
||||
" SENDIMAGESTATUS The status is the result of the attempt, one of:\n"
|
||||
" OK | NOSUPPORT \n";
|
||||
|
||||
|
||||
static int sendimage_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "SendImage requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!ast_supports_images(chan)) {
|
||||
/* Does not support transport */
|
||||
pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "NOSUPPORT");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(res = ast_send_image(chan, data)))
|
||||
pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "OK");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, sendimage_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Image Transmission Application");
|
||||
115
trunk/apps/app_ivrdemo.c
Normal file
115
trunk/apps/app_ivrdemo.c
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 IVR Demo application
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<defaultenabled>no</defaultenabled>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *tdesc = "IVR Demo Application";
|
||||
static char *app = "IVRDemo";
|
||||
static char *synopsis =
|
||||
" This is a skeleton application that shows you the basic structure to create your\n"
|
||||
"own asterisk applications and demonstrates the IVR demo.\n";
|
||||
|
||||
static int ivr_demo_func(struct ast_channel *chan, void *data)
|
||||
{
|
||||
ast_verbose("IVR Demo, data is %s!\n", (char *)data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AST_IVR_DECLARE_MENU(ivr_submenu, "IVR Demo Sub Menu", 0,
|
||||
{
|
||||
{ "s", AST_ACTION_BACKGROUND, "demo-abouttotry" },
|
||||
{ "s", AST_ACTION_WAITOPTION },
|
||||
{ "1", AST_ACTION_PLAYBACK, "digits/1" },
|
||||
{ "1", AST_ACTION_PLAYBACK, "digits/1" },
|
||||
{ "1", AST_ACTION_RESTART },
|
||||
{ "2", AST_ACTION_PLAYLIST, "digits/2;digits/3" },
|
||||
{ "3", AST_ACTION_CALLBACK, ivr_demo_func },
|
||||
{ "4", AST_ACTION_TRANSFER, "demo|s|1" },
|
||||
{ "*", AST_ACTION_REPEAT },
|
||||
{ "#", AST_ACTION_UPONE },
|
||||
{ NULL }
|
||||
});
|
||||
|
||||
AST_IVR_DECLARE_MENU(ivr_demo, "IVR Demo Main Menu", 0,
|
||||
{
|
||||
{ "s", AST_ACTION_BACKGROUND, "demo-congrats" },
|
||||
{ "g", AST_ACTION_BACKGROUND, "demo-instruct" },
|
||||
{ "g", AST_ACTION_WAITOPTION },
|
||||
{ "1", AST_ACTION_PLAYBACK, "digits/1" },
|
||||
{ "1", AST_ACTION_RESTART },
|
||||
{ "2", AST_ACTION_MENU, &ivr_submenu },
|
||||
{ "2", AST_ACTION_RESTART },
|
||||
{ "i", AST_ACTION_PLAYBACK, "invalid" },
|
||||
{ "i", AST_ACTION_REPEAT, (void *)(unsigned long)2 },
|
||||
{ "#", AST_ACTION_EXIT },
|
||||
{ NULL },
|
||||
});
|
||||
|
||||
|
||||
static int skel_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=0;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "skel requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Do our thing here */
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
res = ast_answer(chan);
|
||||
if (!res)
|
||||
res = ast_ivr_menu_run(chan, &ivr_demo, data);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, skel_exec, tdesc, synopsis);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "IVR Demo Application");
|
||||
971
trunk/apps/app_jack.c
Normal file
971
trunk/apps/app_jack.c
Normal file
@@ -0,0 +1,971 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2007 - 2008, Russell Bryant
|
||||
*
|
||||
* Russell Bryant <russell@digium.com>
|
||||
*
|
||||
* 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 Jack Application
|
||||
*
|
||||
* \author Russell Bryant <russell@digium.com>
|
||||
*
|
||||
* This is an application to connect an Asterisk channel to an input
|
||||
* and output jack port so that the audio can be processed through
|
||||
* another application, or to play audio from another application.
|
||||
*
|
||||
* \arg http://www.jackaudio.org/
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend>jack</depend>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/strings.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/libresample.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/audiohook.h"
|
||||
|
||||
#define RESAMPLE_QUALITY 0
|
||||
|
||||
#define RINGBUFFER_SIZE 16384
|
||||
|
||||
/*! \brief Common options between the Jack() app and JACK_HOOK() function */
|
||||
#define COMMON_OPTIONS \
|
||||
" s(<name>) - Connect to the specified jack server name.\n" \
|
||||
" i(<name>) - Connect the output port that gets created to the specified\n" \
|
||||
" jack input port.\n" \
|
||||
" o(<name>) - Connect the input port that gets created to the specified\n" \
|
||||
" jack output port.\n" \
|
||||
" n - Do not automatically start the JACK server if it is not already\n" \
|
||||
" running.\n"
|
||||
|
||||
static char *jack_app = "JACK";
|
||||
static char *jack_synopsis =
|
||||
"JACK (Jack Audio Connection Kit) Application";
|
||||
static char *jack_desc =
|
||||
"JACK([options])\n"
|
||||
" When this application is executed, two jack ports will be created; one input\n"
|
||||
"and one output. Other applications can be hooked up to these ports to access\n"
|
||||
"the audio coming from, or being sent to the channel.\n"
|
||||
" Valid options:\n"
|
||||
COMMON_OPTIONS
|
||||
"";
|
||||
|
||||
struct jack_data {
|
||||
AST_DECLARE_STRING_FIELDS(
|
||||
AST_STRING_FIELD(server_name);
|
||||
AST_STRING_FIELD(connect_input_port);
|
||||
AST_STRING_FIELD(connect_output_port);
|
||||
);
|
||||
jack_client_t *client;
|
||||
jack_port_t *input_port;
|
||||
jack_port_t *output_port;
|
||||
jack_ringbuffer_t *input_rb;
|
||||
jack_ringbuffer_t *output_rb;
|
||||
void *output_resampler;
|
||||
double output_resample_factor;
|
||||
void *input_resampler;
|
||||
double input_resample_factor;
|
||||
unsigned int stop:1;
|
||||
unsigned int has_audiohook:1;
|
||||
unsigned int no_start_server:1;
|
||||
/*! Only used with JACK_HOOK */
|
||||
struct ast_audiohook audiohook;
|
||||
};
|
||||
|
||||
static const struct {
|
||||
jack_status_t status;
|
||||
const char *str;
|
||||
} jack_status_table[] = {
|
||||
{ JackFailure, "Failure" },
|
||||
{ JackInvalidOption, "Invalid Option" },
|
||||
{ JackNameNotUnique, "Name Not Unique" },
|
||||
{ JackServerStarted, "Server Started" },
|
||||
{ JackServerFailed, "Server Failed" },
|
||||
{ JackServerError, "Server Error" },
|
||||
{ JackNoSuchClient, "No Such Client" },
|
||||
{ JackLoadFailure, "Load Failure" },
|
||||
{ JackInitFailure, "Init Failure" },
|
||||
{ JackShmFailure, "Shared Memory Access Failure" },
|
||||
{ JackVersionError, "Version Mismatch" },
|
||||
};
|
||||
|
||||
static const char *jack_status_to_str(jack_status_t status)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_LEN(jack_status_table); i++) {
|
||||
if (jack_status_table[i].status == status)
|
||||
return jack_status_table[i].str;
|
||||
}
|
||||
|
||||
return "Unknown Error";
|
||||
}
|
||||
|
||||
static void log_jack_status(const char *prefix, jack_status_t status)
|
||||
{
|
||||
struct ast_str *str = ast_str_alloca(512);
|
||||
int i, first = 0;
|
||||
|
||||
for (i = 0; i < (sizeof(status) * 8); i++) {
|
||||
if (!(status & (1 << i)))
|
||||
continue;
|
||||
|
||||
if (!first) {
|
||||
ast_str_set(&str, 0, "%s", jack_status_to_str((1 << i)));
|
||||
first = 1;
|
||||
} else
|
||||
ast_str_append(&str, 0, ", %s", jack_status_to_str((1 << i)));
|
||||
}
|
||||
|
||||
ast_log(LOG_NOTICE, "%s: %s\n", prefix, str->str);
|
||||
}
|
||||
|
||||
static int alloc_resampler(struct jack_data *jack_data, int input)
|
||||
{
|
||||
double from_srate, to_srate, jack_srate;
|
||||
void **resampler;
|
||||
double *resample_factor;
|
||||
|
||||
if (input && jack_data->input_resampler)
|
||||
return 0;
|
||||
|
||||
if (!input && jack_data->output_resampler)
|
||||
return 0;
|
||||
|
||||
jack_srate = jack_get_sample_rate(jack_data->client);
|
||||
|
||||
/* XXX Hard coded 8 kHz */
|
||||
|
||||
to_srate = input ? 8000.0 : jack_srate;
|
||||
from_srate = input ? jack_srate : 8000.0;
|
||||
|
||||
resample_factor = input ? &jack_data->input_resample_factor :
|
||||
&jack_data->output_resample_factor;
|
||||
|
||||
if (from_srate == to_srate) {
|
||||
/* Awesome! The jack sample rate is the same as ours.
|
||||
* Resampling isn't needed. */
|
||||
*resample_factor = 1.0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*resample_factor = to_srate / from_srate;
|
||||
|
||||
resampler = input ? &jack_data->input_resampler :
|
||||
&jack_data->output_resampler;
|
||||
|
||||
if (!(*resampler = resample_open(RESAMPLE_QUALITY,
|
||||
*resample_factor, *resample_factor))) {
|
||||
ast_log(LOG_ERROR, "Failed to open %s resampler\n",
|
||||
input ? "input" : "output");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Handle jack input port
|
||||
*
|
||||
* Read nframes number of samples from the input buffer, resample it
|
||||
* if necessary, and write it into the appropriate ringbuffer.
|
||||
*/
|
||||
static void handle_input(void *buf, jack_nframes_t nframes,
|
||||
struct jack_data *jack_data)
|
||||
{
|
||||
short s_buf[nframes];
|
||||
float *in_buf = buf;
|
||||
size_t res;
|
||||
int i;
|
||||
size_t write_len = sizeof(s_buf);
|
||||
|
||||
if (jack_data->input_resampler) {
|
||||
int total_in_buf_used = 0;
|
||||
int total_out_buf_used = 0;
|
||||
float f_buf[nframes + 1];
|
||||
|
||||
memset(f_buf, 0, sizeof(f_buf));
|
||||
|
||||
while (total_in_buf_used < nframes) {
|
||||
int in_buf_used;
|
||||
int out_buf_used;
|
||||
|
||||
out_buf_used = resample_process(jack_data->input_resampler,
|
||||
jack_data->input_resample_factor,
|
||||
&in_buf[total_in_buf_used], nframes - total_in_buf_used,
|
||||
0, &in_buf_used,
|
||||
&f_buf[total_out_buf_used], ARRAY_LEN(f_buf) - total_out_buf_used);
|
||||
|
||||
if (out_buf_used < 0)
|
||||
break;
|
||||
|
||||
total_out_buf_used += out_buf_used;
|
||||
total_in_buf_used += in_buf_used;
|
||||
|
||||
if (total_out_buf_used == ARRAY_LEN(f_buf)) {
|
||||
ast_log(LOG_ERROR, "Output buffer filled ... need to increase its size, "
|
||||
"nframes '%d', total_out_buf_used '%d'\n", nframes, total_out_buf_used);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < total_out_buf_used; i++)
|
||||
s_buf[i] = f_buf[i] * (SHRT_MAX / 1.0);
|
||||
|
||||
write_len = total_out_buf_used * sizeof(int16_t);
|
||||
} else {
|
||||
/* No resampling needed */
|
||||
|
||||
for (i = 0; i < nframes; i++)
|
||||
s_buf[i] = in_buf[i] * (SHRT_MAX / 1.0);
|
||||
}
|
||||
|
||||
res = jack_ringbuffer_write(jack_data->input_rb, (const char *) s_buf, write_len);
|
||||
if (res != write_len) {
|
||||
ast_debug(2, "Tried to write %d bytes to the ringbuffer, but only wrote %d\n",
|
||||
(int) sizeof(s_buf), (int) res);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Handle jack output port
|
||||
*
|
||||
* Read nframes number of samples from the ringbuffer and write it out to the
|
||||
* output port buffer.
|
||||
*/
|
||||
static void handle_output(void *buf, jack_nframes_t nframes,
|
||||
struct jack_data *jack_data)
|
||||
{
|
||||
size_t res, len;
|
||||
|
||||
len = nframes * sizeof(float);
|
||||
|
||||
res = jack_ringbuffer_read(jack_data->output_rb, buf, len);
|
||||
|
||||
if (len != res) {
|
||||
ast_debug(2, "Wanted %d bytes to send to the output port, "
|
||||
"but only got %d\n", (int) len, (int) res);
|
||||
}
|
||||
}
|
||||
|
||||
static int jack_process(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
struct jack_data *jack_data = arg;
|
||||
void *input_port_buf, *output_port_buf;
|
||||
|
||||
if (!jack_data->input_resample_factor)
|
||||
alloc_resampler(jack_data, 1);
|
||||
|
||||
input_port_buf = jack_port_get_buffer(jack_data->input_port, nframes);
|
||||
handle_input(input_port_buf, nframes, jack_data);
|
||||
|
||||
output_port_buf = jack_port_get_buffer(jack_data->output_port, nframes);
|
||||
handle_output(output_port_buf, nframes, jack_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void jack_shutdown(void *arg)
|
||||
{
|
||||
struct jack_data *jack_data = arg;
|
||||
|
||||
jack_data->stop = 1;
|
||||
}
|
||||
|
||||
static struct jack_data *destroy_jack_data(struct jack_data *jack_data)
|
||||
{
|
||||
if (jack_data->input_port) {
|
||||
jack_port_unregister(jack_data->client, jack_data->input_port);
|
||||
jack_data->input_port = NULL;
|
||||
}
|
||||
|
||||
if (jack_data->output_port) {
|
||||
jack_port_unregister(jack_data->client, jack_data->output_port);
|
||||
jack_data->output_port = NULL;
|
||||
}
|
||||
|
||||
if (jack_data->client) {
|
||||
jack_client_close(jack_data->client);
|
||||
jack_data->client = NULL;
|
||||
}
|
||||
|
||||
if (jack_data->input_rb) {
|
||||
jack_ringbuffer_free(jack_data->input_rb);
|
||||
jack_data->input_rb = NULL;
|
||||
}
|
||||
|
||||
if (jack_data->output_rb) {
|
||||
jack_ringbuffer_free(jack_data->output_rb);
|
||||
jack_data->output_rb = NULL;
|
||||
}
|
||||
|
||||
if (jack_data->output_resampler) {
|
||||
resample_close(jack_data->output_resampler);
|
||||
jack_data->output_resampler = NULL;
|
||||
}
|
||||
|
||||
if (jack_data->input_resampler) {
|
||||
resample_close(jack_data->input_resampler);
|
||||
jack_data->input_resampler = NULL;
|
||||
}
|
||||
|
||||
if (jack_data->has_audiohook)
|
||||
ast_audiohook_destroy(&jack_data->audiohook);
|
||||
|
||||
ast_string_field_free_memory(jack_data);
|
||||
|
||||
ast_free(jack_data);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int init_jack_data(struct ast_channel *chan, struct jack_data *jack_data)
|
||||
{
|
||||
const char *chan_name;
|
||||
jack_status_t status = 0;
|
||||
jack_options_t jack_options = JackNullOption;
|
||||
|
||||
ast_channel_lock(chan);
|
||||
chan_name = ast_strdupa(chan->name);
|
||||
ast_channel_unlock(chan);
|
||||
|
||||
if (!(jack_data->output_rb = jack_ringbuffer_create(RINGBUFFER_SIZE)))
|
||||
return -1;
|
||||
|
||||
if (!(jack_data->input_rb = jack_ringbuffer_create(RINGBUFFER_SIZE)))
|
||||
return -1;
|
||||
|
||||
if (jack_data->no_start_server)
|
||||
jack_options |= JackNoStartServer;
|
||||
|
||||
if (!ast_strlen_zero(jack_data->server_name)) {
|
||||
jack_options |= JackServerName;
|
||||
jack_data->client = jack_client_open(chan_name, jack_options, &status,
|
||||
jack_data->server_name);
|
||||
} else {
|
||||
jack_data->client = jack_client_open(chan_name, jack_options, &status);
|
||||
}
|
||||
|
||||
if (status)
|
||||
log_jack_status("Client Open Status", status);
|
||||
|
||||
if (!jack_data->client)
|
||||
return -1;
|
||||
|
||||
jack_data->input_port = jack_port_register(jack_data->client, "input",
|
||||
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsTerminal, 0);
|
||||
if (!jack_data->input_port) {
|
||||
ast_log(LOG_ERROR, "Failed to create input port for jack port\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
jack_data->output_port = jack_port_register(jack_data->client, "output",
|
||||
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0);
|
||||
if (!jack_data->output_port) {
|
||||
ast_log(LOG_ERROR, "Failed to create output port for jack port\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (jack_set_process_callback(jack_data->client, jack_process, jack_data)) {
|
||||
ast_log(LOG_ERROR, "Failed to register process callback with jack client\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
jack_on_shutdown(jack_data->client, jack_shutdown, jack_data);
|
||||
|
||||
if (jack_activate(jack_data->client)) {
|
||||
ast_log(LOG_ERROR, "Unable to activate jack client\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (!ast_strlen_zero(jack_data->connect_input_port)) {
|
||||
const char **ports;
|
||||
int i;
|
||||
|
||||
ports = jack_get_ports(jack_data->client, jack_data->connect_input_port,
|
||||
NULL, JackPortIsInput);
|
||||
|
||||
if (!ports) {
|
||||
ast_log(LOG_ERROR, "No input port matching '%s' was found\n",
|
||||
jack_data->connect_input_port);
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; ports[i]; i++) {
|
||||
ast_debug(1, "Found port '%s' that matched specified input port '%s'\n",
|
||||
ports[i], jack_data->connect_input_port);
|
||||
}
|
||||
|
||||
if (jack_connect(jack_data->client, jack_port_name(jack_data->output_port), ports[0])) {
|
||||
ast_log(LOG_ERROR, "Failed to connect '%s' to '%s'\n", ports[0],
|
||||
jack_port_name(jack_data->output_port));
|
||||
} else {
|
||||
ast_debug(1, "Connected '%s' to '%s'\n", ports[0],
|
||||
jack_port_name(jack_data->output_port));
|
||||
}
|
||||
|
||||
free((void *) ports);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
while (!ast_strlen_zero(jack_data->connect_output_port)) {
|
||||
const char **ports;
|
||||
int i;
|
||||
|
||||
ports = jack_get_ports(jack_data->client, jack_data->connect_output_port,
|
||||
NULL, JackPortIsOutput);
|
||||
|
||||
if (!ports) {
|
||||
ast_log(LOG_ERROR, "No output port matching '%s' was found\n",
|
||||
jack_data->connect_output_port);
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; ports[i]; i++) {
|
||||
ast_debug(1, "Found port '%s' that matched specified output port '%s'\n",
|
||||
ports[i], jack_data->connect_output_port);
|
||||
}
|
||||
|
||||
if (jack_connect(jack_data->client, ports[0], jack_port_name(jack_data->input_port))) {
|
||||
ast_log(LOG_ERROR, "Failed to connect '%s' to '%s'\n", ports[0],
|
||||
jack_port_name(jack_data->input_port));
|
||||
} else {
|
||||
ast_debug(1, "Connected '%s' to '%s'\n", ports[0],
|
||||
jack_port_name(jack_data->input_port));
|
||||
}
|
||||
|
||||
free((void *) ports);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int queue_voice_frame(struct jack_data *jack_data, struct ast_frame *f)
|
||||
{
|
||||
float f_buf[f->samples * 8];
|
||||
size_t f_buf_used = 0;
|
||||
int i;
|
||||
int16_t *s_buf = f->data;
|
||||
size_t res;
|
||||
|
||||
memset(f_buf, 0, sizeof(f_buf));
|
||||
|
||||
if (!jack_data->output_resample_factor)
|
||||
alloc_resampler(jack_data, 0);
|
||||
|
||||
if (jack_data->output_resampler) {
|
||||
float in_buf[f->samples];
|
||||
int total_in_buf_used = 0;
|
||||
int total_out_buf_used = 0;
|
||||
|
||||
memset(in_buf, 0, sizeof(in_buf));
|
||||
|
||||
for (i = 0; i < f->samples; i++)
|
||||
in_buf[i] = s_buf[i] * (1.0 / SHRT_MAX);
|
||||
|
||||
while (total_in_buf_used < ARRAY_LEN(in_buf)) {
|
||||
int in_buf_used;
|
||||
int out_buf_used;
|
||||
|
||||
out_buf_used = resample_process(jack_data->output_resampler,
|
||||
jack_data->output_resample_factor,
|
||||
&in_buf[total_in_buf_used], ARRAY_LEN(in_buf) - total_in_buf_used,
|
||||
0, &in_buf_used,
|
||||
&f_buf[total_out_buf_used], ARRAY_LEN(f_buf) - total_out_buf_used);
|
||||
|
||||
if (out_buf_used < 0)
|
||||
break;
|
||||
|
||||
total_out_buf_used += out_buf_used;
|
||||
total_in_buf_used += in_buf_used;
|
||||
|
||||
if (total_out_buf_used == ARRAY_LEN(f_buf)) {
|
||||
ast_log(LOG_ERROR, "Output buffer filled ... need to increase its size\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
f_buf_used = total_out_buf_used;
|
||||
if (f_buf_used > ARRAY_LEN(f_buf))
|
||||
f_buf_used = ARRAY_LEN(f_buf);
|
||||
} else {
|
||||
/* No resampling needed */
|
||||
|
||||
for (i = 0; i < f->samples; i++)
|
||||
f_buf[i] = s_buf[i] * (1.0 / SHRT_MAX);
|
||||
|
||||
f_buf_used = f->samples;
|
||||
}
|
||||
|
||||
res = jack_ringbuffer_write(jack_data->output_rb, (const char *) f_buf, f_buf_used * sizeof(float));
|
||||
if (res != (f_buf_used * sizeof(float))) {
|
||||
ast_debug(2, "Tried to write %d bytes to the ringbuffer, but only wrote %d\n",
|
||||
(int) (f_buf_used * sizeof(float)), (int) res);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief handle jack audio
|
||||
*
|
||||
* \param[in] chan The Asterisk channel to write the frames to if no output frame
|
||||
* is provided.
|
||||
* \param[in] jack_data This is the jack_data struct that contains the input
|
||||
* ringbuffer that audio will be read from.
|
||||
* \param[out] out_frame If this argument is non-NULL, then assuming there is
|
||||
* enough data avilable in the ringbuffer, the audio in this frame
|
||||
* will get replaced with audio from the input buffer. If there is
|
||||
* not enough data available to read at this time, then the frame
|
||||
* data gets zeroed out.
|
||||
*
|
||||
* Read data from the input ringbuffer, which is the properly resampled audio
|
||||
* that was read from the jack input port. Write it to the channel in 20 ms frames,
|
||||
* or fill up an output frame instead if one is provided.
|
||||
*
|
||||
* \return Nothing.
|
||||
*/
|
||||
static void handle_jack_audio(struct ast_channel *chan, struct jack_data *jack_data,
|
||||
struct ast_frame *out_frame)
|
||||
{
|
||||
short buf[160];
|
||||
struct ast_frame f = {
|
||||
.frametype = AST_FRAME_VOICE,
|
||||
.subclass = AST_FORMAT_SLINEAR,
|
||||
.src = "JACK",
|
||||
.data = buf,
|
||||
.datalen = sizeof(buf),
|
||||
.samples = ARRAY_LEN(buf),
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
size_t res, read_len;
|
||||
char *read_buf;
|
||||
|
||||
read_len = out_frame ? out_frame->datalen : sizeof(buf);
|
||||
read_buf = out_frame ? out_frame->data : buf;
|
||||
|
||||
res = jack_ringbuffer_read_space(jack_data->input_rb);
|
||||
|
||||
if (res < read_len) {
|
||||
/* Not enough data ready for another frame, move on ... */
|
||||
if (out_frame) {
|
||||
ast_debug(1, "Sending an empty frame for the JACK_HOOK\n");
|
||||
memset(out_frame->data, 0, out_frame->datalen);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
res = jack_ringbuffer_read(jack_data->input_rb, (char *) read_buf, read_len);
|
||||
|
||||
if (res < read_len) {
|
||||
ast_log(LOG_ERROR, "Error reading from ringbuffer, even though it said there was enough data\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (out_frame) {
|
||||
/* If an output frame was provided, then we just want to fill up the
|
||||
* buffer in that frame and return. */
|
||||
break;
|
||||
}
|
||||
|
||||
ast_write(chan, &f);
|
||||
}
|
||||
}
|
||||
|
||||
enum {
|
||||
OPT_SERVER_NAME = (1 << 0),
|
||||
OPT_INPUT_PORT = (1 << 1),
|
||||
OPT_OUTPUT_PORT = (1 << 2),
|
||||
OPT_NOSTART_SERVER = (1 << 3),
|
||||
};
|
||||
|
||||
enum {
|
||||
OPT_ARG_SERVER_NAME,
|
||||
OPT_ARG_INPUT_PORT,
|
||||
OPT_ARG_OUTPUT_PORT,
|
||||
/* Must be the last element */
|
||||
OPT_ARG_ARRAY_SIZE,
|
||||
};
|
||||
|
||||
AST_APP_OPTIONS(jack_exec_options, BEGIN_OPTIONS
|
||||
AST_APP_OPTION_ARG('s', OPT_SERVER_NAME, OPT_ARG_SERVER_NAME),
|
||||
AST_APP_OPTION_ARG('i', OPT_INPUT_PORT, OPT_ARG_INPUT_PORT),
|
||||
AST_APP_OPTION_ARG('o', OPT_OUTPUT_PORT, OPT_ARG_OUTPUT_PORT),
|
||||
AST_APP_OPTION('n', OPT_NOSTART_SERVER),
|
||||
END_OPTIONS );
|
||||
|
||||
static struct jack_data *jack_data_alloc(void)
|
||||
{
|
||||
struct jack_data *jack_data;
|
||||
|
||||
if (!(jack_data = ast_calloc(1, sizeof(*jack_data))))
|
||||
return NULL;
|
||||
|
||||
if (ast_string_field_init(jack_data, 32)) {
|
||||
ast_free(jack_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return jack_data;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \note This must be done before calling init_jack_data().
|
||||
*/
|
||||
static int handle_options(struct jack_data *jack_data, const char *__options_str)
|
||||
{
|
||||
struct ast_flags options = { 0, };
|
||||
char *option_args[OPT_ARG_ARRAY_SIZE];
|
||||
char *options_str;
|
||||
|
||||
options_str = ast_strdupa(__options_str);
|
||||
|
||||
ast_app_parse_options(jack_exec_options, &options, option_args, options_str);
|
||||
|
||||
if (ast_test_flag(&options, OPT_SERVER_NAME)) {
|
||||
if (!ast_strlen_zero(option_args[OPT_ARG_SERVER_NAME]))
|
||||
ast_string_field_set(jack_data, server_name, option_args[OPT_ARG_SERVER_NAME]);
|
||||
else {
|
||||
ast_log(LOG_ERROR, "A server name must be provided with the s() option\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ast_test_flag(&options, OPT_INPUT_PORT)) {
|
||||
if (!ast_strlen_zero(option_args[OPT_ARG_INPUT_PORT]))
|
||||
ast_string_field_set(jack_data, connect_input_port, option_args[OPT_ARG_INPUT_PORT]);
|
||||
else {
|
||||
ast_log(LOG_ERROR, "A name must be provided with the i() option\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ast_test_flag(&options, OPT_OUTPUT_PORT)) {
|
||||
if (!ast_strlen_zero(option_args[OPT_ARG_OUTPUT_PORT]))
|
||||
ast_string_field_set(jack_data, connect_output_port, option_args[OPT_ARG_OUTPUT_PORT]);
|
||||
else {
|
||||
ast_log(LOG_ERROR, "A name must be provided with the o() option\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
jack_data->no_start_server = ast_test_flag(&options, OPT_NOSTART_SERVER) ? 1 : 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jack_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct jack_data *jack_data;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (!(jack_data = jack_data_alloc()))
|
||||
return -1;
|
||||
|
||||
args.options = data;
|
||||
|
||||
if (!ast_strlen_zero(args.options) && handle_options(jack_data, args.options)) {
|
||||
destroy_jack_data(jack_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (init_jack_data(chan, jack_data)) {
|
||||
destroy_jack_data(jack_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
|
||||
destroy_jack_data(jack_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
|
||||
destroy_jack_data(jack_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (!jack_data->stop) {
|
||||
struct ast_frame *f;
|
||||
|
||||
ast_waitfor(chan, -1);
|
||||
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
jack_data->stop = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (f->frametype) {
|
||||
case AST_FRAME_CONTROL:
|
||||
if (f->subclass == AST_CONTROL_HANGUP)
|
||||
jack_data->stop = 1;
|
||||
break;
|
||||
case AST_FRAME_VOICE:
|
||||
queue_voice_frame(jack_data, f);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ast_frfree(f);
|
||||
|
||||
handle_jack_audio(chan, jack_data, NULL);
|
||||
}
|
||||
|
||||
jack_data = destroy_jack_data(jack_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void jack_hook_ds_destroy(void *data)
|
||||
{
|
||||
struct jack_data *jack_data = data;
|
||||
|
||||
destroy_jack_data(jack_data);
|
||||
}
|
||||
|
||||
static const struct ast_datastore_info jack_hook_ds_info = {
|
||||
.type = "JACK_HOOK",
|
||||
.destroy = jack_hook_ds_destroy,
|
||||
};
|
||||
|
||||
static int jack_hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan,
|
||||
struct ast_frame *frame, enum ast_audiohook_direction direction)
|
||||
{
|
||||
struct ast_datastore *datastore;
|
||||
struct jack_data *jack_data;
|
||||
|
||||
if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE)
|
||||
return 0;
|
||||
|
||||
if (direction != AST_AUDIOHOOK_DIRECTION_READ)
|
||||
return 0;
|
||||
|
||||
if (frame->frametype != AST_FRAME_VOICE)
|
||||
return 0;
|
||||
|
||||
if (frame->subclass != AST_FORMAT_SLINEAR) {
|
||||
ast_log(LOG_WARNING, "Expected frame in SLINEAR for the audiohook, but got format %d\n",
|
||||
frame->subclass);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ast_channel_lock(chan);
|
||||
|
||||
if (!(datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) {
|
||||
ast_log(LOG_ERROR, "JACK_HOOK datastore not found for '%s'\n", chan->name);
|
||||
ast_channel_unlock(chan);
|
||||
return -1;
|
||||
}
|
||||
|
||||
jack_data = datastore->data;
|
||||
|
||||
queue_voice_frame(jack_data, frame);
|
||||
|
||||
handle_jack_audio(chan, jack_data, frame);
|
||||
|
||||
ast_channel_unlock(chan);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enable_jack_hook(struct ast_channel *chan, char *data)
|
||||
{
|
||||
struct ast_datastore *datastore;
|
||||
struct jack_data *jack_data = NULL;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(mode);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, data);
|
||||
|
||||
ast_channel_lock(chan);
|
||||
|
||||
if ((datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) {
|
||||
ast_log(LOG_ERROR, "JACK_HOOK already enabled for '%s'\n", chan->name);
|
||||
goto return_error;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(args.mode) || strcasecmp(args.mode, "manipulate")) {
|
||||
ast_log(LOG_ERROR, "'%s' is not a supported mode. Only manipulate is supported.\n",
|
||||
S_OR(args.mode, "<none>"));
|
||||
goto return_error;
|
||||
}
|
||||
|
||||
if (!(jack_data = jack_data_alloc()))
|
||||
goto return_error;
|
||||
|
||||
if (!ast_strlen_zero(args.options) && handle_options(jack_data, args.options))
|
||||
goto return_error;
|
||||
|
||||
if (init_jack_data(chan, jack_data))
|
||||
goto return_error;
|
||||
|
||||
if (!(datastore = ast_channel_datastore_alloc(&jack_hook_ds_info, NULL)))
|
||||
goto return_error;
|
||||
|
||||
jack_data->has_audiohook = 1;
|
||||
ast_audiohook_init(&jack_data->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE, "JACK_HOOK");
|
||||
jack_data->audiohook.manipulate_callback = jack_hook_callback;
|
||||
|
||||
datastore->data = jack_data;
|
||||
|
||||
if (ast_audiohook_attach(chan, &jack_data->audiohook))
|
||||
goto return_error;
|
||||
|
||||
if (ast_channel_datastore_add(chan, datastore))
|
||||
goto return_error;
|
||||
|
||||
ast_channel_unlock(chan);
|
||||
|
||||
return 0;
|
||||
|
||||
return_error:
|
||||
ast_channel_unlock(chan);
|
||||
|
||||
if (jack_data)
|
||||
destroy_jack_data(jack_data);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int disable_jack_hook(struct ast_channel *chan)
|
||||
{
|
||||
struct ast_datastore *datastore;
|
||||
struct jack_data *jack_data;
|
||||
|
||||
ast_channel_lock(chan);
|
||||
|
||||
if (!(datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) {
|
||||
ast_channel_unlock(chan);
|
||||
ast_log(LOG_WARNING, "No JACK_HOOK found to disable\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_channel_datastore_remove(chan, datastore);
|
||||
|
||||
jack_data = datastore->data;
|
||||
ast_audiohook_detach(&jack_data->audiohook);
|
||||
|
||||
/* Keep the channel locked while we destroy the datastore, so that we can
|
||||
* ensure that all of the jack stuff is stopped just in case another frame
|
||||
* tries to come through the audiohook callback. */
|
||||
ast_channel_datastore_free(datastore);
|
||||
|
||||
ast_channel_unlock(chan);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jack_hook_write(struct ast_channel *chan, const char *cmd, char *data,
|
||||
const char *value)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (!strcasecmp(value, "on"))
|
||||
res = enable_jack_hook(chan, data);
|
||||
else if (!strcasecmp(value, "off"))
|
||||
res = disable_jack_hook(chan);
|
||||
else {
|
||||
ast_log(LOG_ERROR, "'%s' is not a valid value for JACK_HOOK()\n", value);
|
||||
res = -1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static struct ast_custom_function jack_hook_function = {
|
||||
.name = "JACK_HOOK",
|
||||
.synopsis = "Enable a jack hook on a channel",
|
||||
.syntax = "JACK_HOOK(<mode>,[options])",
|
||||
.desc =
|
||||
" The JACK_HOOK allows turning on or off jack connectivity to this channel.\n"
|
||||
"When the JACK_HOOK is turned on, jack ports will get created that allow\n"
|
||||
"access to the audio stream for this channel. The mode specifies which mode\n"
|
||||
"this hook should run in. A mode must be specified when turning the JACK_HOOK.\n"
|
||||
"on. However, all arguments are optional when turning it off.\n"
|
||||
"\n"
|
||||
" Valid modes are:\n"
|
||||
#if 0
|
||||
/* XXX TODO */
|
||||
" spy - Create a read-only audio hook. Only an output jack port will\n"
|
||||
" get created.\n"
|
||||
" whisper - Create a write-only audio hook. Only an input jack port will\n"
|
||||
" get created.\n"
|
||||
#endif
|
||||
" manipulate - Create a read/write audio hook. Both an input and an output\n"
|
||||
" jack port will get created. Audio from the channel will be\n"
|
||||
" sent out the output port and will be replaced by the audio\n"
|
||||
" coming in on the input port as it gets passed on.\n"
|
||||
"\n"
|
||||
" Valid options are:\n"
|
||||
COMMON_OPTIONS
|
||||
"\n"
|
||||
" Examples:\n"
|
||||
" To turn on the JACK_HOOK,\n"
|
||||
" Set(JACK_HOOK(manipulate,i(pure_data_0:input0)o(pure_data_0:output0))=on)\n"
|
||||
" To turn off the JACK_HOOK,\n"
|
||||
" Set(JACK_HOOK()=off)\n"
|
||||
"",
|
||||
.write = jack_hook_write,
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(jack_app);
|
||||
res |= ast_custom_function_unregister(&jack_hook_function);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
if (ast_register_application(jack_app, jack_exec, jack_synopsis, jack_desc))
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
|
||||
if (ast_custom_function_register(&jack_hook_function)) {
|
||||
ast_unregister_application(jack_app);
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "JACK Interface");
|
||||
527
trunk/apps/app_macro.c
Normal file
527
trunk/apps/app_macro.c
Normal file
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Dial plan macro Implementation
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/config.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/lock.h"
|
||||
|
||||
#define MAX_ARGS 80
|
||||
|
||||
/* special result value used to force macro exit */
|
||||
#define MACRO_EXIT_RESULT 1024
|
||||
|
||||
static char *descrip =
|
||||
" Macro(macroname,arg1,arg2...): Executes a macro using the context\n"
|
||||
"'macro-<macroname>', jumping to the 's' extension of that context and\n"
|
||||
"executing each step, then returning when the steps end. \n"
|
||||
"The calling extension, context, and priority are stored in ${MACRO_EXTEN}, \n"
|
||||
"${MACRO_CONTEXT} and ${MACRO_PRIORITY} respectively. Arguments become\n"
|
||||
"${ARG1}, ${ARG2}, etc in the macro context.\n"
|
||||
"If you Goto out of the Macro context, the Macro will terminate and control\n"
|
||||
"will be returned at the location of the Goto.\n"
|
||||
"If ${MACRO_OFFSET} is set at termination, Macro will attempt to continue\n"
|
||||
"at priority MACRO_OFFSET + N + 1 if such a step exists, and N + 1 otherwise.\n"
|
||||
"Extensions: While a macro is being executed, it becomes the current context.\n"
|
||||
" This means that if a hangup occurs, for instance, that the macro\n"
|
||||
" will be searched for an 'h' extension, NOT the context from which\n"
|
||||
" the macro was called. So, make sure to define all appropriate\n"
|
||||
" extensions in your macro! (Note: AEL does not use macros)\n"
|
||||
"WARNING: Because of the way Macro is implemented (it executes the priorities\n"
|
||||
" contained within it via sub-engine), and a fixed per-thread\n"
|
||||
" memory stack allowance, macros are limited to 7 levels\n"
|
||||
" of nesting (macro calling macro calling macro, etc.); It\n"
|
||||
" may be possible that stack-intensive applications in deeply nested macros\n"
|
||||
" could cause asterisk to crash earlier than this limit. It is advised that\n"
|
||||
" if you need to deeply nest macro calls, that you use the Gosub application\n"
|
||||
" (now allows arguments like a Macro) with explict Return() calls instead.\n";
|
||||
|
||||
static char *if_descrip =
|
||||
" MacroIf(<expr>?macroname_a[,arg1][:macroname_b[,arg1]])\n"
|
||||
"Executes macro defined in <macroname_a> if <expr> is true\n"
|
||||
"(otherwise <macroname_b> if provided)\n"
|
||||
"Arguments and return values as in application Macro()\n";
|
||||
|
||||
static char *exclusive_descrip =
|
||||
" MacroExclusive(macroname,arg1,arg2...):\n"
|
||||
"Executes macro defined in the context 'macro-macroname'\n"
|
||||
"Only one call at a time may run the macro.\n"
|
||||
"(we'll wait if another call is busy executing in the Macro)\n"
|
||||
"Arguments and return values as in application Macro()\n";
|
||||
|
||||
static char *exit_descrip =
|
||||
" MacroExit():\n"
|
||||
"Causes the currently running macro to exit as if it had\n"
|
||||
"ended normally by running out of priorities to execute.\n"
|
||||
"If used outside a macro, will likely cause unexpected\n"
|
||||
"behavior.\n";
|
||||
|
||||
static char *app = "Macro";
|
||||
static char *if_app = "MacroIf";
|
||||
static char *exclusive_app = "MacroExclusive";
|
||||
static char *exit_app = "MacroExit";
|
||||
|
||||
static char *synopsis = "Macro Implementation";
|
||||
static char *if_synopsis = "Conditional Macro Implementation";
|
||||
static char *exclusive_synopsis = "Exclusive Macro Implementation";
|
||||
static char *exit_synopsis = "Exit From Macro";
|
||||
|
||||
|
||||
static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
|
||||
{
|
||||
struct ast_exten *e;
|
||||
struct ast_include *i;
|
||||
struct ast_context *c2;
|
||||
|
||||
for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
|
||||
if (ast_extension_match(ast_get_extension_name(e), exten)) {
|
||||
int needmatch = ast_get_extension_matchcid(e);
|
||||
if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
|
||||
(!needmatch)) {
|
||||
/* This is the matching extension we want */
|
||||
struct ast_exten *p;
|
||||
for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
|
||||
if (priority != ast_get_extension_priority(p))
|
||||
continue;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* No match; run through includes */
|
||||
for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
|
||||
for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
|
||||
if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
|
||||
e = find_matching_priority(c2, exten, priority, callerid);
|
||||
if (e)
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int _macro_exec(struct ast_channel *chan, void *data, int exclusive)
|
||||
{
|
||||
const char *s;
|
||||
char *tmp;
|
||||
char *cur, *rest;
|
||||
char *macro;
|
||||
char fullmacro[80];
|
||||
char varname[80];
|
||||
char runningapp[80], runningdata[1024];
|
||||
char *oldargs[MAX_ARGS + 1] = { NULL, };
|
||||
int argc, x;
|
||||
int res=0;
|
||||
char oldexten[256]="";
|
||||
int oldpriority, gosub_level = 0;
|
||||
char pc[80], depthc[12];
|
||||
char oldcontext[AST_MAX_CONTEXT] = "";
|
||||
const char *inhangupc;
|
||||
int offset, depth = 0, maxdepth = 7;
|
||||
int setmacrocontext=0;
|
||||
int autoloopflag, dead = 0, inhangup = 0;
|
||||
|
||||
char *save_macro_exten;
|
||||
char *save_macro_context;
|
||||
char *save_macro_priority;
|
||||
char *save_macro_offset;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "Macro() requires arguments. See \"core show application macro\" for help.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* does the user want a deeper rabbit hole? */
|
||||
s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
|
||||
if (s)
|
||||
sscanf(s, "%d", &maxdepth);
|
||||
|
||||
/* Count how many levels deep the rabbit hole goes */
|
||||
s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
|
||||
if (s)
|
||||
sscanf(s, "%d", &depth);
|
||||
/* Used for detecting whether to return when a Macro is called from another Macro after hangup */
|
||||
if (strcmp(chan->exten, "h") == 0)
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
|
||||
inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP");
|
||||
if (!ast_strlen_zero(inhangupc))
|
||||
sscanf(inhangupc, "%d", &inhangup);
|
||||
|
||||
if (depth >= maxdepth) {
|
||||
ast_log(LOG_ERROR, "Macro(): possible infinite loop detected. Returning early.\n");
|
||||
return 0;
|
||||
}
|
||||
snprintf(depthc, sizeof(depthc), "%d", depth + 1);
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
|
||||
|
||||
tmp = ast_strdupa(data);
|
||||
rest = tmp;
|
||||
macro = strsep(&rest, ",");
|
||||
if (ast_strlen_zero(macro)) {
|
||||
ast_log(LOG_WARNING, "Invalid macro name specified\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro);
|
||||
if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) {
|
||||
if (!ast_context_find(fullmacro))
|
||||
ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro);
|
||||
else
|
||||
ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If we are to run the macro exclusively, take the mutex */
|
||||
if (exclusive) {
|
||||
ast_debug(1, "Locking macrolock for '%s'\n", fullmacro);
|
||||
ast_autoservice_start(chan);
|
||||
if (ast_context_lockmacro(fullmacro)) {
|
||||
ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro);
|
||||
ast_autoservice_stop(chan);
|
||||
return 0;
|
||||
}
|
||||
ast_autoservice_stop(chan);
|
||||
}
|
||||
|
||||
/* Save old info */
|
||||
oldpriority = chan->priority;
|
||||
ast_copy_string(oldexten, chan->exten, sizeof(oldexten));
|
||||
ast_copy_string(oldcontext, chan->context, sizeof(oldcontext));
|
||||
if (ast_strlen_zero(chan->macrocontext)) {
|
||||
ast_copy_string(chan->macrocontext, chan->context, sizeof(chan->macrocontext));
|
||||
ast_copy_string(chan->macroexten, chan->exten, sizeof(chan->macroexten));
|
||||
chan->macropriority = chan->priority;
|
||||
setmacrocontext=1;
|
||||
}
|
||||
argc = 1;
|
||||
/* Save old macro variables */
|
||||
save_macro_exten = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_EXTEN"));
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", oldexten);
|
||||
|
||||
save_macro_context = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"));
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", oldcontext);
|
||||
|
||||
save_macro_priority = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_PRIORITY"));
|
||||
snprintf(pc, sizeof(pc), "%d", oldpriority);
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", pc);
|
||||
|
||||
save_macro_offset = ast_strdup(pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"));
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", NULL);
|
||||
|
||||
/* Setup environment for new run */
|
||||
chan->exten[0] = 's';
|
||||
chan->exten[1] = '\0';
|
||||
ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
|
||||
chan->priority = 1;
|
||||
|
||||
while((cur = strsep(&rest, ",")) && (argc < MAX_ARGS)) {
|
||||
const char *s;
|
||||
/* Save copy of old arguments if we're overwriting some, otherwise
|
||||
let them pass through to the other macro */
|
||||
snprintf(varname, sizeof(varname), "ARG%d", argc);
|
||||
s = pbx_builtin_getvar_helper(chan, varname);
|
||||
if (s)
|
||||
oldargs[argc] = ast_strdup(s);
|
||||
pbx_builtin_setvar_helper(chan, varname, cur);
|
||||
argc++;
|
||||
}
|
||||
autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
|
||||
ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
|
||||
while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
|
||||
struct ast_context *c;
|
||||
struct ast_exten *e;
|
||||
int foundx;
|
||||
runningapp[0] = '\0';
|
||||
runningdata[0] = '\0';
|
||||
|
||||
/* What application will execute? */
|
||||
if (ast_rdlock_contexts()) {
|
||||
ast_log(LOG_WARNING, "Failed to lock contexts list\n");
|
||||
} else {
|
||||
for (c = ast_walk_contexts(NULL), e = NULL; c; c = ast_walk_contexts(c)) {
|
||||
if (!strcmp(ast_get_context_name(c), chan->context)) {
|
||||
if (ast_rdlock_context(c)) {
|
||||
ast_log(LOG_WARNING, "Unable to lock context?\n");
|
||||
} else {
|
||||
e = find_matching_priority(c, chan->exten, chan->priority, chan->cid.cid_num);
|
||||
if (e) { /* This will only be undefined for pbx_realtime, which is majorly broken. */
|
||||
ast_copy_string(runningapp, ast_get_extension_app(e), sizeof(runningapp));
|
||||
ast_copy_string(runningdata, ast_get_extension_app_data(e), sizeof(runningdata));
|
||||
}
|
||||
ast_unlock_context(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ast_unlock_contexts();
|
||||
|
||||
/* Reset the macro depth, if it was changed in the last iteration */
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
|
||||
|
||||
if ((res = ast_spawn_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num, &foundx,1))) {
|
||||
/* Something bad happened, or a hangup has been requested. */
|
||||
if (((res >= '0') && (res <= '9')) || ((res >= 'A') && (res <= 'F')) ||
|
||||
(res == '*') || (res == '#')) {
|
||||
/* Just return result as to the previous application as if it had been dialed */
|
||||
ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res);
|
||||
break;
|
||||
}
|
||||
switch(res) {
|
||||
case MACRO_EXIT_RESULT:
|
||||
res = 0;
|
||||
goto out;
|
||||
case AST_PBX_KEEPALIVE:
|
||||
ast_debug(2, "Spawn extension (%s,%s,%d) exited KEEPALIVE in macro %s on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
|
||||
ast_verb(2, "Spawn extension (%s, %s, %d) exited KEEPALIVE in macro '%s' on '%s'\n", chan->context, chan->exten, chan->priority, macro, chan->name);
|
||||
goto out;
|
||||
break;
|
||||
default:
|
||||
ast_debug(2, "Spawn extension (%s,%s,%d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
|
||||
ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s' in macro '%s'\n", chan->context, chan->exten, chan->priority, chan->name, macro);
|
||||
dead = 1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ast_debug(1, "Executed application: %s\n", runningapp);
|
||||
|
||||
if (!strcasecmp(runningapp, "GOSUB")) {
|
||||
gosub_level++;
|
||||
ast_debug(1, "Incrementing gosub_level\n");
|
||||
} else if (!strcasecmp(runningapp, "GOSUBIF")) {
|
||||
char tmp2[1024], *cond, *app, *app2 = tmp2;
|
||||
pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
|
||||
cond = strsep(&app2, "?");
|
||||
app = strsep(&app2, ":");
|
||||
if (pbx_checkcondition(cond)) {
|
||||
if (!ast_strlen_zero(app)) {
|
||||
gosub_level++;
|
||||
ast_debug(1, "Incrementing gosub_level\n");
|
||||
}
|
||||
} else {
|
||||
if (!ast_strlen_zero(app2)) {
|
||||
gosub_level++;
|
||||
ast_debug(1, "Incrementing gosub_level\n");
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(runningapp, "RETURN")) {
|
||||
gosub_level--;
|
||||
ast_debug(1, "Decrementing gosub_level\n");
|
||||
} else if (!strcasecmp(runningapp, "STACKPOP")) {
|
||||
gosub_level--;
|
||||
ast_debug(1, "Decrementing gosub_level\n");
|
||||
} else if (!strncasecmp(runningapp, "EXEC", 4)) {
|
||||
/* Must evaluate args to find actual app */
|
||||
char tmp2[1024], *tmp3 = NULL;
|
||||
pbx_substitute_variables_helper(chan, runningdata, tmp2, sizeof(tmp2) - 1);
|
||||
if (!strcasecmp(runningapp, "EXECIF")) {
|
||||
tmp3 = strchr(tmp2, '|');
|
||||
if (tmp3)
|
||||
*tmp3++ = '\0';
|
||||
if (!pbx_checkcondition(tmp2))
|
||||
tmp3 = NULL;
|
||||
} else
|
||||
tmp3 = tmp2;
|
||||
|
||||
if (tmp3)
|
||||
ast_debug(1, "Last app: %s\n", tmp3);
|
||||
|
||||
if (tmp3 && !strncasecmp(tmp3, "GOSUB", 5)) {
|
||||
gosub_level++;
|
||||
ast_debug(1, "Incrementing gosub_level\n");
|
||||
} else if (tmp3 && !strncasecmp(tmp3, "RETURN", 6)) {
|
||||
gosub_level--;
|
||||
ast_debug(1, "Decrementing gosub_level\n");
|
||||
} else if (tmp3 && !strncasecmp(tmp3, "STACKPOP", 8)) {
|
||||
gosub_level--;
|
||||
ast_debug(1, "Decrementing gosub_level\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (gosub_level == 0 && strcasecmp(chan->context, fullmacro)) {
|
||||
ast_verb(2, "Channel '%s' jumping out of macro '%s'\n", chan->name, macro);
|
||||
break;
|
||||
}
|
||||
|
||||
/* don't stop executing extensions when we're in "h" */
|
||||
if (ast_check_hangup(chan) && !inhangup) {
|
||||
ast_debug(1, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n", chan->exten, chan->macroexten, chan->priority);
|
||||
goto out;
|
||||
}
|
||||
chan->priority++;
|
||||
}
|
||||
out:
|
||||
/* Reset the depth back to what it was when the routine was entered (like if we called Macro recursively) */
|
||||
snprintf(depthc, sizeof(depthc), "%d", depth);
|
||||
if (!dead) {
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc);
|
||||
ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP);
|
||||
}
|
||||
|
||||
for (x = 1; x < argc; x++) {
|
||||
/* Restore old arguments and delete ours */
|
||||
snprintf(varname, sizeof(varname), "ARG%d", x);
|
||||
if (oldargs[x]) {
|
||||
if (!dead)
|
||||
pbx_builtin_setvar_helper(chan, varname, oldargs[x]);
|
||||
ast_free(oldargs[x]);
|
||||
} else if (!dead) {
|
||||
pbx_builtin_setvar_helper(chan, varname, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Restore macro variables */
|
||||
if (!dead) {
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_EXTEN", save_macro_exten);
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_CONTEXT", save_macro_context);
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_PRIORITY", save_macro_priority);
|
||||
}
|
||||
if (save_macro_exten)
|
||||
ast_free(save_macro_exten);
|
||||
if (save_macro_context)
|
||||
ast_free(save_macro_context);
|
||||
if (save_macro_priority)
|
||||
ast_free(save_macro_priority);
|
||||
|
||||
if (!dead && setmacrocontext) {
|
||||
chan->macrocontext[0] = '\0';
|
||||
chan->macroexten[0] = '\0';
|
||||
chan->macropriority = 0;
|
||||
}
|
||||
|
||||
if (!dead && !strcasecmp(chan->context, fullmacro)) {
|
||||
/* If we're leaving the macro normally, restore original information */
|
||||
chan->priority = oldpriority;
|
||||
ast_copy_string(chan->context, oldcontext, sizeof(chan->context));
|
||||
if (!(ast_check_hangup(chan) & AST_SOFTHANGUP_ASYNCGOTO)) {
|
||||
/* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
|
||||
const char *offsets;
|
||||
ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
|
||||
if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
|
||||
/* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
|
||||
normally if there is any problem */
|
||||
if (sscanf(offsets, "%d", &offset) == 1) {
|
||||
if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + offset + 1, chan->cid.cid_num)) {
|
||||
chan->priority += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dead)
|
||||
pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset);
|
||||
if (save_macro_offset)
|
||||
ast_free(save_macro_offset);
|
||||
|
||||
/* Unlock the macro */
|
||||
if (exclusive) {
|
||||
ast_debug(1, "Unlocking macrolock for '%s'\n", fullmacro);
|
||||
if (ast_context_unlockmacro(fullmacro)) {
|
||||
ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro);
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int macro_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
return _macro_exec(chan, data, 0);
|
||||
}
|
||||
|
||||
static int macroexclusive_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
return _macro_exec(chan, data, 1);
|
||||
}
|
||||
|
||||
static int macroif_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *expr = NULL, *label_a = NULL, *label_b = NULL;
|
||||
int res = 0;
|
||||
|
||||
if (!(expr = ast_strdupa(data)))
|
||||
return -1;
|
||||
|
||||
if ((label_a = strchr(expr, '?'))) {
|
||||
*label_a = '\0';
|
||||
label_a++;
|
||||
if ((label_b = strchr(label_a, ':'))) {
|
||||
*label_b = '\0';
|
||||
label_b++;
|
||||
}
|
||||
if (pbx_checkcondition(expr))
|
||||
res = macro_exec(chan, label_a);
|
||||
else if (label_b)
|
||||
res = macro_exec(chan, label_b);
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Invalid Syntax.\n");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int macro_exit_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
return MACRO_EXIT_RESULT;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(if_app);
|
||||
res |= ast_unregister_application(exit_app);
|
||||
res |= ast_unregister_application(app);
|
||||
res |= ast_unregister_application(exclusive_app);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip);
|
||||
res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip);
|
||||
res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip);
|
||||
res |= ast_register_application(app, macro_exec, synopsis, descrip);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Macros");
|
||||
5592
trunk/apps/app_meetme.c
Normal file
5592
trunk/apps/app_meetme.c
Normal file
File diff suppressed because it is too large
Load Diff
134
trunk/apps/app_milliwatt.c
Normal file
134
trunk/apps/app_milliwatt.c
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Digital Milliwatt Test
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
static char *app = "Milliwatt";
|
||||
|
||||
static char *synopsis = "Generate a Constant 1000Hz tone at 0dbm (mu-law)";
|
||||
|
||||
static char *descrip =
|
||||
"Milliwatt(): Generate a Constant 1000Hz tone at 0dbm (mu-law)\n";
|
||||
|
||||
static char digital_milliwatt[] = {0x1e,0x0b,0x0b,0x1e,0x9e,0x8b,0x8b,0x9e} ;
|
||||
|
||||
static void *milliwatt_alloc(struct ast_channel *chan, void *params)
|
||||
{
|
||||
return ast_calloc(1, sizeof(int));
|
||||
}
|
||||
|
||||
static void milliwatt_release(struct ast_channel *chan, void *data)
|
||||
{
|
||||
ast_free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
static int milliwatt_generate(struct ast_channel *chan, void *data, int len, int samples)
|
||||
{
|
||||
unsigned char buf[AST_FRIENDLY_OFFSET + 640];
|
||||
const int maxsamples = sizeof (buf) / sizeof (buf[0]);
|
||||
int i, *indexp = (int *) data;
|
||||
struct ast_frame wf = {
|
||||
.frametype = AST_FRAME_VOICE,
|
||||
.subclass = AST_FORMAT_ULAW,
|
||||
.offset = AST_FRIENDLY_OFFSET,
|
||||
.data = buf + AST_FRIENDLY_OFFSET,
|
||||
.src = __FUNCTION__,
|
||||
};
|
||||
|
||||
/* Instead of len, use samples, because channel.c generator_force
|
||||
* generate(chan, tmp, 0, 160) ignores len. In any case, len is
|
||||
* a multiple of samples, given by number of samples times bytes per
|
||||
* sample. In the case of ulaw, len = samples. for signed linear
|
||||
* len = 2 * samples */
|
||||
if (samples > maxsamples) {
|
||||
ast_log(LOG_WARNING, "Only doing %d samples (%d requested)\n", maxsamples, samples);
|
||||
samples = maxsamples;
|
||||
}
|
||||
len = samples * sizeof (buf[0]);
|
||||
wf.datalen = len;
|
||||
wf.samples = samples;
|
||||
|
||||
/* create a buffer containing the digital milliwatt pattern */
|
||||
for (i = 0; i < len; i++) {
|
||||
buf[AST_FRIENDLY_OFFSET + i] = digital_milliwatt[(*indexp)++];
|
||||
*indexp &= 7;
|
||||
}
|
||||
|
||||
if (ast_write(chan,&wf) < 0) {
|
||||
ast_log(LOG_WARNING,"Failed to write frame to '%s': %s\n",chan->name,strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_generator milliwattgen =
|
||||
{
|
||||
alloc: milliwatt_alloc,
|
||||
release: milliwatt_release,
|
||||
generate: milliwatt_generate,
|
||||
};
|
||||
|
||||
static int milliwatt_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
|
||||
ast_set_write_format(chan, AST_FORMAT_ULAW);
|
||||
ast_set_read_format(chan, AST_FORMAT_ULAW);
|
||||
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
ast_answer(chan);
|
||||
|
||||
if (ast_activate_generator(chan,&milliwattgen,"milliwatt") < 0) {
|
||||
ast_log(LOG_WARNING,"Failed to activate generator on '%s'\n",chan->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(!ast_safe_sleep(chan, 10000));
|
||||
|
||||
ast_deactivate_generator(chan);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, milliwatt_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Digital Milliwatt (mu-law) Test Application");
|
||||
3109
trunk/apps/app_minivm.c
Normal file
3109
trunk/apps/app_minivm.c
Normal file
File diff suppressed because it is too large
Load Diff
424
trunk/apps/app_mixmonitor.c
Normal file
424
trunk/apps/app_mixmonitor.c
Normal file
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2005, Anthony Minessale II
|
||||
* Copyright (C) 2005 - 2006, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
* Kevin P. Fleming <kpfleming@digium.com>
|
||||
*
|
||||
* Based on app_muxmon.c provided by
|
||||
* Anthony Minessale II <anthmct@yahoo.com>
|
||||
*
|
||||
* 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 MixMonitor() - Record a call and mix the audio during the recording
|
||||
* \ingroup applications
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
* \author Kevin P. Fleming <kpfleming@digium.com>
|
||||
*
|
||||
* \note Based on app_muxmon.c provided by
|
||||
* Anthony Minessale II <anthmct@yahoo.com>
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/audiohook.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/cli.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
#define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
|
||||
|
||||
static const char *app = "MixMonitor";
|
||||
static const char *synopsis = "Record a call and mix the audio during the recording";
|
||||
static const char *desc = ""
|
||||
" MixMonitor(<file>.<ext>[,<options>[,<command>]]):\n"
|
||||
"Records the audio on the current channel to the specified file.\n"
|
||||
"If the filename is an absolute path, uses that path, otherwise\n"
|
||||
"creates the file in the configured monitoring directory from\n"
|
||||
"asterisk.conf.\n\n"
|
||||
"Valid options:\n"
|
||||
" a - Append to the file instead of overwriting it.\n"
|
||||
" b - Only save audio to the file while the channel is bridged.\n"
|
||||
" Note: Does not include conferences or sounds played to each bridged\n"
|
||||
" party.\n"
|
||||
" v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"
|
||||
" V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"
|
||||
" W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
|
||||
" (range -4 to 4)\n\n"
|
||||
"<command> will be executed when the recording is over\n"
|
||||
"Any strings matching ^{X} will be unescaped to ${X}.\n"
|
||||
"All variables will be evaluated at the time MixMonitor is called.\n"
|
||||
"The variable MIXMONITOR_FILENAME will contain the filename used to record.\n"
|
||||
"";
|
||||
|
||||
static const char *stop_app = "StopMixMonitor";
|
||||
static const char *stop_synopsis = "Stop recording a call through MixMonitor";
|
||||
static const char *stop_desc = ""
|
||||
" StopMixMonitor():\n"
|
||||
"Stops the audio recording that was started with a call to MixMonitor()\n"
|
||||
"on the current channel.\n"
|
||||
"";
|
||||
|
||||
struct module_symbols *me;
|
||||
|
||||
static const char *mixmonitor_spy_type = "MixMonitor";
|
||||
|
||||
struct mixmonitor {
|
||||
struct ast_audiohook audiohook;
|
||||
char *filename;
|
||||
char *post_process;
|
||||
char *name;
|
||||
unsigned int flags;
|
||||
struct ast_channel *chan;
|
||||
};
|
||||
|
||||
enum {
|
||||
MUXFLAG_APPEND = (1 << 1),
|
||||
MUXFLAG_BRIDGED = (1 << 2),
|
||||
MUXFLAG_VOLUME = (1 << 3),
|
||||
MUXFLAG_READVOLUME = (1 << 4),
|
||||
MUXFLAG_WRITEVOLUME = (1 << 5),
|
||||
} mixmonitor_flags;
|
||||
|
||||
enum {
|
||||
OPT_ARG_READVOLUME = 0,
|
||||
OPT_ARG_WRITEVOLUME,
|
||||
OPT_ARG_VOLUME,
|
||||
OPT_ARG_ARRAY_SIZE,
|
||||
} mixmonitor_args;
|
||||
|
||||
AST_APP_OPTIONS(mixmonitor_opts, {
|
||||
AST_APP_OPTION('a', MUXFLAG_APPEND),
|
||||
AST_APP_OPTION('b', MUXFLAG_BRIDGED),
|
||||
AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
|
||||
AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
|
||||
AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
|
||||
});
|
||||
|
||||
static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
|
||||
{
|
||||
struct ast_channel *peer = NULL;
|
||||
int res = 0;
|
||||
|
||||
if (!chan)
|
||||
return -1;
|
||||
|
||||
ast_audiohook_attach(chan, audiohook);
|
||||
|
||||
if (!res && ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
|
||||
ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#define SAMPLES_PER_FRAME 160
|
||||
|
||||
static void *mixmonitor_thread(void *obj)
|
||||
{
|
||||
struct mixmonitor *mixmonitor = obj;
|
||||
struct ast_filestream *fs = NULL;
|
||||
unsigned int oflags;
|
||||
char *ext;
|
||||
int errflag = 0;
|
||||
|
||||
ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
|
||||
|
||||
ast_audiohook_lock(&mixmonitor->audiohook);
|
||||
|
||||
while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
|
||||
struct ast_frame *fr = NULL;
|
||||
|
||||
ast_audiohook_trigger_wait(&mixmonitor->audiohook);
|
||||
|
||||
if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
|
||||
break;
|
||||
|
||||
if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
|
||||
continue;
|
||||
|
||||
if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || ast_bridged_channel(mixmonitor->chan)) {
|
||||
/* Initialize the file if not already done so */
|
||||
if (!fs && !errflag) {
|
||||
oflags = O_CREAT | O_WRONLY;
|
||||
oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
|
||||
|
||||
if ((ext = strrchr(mixmonitor->filename, '.')))
|
||||
*(ext++) = '\0';
|
||||
else
|
||||
ext = "raw";
|
||||
|
||||
if (!(fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0644))) {
|
||||
ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
|
||||
errflag = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write out frame */
|
||||
if (fs)
|
||||
ast_writestream(fs, fr);
|
||||
}
|
||||
|
||||
/* All done! free it. */
|
||||
ast_frame_free(fr, 0);
|
||||
|
||||
}
|
||||
|
||||
ast_audiohook_detach(&mixmonitor->audiohook);
|
||||
ast_audiohook_unlock(&mixmonitor->audiohook);
|
||||
ast_audiohook_destroy(&mixmonitor->audiohook);
|
||||
|
||||
ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
|
||||
|
||||
if (fs)
|
||||
ast_closestream(fs);
|
||||
|
||||
if (mixmonitor->post_process) {
|
||||
ast_verb(2, "Executing [%s]\n", mixmonitor->post_process);
|
||||
ast_safe_system(mixmonitor->post_process);
|
||||
}
|
||||
|
||||
ast_free(mixmonitor);
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
|
||||
int readvol, int writevol, const char *post_process)
|
||||
{
|
||||
pthread_t thread;
|
||||
struct mixmonitor *mixmonitor;
|
||||
char postprocess2[1024] = "";
|
||||
size_t len;
|
||||
|
||||
len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
|
||||
|
||||
postprocess2[0] = 0;
|
||||
/* If a post process system command is given attach it to the structure */
|
||||
if (!ast_strlen_zero(post_process)) {
|
||||
char *p1, *p2;
|
||||
|
||||
p1 = ast_strdupa(post_process);
|
||||
for (p2 = p1; *p2 ; p2++) {
|
||||
if (*p2 == '^' && *(p2+1) == '{') {
|
||||
*p2 = '$';
|
||||
}
|
||||
}
|
||||
pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
|
||||
if (!ast_strlen_zero(postprocess2))
|
||||
len += strlen(postprocess2) + 1;
|
||||
}
|
||||
|
||||
/* Pre-allocate mixmonitor structure and spy */
|
||||
if (!(mixmonitor = ast_calloc(1, len))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Copy over flags and channel name */
|
||||
mixmonitor->flags = flags;
|
||||
mixmonitor->chan = chan;
|
||||
mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
|
||||
strcpy(mixmonitor->name, chan->name);
|
||||
if (!ast_strlen_zero(postprocess2)) {
|
||||
mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
|
||||
strcpy(mixmonitor->post_process, postprocess2);
|
||||
}
|
||||
|
||||
mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
|
||||
strcpy(mixmonitor->filename, filename);
|
||||
|
||||
/* Setup the actual spy before creating our thread */
|
||||
if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type)) {
|
||||
ast_free(mixmonitor);
|
||||
return;
|
||||
}
|
||||
|
||||
ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_WRITE);
|
||||
|
||||
if (readvol)
|
||||
mixmonitor->audiohook.options.read_volume = readvol;
|
||||
if (writevol)
|
||||
mixmonitor->audiohook.options.write_volume = writevol;
|
||||
|
||||
if (startmon(chan, &mixmonitor->audiohook)) {
|
||||
ast_log(LOG_WARNING, "Unable to add '%s' spy to channel '%s'\n",
|
||||
mixmonitor_spy_type, chan->name);
|
||||
ast_audiohook_destroy(&mixmonitor->audiohook);
|
||||
ast_free(mixmonitor);
|
||||
return;
|
||||
}
|
||||
|
||||
ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
|
||||
|
||||
}
|
||||
|
||||
static int mixmonitor_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int x, readvol = 0, writevol = 0;
|
||||
struct ast_flags flags = {0};
|
||||
char *parse, *tmp, *slash;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(filename);
|
||||
AST_APP_ARG(options);
|
||||
AST_APP_ARG(post_process);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (ast_strlen_zero(args.filename)) {
|
||||
ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (args.options) {
|
||||
char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
|
||||
|
||||
ast_app_parse_options(mixmonitor_opts, &flags, opts, args.options);
|
||||
|
||||
if (ast_test_flag(&flags, MUXFLAG_READVOLUME)) {
|
||||
if (ast_strlen_zero(opts[OPT_ARG_READVOLUME])) {
|
||||
ast_log(LOG_WARNING, "No volume level was provided for the heard volume ('v') option.\n");
|
||||
} else if ((sscanf(opts[OPT_ARG_READVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
|
||||
ast_log(LOG_NOTICE, "Heard volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_READVOLUME]);
|
||||
} else {
|
||||
readvol = get_volfactor(x);
|
||||
}
|
||||
}
|
||||
|
||||
if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME)) {
|
||||
if (ast_strlen_zero(opts[OPT_ARG_WRITEVOLUME])) {
|
||||
ast_log(LOG_WARNING, "No volume level was provided for the spoken volume ('V') option.\n");
|
||||
} else if ((sscanf(opts[OPT_ARG_WRITEVOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
|
||||
ast_log(LOG_NOTICE, "Spoken volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_WRITEVOLUME]);
|
||||
} else {
|
||||
writevol = get_volfactor(x);
|
||||
}
|
||||
}
|
||||
|
||||
if (ast_test_flag(&flags, MUXFLAG_VOLUME)) {
|
||||
if (ast_strlen_zero(opts[OPT_ARG_VOLUME])) {
|
||||
ast_log(LOG_WARNING, "No volume level was provided for the combined volume ('W') option.\n");
|
||||
} else if ((sscanf(opts[OPT_ARG_VOLUME], "%d", &x) != 1) || (x < -4) || (x > 4)) {
|
||||
ast_log(LOG_NOTICE, "Combined volume must be a number between -4 and 4, not '%s'\n", opts[OPT_ARG_VOLUME]);
|
||||
} else {
|
||||
readvol = writevol = get_volfactor(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if not provided an absolute path, use the system-configured monitoring directory */
|
||||
if (args.filename[0] != '/') {
|
||||
char *build;
|
||||
|
||||
build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
|
||||
sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
|
||||
args.filename = build;
|
||||
}
|
||||
|
||||
tmp = ast_strdupa(args.filename);
|
||||
if ((slash = strrchr(tmp, '/')))
|
||||
*slash = '\0';
|
||||
ast_mkdir(tmp, 0777);
|
||||
|
||||
pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
|
||||
launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stop_mixmonitor_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
ast_audiohook_detach_source(chan, mixmonitor_spy_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
||||
{
|
||||
struct ast_channel *chan;
|
||||
|
||||
switch (cmd) {
|
||||
case CLI_INIT:
|
||||
e->command = "mixmonitor [start|stop]";
|
||||
e->usage =
|
||||
"Usage: mixmonitor <start|stop> <chan_name> [args]\n"
|
||||
" The optional arguments are passed to the MixMonitor\n"
|
||||
" application when the 'start' command is used.\n";
|
||||
return NULL;
|
||||
case CLI_GENERATE:
|
||||
return ast_complete_channels(a->line, a->word, a->pos, a->n, 2);
|
||||
}
|
||||
|
||||
if (a->argc < 3)
|
||||
return CLI_SHOWUSAGE;
|
||||
|
||||
if (!(chan = ast_get_channel_by_name_prefix_locked(a->argv[2], strlen(a->argv[2])))) {
|
||||
ast_cli(a->fd, "No channel matching '%s' found.\n", a->argv[2]);
|
||||
/* Technically this is a failure, but we don't want 2 errors printing out */
|
||||
return CLI_SUCCESS;
|
||||
}
|
||||
|
||||
if (!strcasecmp(a->argv[1], "start")) {
|
||||
mixmonitor_exec(chan, a->argv[3]);
|
||||
ast_channel_unlock(chan);
|
||||
} else {
|
||||
ast_channel_unlock(chan);
|
||||
ast_audiohook_detach_source(chan, mixmonitor_spy_type);
|
||||
}
|
||||
|
||||
return CLI_SUCCESS;
|
||||
}
|
||||
|
||||
static struct ast_cli_entry cli_mixmonitor[] = {
|
||||
AST_CLI_DEFINE(handle_cli_mixmonitor, "Execute a MixMonitor command")
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
ast_cli_unregister_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
|
||||
res = ast_unregister_application(stop_app);
|
||||
res |= ast_unregister_application(app);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
ast_cli_register_multiple(cli_mixmonitor, sizeof(cli_mixmonitor) / sizeof(struct ast_cli_entry));
|
||||
res = ast_register_application(app, mixmonitor_exec, synopsis, desc);
|
||||
res |= ast_register_application(stop_app, stop_mixmonitor_exec, stop_synopsis, stop_desc);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Mixed Audio Monitoring Application");
|
||||
161
trunk/apps/app_morsecode.c
Normal file
161
trunk/apps/app_morsecode.c
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (c) 2006, Tilghman Lesher. All rights reserved.
|
||||
*
|
||||
* Tilghman Lesher <app_morsecode__v001@the-tilghman.com>
|
||||
*
|
||||
* This code is released by the author with no restrictions on usage.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Morsecode application
|
||||
*
|
||||
* \author Tilghman Lesher <app_morsecode__v001@the-tilghman.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/indications.h"
|
||||
|
||||
static char *app_morsecode = "Morsecode";
|
||||
|
||||
static char *morsecode_synopsis = "Plays morse code";
|
||||
|
||||
static char *morsecode_descrip =
|
||||
" Morsecode(<string>):\n"
|
||||
"Plays the Morse code equivalent of the passed string. If the variable\n"
|
||||
"MORSEDITLEN is set, it will use that value for the length (in ms) of the dit\n"
|
||||
"(defaults to 80). Additionally, if MORSETONE is set, it will use that tone\n"
|
||||
"(in Hz). The tone default is 800.\n";
|
||||
|
||||
|
||||
static char *morsecode[] = {
|
||||
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 0-15 */
|
||||
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", /* 16-31 */
|
||||
" ", /* 32 - <space> */
|
||||
".-.-.-", /* 33 - ! */
|
||||
".-..-.", /* 34 - " */
|
||||
"", /* 35 - # */
|
||||
"", /* 36 - $ */
|
||||
"", /* 37 - % */
|
||||
"", /* 38 - & */
|
||||
".----.", /* 39 - ' */
|
||||
"-.--.-", /* 40 - ( */
|
||||
"-.--.-", /* 41 - ) */
|
||||
"", /* 42 - * */
|
||||
"", /* 43 - + */
|
||||
"--..--", /* 44 - , */
|
||||
"-....-", /* 45 - - */
|
||||
".-.-.-", /* 46 - . */
|
||||
"-..-.", /* 47 - / */
|
||||
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.", /* 48-57 - 0-9 */
|
||||
"---...", /* 58 - : */
|
||||
"-.-.-.", /* 59 - ; */
|
||||
"", /* 60 - < */
|
||||
"-...-", /* 61 - = */
|
||||
"", /* 62 - > */
|
||||
"..--..", /* 63 - ? */
|
||||
".--.-.", /* 64 - @ */
|
||||
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
|
||||
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
|
||||
"-.--.-", /* 91 - [ (really '(') */
|
||||
"-..-.", /* 92 - \ (really '/') */
|
||||
"-.--.-", /* 93 - ] (really ')') */
|
||||
"", /* 94 - ^ */
|
||||
"..--.-", /* 95 - _ */
|
||||
".----.", /* 96 - ` */
|
||||
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
|
||||
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
|
||||
"-.--.-", /* 123 - { (really '(') */
|
||||
"", /* 124 - | */
|
||||
"-.--.-", /* 125 - } (really ')') */
|
||||
"-..-.", /* 126 - ~ (really bar) */
|
||||
". . .", /* 127 - <del> (error) */
|
||||
};
|
||||
|
||||
static void playtone(struct ast_channel *chan, int tone, int len)
|
||||
{
|
||||
char dtmf[20];
|
||||
snprintf(dtmf, sizeof(dtmf), "%d/%d", tone, len);
|
||||
ast_playtones_start(chan, 0, dtmf, 0);
|
||||
ast_safe_sleep(chan, len);
|
||||
ast_playtones_stop(chan);
|
||||
}
|
||||
|
||||
static int morsecode_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=0, ditlen, tone;
|
||||
char *digit;
|
||||
const char *ditlenc, *tonec;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "Syntax: Morsecode(<string>) - no argument found\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Use variable MORESEDITLEN, if set (else 80) */
|
||||
ditlenc = pbx_builtin_getvar_helper(chan, "MORSEDITLEN");
|
||||
if (ast_strlen_zero(ditlenc) || (sscanf(ditlenc, "%d", &ditlen) != 1)) {
|
||||
ditlen = 80;
|
||||
}
|
||||
|
||||
/* Use variable MORSETONE, if set (else 800) */
|
||||
tonec = pbx_builtin_getvar_helper(chan, "MORSETONE");
|
||||
if (ast_strlen_zero(tonec) || (sscanf(tonec, "%d", &tone) != 1)) {
|
||||
tone = 800;
|
||||
}
|
||||
|
||||
for (digit = data; *digit; digit++) {
|
||||
int digit2 = *digit;
|
||||
char *dahdit;
|
||||
if (digit2 < 0) {
|
||||
continue;
|
||||
}
|
||||
for (dahdit = morsecode[digit2]; *dahdit; dahdit++) {
|
||||
if (*dahdit == '-') {
|
||||
playtone(chan, tone, 3 * ditlen);
|
||||
} else if (*dahdit == '.') {
|
||||
playtone(chan, tone, 1 * ditlen);
|
||||
} else {
|
||||
/* Account for ditlen of silence immediately following */
|
||||
playtone(chan, 0, 2 * ditlen);
|
||||
}
|
||||
|
||||
/* Pause slightly between each dit and dah */
|
||||
playtone(chan, 0, 1 * ditlen);
|
||||
}
|
||||
/* Pause between characters */
|
||||
playtone(chan, 0, 2 * ditlen);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app_morsecode);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app_morsecode, morsecode_exec, morsecode_synopsis, morsecode_descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Morse code");
|
||||
235
trunk/apps/app_mp3.c
Normal file
235
trunk/apps/app_mp3.c
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Silly application to play an MP3 file -- uses mpg123
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/frame.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
|
||||
#define LOCAL_MPG_123 "/usr/local/bin/mpg123"
|
||||
#define MPG_123 "/usr/bin/mpg123"
|
||||
|
||||
static char *app = "MP3Player";
|
||||
|
||||
static char *synopsis = "Play an MP3 file or stream";
|
||||
|
||||
static char *descrip =
|
||||
" MP3Player(location): Executes mpg123 to play the given location,\n"
|
||||
"which typically would be a filename or a URL. User can exit by pressing\n"
|
||||
"any key on the dialpad, or by hanging up.";
|
||||
|
||||
|
||||
static int mp3play(char *filename, int fd)
|
||||
{
|
||||
int res;
|
||||
int x;
|
||||
sigset_t fullset, oldset;
|
||||
|
||||
sigfillset(&fullset);
|
||||
pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
|
||||
|
||||
res = fork();
|
||||
if (res < 0)
|
||||
ast_log(LOG_WARNING, "Fork failed\n");
|
||||
if (res) {
|
||||
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
|
||||
return res;
|
||||
}
|
||||
if (ast_opt_high_priority)
|
||||
ast_set_priority(0);
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
|
||||
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
for (x=STDERR_FILENO + 1;x<256;x++) {
|
||||
if (x != STDOUT_FILENO)
|
||||
close(x);
|
||||
}
|
||||
/* Execute mpg123, but buffer if it's a net connection */
|
||||
if (!strncasecmp(filename, "http://", 7)) {
|
||||
/* Most commonly installed in /usr/local/bin */
|
||||
execl(LOCAL_MPG_123, "mpg123", "-q", "-s", "-b", "1024", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
|
||||
/* But many places has it in /usr/bin */
|
||||
execl(MPG_123, "mpg123", "-q", "-s", "-b", "1024","-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
|
||||
/* As a last-ditch effort, try to use PATH */
|
||||
execlp("mpg123", "mpg123", "-q", "-s", "-b", "1024", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
|
||||
}
|
||||
else {
|
||||
/* Most commonly installed in /usr/local/bin */
|
||||
execl(MPG_123, "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
|
||||
/* But many places has it in /usr/bin */
|
||||
execl(LOCAL_MPG_123, "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
|
||||
/* As a last-ditch effort, try to use PATH */
|
||||
execlp("mpg123", "mpg123", "-q", "-s", "-f", "8192", "--mono", "-r", "8000", filename, (char *)NULL);
|
||||
}
|
||||
ast_log(LOG_WARNING, "Execute of mpg123 failed\n");
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
static int timed_read(int fd, void *data, int datalen, int timeout)
|
||||
{
|
||||
int res;
|
||||
struct pollfd fds[1];
|
||||
fds[0].fd = fd;
|
||||
fds[0].events = POLLIN;
|
||||
res = poll(fds, 1, timeout);
|
||||
if (res < 1) {
|
||||
ast_log(LOG_NOTICE, "Poll timed out/errored out with %d\n", res);
|
||||
return -1;
|
||||
}
|
||||
return read(fd, data, datalen);
|
||||
|
||||
}
|
||||
|
||||
static int mp3_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=0;
|
||||
int fds[2];
|
||||
int ms = -1;
|
||||
int pid = -1;
|
||||
int owriteformat;
|
||||
int timeout = 2000;
|
||||
struct timeval next;
|
||||
struct ast_frame *f;
|
||||
struct myframe {
|
||||
struct ast_frame f;
|
||||
char offset[AST_FRIENDLY_OFFSET];
|
||||
short frdata[160];
|
||||
} myf;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "MP3 Playback requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pipe(fds)) {
|
||||
ast_log(LOG_WARNING, "Unable to create pipe\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_stopstream(chan);
|
||||
|
||||
owriteformat = chan->writeformat;
|
||||
res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
|
||||
if (res < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = mp3play((char *)data, fds[1]);
|
||||
if (!strncasecmp((char *)data, "http://", 7)) {
|
||||
timeout = 10000;
|
||||
}
|
||||
/* Wait 1000 ms first */
|
||||
next = ast_tvnow();
|
||||
next.tv_sec += 1;
|
||||
if (res >= 0) {
|
||||
pid = res;
|
||||
/* Order is important -- there's almost always going to be mp3... we want to prioritize the
|
||||
user */
|
||||
for (;;) {
|
||||
ms = ast_tvdiff_ms(next, ast_tvnow());
|
||||
if (ms <= 0) {
|
||||
res = timed_read(fds[0], myf.frdata, sizeof(myf.frdata), timeout);
|
||||
if (res > 0) {
|
||||
myf.f.frametype = AST_FRAME_VOICE;
|
||||
myf.f.subclass = AST_FORMAT_SLINEAR;
|
||||
myf.f.datalen = res;
|
||||
myf.f.samples = res / 2;
|
||||
myf.f.mallocd = 0;
|
||||
myf.f.offset = AST_FRIENDLY_OFFSET;
|
||||
myf.f.src = __PRETTY_FUNCTION__;
|
||||
myf.f.delivery.tv_sec = 0;
|
||||
myf.f.delivery.tv_usec = 0;
|
||||
myf.f.data = myf.frdata;
|
||||
if (ast_write(chan, &myf.f) < 0) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ast_debug(1, "No more mp3\n");
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
next = ast_tvadd(next, ast_samp2tv(myf.f.samples, 8000));
|
||||
} else {
|
||||
ms = ast_waitfor(chan, ms);
|
||||
if (ms < 0) {
|
||||
ast_debug(1, "Hangup detected\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if (ms) {
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
ast_debug(1, "Null frame == hangup() detected\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if (f->frametype == AST_FRAME_DTMF) {
|
||||
ast_debug(1, "User pressed a key\n");
|
||||
ast_frfree(f);
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
|
||||
if (pid > -1)
|
||||
kill(pid, SIGKILL);
|
||||
if (!res && owriteformat)
|
||||
ast_set_write_format(chan, owriteformat);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, mp3_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Silly MP3 Application");
|
||||
218
trunk/apps/app_nbscat.c
Normal file
218
trunk/apps/app_nbscat.c
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Silly application to play an NBScat file -- uses nbscat8k
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/frame.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
|
||||
#define LOCAL_NBSCAT "/usr/local/bin/nbscat8k"
|
||||
#define NBSCAT "/usr/bin/nbscat8k"
|
||||
|
||||
#ifndef AF_LOCAL
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
static char *app = "NBScat";
|
||||
|
||||
static char *synopsis = "Play an NBS local stream";
|
||||
|
||||
static char *descrip =
|
||||
" NBScat(): Executes nbscat to listen to the local NBS stream.\n"
|
||||
"User can exit by pressing any key.\n";
|
||||
|
||||
|
||||
static int NBScatplay(int fd)
|
||||
{
|
||||
int res;
|
||||
int x;
|
||||
sigset_t fullset, oldset;
|
||||
|
||||
sigfillset(&fullset);
|
||||
pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
|
||||
|
||||
res = fork();
|
||||
if (res < 0)
|
||||
ast_log(LOG_WARNING, "Fork failed\n");
|
||||
if (res) {
|
||||
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
|
||||
return res;
|
||||
}
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
|
||||
|
||||
if (ast_opt_high_priority)
|
||||
ast_set_priority(0);
|
||||
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
for (x = STDERR_FILENO + 1; x < 1024; x++) {
|
||||
if (x != STDOUT_FILENO)
|
||||
close(x);
|
||||
}
|
||||
/* Most commonly installed in /usr/local/bin */
|
||||
execl(NBSCAT, "nbscat8k", "-d", (char *)NULL);
|
||||
execl(LOCAL_NBSCAT, "nbscat8k", "-d", (char *)NULL);
|
||||
ast_log(LOG_WARNING, "Execute of nbscat8k failed\n");
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
static int timed_read(int fd, void *data, int datalen)
|
||||
{
|
||||
int res;
|
||||
struct pollfd fds[1];
|
||||
fds[0].fd = fd;
|
||||
fds[0].events = POLLIN;
|
||||
res = poll(fds, 1, 2000);
|
||||
if (res < 1) {
|
||||
ast_log(LOG_NOTICE, "Selected timed out/errored out with %d\n", res);
|
||||
return -1;
|
||||
}
|
||||
return read(fd, data, datalen);
|
||||
|
||||
}
|
||||
|
||||
static int NBScat_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=0;
|
||||
int fds[2];
|
||||
int ms = -1;
|
||||
int pid = -1;
|
||||
int owriteformat;
|
||||
struct timeval next;
|
||||
struct ast_frame *f;
|
||||
struct myframe {
|
||||
struct ast_frame f;
|
||||
char offset[AST_FRIENDLY_OFFSET];
|
||||
short frdata[160];
|
||||
} myf;
|
||||
|
||||
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)) {
|
||||
ast_log(LOG_WARNING, "Unable to create socketpair\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_stopstream(chan);
|
||||
|
||||
owriteformat = chan->writeformat;
|
||||
res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
|
||||
if (res < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = NBScatplay(fds[1]);
|
||||
/* Wait 1000 ms first */
|
||||
next = ast_tvnow();
|
||||
next.tv_sec += 1;
|
||||
if (res >= 0) {
|
||||
pid = res;
|
||||
/* Order is important -- there's almost always going to be mp3... we want to prioritize the
|
||||
user */
|
||||
for (;;) {
|
||||
ms = ast_tvdiff_ms(next, ast_tvnow());
|
||||
if (ms <= 0) {
|
||||
res = timed_read(fds[0], myf.frdata, sizeof(myf.frdata));
|
||||
if (res > 0) {
|
||||
myf.f.frametype = AST_FRAME_VOICE;
|
||||
myf.f.subclass = AST_FORMAT_SLINEAR;
|
||||
myf.f.datalen = res;
|
||||
myf.f.samples = res / 2;
|
||||
myf.f.mallocd = 0;
|
||||
myf.f.offset = AST_FRIENDLY_OFFSET;
|
||||
myf.f.src = __PRETTY_FUNCTION__;
|
||||
myf.f.delivery.tv_sec = 0;
|
||||
myf.f.delivery.tv_usec = 0;
|
||||
myf.f.data = myf.frdata;
|
||||
if (ast_write(chan, &myf.f) < 0) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ast_debug(1, "No more mp3\n");
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
next = ast_tvadd(next, ast_samp2tv(myf.f.samples, 8000));
|
||||
} else {
|
||||
ms = ast_waitfor(chan, ms);
|
||||
if (ms < 0) {
|
||||
ast_debug(1, "Hangup detected\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if (ms) {
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
ast_debug(1, "Null frame == hangup() detected\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if (f->frametype == AST_FRAME_DTMF) {
|
||||
ast_debug(1, "User pressed a key\n");
|
||||
ast_frfree(f);
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
|
||||
if (pid > -1)
|
||||
kill(pid, SIGKILL);
|
||||
if (!res && owriteformat)
|
||||
ast_set_write_format(chan, owriteformat);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, NBScat_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Silly NBS Stream Application");
|
||||
2041
trunk/apps/app_osplookup.c
Normal file
2041
trunk/apps/app_osplookup.c
Normal file
File diff suppressed because it is too large
Load Diff
193
trunk/apps/app_page.c
Normal file
193
trunk/apps/app_page.c
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (c) 2004 - 2006 Digium, Inc. All rights reserved.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* This code is released under the GNU General Public License
|
||||
* version 2.0. See LICENSE for more information.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief page() - Paging application
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend>zaptel</depend>
|
||||
<depend>app_meetme</depend>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/chanvars.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/devicestate.h"
|
||||
#include "asterisk/dial.h"
|
||||
|
||||
static const char *app_page= "Page";
|
||||
|
||||
static const char *page_synopsis = "Pages phones";
|
||||
|
||||
static const char *page_descrip =
|
||||
"Page(Technology/Resource&Technology2/Resource2[,options])\n"
|
||||
" Places outbound calls to the given technology / resource and dumps\n"
|
||||
"them into a conference bridge as muted participants. The original\n"
|
||||
"caller is dumped into the conference as a speaker and the room is\n"
|
||||
"destroyed when the original caller leaves. Valid options are:\n"
|
||||
" d - full duplex audio\n"
|
||||
" q - quiet, do not play beep to caller\n"
|
||||
" r - record the page into a file (see 'r' for app_meetme)\n"
|
||||
" s - only dial channel if devicestate says it is not in use\n";
|
||||
|
||||
enum {
|
||||
PAGE_DUPLEX = (1 << 0),
|
||||
PAGE_QUIET = (1 << 1),
|
||||
PAGE_RECORD = (1 << 2),
|
||||
PAGE_SKIP = (1 << 3),
|
||||
} page_opt_flags;
|
||||
|
||||
AST_APP_OPTIONS(page_opts, {
|
||||
AST_APP_OPTION('d', PAGE_DUPLEX),
|
||||
AST_APP_OPTION('q', PAGE_QUIET),
|
||||
AST_APP_OPTION('r', PAGE_RECORD),
|
||||
AST_APP_OPTION('s', PAGE_SKIP),
|
||||
});
|
||||
|
||||
#define MAX_DIALS 128
|
||||
|
||||
static int page_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *options, *tech, *resource, *tmp;
|
||||
char meetmeopts[88], originator[AST_CHANNEL_NAME], *opts[0];
|
||||
struct ast_flags flags = { 0 };
|
||||
unsigned int confid = ast_random();
|
||||
struct ast_app *app;
|
||||
int res = 0, pos = 0, i = 0;
|
||||
struct ast_dial *dials[MAX_DIALS];
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "This application requires at least one argument (destination(s) to page)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(app = pbx_findapp("MeetMe"))) {
|
||||
ast_log(LOG_WARNING, "There is no MeetMe application available!\n");
|
||||
return -1;
|
||||
};
|
||||
|
||||
options = ast_strdupa(data);
|
||||
|
||||
ast_copy_string(originator, chan->name, sizeof(originator));
|
||||
if ((tmp = strchr(originator, '-')))
|
||||
*tmp = '\0';
|
||||
|
||||
tmp = strsep(&options, ",");
|
||||
if (options)
|
||||
ast_app_parse_options(page_opts, &flags, opts, options);
|
||||
|
||||
snprintf(meetmeopts, sizeof(meetmeopts), "MeetMe,%ud,%s%sqxdw(5)", confid, (ast_test_flag(&flags, PAGE_DUPLEX) ? "" : "m"),
|
||||
(ast_test_flag(&flags, PAGE_RECORD) ? "r" : "") );
|
||||
|
||||
/* Go through parsing/calling each device */
|
||||
while ((tech = strsep(&tmp, "&"))) {
|
||||
int state = 0;
|
||||
struct ast_dial *dial = NULL;
|
||||
|
||||
/* don't call the originating device */
|
||||
if (!strcasecmp(tech, originator))
|
||||
continue;
|
||||
|
||||
/* If no resource is available, continue on */
|
||||
if (!(resource = strchr(tech, '/'))) {
|
||||
ast_log(LOG_WARNING, "Incomplete destination '%s' supplied.\n", tech);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Ensure device is not in use if skip option is enabled */
|
||||
if (ast_test_flag(&flags, PAGE_SKIP) && (state = ast_device_state(tech)) != AST_DEVICE_NOT_INUSE) {
|
||||
ast_log(LOG_WARNING, "Destination '%s' has device state '%s'.\n", tech, devstate2str(state));
|
||||
continue;
|
||||
}
|
||||
|
||||
*resource++ = '\0';
|
||||
|
||||
/* Create a dialing structure */
|
||||
if (!(dial = ast_dial_create())) {
|
||||
ast_log(LOG_WARNING, "Failed to create dialing structure.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Append technology and resource */
|
||||
ast_dial_append(dial, tech, resource);
|
||||
|
||||
/* Set ANSWER_EXEC as global option */
|
||||
ast_dial_option_global_enable(dial, AST_DIAL_OPTION_ANSWER_EXEC, meetmeopts);
|
||||
|
||||
/* Run this dial in async mode */
|
||||
ast_dial_run(dial, chan, 1);
|
||||
|
||||
/* Put in our dialing array */
|
||||
dials[pos++] = dial;
|
||||
}
|
||||
|
||||
if (!ast_test_flag(&flags, PAGE_QUIET)) {
|
||||
res = ast_streamfile(chan, "beep", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, "");
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
snprintf(meetmeopts, sizeof(meetmeopts), "%ud,A%s%sqxd", confid, (ast_test_flag(&flags, PAGE_DUPLEX) ? "" : "t"),
|
||||
(ast_test_flag(&flags, PAGE_RECORD) ? "r" : "") );
|
||||
pbx_exec(chan, app, meetmeopts);
|
||||
}
|
||||
|
||||
/* Go through each dial attempt cancelling, joining, and destroying */
|
||||
for (i = 0; i < pos; i++) {
|
||||
struct ast_dial *dial = dials[i];
|
||||
|
||||
/* We have to wait for the async thread to exit as it's possible Meetme won't throw them out immediately */
|
||||
ast_dial_join(dial);
|
||||
|
||||
/* Hangup all channels */
|
||||
ast_dial_hangup(dial);
|
||||
|
||||
/* Destroy dialing structure */
|
||||
ast_dial_destroy(dial);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app_page);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app_page, page_exec, page_synopsis, page_descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Page Multiple Phones");
|
||||
|
||||
183
trunk/apps/app_parkandannounce.c
Normal file
183
trunk/apps/app_parkandannounce.c
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2006, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* Author: Ben Miller <bgmiller@dccinc.com>
|
||||
* With TONS of help from Mark!
|
||||
*
|
||||
* 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 ParkAndAnnounce application for Asterisk
|
||||
*
|
||||
* \author Ben Miller <bgmiller@dccinc.com>
|
||||
* \arg With TONS of help from Mark!
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/features.h"
|
||||
#include "asterisk/say.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *app = "ParkAndAnnounce";
|
||||
|
||||
static char *synopsis = "Park and Announce";
|
||||
|
||||
static char *descrip =
|
||||
" ParkAndAnnounce(announce:template,timeout,dial[,return_context]):\n"
|
||||
"Park a call into the parkinglot and announce the call to another channel.\n"
|
||||
"\n"
|
||||
"announce template: Colon-separated list of files to announce. The word PARKED\n"
|
||||
" will be replaced by a say_digits of the extension in which\n"
|
||||
" the call is parked.\n"
|
||||
"timeout: Time in seconds before the call returns into the return\n"
|
||||
" context.\n"
|
||||
"dial: The app_dial style resource to call to make the\n"
|
||||
" announcement. Console/dsp calls the console.\n"
|
||||
"return_context: The goto-style label to jump the call back into after\n"
|
||||
" timeout. Default <priority+1>.\n"
|
||||
"\n"
|
||||
"The variable ${PARKEDAT} will contain the parking extension into which the\n"
|
||||
"call was placed. Use with the Local channel to allow the dialplan to make\n"
|
||||
"use of this information.\n";
|
||||
|
||||
|
||||
static int parkandannounce_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = -1;
|
||||
int lot, timeout = 0, dres;
|
||||
char *dialtech, *tmp[100], buf[13];
|
||||
int looptemp, i;
|
||||
char *s;
|
||||
|
||||
struct ast_channel *dchan;
|
||||
struct outgoing_helper oh = { 0, };
|
||||
int outstate;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(template);
|
||||
AST_APP_ARG(timeout);
|
||||
AST_APP_ARG(dial);
|
||||
AST_APP_ARG(return_context);
|
||||
);
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "ParkAndAnnounce requires arguments: (announce:template|timeout|dial|[return_context])\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
s = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, s);
|
||||
|
||||
if (args.timeout)
|
||||
timeout = atoi(args.timeout) * 1000;
|
||||
|
||||
if (ast_strlen_zero(args.dial)) {
|
||||
ast_log(LOG_WARNING, "PARK: A dial resource must be specified i.e: Console/dsp or Zap/g1/5551212\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dialtech = strsep(&args.dial, "/");
|
||||
ast_verb(3, "Dial Tech,String: (%s,%s)\n", dialtech, args.dial);
|
||||
|
||||
if (!ast_strlen_zero(args.return_context))
|
||||
ast_parseable_goto(chan, args.return_context);
|
||||
|
||||
ast_verb(3, "Return Context: (%s,%s,%d) ID: %s\n", chan->context, chan->exten, chan->priority, chan->cid.cid_num);
|
||||
if (!ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
|
||||
ast_verb(3, "Warning: Return Context Invalid, call will return to default|s\n");
|
||||
}
|
||||
|
||||
/* we are using masq_park here to protect * from touching the channel once we park it. If the channel comes out of timeout
|
||||
before we are done announcing and the channel is messed with, Kablooeee. So we use Masq to prevent this. */
|
||||
|
||||
ast_masq_park_call(chan, NULL, timeout, &lot);
|
||||
|
||||
ast_verb(3, "Call Parking Called, lot: %d, timeout: %d, context: %s\n", lot, timeout, args.return_context);
|
||||
|
||||
/* Now place the call to the extension */
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", lot);
|
||||
oh.parent_channel = chan;
|
||||
oh.vars = ast_variable_new("_PARKEDAT", buf, "");
|
||||
dchan = __ast_request_and_dial(dialtech, AST_FORMAT_SLINEAR, args.dial, 30000, &outstate, chan->cid.cid_num, chan->cid.cid_name, &oh);
|
||||
|
||||
if (dchan) {
|
||||
if (dchan->_state == AST_STATE_UP) {
|
||||
ast_verb(4, "Channel %s was answered.\n", dchan->name);
|
||||
} else {
|
||||
ast_verb(4, "Channel %s was never answered.\n", dchan->name);
|
||||
ast_log(LOG_WARNING, "PARK: Channel %s was never answered for the announce.\n", dchan->name);
|
||||
ast_hangup(dchan);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_stopstream(dchan);
|
||||
|
||||
/* now we have the call placed and are ready to play stuff to it */
|
||||
|
||||
ast_verb(4, "Announce Template:%s\n", args.template);
|
||||
|
||||
for (looptemp = 0, tmp[looptemp++] = strsep(&args.template, ":");
|
||||
looptemp < sizeof(tmp) / sizeof(tmp[0]);
|
||||
tmp[looptemp++] = strsep(&args.template, ":"));
|
||||
|
||||
for (i = 0; i < looptemp; i++) {
|
||||
ast_verb(4, "Announce:%s\n", tmp[i]);
|
||||
if (!strcmp(tmp[i], "PARKED")) {
|
||||
ast_say_digits(dchan, lot, "", dchan->language);
|
||||
} else {
|
||||
dres = ast_streamfile(dchan, tmp[i], dchan->language);
|
||||
if (!dres) {
|
||||
dres = ast_waitstream(dchan, "");
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", tmp[i], dchan->name);
|
||||
dres = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ast_stopstream(dchan);
|
||||
ast_hangup(dchan);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
/* return ast_register_application(app, park_exec); */
|
||||
return ast_register_application(app, parkandannounce_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Parking and Announce Application");
|
||||
174
trunk/apps/app_pickupchan.c
Normal file
174
trunk/apps/app_pickupchan.c
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2008, Gary Cook
|
||||
*
|
||||
* 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 Pickup a ringing channel
|
||||
*
|
||||
* \author Gary Cook
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/logger.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/options.h"
|
||||
|
||||
static const char *app = "PickupChan";
|
||||
static const char *synopsis = "Pickup a ringing channel";
|
||||
static const char *descrip =
|
||||
" PickupChan(channel[&channel...]): This application can pickup any ringing channel\n";
|
||||
|
||||
/*! \todo This application should return a result code, like PICKUPRESULT */
|
||||
|
||||
/*! \brief Helper function that determines whether a channel is capable of being picked up */
|
||||
static int can_pickup(struct ast_channel *chan)
|
||||
{
|
||||
ast_debug(3, "Checking Pickup '%s' state '%s ( %d )'\n", chan->name, ast_state2str(chan->_state), chan->_state);
|
||||
|
||||
if (!chan->pbx && (chan->_state == AST_STATE_RINGING || chan->_state == AST_STATE_RING)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Helper Function to walk through ALL channels checking NAME and STATE */
|
||||
static struct ast_channel *my_ast_get_channel_by_name_locked(char *channame)
|
||||
{
|
||||
struct ast_channel *chan;
|
||||
char *chkchan = alloca(strlen(channame) + 2);
|
||||
|
||||
/* need to append a '-' for the comparison so we check full channel name,
|
||||
* i.e SIP/hgc- , use a temporary variable so original stays the same for
|
||||
* debugging.
|
||||
*/
|
||||
strcpy(chkchan, channame);
|
||||
strcat(chkchan, "-");
|
||||
|
||||
for (chan = ast_walk_channel_by_name_prefix_locked(NULL, channame, strlen(channame));
|
||||
chan;
|
||||
chan = ast_walk_channel_by_name_prefix_locked(chan, channame, strlen(channame))) {
|
||||
if (!strncasecmp(chan->name, chkchan, strlen(chkchan)) && can_pickup(chan))
|
||||
return chan;
|
||||
ast_channel_unlock(chan);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*! \brief Perform actual pickup between two channels */
|
||||
static int pickup_do(struct ast_channel *chan, struct ast_channel *target)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
ast_debug(3, "Call pickup on '%s' by '%s'\n", target->name, chan->name);
|
||||
|
||||
if ((res = ast_answer(chan))) {
|
||||
ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((res = ast_queue_control(chan, AST_CONTROL_ANSWER))) {
|
||||
ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((res = ast_channel_masquerade(target, chan))) {
|
||||
ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, target->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief Attempt to pick up specified channel named , does not use context */
|
||||
static int pickup_by_channel(struct ast_channel *chan, char *pickup)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_channel *target;
|
||||
|
||||
if (!(target = my_ast_get_channel_by_name_locked(pickup)))
|
||||
return -1;
|
||||
|
||||
/* Just check that we are not picking up the SAME as target */
|
||||
if (chan->name != target->name && chan != target) {
|
||||
res = pickup_do(chan, target);
|
||||
ast_channel_unlock(target);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief Main application entry point */
|
||||
static int pickupchan_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_module_user *u = NULL;
|
||||
char *tmp = ast_strdupa(data);
|
||||
char *pickup = NULL, *context = NULL;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "Pickup requires an argument (channel)!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
u = ast_module_user_add(chan);
|
||||
|
||||
/* Parse channel (and ignore context if there) */
|
||||
while (!ast_strlen_zero(tmp) && (pickup = strsep(&tmp, "&"))) {
|
||||
if ((context = strchr(pickup , '@'))) {
|
||||
*context++ = '\0';
|
||||
}
|
||||
if (!strncasecmp(chan->name, pickup , strlen(pickup))) {
|
||||
ast_log(LOG_NOTICE, "Cannot pickup your own channel %s.\n", pickup);
|
||||
} else {
|
||||
if (!pickup_by_channel(chan, pickup)) {
|
||||
break;
|
||||
}
|
||||
ast_log(LOG_NOTICE, "No target channel found for %s.\n", pickup);
|
||||
}
|
||||
}
|
||||
|
||||
ast_module_user_remove(u);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, pickupchan_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Channel Pickup Application");
|
||||
524
trunk/apps/app_playback.c
Normal file
524
trunk/apps/app_playback.c
Normal file
@@ -0,0 +1,524 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Trivial application to playback a sound file
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
/* This file provides config-file based 'say' functions, and implenents
|
||||
* some CLI commands.
|
||||
*/
|
||||
#include "asterisk/say.h" /* provides config-file based 'say' functions */
|
||||
#include "asterisk/cli.h"
|
||||
|
||||
static char *app = "Playback";
|
||||
|
||||
static char *synopsis = "Play a file";
|
||||
|
||||
static char *descrip =
|
||||
" Playback(filename[&filename2...][,option]): Plays back given filenames (do not put\n"
|
||||
"extension). Options may also be included following a comma.\n"
|
||||
"The 'skip' option causes the playback of the message to be skipped if the channel\n"
|
||||
"is not in the 'up' state (i.e. it hasn't been answered yet). If 'skip' is \n"
|
||||
"specified, the application will return immediately should the channel not be\n"
|
||||
"off hook. Otherwise, unless 'noanswer' is specified, the channel will\n"
|
||||
"be answered before the sound is played. Not all channels support playing\n"
|
||||
"messages while still on hook.\n"
|
||||
"This application sets the following channel variable upon completion:\n"
|
||||
" PLAYBACKSTATUS The status of the playback attempt as a text string, one of\n"
|
||||
" SUCCESS | FAILED\n"
|
||||
;
|
||||
|
||||
|
||||
static struct ast_config *say_cfg = NULL;
|
||||
/* save the say' api calls.
|
||||
* The first entry is NULL if we have the standard source,
|
||||
* otherwise we are sourcing from here.
|
||||
* 'say load [new|old]' will enable the new or old method, or report status
|
||||
*/
|
||||
static const void *say_api_buf[40];
|
||||
static const char *say_old = "old";
|
||||
static const char *say_new = "new";
|
||||
|
||||
static void save_say_mode(const void *arg)
|
||||
{
|
||||
int i = 0;
|
||||
say_api_buf[i++] = arg;
|
||||
|
||||
say_api_buf[i++] = ast_say_number_full;
|
||||
say_api_buf[i++] = ast_say_enumeration_full;
|
||||
say_api_buf[i++] = ast_say_digit_str_full;
|
||||
say_api_buf[i++] = ast_say_character_str_full;
|
||||
say_api_buf[i++] = ast_say_phonetic_str_full;
|
||||
say_api_buf[i++] = ast_say_datetime;
|
||||
say_api_buf[i++] = ast_say_time;
|
||||
say_api_buf[i++] = ast_say_date;
|
||||
say_api_buf[i++] = ast_say_datetime_from_now;
|
||||
say_api_buf[i++] = ast_say_date_with_format;
|
||||
}
|
||||
|
||||
static void restore_say_mode(void *arg)
|
||||
{
|
||||
int i = 0;
|
||||
say_api_buf[i++] = arg;
|
||||
|
||||
ast_say_number_full = say_api_buf[i++];
|
||||
ast_say_enumeration_full = say_api_buf[i++];
|
||||
ast_say_digit_str_full = say_api_buf[i++];
|
||||
ast_say_character_str_full = say_api_buf[i++];
|
||||
ast_say_phonetic_str_full = say_api_buf[i++];
|
||||
ast_say_datetime = say_api_buf[i++];
|
||||
ast_say_time = say_api_buf[i++];
|
||||
ast_say_date = say_api_buf[i++];
|
||||
ast_say_datetime_from_now = say_api_buf[i++];
|
||||
ast_say_date_with_format = say_api_buf[i++];
|
||||
}
|
||||
|
||||
/*
|
||||
* Typical 'say' arguments in addition to the date or number or string
|
||||
* to say. We do not include 'options' because they may be different
|
||||
* in recursive calls, and so they are better left as an external
|
||||
* parameter.
|
||||
*/
|
||||
typedef struct {
|
||||
struct ast_channel *chan;
|
||||
const char *ints;
|
||||
const char *language;
|
||||
int audiofd;
|
||||
int ctrlfd;
|
||||
} say_args_t;
|
||||
|
||||
static int s_streamwait3(const say_args_t *a, const char *fn)
|
||||
{
|
||||
int res = ast_streamfile(a->chan, fn, a->language);
|
||||
if (res) {
|
||||
ast_log(LOG_WARNING, "Unable to play message %s\n", fn);
|
||||
return res;
|
||||
}
|
||||
res = (a->audiofd > -1 && a->ctrlfd > -1) ?
|
||||
ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) :
|
||||
ast_waitstream(a->chan, a->ints);
|
||||
ast_stopstream(a->chan);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* the string is 'prefix:data' or prefix:fmt:data'
|
||||
* with ':' being invalid in strings.
|
||||
*/
|
||||
static int do_say(say_args_t *a, const char *s, const char *options, int depth)
|
||||
{
|
||||
struct ast_variable *v;
|
||||
char *lang, *x, *rule = NULL;
|
||||
int ret = 0;
|
||||
struct varshead head = { .first = NULL, .last = NULL };
|
||||
struct ast_var_t *n;
|
||||
|
||||
ast_debug(2, "string <%s> depth <%d>\n", s, depth);
|
||||
if (depth++ > 10) {
|
||||
ast_log(LOG_WARNING, "recursion too deep, exiting\n");
|
||||
return -1;
|
||||
} else if (!say_cfg) {
|
||||
ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* scan languages same as in file.c */
|
||||
if (a->language == NULL)
|
||||
a->language = "en"; /* default */
|
||||
ast_debug(2, "try <%s> in <%s>\n", s, a->language);
|
||||
lang = ast_strdupa(a->language);
|
||||
for (;;) {
|
||||
for (v = ast_variable_browse(say_cfg, lang); v ; v = v->next) {
|
||||
if (ast_extension_match(v->name, s)) {
|
||||
rule = ast_strdupa(v->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rule)
|
||||
break;
|
||||
if ( (x = strchr(lang, '_')) )
|
||||
*x = '\0'; /* try without suffix */
|
||||
else if (strcmp(lang, "en"))
|
||||
lang = "en"; /* last resort, try 'en' if not done yet */
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (!rule)
|
||||
return 0;
|
||||
|
||||
/* skip up to two prefixes to get the value */
|
||||
if ( (x = strchr(s, ':')) )
|
||||
s = x + 1;
|
||||
if ( (x = strchr(s, ':')) )
|
||||
s = x + 1;
|
||||
ast_debug(2, "value is <%s>\n", s);
|
||||
n = ast_var_assign("SAY", s);
|
||||
AST_LIST_INSERT_HEAD(&head, n, entries);
|
||||
|
||||
/* scan the body, one piece at a time */
|
||||
while ( !ret && (x = strsep(&rule, ",")) ) { /* exit on key */
|
||||
char fn[128];
|
||||
const char *p, *fmt, *data; /* format and data pointers */
|
||||
|
||||
/* prepare a decent file name */
|
||||
x = ast_skip_blanks(x);
|
||||
ast_trim_blanks(x);
|
||||
|
||||
/* replace variables */
|
||||
pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn));
|
||||
ast_debug(2, "doing [%s]\n", fn);
|
||||
|
||||
/* locate prefix and data, if any */
|
||||
fmt = index(fn, ':');
|
||||
if (!fmt || fmt == fn) { /* regular filename */
|
||||
ret = s_streamwait3(a, fn);
|
||||
continue;
|
||||
}
|
||||
fmt++;
|
||||
data = index(fmt, ':'); /* colon before data */
|
||||
if (!data || data == fmt) { /* simple prefix-fmt */
|
||||
ret = do_say(a, fn, options, depth);
|
||||
continue;
|
||||
}
|
||||
/* prefix:fmt:data */
|
||||
for (p = fmt; p < data && ret <= 0; p++) {
|
||||
char fn2[sizeof(fn)];
|
||||
if (*p == ' ' || *p == '\t') /* skip blanks */
|
||||
continue;
|
||||
if (*p == '\'') {/* file name - we trim them */
|
||||
char *y;
|
||||
strcpy(fn2, ast_skip_blanks(p+1)); /* make a full copy */
|
||||
y = index(fn2, '\'');
|
||||
if (!y) {
|
||||
p = data; /* invalid. prepare to end */
|
||||
break;
|
||||
}
|
||||
*y = '\0';
|
||||
ast_trim_blanks(fn2);
|
||||
p = index(p+1, '\'');
|
||||
ret = s_streamwait3(a, fn2);
|
||||
} else {
|
||||
int l = fmt-fn;
|
||||
strcpy(fn2, fn); /* copy everything */
|
||||
/* after prefix, append the format */
|
||||
fn2[l++] = *p;
|
||||
strcpy(fn2 + l, data);
|
||||
ret = do_say(a, fn2, options, depth);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ast_var_delete(n);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int say_full(struct ast_channel *chan, const char *string,
|
||||
const char *ints, const char *lang, const char *options,
|
||||
int audiofd, int ctrlfd)
|
||||
{
|
||||
say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
|
||||
return do_say(&a, string, options, 0);
|
||||
}
|
||||
|
||||
static int say_number_full(struct ast_channel *chan, int num,
|
||||
const char *ints, const char *lang, const char *options,
|
||||
int audiofd, int ctrlfd)
|
||||
{
|
||||
char buf[64];
|
||||
say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
|
||||
snprintf(buf, sizeof(buf), "num:%d", num);
|
||||
return do_say(&a, buf, options, 0);
|
||||
}
|
||||
|
||||
static int say_enumeration_full(struct ast_channel *chan, int num,
|
||||
const char *ints, const char *lang, const char *options,
|
||||
int audiofd, int ctrlfd)
|
||||
{
|
||||
char buf[64];
|
||||
say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
|
||||
snprintf(buf, sizeof(buf), "enum:%d", num);
|
||||
return do_say(&a, buf, options, 0);
|
||||
}
|
||||
|
||||
static int say_date_generic(struct ast_channel *chan, time_t t,
|
||||
const char *ints, const char *lang, const char *format, const char *timezone, const char *prefix)
|
||||
{
|
||||
char buf[128];
|
||||
struct ast_tm tm;
|
||||
struct timeval tv = { t, 0 };
|
||||
say_args_t a = { chan, ints, lang, -1, -1 };
|
||||
if (format == NULL)
|
||||
format = "";
|
||||
|
||||
ast_localtime(&tv, &tm, NULL);
|
||||
snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
|
||||
prefix,
|
||||
format,
|
||||
tm.tm_year+1900,
|
||||
tm.tm_mon+1,
|
||||
tm.tm_mday,
|
||||
tm.tm_hour,
|
||||
tm.tm_min,
|
||||
tm.tm_sec,
|
||||
tm.tm_wday,
|
||||
tm.tm_yday);
|
||||
return do_say(&a, buf, NULL, 0);
|
||||
}
|
||||
|
||||
static int say_date_with_format(struct ast_channel *chan, time_t t,
|
||||
const char *ints, const char *lang, const char *format, const char *timezone)
|
||||
{
|
||||
return say_date_generic(chan, t, ints, lang, format, timezone, "datetime");
|
||||
}
|
||||
|
||||
static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
|
||||
{
|
||||
return say_date_generic(chan, t, ints, lang, "", NULL, "date");
|
||||
}
|
||||
|
||||
static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
|
||||
{
|
||||
return say_date_generic(chan, t, ints, lang, "", NULL, "time");
|
||||
}
|
||||
|
||||
static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
|
||||
{
|
||||
return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
|
||||
}
|
||||
|
||||
/*
|
||||
* remap the 'say' functions to use those in this file
|
||||
*/
|
||||
static int say_init_mode(const char *mode) {
|
||||
if (!strcmp(mode, say_new)) {
|
||||
if (say_cfg == NULL) {
|
||||
ast_log(LOG_ERROR, "There is no say.conf file to use new mode\n");
|
||||
return -1;
|
||||
}
|
||||
save_say_mode(say_new);
|
||||
ast_say_number_full = say_number_full;
|
||||
|
||||
ast_say_enumeration_full = say_enumeration_full;
|
||||
#if 0
|
||||
ast_say_digits_full = say_digits_full;
|
||||
ast_say_digit_str_full = say_digit_str_full;
|
||||
ast_say_character_str_full = say_character_str_full;
|
||||
ast_say_phonetic_str_full = say_phonetic_str_full;
|
||||
ast_say_datetime_from_now = say_datetime_from_now;
|
||||
#endif
|
||||
ast_say_datetime = say_datetime;
|
||||
ast_say_time = say_time;
|
||||
ast_say_date = say_date;
|
||||
ast_say_date_with_format = say_date_with_format;
|
||||
} else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
|
||||
restore_say_mode(NULL);
|
||||
} else if (strcmp(mode, say_old)) {
|
||||
ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *__say_cli_init(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
||||
{
|
||||
const char *old_mode = say_api_buf[0] ? say_new : say_old;
|
||||
char *mode;
|
||||
switch (cmd) {
|
||||
case CLI_INIT:
|
||||
e->command = "say load [new|old]";
|
||||
e->usage =
|
||||
"Usage: say load [new|old]\n"
|
||||
" say load\n"
|
||||
" Report status of current say mode\n"
|
||||
" say load new\n"
|
||||
" Set say method, configured in say.conf\n"
|
||||
" say load old\n"
|
||||
" Set old say method, coded in asterisk core\n";
|
||||
return NULL;
|
||||
case CLI_GENERATE:
|
||||
return NULL;
|
||||
}
|
||||
if (a->argc == 2) {
|
||||
ast_cli(a->fd, "say mode is [%s]\n", old_mode);
|
||||
return CLI_SUCCESS;
|
||||
} else if (a->argc != 3)
|
||||
return CLI_SHOWUSAGE;
|
||||
mode = a->argv[2];
|
||||
if (!strcmp(mode, old_mode))
|
||||
ast_cli(a->fd, "say mode is %s already\n", mode);
|
||||
else
|
||||
if (say_init_mode(mode) == 0)
|
||||
ast_cli(a->fd, "setting say mode from %s to %s\n", old_mode, mode);
|
||||
|
||||
return CLI_SUCCESS;
|
||||
}
|
||||
|
||||
static struct ast_cli_entry cli_playback[] = {
|
||||
AST_CLI_DEFINE(__say_cli_init, "Set or show the say mode"),
|
||||
};
|
||||
|
||||
static int playback_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
int mres = 0;
|
||||
char *tmp;
|
||||
int option_skip=0;
|
||||
int option_say=0;
|
||||
int option_noanswer = 0;
|
||||
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(filenames);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, tmp);
|
||||
|
||||
if (args.options) {
|
||||
if (strcasestr(args.options, "skip"))
|
||||
option_skip = 1;
|
||||
if (strcasestr(args.options, "say"))
|
||||
option_say = 1;
|
||||
if (strcasestr(args.options, "noanswer"))
|
||||
option_noanswer = 1;
|
||||
}
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if (option_skip) {
|
||||
/* At the user's option, skip if the line is not up */
|
||||
goto done;
|
||||
} else if (!option_noanswer)
|
||||
/* Otherwise answer unless we're supposed to send this while on-hook */
|
||||
res = ast_answer(chan);
|
||||
}
|
||||
if (!res) {
|
||||
char *back = args.filenames;
|
||||
char *front;
|
||||
|
||||
ast_stopstream(chan);
|
||||
while (!res && (front = strsep(&back, "&"))) {
|
||||
if (option_say)
|
||||
res = say_full(chan, front, "", chan->language, NULL, -1, -1);
|
||||
else
|
||||
res = ast_streamfile(chan, front, chan->language);
|
||||
if (!res) {
|
||||
res = ast_waitstream(chan, "");
|
||||
ast_stopstream(chan);
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
|
||||
res = 0;
|
||||
mres = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
done:
|
||||
pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
|
||||
return res;
|
||||
}
|
||||
|
||||
static int reload(void)
|
||||
{
|
||||
struct ast_variable *v;
|
||||
struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
|
||||
struct ast_config *newcfg;
|
||||
|
||||
if ((newcfg = ast_config_load("say.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
|
||||
return 0;
|
||||
|
||||
if (say_cfg) {
|
||||
ast_config_destroy(say_cfg);
|
||||
ast_log(LOG_NOTICE, "Reloading say.conf\n");
|
||||
say_cfg = newcfg;
|
||||
}
|
||||
|
||||
if (say_cfg) {
|
||||
for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) {
|
||||
if (ast_extension_match(v->name, "mode")) {
|
||||
say_init_mode(v->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX here we should sort rules according to the same order
|
||||
* we have in pbx.c so we have the same matching behaviour.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(app);
|
||||
|
||||
ast_cli_unregister_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
|
||||
|
||||
if (say_cfg)
|
||||
ast_config_destroy(say_cfg);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
struct ast_variable *v;
|
||||
struct ast_flags config_flags = { 0 };
|
||||
|
||||
say_cfg = ast_config_load("say.conf", config_flags);
|
||||
if (say_cfg) {
|
||||
for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) {
|
||||
if (ast_extension_match(v->name, "mode")) {
|
||||
say_init_mode(v->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ast_cli_register_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
|
||||
return ast_register_application(app, playback_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application",
|
||||
.load = load_module,
|
||||
.unload = unload_module,
|
||||
.reload = reload,
|
||||
);
|
||||
173
trunk/apps/app_privacy.c
Normal file
173
trunk/apps/app_privacy.c
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Block all calls without Caller*ID, require phone # to be entered
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
#include "asterisk/image.h"
|
||||
#include "asterisk/callerid.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/config.h"
|
||||
|
||||
static char *app = "PrivacyManager";
|
||||
|
||||
static char *synopsis = "Require phone number to be entered, if no CallerID sent";
|
||||
|
||||
static char *descrip =
|
||||
" PrivacyManager([maxretries][,minlength]): If no Caller*ID \n"
|
||||
"is sent, PrivacyManager answers the channel and asks the caller to\n"
|
||||
"enter their phone number. The caller is given 'maxretries' attempts to do so.\n"
|
||||
"The application does nothing if Caller*ID was received on the channel.\n"
|
||||
" maxretries default 3 -maximum number of attempts the caller is allowed \n"
|
||||
" to input a callerid.\n"
|
||||
" minlength default 10 -minimum allowable digits in the input callerid number.\n"
|
||||
"The application sets the following channel variable upon completion: \n"
|
||||
"PRIVACYMGRSTATUS The status of the privacy manager's attempt to collect \n"
|
||||
" a phone number from the user. A text string that is either:\n"
|
||||
" SUCCESS | FAILED \n"
|
||||
;
|
||||
|
||||
|
||||
static int privacy_exec (struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=0;
|
||||
int retries;
|
||||
int maxretries = 3;
|
||||
int minlength = 10;
|
||||
int x = 0;
|
||||
char phone[30];
|
||||
char *parse = NULL;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(maxretries);
|
||||
AST_APP_ARG(minlength);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (!ast_strlen_zero(chan->cid.cid_num)) {
|
||||
if (option_verbose > 2)
|
||||
ast_verbose (VERBOSE_PREFIX_3 "CallerID Present: Skipping\n");
|
||||
} else {
|
||||
/*Answer the channel if it is not already*/
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if ((res = ast_answer(chan)))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(data)) {
|
||||
parse = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (args.maxretries) {
|
||||
if (sscanf(args.maxretries, "%d", &x) == 1)
|
||||
maxretries = x;
|
||||
else
|
||||
ast_log(LOG_WARNING, "Invalid max retries argument\n");
|
||||
}
|
||||
if (args.minlength) {
|
||||
if (sscanf(args.minlength, "%d", &x) == 1)
|
||||
minlength = x;
|
||||
else
|
||||
ast_log(LOG_WARNING, "Invalid min length argument\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Play unidentified call */
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
if (!res)
|
||||
res = ast_streamfile(chan, "privacy-unident", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, "");
|
||||
|
||||
/* Ask for 10 digit number, give 3 attempts */
|
||||
for (retries = 0; retries < maxretries; retries++) {
|
||||
if (!res)
|
||||
res = ast_streamfile(chan, "privacy-prompt", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, "");
|
||||
|
||||
if (!res )
|
||||
res = ast_readstring(chan, phone, sizeof(phone) - 1, /* digit timeout ms */ 3200, /* first digit timeout */ 5000, "#");
|
||||
|
||||
if (res < 0)
|
||||
break;
|
||||
|
||||
/* Make sure we get at least digits */
|
||||
if (strlen(phone) >= minlength )
|
||||
break;
|
||||
else {
|
||||
res = ast_streamfile(chan, "privacy-incorrect", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, "");
|
||||
}
|
||||
}
|
||||
|
||||
/* Got a number, play sounds and send them on their way */
|
||||
if ((retries < maxretries) && res >= 0 ) {
|
||||
res = ast_streamfile(chan, "privacy-thankyou", chan->language);
|
||||
if (!res)
|
||||
res = ast_waitstream(chan, "");
|
||||
|
||||
ast_set_callerid (chan, phone, "Privacy Manager", NULL);
|
||||
|
||||
/* Clear the unavailable presence bit so if it came in on PRI
|
||||
* the caller id will now be passed out to other channels
|
||||
*/
|
||||
chan->cid.cid_pres &= (AST_PRES_UNAVAILABLE ^ 0xFF);
|
||||
|
||||
if (option_verbose > 2) {
|
||||
ast_verbose (VERBOSE_PREFIX_3 "Changed Caller*ID to %s, callerpres to %d\n",phone,chan->cid.cid_pres);
|
||||
}
|
||||
pbx_builtin_setvar_helper(chan, "PRIVACYMGRSTATUS", "SUCCESS");
|
||||
} else {
|
||||
pbx_builtin_setvar_helper(chan, "PRIVACYMGRSTATUS", "FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application (app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, privacy_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Require phone number to be entered, if no CallerID sent");
|
||||
6153
trunk/apps/app_queue.c
Normal file
6153
trunk/apps/app_queue.c
Normal file
File diff suppressed because it is too large
Load Diff
231
trunk/apps/app_read.c
Normal file
231
trunk/apps/app_read.c
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Trivial application to read a variable
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/indications.h"
|
||||
|
||||
enum {
|
||||
OPT_SKIP = (1 << 0),
|
||||
OPT_INDICATION = (1 << 1),
|
||||
OPT_NOANSWER = (1 << 2),
|
||||
} read_option_flags;
|
||||
|
||||
AST_APP_OPTIONS(read_app_options, {
|
||||
AST_APP_OPTION('s', OPT_SKIP),
|
||||
AST_APP_OPTION('i', OPT_INDICATION),
|
||||
AST_APP_OPTION('n', OPT_NOANSWER),
|
||||
});
|
||||
|
||||
static char *app = "Read";
|
||||
|
||||
static char *synopsis = "Read a variable";
|
||||
|
||||
static char *descrip =
|
||||
" Read(variable[,filename[&filename2...]][,maxdigits][,option][,attempts][,timeout])\n\n"
|
||||
"Reads a #-terminated string of digits a certain number of times from the\n"
|
||||
"user in to the given variable.\n"
|
||||
" filename -- file(s) to play before reading digits or tone with option i\n"
|
||||
" maxdigits -- maximum acceptable number of digits. Stops reading after\n"
|
||||
" maxdigits have been entered (without requiring the user to\n"
|
||||
" press the '#' key).\n"
|
||||
" Defaults to 0 - no limit - wait for the user press the '#' key.\n"
|
||||
" Any value below 0 means the same. Max accepted value is 255.\n"
|
||||
" option -- options are 's' , 'i', 'n'\n"
|
||||
" 's' to return immediately if the line is not up,\n"
|
||||
" 'i' to play filename as an indication tone from your indications.conf\n"
|
||||
" 'n' to read digits even if the line is not up.\n"
|
||||
" attempts -- if greater than 1, that many attempts will be made in the \n"
|
||||
" event no data is entered.\n"
|
||||
" timeout -- The number of seconds to wait for a digit response. If greater\n"
|
||||
" than 0, that value will override the default timeout. Can be floating point.\n"
|
||||
"This application sets the following channel variable upon completion:\n"
|
||||
" READSTATUS - This is the status of the read operation.\n"
|
||||
" Possible values are:\n"
|
||||
" OK | ERROR | HANGUP | INTERRUPTED | SKIPPED | TIMEOUT\n";
|
||||
|
||||
|
||||
#define ast_next_data(instr,ptr,delim) if((ptr=strchr(instr,delim))) { *(ptr) = '\0' ; ptr++;}
|
||||
|
||||
static int read_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char tmp[256] = "";
|
||||
int maxdigits = 255;
|
||||
int tries = 1, to = 0, x = 0;
|
||||
double tosec;
|
||||
char *argcopy = NULL;
|
||||
struct ind_tone_zone_sound *ts = NULL;
|
||||
struct ast_flags flags = {0};
|
||||
const char *status = "ERROR";
|
||||
|
||||
AST_DECLARE_APP_ARGS(arglist,
|
||||
AST_APP_ARG(variable);
|
||||
AST_APP_ARG(filename);
|
||||
AST_APP_ARG(maxdigits);
|
||||
AST_APP_ARG(options);
|
||||
AST_APP_ARG(attempts);
|
||||
AST_APP_ARG(timeout);
|
||||
);
|
||||
|
||||
pbx_builtin_setvar_helper(chan, "READSTATUS", status);
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "Read requires an argument (variable)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
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.attempts)) {
|
||||
tries = atoi(arglist.attempts);
|
||||
if (tries <= 0)
|
||||
tries = 1;
|
||||
}
|
||||
|
||||
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.filename)) {
|
||||
arglist.filename = NULL;
|
||||
}
|
||||
if (!ast_strlen_zero(arglist.maxdigits)) {
|
||||
maxdigits = atoi(arglist.maxdigits);
|
||||
if ((maxdigits < 1) || (maxdigits > 255)) {
|
||||
maxdigits = 255;
|
||||
} else
|
||||
ast_verb(3, "Accepting a maximum of %d digits.\n", maxdigits);
|
||||
}
|
||||
if (ast_strlen_zero(arglist.variable)) {
|
||||
ast_log(LOG_WARNING, "Invalid! Usage: Read(variable[,filename][,maxdigits][,option][,attempts][,timeout])\n\n");
|
||||
return 0;
|
||||
}
|
||||
if (ast_test_flag(&flags, OPT_INDICATION)) {
|
||||
if (! ast_strlen_zero(arglist.filename)) {
|
||||
ts = ast_get_indication_tone(chan->zone, arglist.filename);
|
||||
}
|
||||
}
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if (ast_test_flag(&flags, OPT_SKIP)) {
|
||||
/* At the user's option, skip if the line is not up */
|
||||
pbx_builtin_setvar_helper(chan, arglist.variable, "");
|
||||
pbx_builtin_setvar_helper(chan, "READSTATUS", "SKIPPED");
|
||||
return 0;
|
||||
} else if (!ast_test_flag(&flags, OPT_NOANSWER)) {
|
||||
/* Otherwise answer unless we're supposed to read while on-hook */
|
||||
res = ast_answer(chan);
|
||||
}
|
||||
}
|
||||
if (!res) {
|
||||
while (tries && !res) {
|
||||
ast_stopstream(chan);
|
||||
if (ts && ts->data[0]) {
|
||||
if (!to)
|
||||
to = chan->pbx ? chan->pbx->rtimeout * 1000 : 6000;
|
||||
res = ast_playtones_start(chan, 0, ts->data, 0);
|
||||
for (x = 0; x < maxdigits; ) {
|
||||
res = ast_waitfordigit(chan, to);
|
||||
ast_playtones_stop(chan);
|
||||
if (res < 1) {
|
||||
if (res == 0)
|
||||
status = "TIMEOUT";
|
||||
tmp[x]='\0';
|
||||
break;
|
||||
}
|
||||
tmp[x++] = res;
|
||||
if (tmp[x-1] == '#') {
|
||||
tmp[x-1] = '\0';
|
||||
status = "OK";
|
||||
break;
|
||||
}
|
||||
if (x >= maxdigits) {
|
||||
status = "OK";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = ast_app_getdata(chan, arglist.filename, tmp, maxdigits, to);
|
||||
if (res == 0)
|
||||
status = "OK";
|
||||
else if (res == 1)
|
||||
status = "TIMEOUT";
|
||||
else if (res == 2)
|
||||
status = "INTERRUPTED";
|
||||
}
|
||||
if (res > -1) {
|
||||
pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
|
||||
if (!ast_strlen_zero(tmp)) {
|
||||
ast_verb(3, "User entered '%s'\n", tmp);
|
||||
tries = 0;
|
||||
} else {
|
||||
tries--;
|
||||
if (tries)
|
||||
ast_verb(3, "User entered nothing, %d chance%s left\n", tries, (tries != 1) ? "s" : "");
|
||||
else
|
||||
ast_verb(3, "User entered nothing.\n");
|
||||
}
|
||||
res = 0;
|
||||
} else {
|
||||
pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
|
||||
ast_verb(3, "User disconnected\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ast_check_hangup(chan))
|
||||
status = "HANGUP";
|
||||
pbx_builtin_setvar_helper(chan, "READSTATUS", status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, read_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Read Variable Application");
|
||||
258
trunk/apps/app_readexten.c
Normal file
258
trunk/apps/app_readexten.c
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2007 Dave Chappell
|
||||
*
|
||||
* David Chappell <David.Chappell@trincoll.edu>
|
||||
*
|
||||
* 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 Trivial application to read an extension into a variable
|
||||
*
|
||||
* \author David Chappell <David.Chappell@trincoll.edu>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/indications.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
enum {
|
||||
OPT_SKIP = (1 << 0),
|
||||
OPT_INDICATION = (1 << 1),
|
||||
OPT_NOANSWER = (1 << 2),
|
||||
} readexten_option_flags;
|
||||
|
||||
AST_APP_OPTIONS(readexten_app_options, {
|
||||
AST_APP_OPTION('s', OPT_SKIP),
|
||||
AST_APP_OPTION('i', OPT_INDICATION),
|
||||
AST_APP_OPTION('n', OPT_NOANSWER),
|
||||
});
|
||||
|
||||
static char *app = "ReadExten";
|
||||
|
||||
static char *synopsis = "Read an extension into a variable";
|
||||
|
||||
static char *descrip =
|
||||
" ReadExten(<variable>[,[<filename>][,[<context>][,[<option>][,<timeout>]]]])\n\n"
|
||||
"Reads a #-terminated string of digits from the user into the given variable.\n"
|
||||
" filename file to play before reading digits or tone with option i\n"
|
||||
" context context in which to match extensions\n"
|
||||
" option options are:\n"
|
||||
" s - Return immediately if the channel is not answered,\n"
|
||||
" i - Play filename as an indication tone from your\n"
|
||||
" indications.conf\n"
|
||||
" n - Read digits even if the channel is not answered.\n"
|
||||
" timeout An integer number of seconds to wait for a digit response. If\n"
|
||||
" greater than 0, that value will override the default timeout.\n\n"
|
||||
"ReadExten will set READEXTENSTATUS on exit with one of the following statuses:\n"
|
||||
" OK A valid extension exists in ${variable}\n"
|
||||
" TIMEOUT No extension was entered in the specified time\n"
|
||||
" INVALID An invalid extension, ${INVALID_EXTEN}, was entered\n"
|
||||
" SKIP Line was not up and the option 's' was specified\n"
|
||||
" ERROR Invalid arguments were passed\n";
|
||||
|
||||
|
||||
static int readexten_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char exten[256] = "";
|
||||
int maxdigits = sizeof(exten) - 1;
|
||||
int timeout = 0, digit_timeout = 0, x = 0;
|
||||
char *argcopy = NULL, *status = "";
|
||||
struct ind_tone_zone_sound *ts = NULL;
|
||||
struct ast_flags flags = {0};
|
||||
|
||||
AST_DECLARE_APP_ARGS(arglist,
|
||||
AST_APP_ARG(variable);
|
||||
AST_APP_ARG(filename);
|
||||
AST_APP_ARG(context);
|
||||
AST_APP_ARG(options);
|
||||
AST_APP_ARG(timeout);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "ReadExten requires at least one argument\n");
|
||||
pbx_builtin_setvar_helper(chan, "READEXTENSTATUS", "ERROR");
|
||||
return 0;
|
||||
}
|
||||
|
||||
argcopy = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(arglist, argcopy);
|
||||
|
||||
if (ast_strlen_zero(arglist.variable)) {
|
||||
ast_log(LOG_WARNING, "Invalid! Usage: ReadExten(variable[|filename][|context][|options][|timeout])\n\n");
|
||||
pbx_builtin_setvar_helper(chan, "READEXTENSTATUS", "ERROR");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(arglist.filename))
|
||||
arglist.filename = NULL;
|
||||
|
||||
if (ast_strlen_zero(arglist.context))
|
||||
arglist.context = chan->context;
|
||||
|
||||
if (!ast_strlen_zero(arglist.options))
|
||||
ast_app_parse_options(readexten_app_options, &flags, NULL, arglist.options);
|
||||
|
||||
if (!ast_strlen_zero(arglist.timeout)) {
|
||||
timeout = atoi(arglist.timeout);
|
||||
if (timeout > 0)
|
||||
timeout *= 1000;
|
||||
}
|
||||
|
||||
if (timeout <= 0)
|
||||
timeout = chan->pbx ? chan->pbx->rtimeout * 1000 : 10000;
|
||||
|
||||
if (digit_timeout <= 0)
|
||||
digit_timeout = chan->pbx ? chan->pbx->dtimeout * 1000 : 5000;
|
||||
|
||||
if (ast_test_flag(&flags, OPT_INDICATION) && !ast_strlen_zero(arglist.filename))
|
||||
ts = ast_get_indication_tone(chan->zone, arglist.filename);
|
||||
|
||||
do {
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if (ast_test_flag(&flags, OPT_SKIP)) {
|
||||
/* At the user's option, skip if the line is not up */
|
||||
pbx_builtin_setvar_helper(chan, arglist.variable, "");
|
||||
status = "SKIP";
|
||||
break;
|
||||
} else if (!ast_test_flag(&flags, OPT_NOANSWER)) {
|
||||
/* Otherwise answer unless we're supposed to read while on-hook */
|
||||
res = ast_answer(chan);
|
||||
}
|
||||
}
|
||||
|
||||
if (res < 0) {
|
||||
status = "HANGUP";
|
||||
break;
|
||||
}
|
||||
|
||||
ast_playtones_stop(chan);
|
||||
ast_stopstream(chan);
|
||||
|
||||
if (ts && ts->data[0])
|
||||
res = ast_playtones_start(chan, 0, ts->data, 0);
|
||||
else if (arglist.filename)
|
||||
res = ast_streamfile(chan, arglist.filename, chan->language);
|
||||
|
||||
for (x = 0; x < maxdigits; x++) {
|
||||
ast_debug(3, "extension so far: '%s', timeout: %d\n", exten, timeout);
|
||||
res = ast_waitfordigit(chan, timeout);
|
||||
|
||||
ast_playtones_stop(chan);
|
||||
ast_stopstream(chan);
|
||||
timeout = digit_timeout;
|
||||
|
||||
if (res < 1) { /* timeout expired or hangup */
|
||||
if (ast_check_hangup(chan))
|
||||
status = "HANGUP";
|
||||
else
|
||||
status = "TIMEOUT";
|
||||
break;
|
||||
} else if (res == '#') {
|
||||
break;
|
||||
}
|
||||
|
||||
exten[x] = res;
|
||||
if (!ast_matchmore_extension(chan, arglist.context, exten, 1 /* priority */, chan->cid.cid_num)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(status))
|
||||
break;
|
||||
|
||||
if (ast_exists_extension(chan, arglist.context, exten, 1, chan->cid.cid_num)) {
|
||||
ast_debug(3, "User entered valid extension '%s'\n", exten);
|
||||
pbx_builtin_setvar_helper(chan, arglist.variable, exten);
|
||||
status = "OK";
|
||||
} else {
|
||||
ast_debug(3, "User dialed invalid extension '%s' in context '%s' on %s\n", exten, arglist.context, chan->name);
|
||||
pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
|
||||
status = "INVALID";
|
||||
}
|
||||
} while (0);
|
||||
|
||||
pbx_builtin_setvar_helper(chan, "READEXTENSTATUS", status);
|
||||
|
||||
return status[0] == 'H' ? -1 : 0;
|
||||
}
|
||||
|
||||
static int acf_isexten_exec(struct ast_channel *chan, const char *cmd, char *parse, char *buffer, size_t buflen)
|
||||
{
|
||||
int priority_int;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(context);
|
||||
AST_APP_ARG(extension);
|
||||
AST_APP_ARG(priority);
|
||||
);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (ast_strlen_zero(args.context))
|
||||
args.context = chan->context;
|
||||
|
||||
if (ast_strlen_zero(args.extension)) {
|
||||
ast_log(LOG_WARNING, "Syntax: VALID_EXTEN([<context>],<extension>[,<priority>]) - missing argument <extension>!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_strlen_zero(args.priority))
|
||||
priority_int = 1;
|
||||
else
|
||||
priority_int = atoi(args.priority);
|
||||
|
||||
if (ast_exists_extension(chan, args.context, args.extension, priority_int, chan->cid.cid_num))
|
||||
ast_copy_string(buffer, "1", buflen);
|
||||
else
|
||||
ast_copy_string(buffer, "0", buflen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_custom_function acf_isexten = {
|
||||
.name = "VALID_EXTEN",
|
||||
.synopsis = "Determine whether an extension exists or not",
|
||||
.syntax = "VALID_EXTEN([<context>],<extension>[,<priority>])",
|
||||
.desc =
|
||||
"Returns a true value if the indicated context, extension, and priority exist.\n"
|
||||
"Context defaults to the current context, priority defaults to 1.\n",
|
||||
.read = acf_isexten_exec,
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res = ast_unregister_application(app);
|
||||
res |= ast_custom_function_unregister(&acf_isexten);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = ast_register_application(app, readexten_exec, synopsis, descrip);
|
||||
res |= ast_custom_function_register(&acf_isexten);
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Read and evaluate extension validity");
|
||||
107
trunk/apps/app_readfile.c
Normal file
107
trunk/apps/app_readfile.c
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Matt O'Gorman <mogorman@digium.com>
|
||||
*
|
||||
* 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 ReadFile application -- Reads in a File for you.
|
||||
*
|
||||
* \author Matt O'Gorman <mogorman@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
static char *app_readfile = "ReadFile";
|
||||
|
||||
static char *readfile_synopsis = "Read the contents of a text file into a channel variable";
|
||||
|
||||
static char *readfile_descrip =
|
||||
"ReadFile(varname=file,length)\n"
|
||||
" varname - Result stored here.\n"
|
||||
" file - The name of the file to read.\n"
|
||||
" length - Maximum number of characters to capture.\n";
|
||||
|
||||
|
||||
static int readfile_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=0;
|
||||
char *s, *varname=NULL, *file=NULL, *length=NULL, *returnvar=NULL;
|
||||
int len=0;
|
||||
static int deprecation_warning = 0;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "ReadFile require an argument!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
s = ast_strdupa(data);
|
||||
|
||||
varname = strsep(&s, "=");
|
||||
file = strsep(&s, ",");
|
||||
length = s;
|
||||
|
||||
if (deprecation_warning++ % 10 == 0)
|
||||
ast_log(LOG_WARNING, "ReadFile has been deprecated in favor of Set(%s=${FILE(%s,0,%s)})\n", varname, file, length);
|
||||
|
||||
if (!varname || !file) {
|
||||
ast_log(LOG_ERROR, "No file or variable specified!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (length) {
|
||||
if ((sscanf(length, "%d", &len) != 1) || (len < 0)) {
|
||||
ast_log(LOG_WARNING, "%s is not a positive number, defaulting length to max\n", length);
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((returnvar = ast_read_textfile(file))) {
|
||||
if (len > 0) {
|
||||
if (len < strlen(returnvar))
|
||||
returnvar[len]='\0';
|
||||
else
|
||||
ast_log(LOG_WARNING, "%s is longer than %d, and %d \n", file, len, (int)strlen(returnvar));
|
||||
}
|
||||
pbx_builtin_setvar_helper(chan, varname, returnvar);
|
||||
ast_free(returnvar);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app_readfile);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app_readfile, readfile_exec, readfile_synopsis, readfile_descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Stores output of file into a variable");
|
||||
365
trunk/apps/app_record.c
Normal file
365
trunk/apps/app_record.c
Normal file
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Matthew Fredrickson <creslin@digium.com>
|
||||
*
|
||||
* 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 Trivial application to record a sound file
|
||||
*
|
||||
* \author Matthew Fredrickson <creslin@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/dsp.h" /* use dsp routines for silence detection */
|
||||
|
||||
|
||||
static char *app = "Record";
|
||||
|
||||
static char *synopsis = "Record to a file";
|
||||
|
||||
static char *descrip =
|
||||
" Record(filename.format,silence[,maxduration][,options])\n\n"
|
||||
"Records from the channel into a given filename. If the file exists it will\n"
|
||||
"be overwritten.\n"
|
||||
"- 'format' is the format of the file type to be recorded (wav, gsm, etc).\n"
|
||||
"- 'silence' is the number of seconds of silence to allow before returning.\n"
|
||||
"- 'maxduration' is the maximum recording duration in seconds. If missing\n"
|
||||
"or 0 there is no maximum.\n"
|
||||
"- 'options' may contain any of the following letters:\n"
|
||||
" 'a' : append to existing recording rather than replacing\n"
|
||||
" 'n' : do not answer, but record anyway if line not yet answered\n"
|
||||
" 'q' : quiet (do not play a beep tone)\n"
|
||||
" 's' : skip recording if the line is not yet answered\n"
|
||||
" 't' : use alternate '*' terminator key (DTMF) instead of default '#'\n"
|
||||
" 'x' : ignore all terminator keys (DTMF) and keep recording until hangup\n"
|
||||
"\n"
|
||||
"If filename contains '%d', these characters will be replaced with a number\n"
|
||||
"incremented by one each time the file is recorded. A channel variable\n"
|
||||
"named RECORDED_FILE will also be set, which contains the final filemname.\n\n"
|
||||
"Use 'core show file formats' to see the available formats on your system\n\n"
|
||||
"User can press '#' to terminate the recording and continue to the next priority.\n\n"
|
||||
"If the user should hangup during a recording, all data will be lost and the\n"
|
||||
"application will teminate. \n";
|
||||
|
||||
enum {
|
||||
OPTION_APPEND = (1 << 0),
|
||||
OPTION_NOANSWER = (1 << 1),
|
||||
OPTION_QUIET = (1 << 2),
|
||||
OPTION_SKIP = (1 << 3),
|
||||
OPTION_STAR_TERMINATE = (1 << 4),
|
||||
OPTION_IGNORE_TERMINATE = (1 << 5),
|
||||
FLAG_HAS_PERCENT = (1 << 6),
|
||||
};
|
||||
|
||||
AST_APP_OPTIONS(app_opts,{
|
||||
AST_APP_OPTION('a', OPTION_APPEND),
|
||||
AST_APP_OPTION('n', OPTION_NOANSWER),
|
||||
AST_APP_OPTION('q', OPTION_QUIET),
|
||||
AST_APP_OPTION('s', OPTION_SKIP),
|
||||
AST_APP_OPTION('t', OPTION_STAR_TERMINATE),
|
||||
AST_APP_OPTION('x', OPTION_IGNORE_TERMINATE),
|
||||
});
|
||||
|
||||
static int record_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
int count = 0;
|
||||
char *ext = NULL, *opts[0];
|
||||
char *parse, *dir, *file;
|
||||
int i = 0;
|
||||
char tmp[256];
|
||||
|
||||
struct ast_filestream *s = NULL;
|
||||
struct ast_frame *f = NULL;
|
||||
|
||||
struct ast_dsp *sildet = NULL; /* silence detector dsp */
|
||||
int totalsilence = 0;
|
||||
int dspsilence = 0;
|
||||
int silence = 0; /* amount of silence to allow */
|
||||
int gotsilence = 0; /* did we timeout for silence? */
|
||||
int maxduration = 0; /* max duration of recording in milliseconds */
|
||||
int gottimeout = 0; /* did we timeout for maxduration exceeded? */
|
||||
int terminator = '#';
|
||||
int rfmt = 0;
|
||||
int ioflags;
|
||||
int waitres;
|
||||
struct ast_silence_generator *silgen = NULL;
|
||||
struct ast_flags flags = { 0, };
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(filename);
|
||||
AST_APP_ARG(silence);
|
||||
AST_APP_ARG(maxduration);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
/* The next few lines of code parse out the filename and header from the input string */
|
||||
if (ast_strlen_zero(data)) { /* no data implies no filename or anything is present */
|
||||
ast_log(LOG_WARNING, "Record requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
if (args.argc == 4)
|
||||
ast_app_parse_options(app_opts, &flags, opts, args.options);
|
||||
|
||||
if (!ast_strlen_zero(args.filename)) {
|
||||
if (strstr(args.filename, "%d"))
|
||||
ast_set_flag(&flags, FLAG_HAS_PERCENT);
|
||||
ext = strrchr(args.filename, '.'); /* to support filename with a . in the filename, not format */
|
||||
if (!ext)
|
||||
ext = strchr(args.filename, ':');
|
||||
if (ext) {
|
||||
*ext = '\0';
|
||||
ext++;
|
||||
}
|
||||
}
|
||||
if (!ext) {
|
||||
ast_log(LOG_WARNING, "No extension specified to filename!\n");
|
||||
return -1;
|
||||
}
|
||||
if (args.silence) {
|
||||
if ((sscanf(args.silence, "%d", &i) == 1) && (i > -1)) {
|
||||
silence = i * 1000;
|
||||
} else if (!ast_strlen_zero(args.silence)) {
|
||||
ast_log(LOG_WARNING, "'%s' is not a valid silence duration\n", args.silence);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.maxduration) {
|
||||
if ((sscanf(args.maxduration, "%d", &i) == 1) && (i > -1))
|
||||
/* Convert duration to milliseconds */
|
||||
maxduration = i * 1000;
|
||||
else if (!ast_strlen_zero(args.maxduration))
|
||||
ast_log(LOG_WARNING, "'%s' is not a valid maximum duration\n", args.maxduration);
|
||||
}
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_STAR_TERMINATE))
|
||||
terminator = '*';
|
||||
if (ast_test_flag(&flags, OPTION_IGNORE_TERMINATE))
|
||||
terminator = '\0';
|
||||
|
||||
/* done parsing */
|
||||
|
||||
/* these are to allow the use of the %d in the config file for a wild card of sort to
|
||||
create a new file with the inputed name scheme */
|
||||
if (ast_test_flag(&flags, FLAG_HAS_PERCENT)) {
|
||||
AST_DECLARE_APP_ARGS(fname,
|
||||
AST_APP_ARG(piece)[100];
|
||||
);
|
||||
char *tmp2 = ast_strdupa(args.filename);
|
||||
char countstring[15];
|
||||
int i;
|
||||
|
||||
/* Separate each piece out by the format specifier */
|
||||
AST_NONSTANDARD_APP_ARGS(fname, tmp2, '%');
|
||||
do {
|
||||
int tmplen;
|
||||
/* First piece has no leading percent, so it's copied verbatim */
|
||||
ast_copy_string(tmp, fname.piece[0], sizeof(tmp));
|
||||
tmplen = strlen(tmp);
|
||||
for (i = 1; i < fname.argc; i++) {
|
||||
if (fname.piece[i][0] == 'd') {
|
||||
/* Substitute the count */
|
||||
snprintf(countstring, sizeof(countstring), "%d", count);
|
||||
ast_copy_string(tmp + tmplen, countstring, sizeof(tmp) - tmplen);
|
||||
tmplen += strlen(countstring);
|
||||
} else if (tmplen + 2 < sizeof(tmp)) {
|
||||
/* Unknown format specifier - just copy it verbatim */
|
||||
tmp[tmplen++] = '%';
|
||||
tmp[tmplen++] = fname.piece[i][0];
|
||||
}
|
||||
/* Copy the remaining portion of the piece */
|
||||
ast_copy_string(tmp + tmplen, &(fname.piece[i][1]), sizeof(tmp) - tmplen);
|
||||
}
|
||||
count++;
|
||||
} while (ast_fileexists(tmp, ext, chan->language) > 0);
|
||||
pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
|
||||
} else
|
||||
ast_copy_string(tmp, args.filename, sizeof(tmp));
|
||||
/* end of routine mentioned */
|
||||
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if (ast_test_flag(&flags, OPTION_SKIP)) {
|
||||
/* At the user's option, skip if the line is not up */
|
||||
return 0;
|
||||
} else if (!ast_test_flag(&flags, OPTION_NOANSWER)) {
|
||||
/* Otherwise answer unless we're supposed to record while on-hook */
|
||||
res = ast_answer(chan);
|
||||
}
|
||||
}
|
||||
|
||||
if (res) {
|
||||
ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!ast_test_flag(&flags, OPTION_QUIET)) {
|
||||
/* Some code to play a nice little beep to signify the start of the record operation */
|
||||
res = ast_streamfile(chan, "beep", chan->language);
|
||||
if (!res) {
|
||||
res = ast_waitstream(chan, "");
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", chan->name);
|
||||
}
|
||||
ast_stopstream(chan);
|
||||
}
|
||||
|
||||
/* The end of beep code. Now the recording starts */
|
||||
|
||||
if (silence > 0) {
|
||||
rfmt = chan->readformat;
|
||||
res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
|
||||
if (res < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
|
||||
return -1;
|
||||
}
|
||||
sildet = ast_dsp_new();
|
||||
if (!sildet) {
|
||||
ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
|
||||
return -1;
|
||||
}
|
||||
ast_dsp_set_threshold(sildet, 256);
|
||||
}
|
||||
|
||||
/* Create the directory if it does not exist. */
|
||||
dir = ast_strdupa(tmp);
|
||||
if ((file = strrchr(dir, '/')))
|
||||
*file++ = '\0';
|
||||
ast_mkdir (dir, 0777);
|
||||
|
||||
ioflags = ast_test_flag(&flags, OPTION_APPEND) ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
|
||||
s = ast_writefile(tmp, ext, NULL, ioflags, 0, AST_FILE_MODE);
|
||||
|
||||
if (!s) {
|
||||
ast_log(LOG_WARNING, "Could not create file %s\n", args.filename);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ast_opt_transmit_silence)
|
||||
silgen = ast_channel_start_silence_generator(chan);
|
||||
|
||||
/* Request a video update */
|
||||
ast_indicate(chan, AST_CONTROL_VIDUPDATE);
|
||||
|
||||
if (maxduration <= 0)
|
||||
maxduration = -1;
|
||||
|
||||
while ((waitres = ast_waitfor(chan, maxduration)) > -1) {
|
||||
if (maxduration > 0) {
|
||||
if (waitres == 0) {
|
||||
gottimeout = 1;
|
||||
break;
|
||||
}
|
||||
maxduration = waitres;
|
||||
}
|
||||
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if (f->frametype == AST_FRAME_VOICE) {
|
||||
res = ast_writestream(s, f);
|
||||
|
||||
if (res) {
|
||||
ast_log(LOG_WARNING, "Problem writing frame\n");
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (silence > 0) {
|
||||
dspsilence = 0;
|
||||
ast_dsp_silence(sildet, f, &dspsilence);
|
||||
if (dspsilence) {
|
||||
totalsilence = dspsilence;
|
||||
} else {
|
||||
totalsilence = 0;
|
||||
}
|
||||
if (totalsilence > silence) {
|
||||
/* Ended happily with silence */
|
||||
ast_frfree(f);
|
||||
gotsilence = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (f->frametype == AST_FRAME_VIDEO) {
|
||||
res = ast_writestream(s, f);
|
||||
|
||||
if (res) {
|
||||
ast_log(LOG_WARNING, "Problem writing frame\n");
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
} else if ((f->frametype == AST_FRAME_DTMF) &&
|
||||
(f->subclass == terminator)) {
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
if (!f) {
|
||||
ast_debug(1, "Got hangup\n");
|
||||
res = -1;
|
||||
}
|
||||
|
||||
if (gotsilence) {
|
||||
ast_stream_rewind(s, silence - 1000);
|
||||
ast_truncstream(s);
|
||||
} else if (!gottimeout) {
|
||||
/* Strip off the last 1/4 second of it */
|
||||
ast_stream_rewind(s, 250);
|
||||
ast_truncstream(s);
|
||||
}
|
||||
ast_closestream(s);
|
||||
|
||||
if (silgen)
|
||||
ast_channel_stop_silence_generator(chan, silgen);
|
||||
|
||||
out:
|
||||
if ((silence > 0) && rfmt) {
|
||||
res = ast_set_read_format(chan, rfmt);
|
||||
if (res)
|
||||
ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name);
|
||||
if (sildet)
|
||||
ast_dsp_free(sildet);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, record_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Trivial Record Application");
|
||||
7482
trunk/apps/app_rpt.c
Normal file
7482
trunk/apps/app_rpt.c
Normal file
File diff suppressed because it is too large
Load Diff
112
trunk/apps/app_sayunixtime.c
Normal file
112
trunk/apps/app_sayunixtime.c
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (c) 2003, 2006 Tilghman Lesher. All rights reserved.
|
||||
* Copyright (c) 2006 Digium, Inc.
|
||||
*
|
||||
* Tilghman Lesher <app_sayunixtime__200309@the-tilghman.com>
|
||||
*
|
||||
* This code is released by the author with no restrictions on usage.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief SayUnixTime application
|
||||
*
|
||||
* \author Tilghman Lesher <app_sayunixtime__200309@the-tilghman.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/say.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *app_sayunixtime = "SayUnixTime";
|
||||
static char *app_datetime = "DateTime";
|
||||
|
||||
static char *sayunixtime_synopsis = "Says a specified time in a custom format";
|
||||
|
||||
static char *sayunixtime_descrip =
|
||||
"SayUnixTime([unixtime][,[timezone][,format]])\n"
|
||||
" unixtime - time, in seconds since Jan 1, 1970. May be negative.\n"
|
||||
" defaults to now.\n"
|
||||
" timezone - timezone, see /usr/share/zoneinfo for a list.\n"
|
||||
" defaults to machine default.\n"
|
||||
" format - a format the time is to be said in. See voicemail.conf.\n"
|
||||
" defaults to \"ABdY 'digits/at' IMp\"\n";
|
||||
static char *datetime_descrip =
|
||||
"DateTime([unixtime][,[timezone][,format]])\n"
|
||||
" unixtime - time, in seconds since Jan 1, 1970. May be negative.\n"
|
||||
" defaults to now.\n"
|
||||
" timezone - timezone, see /usr/share/zoneinfo for a list.\n"
|
||||
" defaults to machine default.\n"
|
||||
" format: - a format the time is to be said in. See voicemail.conf.\n"
|
||||
" defaults to \"ABdY 'digits/at' IMp\"\n";
|
||||
|
||||
|
||||
static int sayunixtime_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(timeval);
|
||||
AST_APP_ARG(timezone);
|
||||
AST_APP_ARG(format);
|
||||
);
|
||||
char *parse;
|
||||
int res = 0;
|
||||
time_t unixtime;
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
ast_get_time_t(args.timeval, &unixtime, time(NULL), NULL);
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
res = ast_answer(chan);
|
||||
|
||||
if (!res)
|
||||
res = ast_say_date_with_format(chan, unixtime, AST_DIGIT_ANY,
|
||||
chan->language, args.format, args.timezone);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(app_sayunixtime);
|
||||
res |= ast_unregister_application(app_datetime);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_register_application(app_sayunixtime, sayunixtime_exec, sayunixtime_synopsis, sayunixtime_descrip);
|
||||
res |= ast_register_application(app_datetime, sayunixtime_exec, sayunixtime_synopsis, datetime_descrip);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Say time");
|
||||
127
trunk/apps/app_senddtmf.c
Normal file
127
trunk/apps/app_senddtmf.c
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 App to send DTMF digits
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/manager.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
static char *app = "SendDTMF";
|
||||
|
||||
static char *synopsis = "Sends arbitrary DTMF digits";
|
||||
|
||||
static char *descrip =
|
||||
" SendDTMF(digits[,timeout_ms]): Sends DTMF digits on a channel. \n"
|
||||
" Accepted digits: 0-9, *#abcd, w (.5s pause)\n"
|
||||
" The application will either pass the assigned digits or terminate if it\n"
|
||||
" encounters an error.\n";
|
||||
|
||||
|
||||
static int senddtmf_exec(struct ast_channel *chan, void *vdata)
|
||||
{
|
||||
int res = 0;
|
||||
char *data;
|
||||
int timeout = 0, duration = 0;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(digits);
|
||||
AST_APP_ARG(timeout);
|
||||
AST_APP_ARG(duration);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(vdata)) {
|
||||
ast_log(LOG_WARNING, "SendDTMF requires an argument (digits or *#aAbBcCdD)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
data = ast_strdupa(vdata);
|
||||
AST_STANDARD_APP_ARGS(args, data);
|
||||
|
||||
if (!ast_strlen_zero(args.timeout))
|
||||
timeout = atoi(args.timeout);
|
||||
if (!ast_strlen_zero(args.duration))
|
||||
duration = atoi(args.duration);
|
||||
res = ast_dtmf_stream(chan, NULL, args.digits, timeout <= 0 ? 250 : timeout, duration);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static char mandescr_playdtmf[] =
|
||||
"Description: Plays a dtmf digit on the specified channel.\n"
|
||||
"Variables: (all are required)\n"
|
||||
" Channel: Channel name to send digit to\n"
|
||||
" Digit: The dtmf digit to play\n";
|
||||
|
||||
static int manager_play_dtmf(struct mansession *s, const struct message *m)
|
||||
{
|
||||
const char *channel = astman_get_header(m, "Channel");
|
||||
const char *digit = astman_get_header(m, "Digit");
|
||||
struct ast_channel *chan = ast_get_channel_by_name_locked(channel);
|
||||
|
||||
if (!chan) {
|
||||
astman_send_error(s, m, "Channel not specified");
|
||||
return 0;
|
||||
}
|
||||
if (!digit) {
|
||||
astman_send_error(s, m, "No digit specified");
|
||||
ast_channel_unlock(chan);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ast_senddigit(chan, *digit, 0);
|
||||
|
||||
ast_channel_unlock(chan);
|
||||
astman_send_ack(s, m, "DTMF successfully queued");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(app);
|
||||
res |= ast_manager_unregister("PlayDTMF");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_manager_register2( "PlayDTMF", EVENT_FLAG_CALL, manager_play_dtmf, "Play DTMF signal on a specific channel.", mandescr_playdtmf );
|
||||
res |= ast_register_application(app, senddtmf_exec, synopsis, descrip);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Send DTMF digits Application");
|
||||
100
trunk/apps/app_sendtext.c
Normal file
100
trunk/apps/app_sendtext.c
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 App to transmit a text message
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \note Requires support of sending text messages from channel driver
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static const char *app = "SendText";
|
||||
|
||||
static const char *synopsis = "Send a Text Message";
|
||||
|
||||
static const char *descrip =
|
||||
" SendText(text[,options]): Sends text to current channel (callee).\n"
|
||||
"Result of transmission will be stored in the SENDTEXTSTATUS\n"
|
||||
"channel variable:\n"
|
||||
" SUCCESS Transmission succeeded\n"
|
||||
" FAILURE Transmission failed\n"
|
||||
" UNSUPPORTED Text transmission not supported by channel\n"
|
||||
"\n"
|
||||
"At this moment, text is supposed to be 7 bit ASCII in most channels.\n";
|
||||
|
||||
static int sendtext_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char *status = "UNSUPPORTED";
|
||||
char *parse = NULL;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(text);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "SendText requires an argument (text[,options])\n");
|
||||
return -1;
|
||||
} else
|
||||
parse = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (args.options) {
|
||||
}
|
||||
|
||||
ast_channel_lock(chan);
|
||||
if (!chan->tech->send_text) {
|
||||
ast_channel_unlock(chan);
|
||||
/* Does not support transport */
|
||||
return 0;
|
||||
}
|
||||
status = "FAILURE";
|
||||
ast_channel_unlock(chan);
|
||||
res = ast_sendtext(chan, args.text);
|
||||
if (!res)
|
||||
status = "SUCCESS";
|
||||
pbx_builtin_setvar_helper(chan, "SENDTEXTSTATUS", status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, sendtext_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Send Text Applications");
|
||||
93
trunk/apps/app_setcallerid.c
Normal file
93
trunk/apps/app_setcallerid.c
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 App to set callerid presentation
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
#include "asterisk/image.h"
|
||||
#include "asterisk/callerid.h"
|
||||
|
||||
static char *app2 = "SetCallerPres";
|
||||
|
||||
static char *synopsis2 = "Set CallerID Presentation";
|
||||
|
||||
|
||||
static char *descrip2 =
|
||||
" SetCallerPres(presentation): Set Caller*ID presentation on a call.\n"
|
||||
" Valid presentations are:\n"
|
||||
"\n"
|
||||
" allowed_not_screened : Presentation Allowed, Not Screened\n"
|
||||
" allowed_passed_screen : Presentation Allowed, Passed Screen\n"
|
||||
" allowed_failed_screen : Presentation Allowed, Failed Screen\n"
|
||||
" allowed : Presentation Allowed, Network Number\n"
|
||||
" prohib_not_screened : Presentation Prohibited, Not Screened\n"
|
||||
" prohib_passed_screen : Presentation Prohibited, Passed Screen\n"
|
||||
" prohib_failed_screen : Presentation Prohibited, Failed Screen\n"
|
||||
" prohib : Presentation Prohibited, Network Number\n"
|
||||
" unavailable : Number Unavailable\n"
|
||||
"\n"
|
||||
;
|
||||
|
||||
static int setcallerid_pres_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int pres = -1;
|
||||
static int deprecated = 0;
|
||||
|
||||
if (!deprecated) {
|
||||
deprecated = 1;
|
||||
ast_log(LOG_WARNING, "SetCallerPres is deprecated. Please use Set(CALLERPRES()=%s) instead.\n", (char *)data);
|
||||
}
|
||||
pres = ast_parse_caller_presentation(data);
|
||||
|
||||
if (pres < 0) {
|
||||
ast_log(LOG_WARNING, "'%s' is not a valid presentation (see 'show application SetCallerPres')\n",
|
||||
(char *) data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
chan->cid.cid_pres = pres;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app2);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app2, setcallerid_pres_exec, synopsis2, descrip2);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Set CallerID Presentation Application");
|
||||
125
trunk/apps/app_skel.c
Normal file
125
trunk/apps/app_skel.c
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) <Year>, <Your Name Here>
|
||||
*
|
||||
* <Your Name Here> <<Your Email Here>>
|
||||
*
|
||||
* 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 Skeleton application
|
||||
*
|
||||
* \author\verbatim <Your Name Here> <<Your Email Here>> \endverbatim
|
||||
*
|
||||
* This is a skeleton for development of an Asterisk application
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<defaultenabled>no</defaultenabled>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *app = "Skel";
|
||||
static char *synopsis =
|
||||
"Skeleton application.";
|
||||
static char *descrip = "This application is a template to build other applications from.\n"
|
||||
" It shows you the basic structure to create your own Asterisk applications.\n";
|
||||
|
||||
enum {
|
||||
OPTION_A = (1 << 0),
|
||||
OPTION_B = (1 << 1),
|
||||
OPTION_C = (1 << 2),
|
||||
} option_flags;
|
||||
|
||||
enum {
|
||||
OPTION_ARG_B = 0,
|
||||
OPTION_ARG_C = 1,
|
||||
/* This *must* be the last value in this enum! */
|
||||
OPTION_ARG_ARRAY_SIZE = 2,
|
||||
} option_args;
|
||||
|
||||
AST_APP_OPTIONS(app_opts,{
|
||||
AST_APP_OPTION('a', OPTION_A),
|
||||
AST_APP_OPTION_ARG('b', OPTION_B, OPTION_ARG_B),
|
||||
AST_APP_OPTION_ARG('c', OPTION_C, OPTION_ARG_C),
|
||||
});
|
||||
|
||||
|
||||
static int app_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_flags flags;
|
||||
char *parse, *opts[OPTION_ARG_ARRAY_SIZE];
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(dummy);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "%s requires an argument (dummy[,options])\n", app);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Do our thing here */
|
||||
|
||||
/* We need to make a copy of the input string if we are going to modify it! */
|
||||
parse = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (args.argc == 2)
|
||||
ast_app_parse_options(app_opts, &flags, opts, args.options);
|
||||
|
||||
if (!ast_strlen_zero(args.dummy))
|
||||
ast_log(LOG_NOTICE, "Dummy value is : %s\n", args.dummy);
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_A))
|
||||
ast_log(LOG_NOTICE, "Option A is set\n");
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_B))
|
||||
ast_log(LOG_NOTICE, "Option B is set with : %s\n", opts[OPTION_ARG_B] ? opts[OPTION_ARG_B] : "<unspecified>");
|
||||
|
||||
if (ast_test_flag(&flags, OPTION_C))
|
||||
ast_log(LOG_NOTICE, "Option C is set with : %s\n", opts[OPTION_ARG_C] ? opts[OPTION_ARG_C] : "<unspecified>");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
res = ast_unregister_application(app);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
if (ast_register_application(app, app_exec, synopsis, descrip))
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Skeleton (sample) Application");
|
||||
1932
trunk/apps/app_sms.c
Normal file
1932
trunk/apps/app_sms.c
Normal file
File diff suppressed because it is too large
Load Diff
120
trunk/apps/app_softhangup.c
Normal file
120
trunk/apps/app_softhangup.c
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 SoftHangup application
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *synopsis = "Soft Hangup Application";
|
||||
|
||||
static char *desc = " SoftHangup(Technology/resource[,options]):\n"
|
||||
"Hangs up the requested channel. If there are no channels to hangup,\n"
|
||||
"the application will report it.\n"
|
||||
" Options:\n"
|
||||
" 'a' - hang up all channels on a specified device instead of a single resource\n";
|
||||
|
||||
static char *app = "SoftHangup";
|
||||
|
||||
enum {
|
||||
OPTION_ALL = (1 << 0),
|
||||
};
|
||||
|
||||
AST_APP_OPTIONS(app_opts,{
|
||||
AST_APP_OPTION('a', OPTION_ALL),
|
||||
});
|
||||
|
||||
static int softhangup_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct ast_channel *c = NULL;
|
||||
char *cut, *opts[0];
|
||||
char name[AST_CHANNEL_NAME] = "", *parse;
|
||||
struct ast_flags flags;
|
||||
int lenmatch;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(channel);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "SoftHangup requires an argument (Technology/resource)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (args.argc == 2)
|
||||
ast_app_parse_options(app_opts, &flags, opts, args.options);
|
||||
lenmatch = strlen(args.channel);
|
||||
|
||||
for (c = ast_walk_channel_by_name_prefix_locked(NULL, args.channel, lenmatch);
|
||||
c;
|
||||
c = ast_walk_channel_by_name_prefix_locked(c, args.channel, lenmatch)) {
|
||||
ast_copy_string(name, c->name, sizeof(name));
|
||||
if (ast_test_flag(&flags, OPTION_ALL)) {
|
||||
/* CAPI is set up like CAPI[foo/bar]/clcnt */
|
||||
if (!strcmp(c->tech->type, "CAPI"))
|
||||
cut = strrchr(name, '/');
|
||||
/* Basically everything else is Foo/Bar-Z */
|
||||
else
|
||||
cut = strchr(name, '-');
|
||||
/* Get rid of what we've cut */
|
||||
if (cut)
|
||||
*cut = 0;
|
||||
}
|
||||
if (!strcasecmp(name, args.channel)) {
|
||||
ast_log(LOG_WARNING, "Soft hanging %s up.\n", c->name);
|
||||
ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
|
||||
if (!ast_test_flag(&flags, OPTION_ALL)) {
|
||||
ast_channel_unlock(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ast_channel_unlock(c);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, softhangup_exec, synopsis, desc);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hangs up the requested channel");
|
||||
796
trunk/apps/app_speech_utils.c
Normal file
796
trunk/apps/app_speech_utils.c
Normal file
@@ -0,0 +1,796 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2006, Digium, Inc.
|
||||
*
|
||||
* Joshua Colp <jcolp@digium.com>
|
||||
*
|
||||
* 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 Speech Recognition Utility Applications
|
||||
*
|
||||
* \author Joshua Colp <jcolp@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/speech.h"
|
||||
|
||||
/* Descriptions for each application */
|
||||
static char *speechcreate_descrip =
|
||||
" SpeechCreate(engine name):\n"
|
||||
"This application creates information to be used by all the other applications.\n"
|
||||
"It must be called before doing any speech recognition activities such as activating a grammar.\n"
|
||||
"It takes the engine name to use as the argument, if not specified the default engine will be used.\n";
|
||||
|
||||
static char *speechactivategrammar_descrip =
|
||||
" SpeechActivateGrammar(Grammar Name):\n"
|
||||
"This activates the specified grammar to be recognized by the engine.\n"
|
||||
"A grammar tells the speech recognition engine what to recognize, and how to portray it back to you \n"
|
||||
"in the dialplan. The grammar name is the only argument to this application.\n";
|
||||
|
||||
static char *speechstart_descrip =
|
||||
" SpeechStart():\n"
|
||||
"Tell the speech recognition engine that it should start trying to get results from audio being \n"
|
||||
"fed to it. This has no arguments.\n";
|
||||
|
||||
static char *speechbackground_descrip =
|
||||
" SpeechBackground(Sound File,Timeout):\n"
|
||||
"This application plays a sound file and waits for the person to speak. Once they start speaking playback\n"
|
||||
"of the file stops, and silence is heard. Once they stop talking the processing sound is played to indicate\n"
|
||||
"the speech recognition engine is working. Once results are available the application returns and results \n"
|
||||
"(score and text) are available using dialplan functions.\n"
|
||||
"The first text and score are ${SPEECH_TEXT(0)} AND ${SPEECH_SCORE(0)} while the second are ${SPEECH_TEXT(1)}\n"
|
||||
"and ${SPEECH_SCORE(1)}.\n"
|
||||
"The first argument is the sound file and the second is the timeout integer in seconds. Note the timeout will\n"
|
||||
"only start once the sound file has stopped playing.\n";
|
||||
|
||||
static char *speechdeactivategrammar_descrip =
|
||||
" SpeechDeactivateGrammar(Grammar Name):\n"
|
||||
"This deactivates the specified grammar so that it is no longer recognized.\n"
|
||||
"The only argument is the grammar name to deactivate.\n";
|
||||
|
||||
static char *speechprocessingsound_descrip =
|
||||
" SpeechProcessingSound(Sound File):\n"
|
||||
"This changes the processing sound that SpeechBackground plays back when the speech recognition engine is\n"
|
||||
"processing and working to get results.\n"
|
||||
"It takes the sound file as the only argument.\n";
|
||||
|
||||
static char *speechdestroy_descrip =
|
||||
" SpeechDestroy():\n"
|
||||
"This destroys the information used by all the other speech recognition applications.\n"
|
||||
"If you call this application but end up wanting to recognize more speech, you must call SpeechCreate\n"
|
||||
"again before calling any other application. It takes no arguments.\n";
|
||||
|
||||
static char *speechload_descrip =
|
||||
" SpeechLoadGrammar(Grammar Name,Path):\n"
|
||||
"Load a grammar only on the channel, not globally.\n"
|
||||
"It takes the grammar name as first argument and path as second.\n";
|
||||
|
||||
static char *speechunload_descrip =
|
||||
" SpeechUnloadGrammar(Grammar Name):\n"
|
||||
"Unload a grammar. It takes the grammar name as the only argument.\n";
|
||||
|
||||
/*! \brief Helper function used by datastores to destroy the speech structure upon hangup */
|
||||
static void destroy_callback(void *data)
|
||||
{
|
||||
struct ast_speech *speech = (struct ast_speech*)data;
|
||||
|
||||
if (speech == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Deallocate now */
|
||||
ast_speech_destroy(speech);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*! \brief Static structure for datastore information */
|
||||
static const struct ast_datastore_info speech_datastore = {
|
||||
.type = "speech",
|
||||
.destroy = destroy_callback
|
||||
};
|
||||
|
||||
/*! \brief Helper function used to find the speech structure attached to a channel */
|
||||
static struct ast_speech *find_speech(struct ast_channel *chan)
|
||||
{
|
||||
struct ast_speech *speech = NULL;
|
||||
struct ast_datastore *datastore = NULL;
|
||||
|
||||
datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
|
||||
if (datastore == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
speech = datastore->data;
|
||||
|
||||
return speech;
|
||||
}
|
||||
|
||||
/* Helper function to find a specific speech recognition result by number and nbest alternative */
|
||||
static struct ast_speech_result *find_result(struct ast_speech_result *results, char *result_num)
|
||||
{
|
||||
struct ast_speech_result *result = results;
|
||||
char *tmp = NULL;
|
||||
int nbest_num = 0, wanted_num = 0, i = 0;
|
||||
|
||||
if (!result)
|
||||
return NULL;
|
||||
|
||||
if ((tmp = strchr(result_num, '/'))) {
|
||||
*tmp++ = '\0';
|
||||
nbest_num = atoi(result_num);
|
||||
wanted_num = atoi(tmp);
|
||||
} else {
|
||||
wanted_num = atoi(result_num);
|
||||
}
|
||||
|
||||
do {
|
||||
if (result->nbest_num != nbest_num)
|
||||
continue;
|
||||
if (i == wanted_num)
|
||||
break;
|
||||
i++;
|
||||
} while ((result = AST_LIST_NEXT(result, list)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! \brief SPEECH_SCORE() Dialplan Function */
|
||||
static int speech_score(struct ast_channel *chan, const char *cmd, char *data,
|
||||
char *buf, size_t len)
|
||||
{
|
||||
struct ast_speech_result *result = NULL;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
char tmp[128] = "";
|
||||
|
||||
if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
|
||||
return -1;
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "%d", result->score);
|
||||
|
||||
ast_copy_string(buf, tmp, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_custom_function speech_score_function = {
|
||||
.name = "SPEECH_SCORE",
|
||||
.synopsis = "Gets the confidence score of a result.",
|
||||
.syntax = "SPEECH_SCORE([nbest number/]result number)",
|
||||
.desc =
|
||||
"Gets the confidence score of a result.\n",
|
||||
.read = speech_score,
|
||||
.write = NULL,
|
||||
};
|
||||
|
||||
/*! \brief SPEECH_TEXT() Dialplan Function */
|
||||
static int speech_text(struct ast_channel *chan, const char *cmd, char *data,
|
||||
char *buf, size_t len)
|
||||
{
|
||||
struct ast_speech_result *result = NULL;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
|
||||
return -1;
|
||||
|
||||
if (result->text != NULL)
|
||||
ast_copy_string(buf, result->text, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_custom_function speech_text_function = {
|
||||
.name = "SPEECH_TEXT",
|
||||
.synopsis = "Gets the recognized text of a result.",
|
||||
.syntax = "SPEECH_TEXT([nbest number/]result number)",
|
||||
.desc =
|
||||
"Gets the recognized text of a result.\n",
|
||||
.read = speech_text,
|
||||
.write = NULL,
|
||||
};
|
||||
|
||||
/*! \brief SPEECH_GRAMMAR() Dialplan Function */
|
||||
static int speech_grammar(struct ast_channel *chan, const char *cmd, char *data,
|
||||
char *buf, size_t len)
|
||||
{
|
||||
struct ast_speech_result *result = NULL;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (data == NULL || speech == NULL || !(result = find_result(speech->results, data)))
|
||||
return -1;
|
||||
|
||||
if (result->grammar != NULL)
|
||||
ast_copy_string(buf, result->grammar, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_custom_function speech_grammar_function = {
|
||||
.name = "SPEECH_GRAMMAR",
|
||||
.synopsis = "Gets the matched grammar of a result if available.",
|
||||
.syntax = "SPEECH_GRAMMAR([nbest number/]result number)",
|
||||
.desc =
|
||||
"Gets the matched grammar of a result if available.\n",
|
||||
.read = speech_grammar,
|
||||
.write = NULL,
|
||||
};
|
||||
|
||||
/*! \brief SPEECH_ENGINE() Dialplan Function */
|
||||
static int speech_engine_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
|
||||
{
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (data == NULL || speech == NULL)
|
||||
return -1;
|
||||
|
||||
ast_speech_change(speech, data, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_custom_function speech_engine_function = {
|
||||
.name = "SPEECH_ENGINE",
|
||||
.synopsis = "Change a speech engine specific attribute.",
|
||||
.syntax = "SPEECH_ENGINE(name)=value",
|
||||
.desc =
|
||||
"Changes a speech engine specific attribute.\n",
|
||||
.read = NULL,
|
||||
.write = speech_engine_write,
|
||||
};
|
||||
|
||||
/*! \brief SPEECH_RESULTS_TYPE() Dialplan Function */
|
||||
static int speech_results_type_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
|
||||
{
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (data == NULL || speech == NULL)
|
||||
return -1;
|
||||
|
||||
if (!strcasecmp(value, "normal"))
|
||||
ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NORMAL);
|
||||
else if (!strcasecmp(value, "nbest"))
|
||||
ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NBEST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_custom_function speech_results_type_function = {
|
||||
.name = "SPEECH_RESULTS_TYPE",
|
||||
.synopsis = "Sets the type of results that will be returned.",
|
||||
.syntax = "SPEECH_RESULTS_TYPE()=results type",
|
||||
.desc =
|
||||
"Sets the type of results that will be returned. Valid options are normal or nbest.",
|
||||
.read = NULL,
|
||||
.write = speech_results_type_write,
|
||||
};
|
||||
|
||||
/*! \brief SPEECH() Dialplan Function */
|
||||
static int speech_read(struct ast_channel *chan, const char *cmd, char *data,
|
||||
char *buf, size_t len)
|
||||
{
|
||||
int results = 0;
|
||||
struct ast_speech_result *result = NULL;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
char tmp[128] = "";
|
||||
|
||||
/* Now go for the various options */
|
||||
if (!strcasecmp(data, "status")) {
|
||||
if (speech != NULL)
|
||||
ast_copy_string(buf, "1", len);
|
||||
else
|
||||
ast_copy_string(buf, "0", len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Make sure we have a speech structure for everything else */
|
||||
if (speech == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check to see if they are checking for silence */
|
||||
if (!strcasecmp(data, "spoke")) {
|
||||
if (ast_test_flag(speech, AST_SPEECH_SPOKE))
|
||||
ast_copy_string(buf, "1", len);
|
||||
else
|
||||
ast_copy_string(buf, "0", len);
|
||||
} else if (!strcasecmp(data, "results")) {
|
||||
/* Count number of results */
|
||||
for (result = speech->results; result; result = AST_LIST_NEXT(result, list))
|
||||
results++;
|
||||
snprintf(tmp, sizeof(tmp), "%d", results);
|
||||
ast_copy_string(buf, tmp, len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_custom_function speech_function = {
|
||||
.name = "SPEECH",
|
||||
.synopsis = "Gets information about speech recognition results.",
|
||||
.syntax = "SPEECH(argument)",
|
||||
.desc =
|
||||
"Gets information about speech recognition results.\n"
|
||||
"status: Returns 1 upon speech object existing, or 0 if not\n"
|
||||
"spoke: Returns 1 if spoker spoke, or 0 if not\n"
|
||||
"results: Returns number of results that were recognized\n",
|
||||
.read = speech_read,
|
||||
.write = NULL,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*! \brief SpeechCreate() Dialplan Application */
|
||||
static int speech_create(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct ast_speech *speech = NULL;
|
||||
struct ast_datastore *datastore = NULL;
|
||||
|
||||
/* Request a speech object */
|
||||
speech = ast_speech_new(data, chan->nativeformats);
|
||||
if (speech == NULL) {
|
||||
/* Not available */
|
||||
pbx_builtin_setvar_helper(chan, "ERROR", "1");
|
||||
return 0;
|
||||
}
|
||||
|
||||
datastore = ast_channel_datastore_alloc(&speech_datastore, NULL);
|
||||
if (datastore == NULL) {
|
||||
ast_speech_destroy(speech);
|
||||
pbx_builtin_setvar_helper(chan, "ERROR", "1");
|
||||
return 0;
|
||||
}
|
||||
datastore->data = speech;
|
||||
ast_channel_datastore_add(chan, datastore);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief SpeechLoadGrammar(Grammar Name,Path) Dialplan Application */
|
||||
static int speech_load(struct ast_channel *chan, void *vdata)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
char *data;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(grammar);
|
||||
AST_APP_ARG(path);
|
||||
);
|
||||
|
||||
data = ast_strdupa(vdata);
|
||||
AST_STANDARD_APP_ARGS(args, data);
|
||||
|
||||
if (speech == NULL)
|
||||
return -1;
|
||||
|
||||
if (args.argc != 2)
|
||||
return -1;
|
||||
|
||||
/* Load the grammar locally on the object */
|
||||
res = ast_speech_grammar_load(speech, args.grammar, args.path);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief SpeechUnloadGrammar(Grammar Name) Dialplan Application */
|
||||
static int speech_unload(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (speech == NULL)
|
||||
return -1;
|
||||
|
||||
/* Unload the grammar */
|
||||
res = ast_speech_grammar_unload(speech, data);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief SpeechDeactivateGrammar(Grammar Name) Dialplan Application */
|
||||
static int speech_deactivate(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (speech == NULL)
|
||||
return -1;
|
||||
|
||||
/* Deactivate the grammar on the speech object */
|
||||
res = ast_speech_grammar_deactivate(speech, data);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief SpeechActivateGrammar(Grammar Name) Dialplan Application */
|
||||
static int speech_activate(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (speech == NULL)
|
||||
return -1;
|
||||
|
||||
/* Activate the grammar on the speech object */
|
||||
res = ast_speech_grammar_activate(speech, data);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief SpeechStart() Dialplan Application */
|
||||
static int speech_start(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (speech == NULL)
|
||||
return -1;
|
||||
|
||||
ast_speech_start(speech);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief SpeechProcessingSound(Sound File) Dialplan Application */
|
||||
static int speech_processing_sound(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
|
||||
if (speech == NULL)
|
||||
return -1;
|
||||
|
||||
if (speech->processing_sound != NULL) {
|
||||
ast_free(speech->processing_sound);
|
||||
speech->processing_sound = NULL;
|
||||
}
|
||||
|
||||
speech->processing_sound = ast_strdup(data);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \brief Helper function used by speech_background to playback a soundfile */
|
||||
static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
|
||||
{
|
||||
struct ast_filestream *fs = NULL;
|
||||
|
||||
if (!(fs = ast_openstream(chan, filename, preflang)))
|
||||
return -1;
|
||||
|
||||
if (ast_applystream(chan, fs))
|
||||
return -1;
|
||||
|
||||
ast_playstream(fs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief SpeechBackground(Sound File,Timeout) Dialplan Application */
|
||||
static int speech_background(struct ast_channel *chan, void *data)
|
||||
{
|
||||
unsigned int timeout = 0;
|
||||
int res = 0, done = 0, started = 0, quieted = 0, max_dtmf_len = 0;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
struct ast_frame *f = NULL;
|
||||
int oldreadformat = AST_FORMAT_SLINEAR;
|
||||
char dtmf[AST_MAX_EXTENSION] = "";
|
||||
time_t start, current;
|
||||
struct ast_datastore *datastore = NULL;
|
||||
char *parse, *filename_tmp = NULL, *filename = NULL, tmp[2] = "", dtmf_terminator = '#';
|
||||
const char *tmp2 = NULL;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(soundfile);
|
||||
AST_APP_ARG(timeout);
|
||||
);
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (speech == NULL)
|
||||
return -1;
|
||||
|
||||
/* If channel is not already answered, then answer it */
|
||||
if (chan->_state != AST_STATE_UP && ast_answer(chan))
|
||||
return -1;
|
||||
|
||||
/* Record old read format */
|
||||
oldreadformat = chan->readformat;
|
||||
|
||||
/* Change read format to be signed linear */
|
||||
if (ast_set_read_format(chan, speech->format))
|
||||
return -1;
|
||||
|
||||
if (!ast_strlen_zero(args.soundfile)) {
|
||||
/* Yay sound file */
|
||||
filename_tmp = ast_strdupa(args.soundfile);
|
||||
if (!ast_strlen_zero(args.timeout)) {
|
||||
if ((timeout = atoi(args.timeout)) == 0)
|
||||
timeout = -1;
|
||||
} else
|
||||
timeout = 0;
|
||||
}
|
||||
|
||||
/* See if the maximum DTMF length variable is set... we use a variable in case they want to carry it through their entire dialplan */
|
||||
if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2))
|
||||
max_dtmf_len = atoi(tmp2);
|
||||
|
||||
/* See if a terminator is specified */
|
||||
if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_TERMINATOR"))) {
|
||||
if (ast_strlen_zero(tmp2))
|
||||
dtmf_terminator = '\0';
|
||||
else
|
||||
dtmf_terminator = tmp2[0];
|
||||
}
|
||||
|
||||
/* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
|
||||
if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
|
||||
ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
|
||||
ast_speech_start(speech);
|
||||
}
|
||||
|
||||
/* Ensure no streams are currently running */
|
||||
ast_stopstream(chan);
|
||||
|
||||
/* Okay it's streaming so go into a loop grabbing frames! */
|
||||
while (done == 0) {
|
||||
/* If the filename is null and stream is not running, start up a new sound file */
|
||||
if (!quieted && (chan->streamid == -1 && chan->timingfunc == NULL) && (filename = strsep(&filename_tmp, "&"))) {
|
||||
/* Discard old stream information */
|
||||
ast_stopstream(chan);
|
||||
/* Start new stream */
|
||||
speech_streamfile(chan, filename, chan->language);
|
||||
}
|
||||
|
||||
/* Run scheduled stuff */
|
||||
ast_sched_runq(chan->sched);
|
||||
|
||||
/* Yay scheduling */
|
||||
res = ast_sched_wait(chan->sched);
|
||||
if (res < 0)
|
||||
res = 1000;
|
||||
|
||||
/* If there is a frame waiting, get it - if not - oh well */
|
||||
if (ast_waitfor(chan, res) > 0) {
|
||||
f = ast_read(chan);
|
||||
if (f == NULL) {
|
||||
/* The channel has hung up most likely */
|
||||
done = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Do timeout check (shared between audio/dtmf) */
|
||||
if ((!quieted || strlen(dtmf)) && started == 1) {
|
||||
time(¤t);
|
||||
if ((current-start) >= timeout) {
|
||||
done = 1;
|
||||
if (f)
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Do checks on speech structure to see if it's changed */
|
||||
ast_mutex_lock(&speech->lock);
|
||||
if (ast_test_flag(speech, AST_SPEECH_QUIET)) {
|
||||
if (chan->stream)
|
||||
ast_stopstream(chan);
|
||||
ast_clear_flag(speech, AST_SPEECH_QUIET);
|
||||
quieted = 1;
|
||||
}
|
||||
/* Check state so we can see what to do */
|
||||
switch (speech->state) {
|
||||
case AST_SPEECH_STATE_READY:
|
||||
/* If audio playback has stopped do a check for timeout purposes */
|
||||
if (chan->streamid == -1 && chan->timingfunc == NULL)
|
||||
ast_stopstream(chan);
|
||||
if (!quieted && chan->stream == NULL && timeout && started == 0 && !filename_tmp) {
|
||||
if (timeout == -1) {
|
||||
done = 1;
|
||||
if (f)
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
time(&start);
|
||||
started = 1;
|
||||
}
|
||||
/* Write audio frame out to speech engine if no DTMF has been received */
|
||||
if (!strlen(dtmf) && f != NULL && f->frametype == AST_FRAME_VOICE) {
|
||||
ast_speech_write(speech, f->data, f->datalen);
|
||||
}
|
||||
break;
|
||||
case AST_SPEECH_STATE_WAIT:
|
||||
/* Cue up waiting sound if not already playing */
|
||||
if (!strlen(dtmf)) {
|
||||
if (chan->stream == NULL) {
|
||||
if (speech->processing_sound != NULL) {
|
||||
if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
|
||||
speech_streamfile(chan, speech->processing_sound, chan->language);
|
||||
}
|
||||
}
|
||||
} else if (chan->streamid == -1 && chan->timingfunc == NULL) {
|
||||
ast_stopstream(chan);
|
||||
if (speech->processing_sound != NULL) {
|
||||
if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound,"none")) {
|
||||
speech_streamfile(chan, speech->processing_sound, chan->language);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AST_SPEECH_STATE_DONE:
|
||||
/* Now that we are done... let's switch back to not ready state */
|
||||
ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
|
||||
if (!strlen(dtmf)) {
|
||||
/* Copy to speech structure the results, if available */
|
||||
speech->results = ast_speech_results_get(speech);
|
||||
/* Break out of our background too */
|
||||
done = 1;
|
||||
/* Stop audio playback */
|
||||
if (chan->stream != NULL) {
|
||||
ast_stopstream(chan);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ast_mutex_unlock(&speech->lock);
|
||||
|
||||
/* Deal with other frame types */
|
||||
if (f != NULL) {
|
||||
/* Free the frame we received */
|
||||
switch (f->frametype) {
|
||||
case AST_FRAME_DTMF:
|
||||
if (dtmf_terminator != '\0' && f->subclass == dtmf_terminator) {
|
||||
done = 1;
|
||||
} else {
|
||||
if (chan->stream != NULL) {
|
||||
ast_stopstream(chan);
|
||||
}
|
||||
if (!started) {
|
||||
/* Change timeout to be 5 seconds for DTMF input */
|
||||
timeout = (chan->pbx && chan->pbx->dtimeout) ? chan->pbx->dtimeout : 5;
|
||||
started = 1;
|
||||
}
|
||||
time(&start);
|
||||
snprintf(tmp, sizeof(tmp), "%c", f->subclass);
|
||||
strncat(dtmf, tmp, sizeof(dtmf));
|
||||
/* If the maximum length of the DTMF has been reached, stop now */
|
||||
if (max_dtmf_len && strlen(dtmf) == max_dtmf_len)
|
||||
done = 1;
|
||||
}
|
||||
break;
|
||||
case AST_FRAME_CONTROL:
|
||||
switch (f->subclass) {
|
||||
case AST_CONTROL_HANGUP:
|
||||
/* Since they hung up we should destroy the speech structure */
|
||||
done = 3;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ast_frfree(f);
|
||||
f = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(dtmf)) {
|
||||
/* We sort of make a results entry */
|
||||
speech->results = ast_calloc(1, sizeof(*speech->results));
|
||||
if (speech->results != NULL) {
|
||||
ast_speech_dtmf(speech, dtmf);
|
||||
speech->results->score = 1000;
|
||||
speech->results->text = ast_strdup(dtmf);
|
||||
speech->results->grammar = ast_strdup("dtmf");
|
||||
}
|
||||
}
|
||||
|
||||
/* See if it was because they hung up */
|
||||
if (done == 3) {
|
||||
/* Destroy speech structure */
|
||||
ast_speech_destroy(speech);
|
||||
datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
|
||||
if (datastore != NULL)
|
||||
ast_channel_datastore_remove(chan, datastore);
|
||||
} else {
|
||||
/* Channel is okay so restore read format */
|
||||
ast_set_read_format(chan, oldreadformat);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*! \brief SpeechDestroy() Dialplan Application */
|
||||
static int speech_destroy(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct ast_speech *speech = find_speech(chan);
|
||||
struct ast_datastore *datastore = NULL;
|
||||
|
||||
if (speech == NULL)
|
||||
return -1;
|
||||
|
||||
/* Destroy speech structure */
|
||||
ast_speech_destroy(speech);
|
||||
|
||||
datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
|
||||
if (datastore != NULL) {
|
||||
ast_channel_datastore_remove(chan, datastore);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
res = ast_unregister_application("SpeechCreate");
|
||||
res |= ast_unregister_application("SpeechLoadGrammar");
|
||||
res |= ast_unregister_application("SpeechUnloadGrammar");
|
||||
res |= ast_unregister_application("SpeechActivateGrammar");
|
||||
res |= ast_unregister_application("SpeechDeactivateGrammar");
|
||||
res |= ast_unregister_application("SpeechStart");
|
||||
res |= ast_unregister_application("SpeechBackground");
|
||||
res |= ast_unregister_application("SpeechDestroy");
|
||||
res |= ast_unregister_application("SpeechProcessingSound");
|
||||
res |= ast_custom_function_unregister(&speech_function);
|
||||
res |= ast_custom_function_unregister(&speech_score_function);
|
||||
res |= ast_custom_function_unregister(&speech_text_function);
|
||||
res |= ast_custom_function_unregister(&speech_grammar_function);
|
||||
res |= ast_custom_function_unregister(&speech_engine_function);
|
||||
res |= ast_custom_function_unregister(&speech_results_type_function);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
res = ast_register_application("SpeechCreate", speech_create, "Create a Speech Structure", speechcreate_descrip);
|
||||
res |= ast_register_application("SpeechLoadGrammar", speech_load, "Load a Grammar", speechload_descrip);
|
||||
res |= ast_register_application("SpeechUnloadGrammar", speech_unload, "Unload a Grammar", speechunload_descrip);
|
||||
res |= ast_register_application("SpeechActivateGrammar", speech_activate, "Activate a Grammar", speechactivategrammar_descrip);
|
||||
res |= ast_register_application("SpeechDeactivateGrammar", speech_deactivate, "Deactivate a Grammar", speechdeactivategrammar_descrip);
|
||||
res |= ast_register_application("SpeechStart", speech_start, "Start recognizing voice in the audio stream", speechstart_descrip);
|
||||
res |= ast_register_application("SpeechBackground", speech_background, "Play a sound file and wait for speech to be recognized", speechbackground_descrip);
|
||||
res |= ast_register_application("SpeechDestroy", speech_destroy, "End speech recognition", speechdestroy_descrip);
|
||||
res |= ast_register_application("SpeechProcessingSound", speech_processing_sound, "Change background processing sound", speechprocessingsound_descrip);
|
||||
res |= ast_custom_function_register(&speech_function);
|
||||
res |= ast_custom_function_register(&speech_score_function);
|
||||
res |= ast_custom_function_register(&speech_text_function);
|
||||
res |= ast_custom_function_register(&speech_grammar_function);
|
||||
res |= ast_custom_function_register(&speech_engine_function);
|
||||
res |= ast_custom_function_register(&speech_results_type_function);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan Speech Applications");
|
||||
416
trunk/apps/app_stack.c
Normal file
416
trunk/apps/app_stack.c
Normal file
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (c) 2004-2006 Tilghman Lesher <app_stack_v003@the-tilghman.com>.
|
||||
*
|
||||
* This code is released by the author with no restrictions on usage.
|
||||
*
|
||||
* 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 Stack applications Gosub, Return, etc.
|
||||
*
|
||||
* \author Tilghman Lesher <app_stack_v003@the-tilghman.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/manager.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
static const char *app_gosub = "Gosub";
|
||||
static const char *app_gosubif = "GosubIf";
|
||||
static const char *app_return = "Return";
|
||||
static const char *app_pop = "StackPop";
|
||||
|
||||
static const char *gosub_synopsis = "Jump to label, saving return address";
|
||||
static const char *gosubif_synopsis = "Conditionally jump to label, saving return address";
|
||||
static const char *return_synopsis = "Return from gosub routine";
|
||||
static const char *pop_synopsis = "Remove one address from gosub stack";
|
||||
|
||||
static const char *gosub_descrip =
|
||||
" Gosub([[context,]exten,]priority[(arg1[,...][,argN])]):\n"
|
||||
"Jumps to the label specified, saving the return address.\n";
|
||||
static const char *gosubif_descrip =
|
||||
" GosubIf(condition?labeliftrue[(arg1[,...])][:labeliffalse[(arg1[,...])]]):\n"
|
||||
"If the condition is true, then jump to labeliftrue. If false, jumps to\n"
|
||||
"labeliffalse, if specified. In either case, a jump saves the return point\n"
|
||||
"in the dialplan, to be returned to with a Return.\n";
|
||||
static const char *return_descrip =
|
||||
" Return([return-value]):\n"
|
||||
"Jumps to the last label on the stack, removing it. The return value, if\n"
|
||||
"any, is saved in the channel variable GOSUB_RETVAL.\n";
|
||||
static const char *pop_descrip =
|
||||
" StackPop():\n"
|
||||
"Removes last label on the stack, discarding it.\n";
|
||||
|
||||
|
||||
static void gosub_free(void *data);
|
||||
|
||||
static struct ast_datastore_info stack_info = {
|
||||
.type = "GOSUB",
|
||||
.destroy = gosub_free,
|
||||
};
|
||||
|
||||
struct gosub_stack_frame {
|
||||
AST_LIST_ENTRY(gosub_stack_frame) entries;
|
||||
/* 100 arguments is all that we support anyway, but this will handle up to 255 */
|
||||
unsigned char arguments;
|
||||
struct varshead varshead;
|
||||
int priority;
|
||||
char *context;
|
||||
char extension[0];
|
||||
};
|
||||
|
||||
static int frame_set_var(struct ast_channel *chan, struct gosub_stack_frame *frame, const char *var, const char *value)
|
||||
{
|
||||
struct ast_var_t *variables;
|
||||
int found = 0;
|
||||
|
||||
/* Does this variable already exist? */
|
||||
AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
|
||||
if (!strcmp(var, ast_var_name(variables))) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(value)) {
|
||||
if (!found) {
|
||||
variables = ast_var_assign(var, "");
|
||||
AST_LIST_INSERT_HEAD(&frame->varshead, variables, entries);
|
||||
pbx_builtin_pushvar_helper(chan, var, value);
|
||||
} else
|
||||
pbx_builtin_setvar_helper(chan, var, value);
|
||||
|
||||
manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
|
||||
"Channel: %s\r\n"
|
||||
"Variable: LOCAL(%s)\r\n"
|
||||
"Value: %s\r\n"
|
||||
"Uniqueid: %s\r\n",
|
||||
chan->name, var, value, chan->uniqueid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gosub_release_frame(struct ast_channel *chan, struct gosub_stack_frame *frame)
|
||||
{
|
||||
unsigned char i;
|
||||
char argname[15];
|
||||
struct ast_var_t *vardata;
|
||||
|
||||
/* If chan is not defined, then we're calling it as part of gosub_free,
|
||||
* and the channel variables will be deallocated anyway. Otherwise, we're
|
||||
* just releasing a single frame, so we need to clean up the arguments for
|
||||
* that frame, so that we re-expose the variables from the previous frame
|
||||
* that were hidden by this one.
|
||||
*/
|
||||
if (chan) {
|
||||
for (i = 1; i <= frame->arguments && i != 0; i++) {
|
||||
snprintf(argname, sizeof(argname), "ARG%hhd", i);
|
||||
pbx_builtin_setvar_helper(chan, argname, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete local variables */
|
||||
while ((vardata = AST_LIST_REMOVE_HEAD(&frame->varshead, entries))) {
|
||||
if (chan)
|
||||
pbx_builtin_setvar_helper(chan, ast_var_name(vardata), NULL);
|
||||
ast_var_delete(vardata);
|
||||
}
|
||||
|
||||
ast_free(frame);
|
||||
}
|
||||
|
||||
static struct gosub_stack_frame *gosub_allocate_frame(const char *context, const char *extension, int priority, unsigned char arguments)
|
||||
{
|
||||
struct gosub_stack_frame *new = NULL;
|
||||
int len_extension = strlen(extension), len_context = strlen(context);
|
||||
|
||||
if ((new = ast_calloc(1, sizeof(*new) + 2 + len_extension + len_context))) {
|
||||
AST_LIST_HEAD_INIT_NOLOCK(&new->varshead);
|
||||
strcpy(new->extension, extension);
|
||||
new->context = new->extension + len_extension + 1;
|
||||
strcpy(new->context, context);
|
||||
new->priority = priority;
|
||||
new->arguments = arguments;
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
static void gosub_free(void *data)
|
||||
{
|
||||
AST_LIST_HEAD(, gosub_stack_frame) *oldlist = data;
|
||||
struct gosub_stack_frame *oldframe;
|
||||
AST_LIST_LOCK(oldlist);
|
||||
while ((oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries))) {
|
||||
gosub_release_frame(NULL, oldframe);
|
||||
}
|
||||
AST_LIST_UNLOCK(oldlist);
|
||||
AST_LIST_HEAD_DESTROY(oldlist);
|
||||
ast_free(oldlist);
|
||||
}
|
||||
|
||||
static int pop_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
|
||||
struct gosub_stack_frame *oldframe;
|
||||
AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
|
||||
|
||||
if (!stack_store) {
|
||||
ast_log(LOG_WARNING, "%s called with no gosub stack allocated.\n", app_pop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
oldlist = stack_store->data;
|
||||
AST_LIST_LOCK(oldlist);
|
||||
oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries);
|
||||
AST_LIST_UNLOCK(oldlist);
|
||||
|
||||
if (oldframe) {
|
||||
gosub_release_frame(chan, oldframe);
|
||||
} else {
|
||||
ast_debug(1, "%s called with an empty gosub stack\n", app_pop);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int return_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
|
||||
struct gosub_stack_frame *oldframe;
|
||||
AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
|
||||
char *retval = data;
|
||||
|
||||
if (!stack_store) {
|
||||
ast_log(LOG_ERROR, "Return without Gosub: stack is unallocated\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldlist = stack_store->data;
|
||||
AST_LIST_LOCK(oldlist);
|
||||
oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries);
|
||||
AST_LIST_UNLOCK(oldlist);
|
||||
|
||||
if (!oldframe) {
|
||||
ast_log(LOG_ERROR, "Return without Gosub: stack is empty\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_explicit_goto(chan, oldframe->context, oldframe->extension, oldframe->priority);
|
||||
gosub_release_frame(chan, oldframe);
|
||||
|
||||
/* Set a return value, if any */
|
||||
pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", S_OR(retval, ""));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gosub_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
|
||||
AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
|
||||
struct gosub_stack_frame *newframe;
|
||||
char argname[15], *tmp = ast_strdupa(data), *label, *endparen;
|
||||
int i;
|
||||
AST_DECLARE_APP_ARGS(args2,
|
||||
AST_APP_ARG(argval)[100];
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_ERROR, "%s requires an argument: %s([[context|]exten|]priority[(arg1[|...][|argN])])\n", app_gosub, app_gosub);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!stack_store) {
|
||||
ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", chan->name);
|
||||
stack_store = ast_channel_datastore_alloc(&stack_info, NULL);
|
||||
if (!stack_store) {
|
||||
ast_log(LOG_ERROR, "Unable to allocate new datastore. Gosub will fail.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldlist = ast_calloc(1, sizeof(*oldlist));
|
||||
if (!oldlist) {
|
||||
ast_log(LOG_ERROR, "Unable to allocate datastore list head. Gosub will fail.\n");
|
||||
ast_channel_datastore_free(stack_store);
|
||||
return -1;
|
||||
}
|
||||
|
||||
stack_store->data = oldlist;
|
||||
AST_LIST_HEAD_INIT(oldlist);
|
||||
ast_channel_datastore_add(chan, stack_store);
|
||||
}
|
||||
|
||||
/* Separate the arguments from the label */
|
||||
/* NOTE: you cannot use ast_app_separate_args for this, because '(' cannot be used as a delimiter. */
|
||||
label = strsep(&tmp, "(");
|
||||
if (tmp) {
|
||||
endparen = strrchr(tmp, ')');
|
||||
if (endparen)
|
||||
*endparen = '\0';
|
||||
else
|
||||
ast_log(LOG_WARNING, "Ouch. No closing paren: '%s'?\n", (char *)data);
|
||||
AST_STANDARD_APP_ARGS(args2, tmp);
|
||||
} else
|
||||
args2.argc = 0;
|
||||
|
||||
/* Create the return address, but don't save it until we know that the Gosub destination exists */
|
||||
newframe = gosub_allocate_frame(chan->context, chan->exten, chan->priority + 1, args2.argc);
|
||||
|
||||
if (!newframe)
|
||||
return -1;
|
||||
|
||||
if (ast_parseable_goto(chan, label)) {
|
||||
ast_log(LOG_ERROR, "Gosub address is invalid: '%s'\n", (char *)data);
|
||||
ast_free(newframe);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Now that we know for certain that we're going to a new location, set our arguments */
|
||||
for (i = 0; i < args2.argc; i++) {
|
||||
snprintf(argname, sizeof(argname), "ARG%d", i + 1);
|
||||
frame_set_var(chan, newframe, argname, args2.argval[i]);
|
||||
ast_debug(1, "Setting '%s' to '%s'\n", argname, args2.argval[i]);
|
||||
}
|
||||
|
||||
/* And finally, save our return address */
|
||||
oldlist = stack_store->data;
|
||||
AST_LIST_LOCK(oldlist);
|
||||
AST_LIST_INSERT_HEAD(oldlist, newframe, entries);
|
||||
AST_LIST_UNLOCK(oldlist);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gosubif_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *args;
|
||||
int res=0;
|
||||
AST_DECLARE_APP_ARGS(cond,
|
||||
AST_APP_ARG(ition);
|
||||
AST_APP_ARG(labels);
|
||||
);
|
||||
AST_DECLARE_APP_ARGS(label,
|
||||
AST_APP_ARG(iftrue);
|
||||
AST_APP_ARG(iffalse);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
args = ast_strdupa(data);
|
||||
AST_NONSTANDARD_APP_ARGS(cond, args, '?');
|
||||
if (cond.argc != 2) {
|
||||
ast_log(LOG_WARNING, "GosubIf requires an argument: GosubIf(cond?label1(args):label2(args)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
AST_NONSTANDARD_APP_ARGS(label, cond.labels, ':');
|
||||
|
||||
if (pbx_checkcondition(cond.ition)) {
|
||||
if (!ast_strlen_zero(label.iftrue))
|
||||
res = gosub_exec(chan, label.iftrue);
|
||||
} else if (!ast_strlen_zero(label.iffalse)) {
|
||||
res = gosub_exec(chan, label.iffalse);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int local_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
|
||||
{
|
||||
struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
|
||||
AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
|
||||
struct gosub_stack_frame *frame;
|
||||
struct ast_var_t *variables;
|
||||
|
||||
if (!stack_store)
|
||||
return -1;
|
||||
|
||||
oldlist = stack_store->data;
|
||||
AST_LIST_LOCK(oldlist);
|
||||
frame = AST_LIST_FIRST(oldlist);
|
||||
AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
|
||||
if (!strcmp(data, ast_var_name(variables))) {
|
||||
const char *tmp = pbx_builtin_getvar_helper(chan, data);
|
||||
ast_copy_string(buf, S_OR(tmp, ""), len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AST_LIST_UNLOCK(oldlist);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int local_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
|
||||
{
|
||||
struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
|
||||
AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
|
||||
struct gosub_stack_frame *frame;
|
||||
|
||||
if (!stack_store) {
|
||||
ast_log(LOG_ERROR, "Tried to set LOCAL(%s), but we aren't within a Gosub routine\n", var);
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldlist = stack_store->data;
|
||||
AST_LIST_LOCK(oldlist);
|
||||
frame = AST_LIST_FIRST(oldlist);
|
||||
|
||||
if (frame)
|
||||
frame_set_var(chan, frame, var, value);
|
||||
|
||||
AST_LIST_UNLOCK(oldlist);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ast_custom_function local_function = {
|
||||
.name = "LOCAL",
|
||||
.synopsis = "Variables local to the gosub stack frame",
|
||||
.syntax = "LOCAL(<varname>)",
|
||||
.write = local_write,
|
||||
.read = local_read,
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
ast_unregister_application(app_return);
|
||||
ast_unregister_application(app_pop);
|
||||
ast_unregister_application(app_gosubif);
|
||||
ast_unregister_application(app_gosub);
|
||||
ast_custom_function_unregister(&local_function);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
ast_register_application(app_pop, pop_exec, pop_synopsis, pop_descrip);
|
||||
ast_register_application(app_return, return_exec, return_synopsis, return_descrip);
|
||||
ast_register_application(app_gosubif, gosubif_exec, gosubif_synopsis, gosubif_descrip);
|
||||
ast_register_application(app_gosub, gosub_exec, gosub_synopsis, gosub_descrip);
|
||||
ast_custom_function_register(&local_function);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan subroutines (Gosub, Return, etc)");
|
||||
129
trunk/apps/app_system.c
Normal file
129
trunk/apps/app_system.c
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Execute arbitrary system commands
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/channel.h" /* autoservice */
|
||||
|
||||
static char *app = "System";
|
||||
|
||||
static char *app2 = "TrySystem";
|
||||
|
||||
static char *synopsis = "Execute a system command";
|
||||
|
||||
static char *synopsis2 = "Try executing a system command";
|
||||
|
||||
static char *chanvar = "SYSTEMSTATUS";
|
||||
|
||||
static char *descrip =
|
||||
" System(command): Executes a command by using system(). If the command\n"
|
||||
"fails, the console should report a fallthrough. \n"
|
||||
"Result of execution is returned in the SYSTEMSTATUS channel variable:\n"
|
||||
" FAILURE Could not execute the specified command\n"
|
||||
" SUCCESS Specified command successfully executed\n";
|
||||
|
||||
static char *descrip2 =
|
||||
" TrySystem(command): Executes a command by using system().\n"
|
||||
"on any situation.\n"
|
||||
"Result of execution is returned in the SYSTEMSTATUS channel variable:\n"
|
||||
" FAILURE Could not execute the specified command\n"
|
||||
" SUCCESS Specified command successfully executed\n"
|
||||
" APPERROR Specified command successfully executed, but returned error code\n";
|
||||
|
||||
static int system_exec_helper(struct ast_channel *chan, void *data, int failmode)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "System requires an argument(command)\n");
|
||||
pbx_builtin_setvar_helper(chan, chanvar, "FAILURE");
|
||||
return failmode;
|
||||
}
|
||||
|
||||
ast_autoservice_start(chan);
|
||||
|
||||
/* Do our thing here */
|
||||
res = ast_safe_system((char *)data);
|
||||
if ((res < 0) && (errno != ECHILD)) {
|
||||
ast_log(LOG_WARNING, "Unable to execute '%s'\n", (char *)data);
|
||||
pbx_builtin_setvar_helper(chan, chanvar, "FAILURE");
|
||||
res = failmode;
|
||||
} else if (res == 127) {
|
||||
ast_log(LOG_WARNING, "Unable to execute '%s'\n", (char *)data);
|
||||
pbx_builtin_setvar_helper(chan, chanvar, "FAILURE");
|
||||
res = failmode;
|
||||
} else {
|
||||
if (res < 0)
|
||||
res = 0;
|
||||
if (res != 0)
|
||||
pbx_builtin_setvar_helper(chan, chanvar, "APPERROR");
|
||||
else
|
||||
pbx_builtin_setvar_helper(chan, chanvar, "SUCCESS");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
ast_autoservice_stop(chan);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int system_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
return system_exec_helper(chan, data, -1);
|
||||
}
|
||||
|
||||
static int trysystem_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
return system_exec_helper(chan, data, 0);
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(app);
|
||||
res |= ast_unregister_application(app2);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_register_application(app2, trysystem_exec, synopsis2, descrip2);
|
||||
res |= ast_register_application(app, system_exec, synopsis, descrip);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Generic System() application");
|
||||
211
trunk/apps/app_talkdetect.c
Normal file
211
trunk/apps/app_talkdetect.c
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Playback a file with audio detect
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/dsp.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *app = "BackgroundDetect";
|
||||
|
||||
static char *synopsis = "Background a file with talk detect";
|
||||
|
||||
static char *descrip =
|
||||
" BackgroundDetect(filename[,sil[,min,[max]]]): Plays back a given\n"
|
||||
"filename, waiting for interruption from a given digit (the digit must\n"
|
||||
"start the beginning of a valid extension, or it will be ignored).\n"
|
||||
"During the playback of the file, audio is monitored in the receive\n"
|
||||
"direction, and if a period of non-silence which is greater than 'min' ms\n"
|
||||
"yet less than 'max' ms is followed by silence for at least 'sil' ms then\n"
|
||||
"the audio playback is aborted and processing jumps to the 'talk' extension\n"
|
||||
"if available. If unspecified, sil, min, and max default to 1000, 100, and\n"
|
||||
"infinity respectively.\n";
|
||||
|
||||
|
||||
static int background_detect_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char *tmp;
|
||||
struct ast_frame *fr;
|
||||
int notsilent = 0;
|
||||
struct timeval start = { 0, 0};
|
||||
int sil = 1000;
|
||||
int min = 100;
|
||||
int max = -1;
|
||||
int x;
|
||||
int origrformat=0;
|
||||
struct ast_dsp *dsp = NULL;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(filename);
|
||||
AST_APP_ARG(silence);
|
||||
AST_APP_ARG(min);
|
||||
AST_APP_ARG(max);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "BackgroundDetect requires an argument (filename)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, tmp);
|
||||
|
||||
if ((sscanf(args.silence, "%d", &x) == 1) && (x > 0))
|
||||
sil = x;
|
||||
if ((sscanf(args.min, "%d", &x) == 1) && (x > 0))
|
||||
min = x;
|
||||
if ((sscanf(args.max, "%d", &x) == 1) && (x > 0))
|
||||
max = x;
|
||||
|
||||
ast_debug(1, "Preparing detect of '%s', sil=%d, min=%d, max=%d\n", args.filename, sil, min, max);
|
||||
do {
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if ((res = ast_answer(chan)))
|
||||
break;
|
||||
}
|
||||
|
||||
origrformat = chan->readformat;
|
||||
if ((ast_set_read_format(chan, AST_FORMAT_SLINEAR))) {
|
||||
ast_log(LOG_WARNING, "Unable to set read format to linear!\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(dsp = ast_dsp_new())) {
|
||||
ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
ast_stopstream(chan);
|
||||
if (ast_streamfile(chan, tmp, chan->language)) {
|
||||
ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
|
||||
break;
|
||||
}
|
||||
|
||||
while (chan->stream) {
|
||||
res = ast_sched_wait(chan->sched);
|
||||
if ((res < 0) && !chan->timingfunc) {
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
if (res < 0)
|
||||
res = 1000;
|
||||
res = ast_waitfor(chan, res);
|
||||
if (res < 0) {
|
||||
ast_log(LOG_WARNING, "Waitfor failed on %s\n", chan->name);
|
||||
break;
|
||||
} else if (res > 0) {
|
||||
fr = ast_read(chan);
|
||||
if (!fr) {
|
||||
res = -1;
|
||||
break;
|
||||
} else if (fr->frametype == AST_FRAME_DTMF) {
|
||||
char t[2];
|
||||
t[0] = fr->subclass;
|
||||
t[1] = '\0';
|
||||
if (ast_canmatch_extension(chan, chan->context, t, 1, chan->cid.cid_num)) {
|
||||
/* They entered a valid extension, or might be anyhow */
|
||||
res = fr->subclass;
|
||||
ast_frfree(fr);
|
||||
break;
|
||||
}
|
||||
} else if ((fr->frametype == AST_FRAME_VOICE) && (fr->subclass == AST_FORMAT_SLINEAR)) {
|
||||
int totalsilence;
|
||||
int ms;
|
||||
res = ast_dsp_silence(dsp, fr, &totalsilence);
|
||||
if (res && (totalsilence > sil)) {
|
||||
/* We've been quiet a little while */
|
||||
if (notsilent) {
|
||||
/* We had heard some talking */
|
||||
ms = ast_tvdiff_ms(ast_tvnow(), start);
|
||||
ms -= sil;
|
||||
if (ms < 0)
|
||||
ms = 0;
|
||||
if ((ms > min) && ((max < 0) || (ms < max))) {
|
||||
char ms_str[10];
|
||||
ast_debug(1, "Found qualified token of %d ms\n", ms);
|
||||
|
||||
/* Save detected talk time (in milliseconds) */
|
||||
sprintf(ms_str, "%d", ms );
|
||||
pbx_builtin_setvar_helper(chan, "TALK_DETECTED", ms_str);
|
||||
|
||||
ast_goto_if_exists(chan, chan->context, "talk", 1);
|
||||
res = 0;
|
||||
ast_frfree(fr);
|
||||
break;
|
||||
} else {
|
||||
ast_debug(1, "Found unqualified token of %d ms\n", ms);
|
||||
}
|
||||
notsilent = 0;
|
||||
}
|
||||
} else {
|
||||
if (!notsilent) {
|
||||
/* Heard some audio, mark the begining of the token */
|
||||
start = ast_tvnow();
|
||||
ast_debug(1, "Start of voice token!\n");
|
||||
notsilent = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
ast_frfree(fr);
|
||||
}
|
||||
ast_sched_runq(chan->sched);
|
||||
}
|
||||
ast_stopstream(chan);
|
||||
} while (0);
|
||||
|
||||
if (res > -1) {
|
||||
if (origrformat && ast_set_read_format(chan, origrformat)) {
|
||||
ast_log(LOG_WARNING, "Failed to restore read format for %s to %s\n",
|
||||
chan->name, ast_getformatname(origrformat));
|
||||
}
|
||||
}
|
||||
if (dsp)
|
||||
ast_dsp_free(dsp);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, background_detect_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Playback with Talk Detection");
|
||||
467
trunk/apps/app_test.c
Normal file
467
trunk/apps/app_test.c
Normal file
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
* Russell Bryant <russelb@clemson.edu>
|
||||
*
|
||||
* 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 Applications to test connection and produce report in text file
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
* \author Russell Bryant <russelb@clemson.edu>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "asterisk/paths.h" /* use ast_config_AST_LOG_DIR */
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/utils.h"
|
||||
|
||||
static char *tests_descrip =
|
||||
" TestServer(): Perform test server function and write call report.\n"
|
||||
"Results stored in /var/log/asterisk/testreports/<testid>-server.txt";
|
||||
static char *tests_app = "TestServer";
|
||||
static char *tests_synopsis = "Execute Interface Test Server";
|
||||
|
||||
static char *testc_descrip =
|
||||
" TestClient(testid): Executes test client with given testid.\n"
|
||||
"Results stored in /var/log/asterisk/testreports/<testid>-client.txt";
|
||||
|
||||
static char *testc_app = "TestClient";
|
||||
static char *testc_synopsis = "Execute Interface Test Client";
|
||||
|
||||
static int measurenoise(struct ast_channel *chan, int ms, char *who)
|
||||
{
|
||||
int res=0;
|
||||
int mssofar;
|
||||
int noise=0;
|
||||
int samples=0;
|
||||
int x;
|
||||
short *foo;
|
||||
struct timeval start;
|
||||
struct ast_frame *f;
|
||||
int rformat;
|
||||
rformat = chan->readformat;
|
||||
if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) {
|
||||
ast_log(LOG_NOTICE, "Unable to set to linear mode!\n");
|
||||
return -1;
|
||||
}
|
||||
start = ast_tvnow();
|
||||
for(;;) {
|
||||
mssofar = ast_tvdiff_ms(ast_tvnow(), start);
|
||||
if (mssofar > ms)
|
||||
break;
|
||||
res = ast_waitfor(chan, ms - mssofar);
|
||||
if (res < 1)
|
||||
break;
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
|
||||
foo = (short *)f->data;
|
||||
for (x=0;x<f->samples;x++) {
|
||||
noise += abs(foo[x]);
|
||||
samples++;
|
||||
}
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
|
||||
if (rformat) {
|
||||
if (ast_set_read_format(chan, rformat)) {
|
||||
ast_log(LOG_NOTICE, "Unable to restore original format!\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (res < 0)
|
||||
return res;
|
||||
if (!samples) {
|
||||
ast_log(LOG_NOTICE, "No samples were received from the other side!\n");
|
||||
return -1;
|
||||
}
|
||||
ast_debug(1, "%s: Noise: %d, samples: %d, avg: %d\n", who, noise, samples, noise / samples);
|
||||
return (noise / samples);
|
||||
}
|
||||
|
||||
static int sendnoise(struct ast_channel *chan, int ms)
|
||||
{
|
||||
int res;
|
||||
res = ast_tonepair_start(chan, 1537, 2195, ms, 8192);
|
||||
if (!res) {
|
||||
res = ast_waitfordigit(chan, ms);
|
||||
ast_tonepair_stop(chan);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int testclient_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char *testid=data;
|
||||
char fn[80];
|
||||
char serverver[80];
|
||||
FILE *f;
|
||||
|
||||
/* Check for test id */
|
||||
if (ast_strlen_zero(testid)) {
|
||||
ast_log(LOG_WARNING, "TestClient requires an argument - the test id\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
res = ast_answer(chan);
|
||||
|
||||
/* Wait a few just to be sure things get started */
|
||||
res = ast_safe_sleep(chan, 3000);
|
||||
/* Transmit client version */
|
||||
if (!res)
|
||||
res = ast_dtmf_stream(chan, NULL, "8378*1#", 0, 0);
|
||||
ast_debug(1, "Transmit client version\n");
|
||||
|
||||
/* Read server version */
|
||||
ast_debug(1, "Read server version\n");
|
||||
if (!res)
|
||||
res = ast_app_getdata(chan, NULL, serverver, sizeof(serverver) - 1, 0);
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
ast_debug(1, "server version: %s\n", serverver);
|
||||
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
|
||||
if (!res)
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
/* Send test id */
|
||||
if (!res)
|
||||
res = ast_dtmf_stream(chan, NULL, testid, 0, 0);
|
||||
if (!res)
|
||||
res = ast_dtmf_stream(chan, NULL, "#", 0, 0);
|
||||
ast_debug(1, "send test identifier: %s\n", testid);
|
||||
|
||||
if ((res >=0) && (!ast_strlen_zero(testid))) {
|
||||
/* Make the directory to hold the test results in case it's not there */
|
||||
snprintf(fn, sizeof(fn), "%s/testresults", ast_config_AST_LOG_DIR);
|
||||
ast_mkdir(fn, 0777);
|
||||
snprintf(fn, sizeof(fn), "%s/testresults/%s-client.txt", ast_config_AST_LOG_DIR, testid);
|
||||
if ((f = fopen(fn, "w+"))) {
|
||||
setlinebuf(f);
|
||||
fprintf(f, "CLIENTCHAN: %s\n", chan->name);
|
||||
fprintf(f, "CLIENTTEST ID: %s\n", testid);
|
||||
fprintf(f, "ANSWER: PASS\n");
|
||||
res = 0;
|
||||
|
||||
if (!res) {
|
||||
/* Step 1: Wait for "1" */
|
||||
ast_debug(1, "TestClient: 2. Wait DTMF 1\n");
|
||||
res = ast_waitfordigit(chan, 3000);
|
||||
fprintf(f, "WAIT DTMF 1: %s\n", (res != '1') ? "FAIL" : "PASS");
|
||||
if (res == '1')
|
||||
res = 0;
|
||||
else
|
||||
res = -1;
|
||||
}
|
||||
if (!res)
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
if (!res) {
|
||||
/* Step 2: Send "2" */
|
||||
ast_debug(1, "TestClient: 2. Send DTMF 2\n");
|
||||
res = ast_dtmf_stream(chan, NULL, "2", 0, 0);
|
||||
fprintf(f, "SEND DTMF 2: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 3: Wait one second */
|
||||
ast_debug(1, "TestClient: 3. Wait one second\n");
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
fprintf(f, "WAIT 1 SEC: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 4: Measure noise */
|
||||
ast_debug(1, "TestClient: 4. Measure noise\n");
|
||||
res = measurenoise(chan, 5000, "TestClient");
|
||||
fprintf(f, "MEASURENOISE: %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 5: Wait for "4" */
|
||||
ast_debug(1, "TestClient: 5. Wait DTMF 4\n");
|
||||
res = ast_waitfordigit(chan, 3000);
|
||||
fprintf(f, "WAIT DTMF 4: %s\n", (res != '4') ? "FAIL" : "PASS");
|
||||
if (res == '4')
|
||||
res = 0;
|
||||
else
|
||||
res = -1;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 6: Transmit tone noise */
|
||||
ast_debug(1, "TestClient: 6. Transmit tone\n");
|
||||
res = sendnoise(chan, 6000);
|
||||
fprintf(f, "SENDTONE: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
}
|
||||
if (!res || (res == '5')) {
|
||||
/* Step 7: Wait for "5" */
|
||||
ast_debug(1, "TestClient: 7. Wait DTMF 5\n");
|
||||
if (!res)
|
||||
res = ast_waitfordigit(chan, 3000);
|
||||
fprintf(f, "WAIT DTMF 5: %s\n", (res != '5') ? "FAIL" : "PASS");
|
||||
if (res == '5')
|
||||
res = 0;
|
||||
else
|
||||
res = -1;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 8: Wait one second */
|
||||
ast_debug(1, "TestClient: 8. Wait one second\n");
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
fprintf(f, "WAIT 1 SEC: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 9: Measure noise */
|
||||
ast_debug(1, "TestClient: 6. Measure tone\n");
|
||||
res = measurenoise(chan, 4000, "TestClient");
|
||||
fprintf(f, "MEASURETONE: %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 10: Send "7" */
|
||||
ast_debug(1, "TestClient: 7. Send DTMF 7\n");
|
||||
res = ast_dtmf_stream(chan, NULL, "7", 0, 0);
|
||||
fprintf(f, "SEND DTMF 7: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res =0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 11: Wait for "8" */
|
||||
ast_debug(1, "TestClient: 11. Wait DTMF 8\n");
|
||||
res = ast_waitfordigit(chan, 3000);
|
||||
fprintf(f, "WAIT DTMF 8: %s\n", (res != '8') ? "FAIL" : "PASS");
|
||||
if (res == '8')
|
||||
res = 0;
|
||||
else
|
||||
res = -1;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 12: Hangup! */
|
||||
ast_debug(1, "TestClient: 12. Hangup\n");
|
||||
}
|
||||
|
||||
ast_debug(1, "-- TEST COMPLETE--\n");
|
||||
fprintf(f, "-- END TEST--\n");
|
||||
fclose(f);
|
||||
res = -1;
|
||||
} else
|
||||
res = -1;
|
||||
} else {
|
||||
ast_log(LOG_NOTICE, "Did not read a test ID on '%s'\n", chan->name);
|
||||
res = -1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int testserver_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char testid[80]="";
|
||||
char fn[80];
|
||||
FILE *f;
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
res = ast_answer(chan);
|
||||
/* Read version */
|
||||
ast_debug(1, "Read client version\n");
|
||||
if (!res)
|
||||
res = ast_app_getdata(chan, NULL, testid, sizeof(testid) - 1, 0);
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
|
||||
ast_debug(1, "client version: %s\n", testid);
|
||||
ast_debug(1, "Transmit server version\n");
|
||||
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
if (!res)
|
||||
res = ast_dtmf_stream(chan, NULL, "8378*1#", 0, 0);
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
|
||||
if (!res)
|
||||
res = ast_app_getdata(chan, NULL, testid, sizeof(testid) - 1, 0);
|
||||
ast_debug(1, "read test identifier: %s\n", testid);
|
||||
/* Check for sneakyness */
|
||||
if (strchr(testid, '/'))
|
||||
res = -1;
|
||||
if ((res >=0) && (!ast_strlen_zero(testid))) {
|
||||
/* Got a Test ID! Whoo hoo! */
|
||||
/* Make the directory to hold the test results in case it's not there */
|
||||
snprintf(fn, sizeof(fn), "%s/testresults", ast_config_AST_LOG_DIR);
|
||||
ast_mkdir(fn, 0777);
|
||||
snprintf(fn, sizeof(fn), "%s/testresults/%s-server.txt", ast_config_AST_LOG_DIR, testid);
|
||||
if ((f = fopen(fn, "w+"))) {
|
||||
setlinebuf(f);
|
||||
fprintf(f, "SERVERCHAN: %s\n", chan->name);
|
||||
fprintf(f, "SERVERTEST ID: %s\n", testid);
|
||||
fprintf(f, "ANSWER: PASS\n");
|
||||
ast_debug(1, "Processing Test ID '%s'\n", testid);
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
if (!res) {
|
||||
/* Step 1: Send "1" */
|
||||
ast_debug(1, "TestServer: 1. Send DTMF 1\n");
|
||||
res = ast_dtmf_stream(chan, NULL, "1", 0,0 );
|
||||
fprintf(f, "SEND DTMF 1: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 2: Wait for "2" */
|
||||
ast_debug(1, "TestServer: 2. Wait DTMF 2\n");
|
||||
res = ast_waitfordigit(chan, 3000);
|
||||
fprintf(f, "WAIT DTMF 2: %s\n", (res != '2') ? "FAIL" : "PASS");
|
||||
if (res == '2')
|
||||
res = 0;
|
||||
else
|
||||
res = -1;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 3: Measure noise */
|
||||
ast_debug(1, "TestServer: 3. Measure noise\n");
|
||||
res = measurenoise(chan, 6000, "TestServer");
|
||||
fprintf(f, "MEASURENOISE: %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 4: Send "4" */
|
||||
ast_debug(1, "TestServer: 4. Send DTMF 4\n");
|
||||
res = ast_dtmf_stream(chan, NULL, "4", 0, 0);
|
||||
fprintf(f, "SEND DTMF 4: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
/* Step 5: Wait one second */
|
||||
ast_debug(1, "TestServer: 5. Wait one second\n");
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
fprintf(f, "WAIT 1 SEC: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
/* Step 6: Measure noise */
|
||||
ast_debug(1, "TestServer: 6. Measure tone\n");
|
||||
res = measurenoise(chan, 4000, "TestServer");
|
||||
fprintf(f, "MEASURETONE: %s (%d)\n", (res < 0) ? "FAIL" : "PASS", res);
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
/* Step 7: Send "5" */
|
||||
ast_debug(1, "TestServer: 7. Send DTMF 5\n");
|
||||
res = ast_dtmf_stream(chan, NULL, "5", 0, 0);
|
||||
fprintf(f, "SEND DTMF 5: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
/* Step 8: Transmit tone noise */
|
||||
ast_debug(1, "TestServer: 8. Transmit tone\n");
|
||||
res = sendnoise(chan, 6000);
|
||||
fprintf(f, "SENDTONE: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
}
|
||||
|
||||
if (!res || (res == '7')) {
|
||||
/* Step 9: Wait for "7" */
|
||||
ast_debug(1, "TestServer: 9. Wait DTMF 7\n");
|
||||
if (!res)
|
||||
res = ast_waitfordigit(chan, 3000);
|
||||
fprintf(f, "WAIT DTMF 7: %s\n", (res != '7') ? "FAIL" : "PASS");
|
||||
if (res == '7')
|
||||
res = 0;
|
||||
else
|
||||
res = -1;
|
||||
}
|
||||
if (!res)
|
||||
res = ast_safe_sleep(chan, 1000);
|
||||
if (!res) {
|
||||
/* Step 10: Send "8" */
|
||||
ast_debug(1, "TestServer: 10. Send DTMF 8\n");
|
||||
res = ast_dtmf_stream(chan, NULL, "8", 0, 0);
|
||||
fprintf(f, "SEND DTMF 8: %s\n", (res < 0) ? "FAIL" : "PASS");
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
}
|
||||
if (!res) {
|
||||
/* Step 11: Wait for hangup to arrive! */
|
||||
ast_debug(1, "TestServer: 11. Waiting for hangup\n");
|
||||
res = ast_safe_sleep(chan, 10000);
|
||||
fprintf(f, "WAIT HANGUP: %s\n", (res < 0) ? "PASS" : "FAIL");
|
||||
}
|
||||
|
||||
ast_log(LOG_NOTICE, "-- TEST COMPLETE--\n");
|
||||
fprintf(f, "-- END TEST--\n");
|
||||
fclose(f);
|
||||
res = -1;
|
||||
} else
|
||||
res = -1;
|
||||
} else {
|
||||
ast_log(LOG_NOTICE, "Did not read a test ID on '%s'\n", chan->name);
|
||||
res = -1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(testc_app);
|
||||
res |= ast_unregister_application(tests_app);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_register_application(testc_app, testclient_exec, testc_synopsis, testc_descrip);
|
||||
res |= ast_register_application(tests_app, testserver_exec, tests_synopsis, tests_descrip);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Interface Test Application");
|
||||
125
trunk/apps/app_transfer.c
Normal file
125
trunk/apps/app_transfer.c
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Transfer a caller
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* Requires transfer support from channel driver
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
|
||||
static const char *app = "Transfer";
|
||||
|
||||
static const char *synopsis = "Transfer caller to remote extension";
|
||||
|
||||
static const char *descrip =
|
||||
" Transfer([Tech/]dest[,options]): Requests the remote caller be transferred\n"
|
||||
"to a given destination. If TECH (SIP, IAX2, LOCAL etc) is used, only\n"
|
||||
"an incoming call with the same channel technology will be transfered.\n"
|
||||
"Note that for SIP, if you transfer before call is setup, a 302 redirect\n"
|
||||
"SIP message will be returned to the caller.\n"
|
||||
"\nThe result of the application will be reported in the TRANSFERSTATUS\n"
|
||||
"channel variable:\n"
|
||||
" SUCCESS Transfer succeeded\n"
|
||||
" FAILURE Transfer failed\n"
|
||||
" UNSUPPORTED Transfer unsupported by channel driver\n";
|
||||
|
||||
static int transfer_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res;
|
||||
int len;
|
||||
char *slash;
|
||||
char *tech = NULL;
|
||||
char *dest = NULL;
|
||||
char *status;
|
||||
char *parse;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(dest);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero((char *)data)) {
|
||||
ast_log(LOG_WARNING, "Transfer requires an argument ([Tech/]destination[,options])\n");
|
||||
pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "FAILURE");
|
||||
return 0;
|
||||
} else
|
||||
parse = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (args.options) {
|
||||
}
|
||||
|
||||
dest = args.dest;
|
||||
|
||||
if ((slash = strchr(dest, '/')) && (len = (slash - dest))) {
|
||||
tech = dest;
|
||||
dest = slash + 1;
|
||||
/* Allow execution only if the Tech/destination agrees with the type of the channel */
|
||||
if (strncasecmp(chan->tech->type, tech, len)) {
|
||||
pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "FAILURE");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the channel supports transfer before we try it */
|
||||
if (!chan->tech->transfer) {
|
||||
pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", "UNSUPPORTED");
|
||||
return 0;
|
||||
}
|
||||
|
||||
res = ast_transfer(chan, dest);
|
||||
|
||||
if (res < 0) {
|
||||
status = "FAILURE";
|
||||
res = 0;
|
||||
} else {
|
||||
status = "SUCCESS";
|
||||
res = 0;
|
||||
}
|
||||
|
||||
pbx_builtin_setvar_helper(chan, "TRANSFERSTATUS", status);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, transfer_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Transfers a caller to another extension");
|
||||
149
trunk/apps/app_url.c
Normal file
149
trunk/apps/app_url.c
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 App to transmit a URL
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
static char *app = "SendURL";
|
||||
|
||||
static char *synopsis = "Send a URL";
|
||||
|
||||
static char *descrip =
|
||||
" SendURL(URL[,option]): Requests client go to URL (IAX2) or sends the \n"
|
||||
"URL to the client (other channels).\n"
|
||||
"Result is returned in the SENDURLSTATUS channel variable:\n"
|
||||
" SUCCESS URL successfully sent to client\n"
|
||||
" FAILURE Failed to send URL\n"
|
||||
" NOLOAD Client failed to load URL (wait enabled)\n"
|
||||
" UNSUPPORTED Channel does not support URL transport\n"
|
||||
"\n"
|
||||
"If the option 'w' is specified, execution will wait for an\n"
|
||||
"acknowledgement that the URL has been loaded before continuing\n"
|
||||
"\n"
|
||||
"SendURL continues normally if the URL was sent correctly or if the channel\n"
|
||||
"does not support HTML transport. Otherwise, the channel is hung up.\n";
|
||||
|
||||
enum {
|
||||
OPTION_WAIT = (1 << 0),
|
||||
} option_flags;
|
||||
|
||||
AST_APP_OPTIONS(app_opts,{
|
||||
AST_APP_OPTION('w', OPTION_WAIT),
|
||||
});
|
||||
|
||||
static int sendurl_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
char *tmp;
|
||||
struct ast_frame *f;
|
||||
char *status = "FAILURE";
|
||||
char *opts[0];
|
||||
struct ast_flags flags;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(url);
|
||||
AST_APP_ARG(options);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "SendURL requires an argument (URL)\n");
|
||||
pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, tmp);
|
||||
if (args.argc == 2)
|
||||
ast_app_parse_options(app_opts, &flags, opts, args.options);
|
||||
|
||||
if (!ast_channel_supports_html(chan)) {
|
||||
/* Does not support transport */
|
||||
pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", "UNSUPPORTED");
|
||||
return 0;
|
||||
}
|
||||
res = ast_channel_sendurl(chan, args.url);
|
||||
if (res == -1) {
|
||||
pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", "FAILURE");
|
||||
return res;
|
||||
}
|
||||
status = "SUCCESS";
|
||||
if (ast_test_flag(&flags, OPTION_WAIT)) {
|
||||
for(;;) {
|
||||
/* Wait for an event */
|
||||
res = ast_waitfor(chan, -1);
|
||||
if (res < 0)
|
||||
break;
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
res = -1;
|
||||
status = "FAILURE";
|
||||
break;
|
||||
}
|
||||
if (f->frametype == AST_FRAME_HTML) {
|
||||
switch(f->subclass) {
|
||||
case AST_HTML_LDCOMPLETE:
|
||||
res = 0;
|
||||
ast_frfree(f);
|
||||
status = "NOLOAD";
|
||||
goto out;
|
||||
break;
|
||||
case AST_HTML_NOSUPPORT:
|
||||
/* Does not support transport */
|
||||
status = "UNSUPPORTED";
|
||||
res = 0;
|
||||
ast_frfree(f);
|
||||
goto out;
|
||||
break;
|
||||
default:
|
||||
ast_log(LOG_WARNING, "Don't know what to do with HTML subclass %d\n", f->subclass);
|
||||
};
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
out:
|
||||
pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", status);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, sendurl_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Send URL Applications");
|
||||
89
trunk/apps/app_userevent.c
Normal file
89
trunk/apps/app_userevent.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* 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 UserEvent application -- send manager event
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/manager.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *app = "UserEvent";
|
||||
|
||||
static char *synopsis = "Send an arbitrary event to the manager interface";
|
||||
|
||||
static char *descrip =
|
||||
" UserEvent(eventname[,body]): Sends an arbitrary event to the manager\n"
|
||||
"interface, with an optional body representing additional arguments. The\n"
|
||||
"body may be specified as a | delimeted list of headers. Each additional\n"
|
||||
"argument will be placed on a new line in the event. The format of the\n"
|
||||
"event will be:\n"
|
||||
" Event: UserEvent\n"
|
||||
" UserEvent: <specified event name>\n"
|
||||
" [body]\n"
|
||||
"If no body is specified, only Event and UserEvent headers will be present.\n";
|
||||
|
||||
|
||||
static int userevent_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *parse, buf[2048] = "";
|
||||
int x, buflen = 0;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(eventname);
|
||||
AST_APP_ARG(extra)[100];
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "UserEvent requires an argument (eventname,optional event body)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
for (x = 0; x < args.argc - 1; x++) {
|
||||
ast_copy_string(buf + buflen, args.extra[x], sizeof(buf) - buflen - 2);
|
||||
buflen += strlen(args.extra[x]);
|
||||
ast_copy_string(buf + buflen, "\r\n", 3);
|
||||
buflen += 2;
|
||||
}
|
||||
|
||||
manager_event(EVENT_FLAG_USER, "UserEvent", "UserEvent: %s\r\n%s", args.eventname, buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, userevent_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Custom User Event Application");
|
||||
158
trunk/apps/app_verbose.c
Normal file
158
trunk/apps/app_verbose.c
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (c) 2004 - 2005 Tilghman Lesher. All rights reserved.
|
||||
*
|
||||
* Tilghman Lesher <app_verbose_v001@the-tilghman.com>
|
||||
*
|
||||
* This code is released by the author with no restrictions on usage.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Verbose logging application
|
||||
*
|
||||
* \author Tilghman Lesher <app_verbose_v001@the-tilghman.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
static char *app_verbose = "Verbose";
|
||||
static char *verbose_synopsis = "Send arbitrary text to verbose output";
|
||||
static char *verbose_descrip =
|
||||
"Verbose([<level>,]<message>)\n"
|
||||
" level must be an integer value. If not specified, defaults to 0.\n";
|
||||
|
||||
static char *app_log = "Log";
|
||||
static char *log_synopsis = "Send arbitrary text to a selected log level";
|
||||
static char *log_descrip =
|
||||
"Log(<level>,<message>)\n"
|
||||
" level must be one of ERROR, WARNING, NOTICE, DEBUG, VERBOSE, DTMF\n";
|
||||
|
||||
|
||||
static int verbose_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int vsize;
|
||||
char *parse;
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(level);
|
||||
AST_APP_ARG(msg);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
if (args.argc == 1) {
|
||||
args.msg = args.level;
|
||||
args.level = "0";
|
||||
}
|
||||
|
||||
if (sscanf(args.level, "%d", &vsize) != 1) {
|
||||
vsize = 0;
|
||||
ast_log(LOG_WARNING, "'%s' is not a verboser number\n", args.level);
|
||||
}
|
||||
if (option_verbose >= vsize) {
|
||||
switch (vsize) {
|
||||
case 0:
|
||||
ast_verbose("%s\n", args.msg);
|
||||
break;
|
||||
case 1:
|
||||
ast_verbose(VERBOSE_PREFIX_1 "%s\n", args.msg);
|
||||
break;
|
||||
case 2:
|
||||
ast_verbose(VERBOSE_PREFIX_2 "%s\n", args.msg);
|
||||
break;
|
||||
case 3:
|
||||
ast_verbose(VERBOSE_PREFIX_3 "%s\n", args.msg);
|
||||
break;
|
||||
default:
|
||||
ast_verbose(VERBOSE_PREFIX_4 "%s\n", args.msg);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int log_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
char *parse;
|
||||
int lnum = -1;
|
||||
char extension[AST_MAX_EXTENSION + 5], context[AST_MAX_EXTENSION + 2];
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(level);
|
||||
AST_APP_ARG(msg);
|
||||
);
|
||||
|
||||
if (ast_strlen_zero(data))
|
||||
return 0;
|
||||
|
||||
parse = ast_strdupa(data);
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
if (!strcasecmp(args.level, "ERROR")) {
|
||||
lnum = __LOG_ERROR;
|
||||
} else if (!strcasecmp(args.level, "WARNING")) {
|
||||
lnum = __LOG_WARNING;
|
||||
} else if (!strcasecmp(args.level, "NOTICE")) {
|
||||
lnum = __LOG_NOTICE;
|
||||
} else if (!strcasecmp(args.level, "DEBUG")) {
|
||||
lnum = __LOG_DEBUG;
|
||||
} else if (!strcasecmp(args.level, "VERBOSE")) {
|
||||
lnum = __LOG_VERBOSE;
|
||||
} else if (!strcasecmp(args.level, "DTMF")) {
|
||||
lnum = __LOG_DTMF;
|
||||
} else if (!strcasecmp(args.level, "EVENT")) {
|
||||
lnum = __LOG_EVENT;
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Unknown log level: '%s'\n", args.level);
|
||||
}
|
||||
|
||||
if (lnum > -1) {
|
||||
snprintf(context, sizeof(context), "@ %s", chan->context);
|
||||
snprintf(extension, sizeof(extension), "Ext. %s", chan->exten);
|
||||
|
||||
ast_log(lnum, extension, chan->priority, context, "%s\n", args.msg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(app_verbose);
|
||||
res |= ast_unregister_application(app_log);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_register_application(app_log, log_exec, log_synopsis, log_descrip);
|
||||
res |= ast_register_application(app_verbose, verbose_exec, verbose_synopsis, verbose_descrip);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Send verbose output");
|
||||
9831
trunk/apps/app_voicemail.c
Normal file
9831
trunk/apps/app_voicemail.c
Normal file
File diff suppressed because it is too large
Load Diff
117
trunk/apps/app_waitforring.c
Normal file
117
trunk/apps/app_waitforring.c
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Wait for Ring Application
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/lock.h"
|
||||
|
||||
static char *synopsis = "Wait for Ring Application";
|
||||
|
||||
static char *desc = " WaitForRing(timeout):\n"
|
||||
"Returns 0 after waiting at least timeout seconds. and\n"
|
||||
"only after the next ring has completed. Returns 0 on\n"
|
||||
"success or -1 on hangup\n";
|
||||
|
||||
static char *app = "WaitForRing";
|
||||
|
||||
|
||||
static int waitforring_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct ast_frame *f;
|
||||
int res = 0;
|
||||
double s;
|
||||
int ms;
|
||||
|
||||
if (!data || (sscanf(data, "%lg", &s) != 1)) {
|
||||
ast_log(LOG_WARNING, "WaitForRing requires an argument (minimum seconds)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ms = s*1000.0;
|
||||
while(ms > 0) {
|
||||
ms = ast_waitfor(chan, ms);
|
||||
if (ms < 0) {
|
||||
res = ms;
|
||||
break;
|
||||
}
|
||||
if (ms > 0) {
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RING)) {
|
||||
ast_verb(3, "Got a ring but still waiting for timeout\n");
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
/* Now we're really ready for the ring */
|
||||
if (!res) {
|
||||
ms = 99999999;
|
||||
while(ms > 0) {
|
||||
ms = ast_waitfor(chan, ms);
|
||||
if (ms < 0) {
|
||||
res = ms;
|
||||
break;
|
||||
}
|
||||
if (ms > 0) {
|
||||
f = ast_read(chan);
|
||||
if (!f) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RING)) {
|
||||
ast_verb(3, "Got a ring after the timeout\n");
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, waitforring_exec, synopsis, desc);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Waits until first ring after time");
|
||||
189
trunk/apps/app_waitforsilence.c
Normal file
189
trunk/apps/app_waitforsilence.c
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* WaitForSilence Application by David C. Troy <dave@popvox.com>
|
||||
* Version 1.11 2006-06-29
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Wait for Silence
|
||||
* - Waits for up to 'x' milliseconds of silence, 'y' times \n
|
||||
* - WaitForSilence(500,2) will wait for 1/2 second of silence, twice \n
|
||||
* - WaitForSilence(1000,1) will wait for 1 second of silence, once \n
|
||||
* - WaitForSilence(300,3,10) will wait for 300ms of silence, 3 times, and return after 10sec \n
|
||||
*
|
||||
* \author David C. Troy <dave@popvox.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/dsp.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
static char *app = "WaitForSilence";
|
||||
static char *synopsis = "Waits for a specified amount of silence";
|
||||
static char *descrip =
|
||||
" WaitForSilence(silencerequired[,iterations][,timeout]):\n"
|
||||
"Wait for Silence: Waits for up to 'silencerequired' \n"
|
||||
"milliseconds of silence, 'iterations' times or once if omitted.\n"
|
||||
"An optional timeout specified the number of seconds to return\n"
|
||||
"after, even if we do not receive the specified amount of silence.\n"
|
||||
"Use 'timeout' with caution, as it may defeat the purpose of this\n"
|
||||
"application, which is to wait indefinitely until silence is detected\n"
|
||||
"on the line. This is particularly useful for reverse-911-type\n"
|
||||
"call broadcast applications where you need to wait for an answering\n"
|
||||
"machine to complete its spiel before playing a message.\n"
|
||||
"The timeout parameter is specified only to avoid an infinite loop in\n"
|
||||
"cases where silence is never achieved. Typically you will want to\n"
|
||||
"include two or more calls to WaitForSilence when dealing with an answering\n"
|
||||
"machine; first waiting for the spiel to finish, then waiting for the beep, etc.\n\n"
|
||||
"Examples:\n"
|
||||
" - WaitForSilence(500,2) will wait for 1/2 second of silence, twice\n"
|
||||
" - WaitForSilence(1000) will wait for 1 second of silence, once\n"
|
||||
" - WaitForSilence(300,3,10) will wait for 300ms silence, 3 times,\n"
|
||||
" and returns after 10 sec, even if silence is not detected\n\n"
|
||||
"Sets the channel variable WAITSTATUS with to one of these values:\n"
|
||||
"SILENCE - if exited with silence detected\n"
|
||||
"TIMEOUT - if exited without silence detected after timeout\n";
|
||||
|
||||
static int do_waiting(struct ast_channel *chan, int silencereqd, time_t waitstart, int timeout) {
|
||||
struct ast_frame *f;
|
||||
int dspsilence = 0;
|
||||
static int silencethreshold = 128;
|
||||
int rfmt = 0;
|
||||
int res = 0;
|
||||
struct ast_dsp *sildet; /* silence detector dsp */
|
||||
time_t now;
|
||||
|
||||
rfmt = chan->readformat; /* Set to linear mode */
|
||||
res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
|
||||
if (res < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set channel to linear mode, giving up\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
sildet = ast_dsp_new(); /* Create the silence detector */
|
||||
if (!sildet) {
|
||||
ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
|
||||
return -1;
|
||||
}
|
||||
ast_dsp_set_threshold(sildet, silencethreshold);
|
||||
|
||||
/* Await silence... */
|
||||
f = NULL;
|
||||
for(;;) {
|
||||
/* Start with no silence received */
|
||||
dspsilence = 0;
|
||||
|
||||
res = ast_waitfor(chan, silencereqd);
|
||||
|
||||
/* Must have gotten a hangup; let's exit */
|
||||
if (res <= 0) {
|
||||
f = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* We waited and got no frame; sounds like digital silence or a muted digital channel */
|
||||
if (!res) {
|
||||
dspsilence = silencereqd;
|
||||
} else {
|
||||
/* Looks like we did get a frame, so let's check it out */
|
||||
f = ast_read(chan);
|
||||
if (!f)
|
||||
break;
|
||||
if (f && f->frametype == AST_FRAME_VOICE) {
|
||||
ast_dsp_silence(sildet, f, &dspsilence);
|
||||
ast_frfree(f);
|
||||
}
|
||||
}
|
||||
|
||||
ast_verb(3, "Got %dms silence< %dms required\n", dspsilence, silencereqd);
|
||||
|
||||
if (dspsilence >= silencereqd) {
|
||||
ast_verb(3, "Exiting with %dms silence >= %dms required\n", dspsilence, silencereqd);
|
||||
/* Ended happily with silence */
|
||||
res = 1;
|
||||
pbx_builtin_setvar_helper(chan, "WAITSTATUS", "SILENCE");
|
||||
ast_debug(1, "WAITSTATUS was set to SILENCE\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if ( timeout && (difftime(time(&now),waitstart) >= timeout) ) {
|
||||
pbx_builtin_setvar_helper(chan, "WAITSTATUS", "TIMEOUT");
|
||||
ast_debug(1, "WAITSTATUS was set to TIMEOUT\n");
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (rfmt && ast_set_read_format(chan, rfmt)) {
|
||||
ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_getformatname(rfmt), chan->name);
|
||||
}
|
||||
ast_dsp_free(sildet);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int waitforsilence_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 1;
|
||||
int silencereqd = 1000;
|
||||
int timeout = 0;
|
||||
int iterations = 1, i;
|
||||
time_t waitstart;
|
||||
|
||||
res = ast_answer(chan); /* Answer the channel */
|
||||
|
||||
if (!data || ( (sscanf(data, "%d,%d,%d", &silencereqd, &iterations, &timeout) != 3) &&
|
||||
(sscanf(data, "%d|%d", &silencereqd, &iterations) != 2) &&
|
||||
(sscanf(data, "%d", &silencereqd) != 1) ) ) {
|
||||
ast_log(LOG_WARNING, "Using default value of 1000ms, 1 iteration, no timeout\n");
|
||||
}
|
||||
|
||||
ast_verb(3, "Waiting %d time(s) for %d ms silence with %d timeout\n", iterations, silencereqd, timeout);
|
||||
|
||||
time(&waitstart);
|
||||
res = 1;
|
||||
for (i=0; (i<iterations) && (res == 1); i++) {
|
||||
res = do_waiting(chan, silencereqd, waitstart, timeout);
|
||||
}
|
||||
if (res > 0)
|
||||
res = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, waitforsilence_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Wait For Silence");
|
||||
|
||||
93
trunk/apps/app_waituntil.c
Normal file
93
trunk/apps/app_waituntil.c
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2007, Redfish Solutions
|
||||
*
|
||||
* Philip Prindeville <philipp@redfish-solutions.com>
|
||||
*
|
||||
* 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 Sleep until the given epoch
|
||||
*
|
||||
* \author Philip Prindeville <philipp@redfish-solutions.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/logger.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
static char *app = "WaitUntil";
|
||||
static char *synopsis = "Wait (sleep) until the current time is the given epoch";
|
||||
static char *descrip =
|
||||
" WaitUntil(<epoch>): Waits until the given time. Sets WAITUNTILSTATUS to\n"
|
||||
"one of the following values:\n"
|
||||
" OK Wait succeeded\n"
|
||||
" FAILURE Invalid argument\n"
|
||||
" HANGUP Channel hung up before time elapsed\n"
|
||||
" PAST The time specified was already past\n";
|
||||
|
||||
static int waituntil_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res;
|
||||
double fraction;
|
||||
struct timeval future = { 0, };
|
||||
struct timeval tv = ast_tvnow();
|
||||
int msec;
|
||||
|
||||
if (ast_strlen_zero(data)) {
|
||||
ast_log(LOG_WARNING, "WaitUntil requires an argument(epoch)\n");
|
||||
pbx_builtin_setvar_helper(chan, "WAITUNTILSTATUS", "FAILURE");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sscanf(data, "%ld%lf", (long *)&future.tv_sec, &fraction) == 0) {
|
||||
ast_log(LOG_WARNING, "WaitUntil called with non-numeric argument\n");
|
||||
pbx_builtin_setvar_helper(chan, "WAITUNTILSTATUS", "FAILURE");
|
||||
return 0;
|
||||
}
|
||||
|
||||
future.tv_usec = fraction * 1000000;
|
||||
|
||||
if ((msec = ast_tvdiff_ms(future, tv)) < 0) {
|
||||
ast_log(LOG_NOTICE, "WaitUntil called in the past (now %ld, arg %ld)\n", (long)tv.tv_sec, (long)future.tv_sec);
|
||||
pbx_builtin_setvar_helper(chan, "WAITUNTILSTATUS", "PAST");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((res = ast_safe_sleep(chan, msec)))
|
||||
pbx_builtin_setvar_helper(chan, "WAITUNTILSTATUS", "HANGUP");
|
||||
else
|
||||
pbx_builtin_setvar_helper(chan, "WAITUNTILSTATUS", "OK");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_register_application(app, waituntil_exec, synopsis, descrip);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Wait until specified time");
|
||||
307
trunk/apps/app_while.c
Normal file
307
trunk/apps/app_while.c
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright 2004 - 2005, Anthony Minessale <anthmct@yahoo.com>
|
||||
*
|
||||
* Anthony Minessale <anthmct@yahoo.com>
|
||||
*
|
||||
* 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 While Loop Implementation
|
||||
*
|
||||
* \author Anthony Minessale <anthmct@yahoo.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/channel.h"
|
||||
|
||||
static char *start_app = "While";
|
||||
static char *start_desc =
|
||||
" While(<expr>): Start a While Loop. Execution will return to this\n"
|
||||
"point when EndWhile() is called until expr is no longer true.\n";
|
||||
|
||||
static char *start_synopsis = "Start a while loop";
|
||||
|
||||
|
||||
static char *stop_app = "EndWhile";
|
||||
static char *stop_desc =
|
||||
" EndWhile(): Return to the previous called While()\n";
|
||||
|
||||
static char *stop_synopsis = "End a while loop";
|
||||
|
||||
static char *exit_app = "ExitWhile";
|
||||
static char *exit_desc =
|
||||
" ExitWhile(): Exits a While() loop, whether or not the conditional has been satisfied.\n";
|
||||
static char *exit_synopsis = "End a While loop";
|
||||
|
||||
static char *continue_app = "ContinueWhile";
|
||||
static char *continue_desc =
|
||||
" ContinueWhile(): Returns to the top of the while loop and re-evaluates the conditional.\n";
|
||||
static char *continue_synopsis = "Restart a While loop";
|
||||
|
||||
#define VAR_SIZE 64
|
||||
|
||||
|
||||
static const char *get_index(struct ast_channel *chan, const char *prefix, int index) {
|
||||
char varname[VAR_SIZE];
|
||||
|
||||
snprintf(varname, VAR_SIZE, "%s_%d", prefix, index);
|
||||
return pbx_builtin_getvar_helper(chan, varname);
|
||||
}
|
||||
|
||||
static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
|
||||
{
|
||||
struct ast_exten *e;
|
||||
struct ast_include *i;
|
||||
struct ast_context *c2;
|
||||
|
||||
for (e=ast_walk_context_extensions(c, NULL); e; e=ast_walk_context_extensions(c, e)) {
|
||||
if (ast_extension_match(ast_get_extension_name(e), exten)) {
|
||||
int needmatch = ast_get_extension_matchcid(e);
|
||||
if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
|
||||
(!needmatch)) {
|
||||
/* This is the matching extension we want */
|
||||
struct ast_exten *p;
|
||||
for (p=ast_walk_extension_priorities(e, NULL); p; p=ast_walk_extension_priorities(e, p)) {
|
||||
if (priority != ast_get_extension_priority(p))
|
||||
continue;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* No match; run through includes */
|
||||
for (i=ast_walk_context_includes(c, NULL); i; i=ast_walk_context_includes(c, i)) {
|
||||
for (c2=ast_walk_contexts(NULL); c2; c2=ast_walk_contexts(c2)) {
|
||||
if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
|
||||
e = find_matching_priority(c2, exten, priority, callerid);
|
||||
if (e)
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int find_matching_endwhile(struct ast_channel *chan)
|
||||
{
|
||||
struct ast_context *c;
|
||||
int res=-1;
|
||||
|
||||
if (ast_rdlock_contexts()) {
|
||||
ast_log(LOG_ERROR, "Failed to lock contexts list\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (c=ast_walk_contexts(NULL); c; c=ast_walk_contexts(c)) {
|
||||
struct ast_exten *e;
|
||||
|
||||
if (!ast_rdlock_context(c)) {
|
||||
if (!strcmp(ast_get_context_name(c), chan->context)) {
|
||||
/* This is the matching context we want */
|
||||
int cur_priority = chan->priority + 1, level=1;
|
||||
|
||||
for (e = find_matching_priority(c, chan->exten, cur_priority, chan->cid.cid_num); e; e = find_matching_priority(c, chan->exten, ++cur_priority, chan->cid.cid_num)) {
|
||||
if (!strcasecmp(ast_get_extension_app(e), "WHILE")) {
|
||||
level++;
|
||||
} else if (!strcasecmp(ast_get_extension_app(e), "ENDWHILE")) {
|
||||
level--;
|
||||
}
|
||||
|
||||
if (level == 0) {
|
||||
res = cur_priority;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ast_unlock_context(c);
|
||||
if (res > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ast_unlock_contexts();
|
||||
return res;
|
||||
}
|
||||
|
||||
static int _while_exec(struct ast_channel *chan, void *data, int end)
|
||||
{
|
||||
int res=0;
|
||||
const char *while_pri = NULL;
|
||||
char *my_name = NULL;
|
||||
const char *condition = NULL, *label = NULL;
|
||||
char varname[VAR_SIZE], end_varname[VAR_SIZE];
|
||||
const char *prefix = "WHILE";
|
||||
size_t size=0;
|
||||
int used_index_i = -1, x=0;
|
||||
char used_index[VAR_SIZE] = "0", new_index[VAR_SIZE] = "0";
|
||||
|
||||
if (!chan) {
|
||||
/* huh ? */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* dont want run away loops if the chan isn't even up
|
||||
this is up for debate since it slows things down a tad ......
|
||||
*/
|
||||
if (ast_waitfordigit(chan,1) < 0)
|
||||
return -1;
|
||||
|
||||
for (x=0;;x++) {
|
||||
if (get_index(chan, prefix, x)) {
|
||||
used_index_i = x;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(used_index, VAR_SIZE, "%d", used_index_i);
|
||||
snprintf(new_index, VAR_SIZE, "%d", used_index_i + 1);
|
||||
|
||||
if (!end)
|
||||
condition = ast_strdupa(data);
|
||||
|
||||
size = strlen(chan->context) + strlen(chan->exten) + 32;
|
||||
my_name = alloca(size);
|
||||
memset(my_name, 0, size);
|
||||
snprintf(my_name, size, "%s_%s_%d", chan->context, chan->exten, chan->priority);
|
||||
|
||||
if (ast_strlen_zero(label)) {
|
||||
if (end)
|
||||
label = used_index;
|
||||
else if (!(label = pbx_builtin_getvar_helper(chan, my_name))) {
|
||||
label = new_index;
|
||||
pbx_builtin_setvar_helper(chan, my_name, label);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
snprintf(varname, VAR_SIZE, "%s_%s", prefix, label);
|
||||
while_pri = pbx_builtin_getvar_helper(chan, varname);
|
||||
|
||||
if ((while_pri = pbx_builtin_getvar_helper(chan, varname)) && !end) {
|
||||
snprintf(end_varname,VAR_SIZE,"END_%s",varname);
|
||||
}
|
||||
|
||||
|
||||
if ((!end && !pbx_checkcondition(condition)) || (end == 2)) {
|
||||
/* Condition Met (clean up helper vars) */
|
||||
const char *goto_str;
|
||||
pbx_builtin_setvar_helper(chan, varname, NULL);
|
||||
pbx_builtin_setvar_helper(chan, my_name, NULL);
|
||||
snprintf(end_varname,VAR_SIZE,"END_%s",varname);
|
||||
if ((goto_str=pbx_builtin_getvar_helper(chan, end_varname))) {
|
||||
ast_parseable_goto(chan, goto_str);
|
||||
pbx_builtin_setvar_helper(chan, end_varname, NULL);
|
||||
} else {
|
||||
int pri = find_matching_endwhile(chan);
|
||||
if (pri > 0) {
|
||||
ast_verb(3, "Jumping to priority %d\n", pri);
|
||||
chan->priority = pri;
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Couldn't find matching EndWhile? (While at %s@%s priority %d)\n", chan->context, chan->exten, chan->priority);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
if (!end && !while_pri) {
|
||||
char *goto_str;
|
||||
size = strlen(chan->context) + strlen(chan->exten) + 32;
|
||||
goto_str = alloca(size);
|
||||
memset(goto_str, 0, size);
|
||||
snprintf(goto_str, size, "%s,%s,%d", chan->context, chan->exten, chan->priority);
|
||||
pbx_builtin_setvar_helper(chan, varname, goto_str);
|
||||
}
|
||||
|
||||
else if (end && while_pri) {
|
||||
/* END of loop */
|
||||
snprintf(end_varname, VAR_SIZE, "END_%s", varname);
|
||||
if (! pbx_builtin_getvar_helper(chan, end_varname)) {
|
||||
char *goto_str;
|
||||
size = strlen(chan->context) + strlen(chan->exten) + 32;
|
||||
goto_str = alloca(size);
|
||||
memset(goto_str, 0, size);
|
||||
snprintf(goto_str, size, "%s,%s,%d", chan->context, chan->exten, chan->priority+1);
|
||||
pbx_builtin_setvar_helper(chan, end_varname, goto_str);
|
||||
}
|
||||
ast_parseable_goto(chan, while_pri);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int while_start_exec(struct ast_channel *chan, void *data) {
|
||||
return _while_exec(chan, data, 0);
|
||||
}
|
||||
|
||||
static int while_end_exec(struct ast_channel *chan, void *data) {
|
||||
return _while_exec(chan, data, 1);
|
||||
}
|
||||
|
||||
static int while_exit_exec(struct ast_channel *chan, void *data) {
|
||||
return _while_exec(chan, data, 2);
|
||||
}
|
||||
|
||||
static int while_continue_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int x;
|
||||
const char *prefix = "WHILE", *while_pri=NULL;
|
||||
|
||||
for (x = 0; ; x++) {
|
||||
const char *tmp = get_index(chan, prefix, x);
|
||||
if (tmp)
|
||||
while_pri = tmp;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (while_pri)
|
||||
ast_parseable_goto(chan, while_pri);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_unregister_application(start_app);
|
||||
res |= ast_unregister_application(stop_app);
|
||||
res |= ast_unregister_application(exit_app);
|
||||
res |= ast_unregister_application(continue_app);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ast_register_application(start_app, while_start_exec, start_synopsis, start_desc);
|
||||
res |= ast_register_application(stop_app, while_end_exec, stop_synopsis, stop_desc);
|
||||
res |= ast_register_application(exit_app, while_exit_exec, exit_synopsis, exit_desc);
|
||||
res |= ast_register_application(continue_app, while_continue_exec, continue_synopsis, continue_desc);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "While Loops and Conditional Execution");
|
||||
113
trunk/apps/app_zapateller.c
Normal file
113
trunk/apps/app_zapateller.c
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Playback the special information tone to get rid of telemarketers
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/translate.h"
|
||||
#include "asterisk/app.h"
|
||||
|
||||
static char *app = "Zapateller";
|
||||
|
||||
static char *synopsis = "Block telemarketers with SIT";
|
||||
|
||||
static char *descrip =
|
||||
" Zapateller(options): Generates special information tone to block\n"
|
||||
"telemarketers from calling you. Options is a pipe-delimited list of\n"
|
||||
"options. The following options are available:\n"
|
||||
" 'answer' - causes the line to be answered before playing the tone,\n"
|
||||
" 'nocallerid' - causes Zapateller to only play the tone if there is no\n"
|
||||
" callerid information available. Options should be\n"
|
||||
" separated by , characters\n\n"
|
||||
" This application will set the following channel variable upon completion:\n"
|
||||
" ZAPATELLERSTATUS - This will contain the last action accomplished by the\n"
|
||||
" Zapateller application. Possible values include:\n"
|
||||
" NOTHING | ANSWERED | ZAPPED\n\n";
|
||||
|
||||
|
||||
static int zapateller_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res = 0;
|
||||
int i, answer = 0, nocallerid = 0;
|
||||
char *parse = ast_strdupa((char *)data);
|
||||
AST_DECLARE_APP_ARGS(args,
|
||||
AST_APP_ARG(options)[2];
|
||||
);
|
||||
|
||||
AST_STANDARD_APP_ARGS(args, parse);
|
||||
|
||||
for (i = 0; i < args.argc; i++) {
|
||||
if (!strcasecmp(args.options[i], "answer"))
|
||||
answer = 1;
|
||||
else if (!strcasecmp(args.options[i], "nocallerid"))
|
||||
nocallerid = 1;
|
||||
}
|
||||
|
||||
pbx_builtin_setvar_helper(chan, "ZAPATELLERSTATUS", "NOTHING");
|
||||
ast_stopstream(chan);
|
||||
if (chan->_state != AST_STATE_UP) {
|
||||
if (answer) {
|
||||
res = ast_answer(chan);
|
||||
pbx_builtin_setvar_helper(chan, "ZAPATELLERSTATUS", "ANSWERED");
|
||||
}
|
||||
if (!res)
|
||||
res = ast_safe_sleep(chan, 500);
|
||||
}
|
||||
|
||||
if (!ast_strlen_zero(chan->cid.cid_num) && nocallerid)
|
||||
return res;
|
||||
|
||||
if (!res)
|
||||
res = ast_tonepair(chan, 950, 0, 330, 0);
|
||||
if (!res)
|
||||
res = ast_tonepair(chan, 1400, 0, 330, 0);
|
||||
if (!res)
|
||||
res = ast_tonepair(chan, 1800, 0, 330, 0);
|
||||
if (!res)
|
||||
res = ast_tonepair(chan, 0, 0, 1000, 0);
|
||||
|
||||
pbx_builtin_setvar_helper(chan, "ZAPATELLERSTATUS", "ZAPPED");
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ((ast_register_application(app, zapateller_exec, synopsis, descrip)) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Block Telemarketers with Special Information Tone");
|
||||
299
trunk/apps/app_zapbarge.c
Normal file
299
trunk/apps/app_zapbarge.c
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* Special thanks to comphealth.com for sponsoring this
|
||||
* GPL application.
|
||||
*
|
||||
* 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 Zap Barge support
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \note Special thanks to comphealth.com for sponsoring this
|
||||
* GPL application.
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend>zaptel</depend>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/zapata.h"
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/config.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/cli.h"
|
||||
#include "asterisk/say.h"
|
||||
#include "asterisk/utils.h"
|
||||
|
||||
static char *app = "ZapBarge";
|
||||
|
||||
static char *synopsis = "Barge in (monitor) Zap channel";
|
||||
|
||||
static char *descrip =
|
||||
" ZapBarge([channel]): Barges in on a specified zap\n"
|
||||
"channel or prompts if one is not specified. Returns\n"
|
||||
"-1 when caller user hangs up and is independent of the\n"
|
||||
"state of the channel being monitored.";
|
||||
|
||||
|
||||
#define CONF_SIZE 160
|
||||
|
||||
static int careful_write(int fd, unsigned char *data, int len)
|
||||
{
|
||||
int res;
|
||||
while(len) {
|
||||
res = write(fd, data, len);
|
||||
if (res < 1) {
|
||||
if (errno != EAGAIN) {
|
||||
ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
|
||||
return -1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
len -= res;
|
||||
data += res;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int conf_run(struct ast_channel *chan, int confno, int confflags)
|
||||
{
|
||||
int fd;
|
||||
struct zt_confinfo ztc;
|
||||
struct ast_frame *f;
|
||||
struct ast_channel *c;
|
||||
struct ast_frame fr;
|
||||
int outfd;
|
||||
int ms;
|
||||
int nfds;
|
||||
int res;
|
||||
int flags;
|
||||
int retryzap;
|
||||
int origfd;
|
||||
int ret = -1;
|
||||
|
||||
ZT_BUFFERINFO bi;
|
||||
char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
|
||||
char *buf = __buf + AST_FRIENDLY_OFFSET;
|
||||
|
||||
/* Set it into U-law mode (write) */
|
||||
if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name);
|
||||
goto outrun;
|
||||
}
|
||||
|
||||
/* Set it into U-law mode (read) */
|
||||
if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name);
|
||||
goto outrun;
|
||||
}
|
||||
ast_indicate(chan, -1);
|
||||
retryzap = strcasecmp(chan->tech->type, "Zap");
|
||||
zapretry:
|
||||
origfd = chan->fds[0];
|
||||
if (retryzap) {
|
||||
fd = open("/dev/zap/pseudo", O_RDWR);
|
||||
if (fd < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
|
||||
goto outrun;
|
||||
}
|
||||
/* Make non-blocking */
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
if (flags < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
|
||||
ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
/* Setup buffering information */
|
||||
memset(&bi, 0, sizeof(bi));
|
||||
bi.bufsize = CONF_SIZE;
|
||||
bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
|
||||
bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
|
||||
bi.numbufs = 4;
|
||||
if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
|
||||
ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
nfds = 1;
|
||||
} else {
|
||||
/* XXX Make sure we're not running on a pseudo channel XXX */
|
||||
fd = chan->fds[0];
|
||||
nfds = 0;
|
||||
}
|
||||
memset(&ztc, 0, sizeof(ztc));
|
||||
/* Check to see if we're in a conference... */
|
||||
ztc.chan = 0;
|
||||
if (ioctl(fd, ZT_GETCONF, &ztc)) {
|
||||
ast_log(LOG_WARNING, "Error getting conference\n");
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
if (ztc.confmode) {
|
||||
/* Whoa, already in a conference... Retry... */
|
||||
if (!retryzap) {
|
||||
ast_debug(1, "Zap channel is in a conference already, retrying with pseudo\n");
|
||||
retryzap = 1;
|
||||
goto zapretry;
|
||||
}
|
||||
}
|
||||
memset(&ztc, 0, sizeof(ztc));
|
||||
/* Add us to the conference */
|
||||
ztc.chan = 0;
|
||||
ztc.confno = confno;
|
||||
ztc.confmode = ZT_CONF_MONITORBOTH;
|
||||
|
||||
if (ioctl(fd, ZT_SETCONF, &ztc)) {
|
||||
ast_log(LOG_WARNING, "Error setting conference\n");
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
ast_debug(1, "Placed channel %s in ZAP channel %d monitor\n", chan->name, confno);
|
||||
|
||||
for(;;) {
|
||||
outfd = -1;
|
||||
ms = -1;
|
||||
c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
|
||||
if (c) {
|
||||
if (c->fds[0] != origfd) {
|
||||
if (retryzap) {
|
||||
/* Kill old pseudo */
|
||||
close(fd);
|
||||
}
|
||||
ast_debug(1, "Ooh, something swapped out under us, starting over\n");
|
||||
retryzap = 0;
|
||||
goto zapretry;
|
||||
}
|
||||
f = ast_read(c);
|
||||
if (!f)
|
||||
break;
|
||||
if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) {
|
||||
ret = 0;
|
||||
ast_frfree(f);
|
||||
break;
|
||||
} else if (fd != chan->fds[0]) {
|
||||
if (f->frametype == AST_FRAME_VOICE) {
|
||||
if (f->subclass == AST_FORMAT_ULAW) {
|
||||
/* Carefully write */
|
||||
careful_write(fd, f->data, f->datalen);
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Huh? Got a non-ulaw (%d) frame in the conference\n", f->subclass);
|
||||
}
|
||||
}
|
||||
ast_frfree(f);
|
||||
} else if (outfd > -1) {
|
||||
res = read(outfd, buf, CONF_SIZE);
|
||||
if (res > 0) {
|
||||
memset(&fr, 0, sizeof(fr));
|
||||
fr.frametype = AST_FRAME_VOICE;
|
||||
fr.subclass = AST_FORMAT_ULAW;
|
||||
fr.datalen = res;
|
||||
fr.samples = res;
|
||||
fr.data = buf;
|
||||
fr.offset = AST_FRIENDLY_OFFSET;
|
||||
if (ast_write(chan, &fr) < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
|
||||
/* break; */
|
||||
}
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
if (fd != chan->fds[0])
|
||||
close(fd);
|
||||
else {
|
||||
/* Take out of conference */
|
||||
/* Add us to the conference */
|
||||
ztc.chan = 0;
|
||||
ztc.confno = 0;
|
||||
ztc.confmode = 0;
|
||||
if (ioctl(fd, ZT_SETCONF, &ztc)) {
|
||||
ast_log(LOG_WARNING, "Error setting conference\n");
|
||||
}
|
||||
}
|
||||
|
||||
outrun:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int conf_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=-1;
|
||||
int retrycnt = 0;
|
||||
int confflags = 0;
|
||||
int confno = 0;
|
||||
char confstr[80] = "";
|
||||
|
||||
if (!ast_strlen_zero(data)) {
|
||||
if ((sscanf(data, "Zap/%d", &confno) != 1) &&
|
||||
(sscanf(data, "%d", &confno) != 1)) {
|
||||
ast_log(LOG_WARNING, "ZapBarge Argument (if specified) must be a channel number, not '%s'\n", (char *)data);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
ast_answer(chan);
|
||||
|
||||
while(!confno && (++retrycnt < 4)) {
|
||||
/* Prompt user for conference number */
|
||||
confstr[0] = '\0';
|
||||
res = ast_app_getdata(chan, "conf-getchannel",confstr, sizeof(confstr) - 1, 0);
|
||||
if (res <0) goto out;
|
||||
if (sscanf(confstr, "%d", &confno) != 1)
|
||||
confno = 0;
|
||||
}
|
||||
if (confno) {
|
||||
/* XXX Should prompt user for pin if pin is required XXX */
|
||||
/* Run the conference */
|
||||
res = conf_run(chan, confno, confflags);
|
||||
}
|
||||
out:
|
||||
/* Do the conference */
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ((ast_register_application(app, conf_exec, synopsis, descrip)) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Barge in on Zap channel application");
|
||||
238
trunk/apps/app_zapras.c
Normal file
238
trunk/apps/app_zapras.c
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* 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 Execute an ISDN RAS
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend>zaptel</depend>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/signal.h>
|
||||
#else
|
||||
#include <signal.h>
|
||||
#endif /* __linux__ */
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "asterisk/zapata.h"
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
static char *app = "ZapRAS";
|
||||
|
||||
static char *synopsis = "Executes Zaptel ISDN RAS application";
|
||||
|
||||
static char *descrip =
|
||||
" ZapRAS(args): Executes a RAS server using pppd on the given channel.\n"
|
||||
"The channel must be a clear channel (i.e. PRI source) and a Zaptel\n"
|
||||
"channel to be able to use this function (No modem emulation is included).\n"
|
||||
"Your pppd must be patched to be zaptel aware. Arguments should be\n"
|
||||
"separated by , characters.\n";
|
||||
|
||||
|
||||
#define PPP_MAX_ARGS 32
|
||||
#define PPP_EXEC "/usr/sbin/pppd"
|
||||
|
||||
static pid_t spawn_ras(struct ast_channel *chan, char *args)
|
||||
{
|
||||
pid_t pid;
|
||||
int x;
|
||||
char *c;
|
||||
|
||||
char *argv[PPP_MAX_ARGS];
|
||||
int argc = 0;
|
||||
char *stringp=NULL;
|
||||
sigset_t fullset, oldset;
|
||||
|
||||
sigfillset(&fullset);
|
||||
pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
|
||||
|
||||
/* Start by forking */
|
||||
pid = fork();
|
||||
if (pid) {
|
||||
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
|
||||
return pid;
|
||||
}
|
||||
|
||||
/* Restore original signal handlers */
|
||||
for (x=0;x<NSIG;x++)
|
||||
signal(x, SIG_DFL);
|
||||
|
||||
pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
|
||||
|
||||
/* Execute RAS on File handles */
|
||||
dup2(chan->fds[0], STDIN_FILENO);
|
||||
|
||||
/* Drop high priority */
|
||||
if (ast_opt_high_priority)
|
||||
ast_set_priority(0);
|
||||
|
||||
/* Close other file descriptors */
|
||||
for (x=STDERR_FILENO + 1;x<1024;x++)
|
||||
close(x);
|
||||
|
||||
/* Reset all arguments */
|
||||
memset(argv, 0, sizeof(argv));
|
||||
|
||||
/* First argument is executable, followed by standard
|
||||
arguments for zaptel PPP */
|
||||
argv[argc++] = PPP_EXEC;
|
||||
argv[argc++] = "nodetach";
|
||||
|
||||
/* And all the other arguments */
|
||||
stringp=args;
|
||||
c = strsep(&stringp, ",");
|
||||
while(c && strlen(c) && (argc < (PPP_MAX_ARGS - 4))) {
|
||||
argv[argc++] = c;
|
||||
c = strsep(&stringp, ",");
|
||||
}
|
||||
|
||||
argv[argc++] = "plugin";
|
||||
argv[argc++] = "zaptel.so";
|
||||
argv[argc++] = "stdin";
|
||||
|
||||
/* Finally launch PPP */
|
||||
execv(PPP_EXEC, argv);
|
||||
fprintf(stderr, "Failed to exec PPPD!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void run_ras(struct ast_channel *chan, char *args)
|
||||
{
|
||||
pid_t pid;
|
||||
int status;
|
||||
int res;
|
||||
int signalled = 0;
|
||||
struct zt_bufferinfo savebi;
|
||||
int x;
|
||||
|
||||
res = ioctl(chan->fds[0], ZT_GET_BUFINFO, &savebi);
|
||||
if(res) {
|
||||
ast_log(LOG_WARNING, "Unable to check buffer policy on channel %s\n", chan->name);
|
||||
return;
|
||||
}
|
||||
|
||||
pid = spawn_ras(chan, args);
|
||||
if (pid < 0) {
|
||||
ast_log(LOG_WARNING, "Failed to spawn RAS\n");
|
||||
} else {
|
||||
for (;;) {
|
||||
res = wait4(pid, &status, WNOHANG, NULL);
|
||||
if (!res) {
|
||||
/* Check for hangup */
|
||||
if (ast_check_hangup(chan) && !signalled) {
|
||||
ast_debug(1, "Channel '%s' hungup. Signalling RAS at %d to die...\n", chan->name, pid);
|
||||
kill(pid, SIGTERM);
|
||||
signalled=1;
|
||||
}
|
||||
/* Try again */
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
if (res < 0) {
|
||||
ast_log(LOG_WARNING, "wait4 returned %d: %s\n", res, strerror(errno));
|
||||
}
|
||||
if (option_verbose > 2) {
|
||||
if (WIFEXITED(status)) {
|
||||
ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated with status %d\n", chan->name, WEXITSTATUS(status));
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated with signal %d\n",
|
||||
chan->name, WTERMSIG(status));
|
||||
} else {
|
||||
ast_verbose(VERBOSE_PREFIX_3 "RAS on %s terminated weirdly.\n", chan->name);
|
||||
}
|
||||
}
|
||||
/* Throw back into audio mode */
|
||||
x = 1;
|
||||
ioctl(chan->fds[0], ZT_AUDIOMODE, &x);
|
||||
|
||||
/* Restore saved values */
|
||||
res = ioctl(chan->fds[0], ZT_SET_BUFINFO, &savebi);
|
||||
if (res < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set buffer policy on channel %s\n", chan->name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int zapras_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=-1;
|
||||
char *args;
|
||||
ZT_PARAMS ztp;
|
||||
|
||||
if (!data)
|
||||
data = "";
|
||||
|
||||
args = ast_strdupa(data);
|
||||
|
||||
/* Answer the channel if it's not up */
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
ast_answer(chan);
|
||||
if (strcasecmp(chan->tech->type, "Zap")) {
|
||||
/* If it's not a zap channel, we're done. Wait a couple of
|
||||
seconds and then hangup... */
|
||||
ast_verb(2, "Channel %s is not a Zap channel\n", chan->name);
|
||||
sleep(2);
|
||||
} else {
|
||||
memset(&ztp, 0, sizeof(ztp));
|
||||
if (ioctl(chan->fds[0], ZT_GET_PARAMS, &ztp)) {
|
||||
ast_log(LOG_WARNING, "Unable to get zaptel parameters\n");
|
||||
} else if (ztp.sigtype != ZT_SIG_CLEAR) {
|
||||
ast_verb(2, "Channel %s is not a clear channel\n", chan->name);
|
||||
} else {
|
||||
/* Everything should be okay. Run PPP. */
|
||||
ast_verb(3, "Starting RAS on %s\n", chan->name);
|
||||
/* Execute RAS */
|
||||
run_ras(chan, args);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ((ast_register_application(app, zapras_exec, synopsis, descrip)) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Zaptel ISDN Remote Access Server");
|
||||
|
||||
363
trunk/apps/app_zapscan.c
Normal file
363
trunk/apps/app_zapscan.c
Normal file
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* Modified from app_zapbarge by David Troy <dave@toad.net>
|
||||
*
|
||||
* Special thanks to comphealth.com for sponsoring this
|
||||
* GPL application.
|
||||
*
|
||||
* 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 Zap Scanner
|
||||
*
|
||||
* \author Mark Spencer <markster@digium.com>
|
||||
*
|
||||
* \ingroup applications
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend>zaptel</depend>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/zapata.h"
|
||||
|
||||
#include "asterisk/lock.h"
|
||||
#include "asterisk/file.h"
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/config.h"
|
||||
#include "asterisk/app.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/cli.h"
|
||||
#include "asterisk/say.h"
|
||||
|
||||
static char *app = "ZapScan";
|
||||
|
||||
static char *synopsis = "Scan Zap channels to monitor calls";
|
||||
|
||||
static char *descrip =
|
||||
" ZapScan([group]) allows a call center manager to monitor Zap channels in\n"
|
||||
"a convenient way. Use '#' to select the next channel and use '*' to exit\n"
|
||||
"Limit scanning to a channel GROUP by setting the option group argument.\n";
|
||||
|
||||
|
||||
#define CONF_SIZE 160
|
||||
|
||||
static struct ast_channel *get_zap_channel_locked(int num) {
|
||||
char name[80];
|
||||
|
||||
snprintf(name,sizeof(name),"Zap/%d-1",num);
|
||||
return ast_get_channel_by_name_locked(name);
|
||||
}
|
||||
|
||||
static int careful_write(int fd, unsigned char *data, int len)
|
||||
{
|
||||
int res;
|
||||
while(len) {
|
||||
res = write(fd, data, len);
|
||||
if (res < 1) {
|
||||
if (errno != EAGAIN) {
|
||||
ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
|
||||
return -1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
len -= res;
|
||||
data += res;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int conf_run(struct ast_channel *chan, int confno, int confflags)
|
||||
{
|
||||
int fd;
|
||||
struct zt_confinfo ztc;
|
||||
struct ast_frame *f;
|
||||
struct ast_channel *c;
|
||||
struct ast_frame fr;
|
||||
int outfd;
|
||||
int ms;
|
||||
int nfds;
|
||||
int res;
|
||||
int flags;
|
||||
int retryzap;
|
||||
int origfd;
|
||||
int ret = -1;
|
||||
char input[4];
|
||||
int ic=0;
|
||||
|
||||
ZT_BUFFERINFO bi;
|
||||
char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
|
||||
char *buf = __buf + AST_FRIENDLY_OFFSET;
|
||||
|
||||
/* Set it into U-law mode (write) */
|
||||
if (ast_set_write_format(chan, AST_FORMAT_ULAW) < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set '%s' to write ulaw mode\n", chan->name);
|
||||
goto outrun;
|
||||
}
|
||||
|
||||
/* Set it into U-law mode (read) */
|
||||
if (ast_set_read_format(chan, AST_FORMAT_ULAW) < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to set '%s' to read ulaw mode\n", chan->name);
|
||||
goto outrun;
|
||||
}
|
||||
ast_indicate(chan, -1);
|
||||
retryzap = strcasecmp(chan->tech->type, "Zap");
|
||||
zapretry:
|
||||
origfd = chan->fds[0];
|
||||
if (retryzap) {
|
||||
fd = open("/dev/zap/pseudo", O_RDWR);
|
||||
if (fd < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
|
||||
goto outrun;
|
||||
}
|
||||
/* Make non-blocking */
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
if (flags < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
|
||||
ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
/* Setup buffering information */
|
||||
memset(&bi, 0, sizeof(bi));
|
||||
bi.bufsize = CONF_SIZE;
|
||||
bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
|
||||
bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
|
||||
bi.numbufs = 4;
|
||||
if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
|
||||
ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
nfds = 1;
|
||||
} else {
|
||||
/* XXX Make sure we're not running on a pseudo channel XXX */
|
||||
fd = chan->fds[0];
|
||||
nfds = 0;
|
||||
}
|
||||
memset(&ztc, 0, sizeof(ztc));
|
||||
/* Check to see if we're in a conference... */
|
||||
ztc.chan = 0;
|
||||
if (ioctl(fd, ZT_GETCONF, &ztc)) {
|
||||
ast_log(LOG_WARNING, "Error getting conference\n");
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
if (ztc.confmode) {
|
||||
/* Whoa, already in a conference... Retry... */
|
||||
if (!retryzap) {
|
||||
ast_debug(1, "Zap channel is in a conference already, retrying with pseudo\n");
|
||||
retryzap = 1;
|
||||
goto zapretry;
|
||||
}
|
||||
}
|
||||
memset(&ztc, 0, sizeof(ztc));
|
||||
/* Add us to the conference */
|
||||
ztc.chan = 0;
|
||||
ztc.confno = confno;
|
||||
ztc.confmode = ZT_CONF_MONITORBOTH;
|
||||
|
||||
if (ioctl(fd, ZT_SETCONF, &ztc)) {
|
||||
ast_log(LOG_WARNING, "Error setting conference\n");
|
||||
close(fd);
|
||||
goto outrun;
|
||||
}
|
||||
ast_debug(1, "Placed channel %s in ZAP channel %d monitor\n", chan->name, confno);
|
||||
|
||||
for(;;) {
|
||||
outfd = -1;
|
||||
ms = -1;
|
||||
c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
|
||||
if (c) {
|
||||
if (c->fds[0] != origfd) {
|
||||
if (retryzap) {
|
||||
/* Kill old pseudo */
|
||||
close(fd);
|
||||
}
|
||||
ast_debug(1, "Ooh, something swapped out under us, starting over\n");
|
||||
retryzap = 0;
|
||||
goto zapretry;
|
||||
}
|
||||
f = ast_read(c);
|
||||
if (!f)
|
||||
break;
|
||||
if(f->frametype == AST_FRAME_DTMF) {
|
||||
if(f->subclass == '#') {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
else if (f->subclass == '*') {
|
||||
ret = -1;
|
||||
break;
|
||||
|
||||
}
|
||||
else {
|
||||
input[ic++] = f->subclass;
|
||||
}
|
||||
if(ic == 3) {
|
||||
input[ic++] = '\0';
|
||||
ic=0;
|
||||
ret = atoi(input);
|
||||
ast_verb(3, "Zapscan: change channel to %d\n",ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fd != chan->fds[0]) {
|
||||
if (f->frametype == AST_FRAME_VOICE) {
|
||||
if (f->subclass == AST_FORMAT_ULAW) {
|
||||
/* Carefully write */
|
||||
careful_write(fd, f->data, f->datalen);
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Huh? Got a non-ulaw (%d) frame in the conference\n", f->subclass);
|
||||
}
|
||||
}
|
||||
ast_frfree(f);
|
||||
} else if (outfd > -1) {
|
||||
res = read(outfd, buf, CONF_SIZE);
|
||||
if (res > 0) {
|
||||
memset(&fr, 0, sizeof(fr));
|
||||
fr.frametype = AST_FRAME_VOICE;
|
||||
fr.subclass = AST_FORMAT_ULAW;
|
||||
fr.datalen = res;
|
||||
fr.samples = res;
|
||||
fr.data = buf;
|
||||
fr.offset = AST_FRIENDLY_OFFSET;
|
||||
if (ast_write(chan, &fr) < 0) {
|
||||
ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
|
||||
/* break; */
|
||||
}
|
||||
} else
|
||||
ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
if (f)
|
||||
ast_frfree(f);
|
||||
if (fd != chan->fds[0])
|
||||
close(fd);
|
||||
else {
|
||||
/* Take out of conference */
|
||||
/* Add us to the conference */
|
||||
ztc.chan = 0;
|
||||
ztc.confno = 0;
|
||||
ztc.confmode = 0;
|
||||
if (ioctl(fd, ZT_SETCONF, &ztc)) {
|
||||
ast_log(LOG_WARNING, "Error setting conference\n");
|
||||
}
|
||||
}
|
||||
|
||||
outrun:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int conf_exec(struct ast_channel *chan, void *data)
|
||||
{
|
||||
int res=-1;
|
||||
int confflags = 0;
|
||||
int confno = 0;
|
||||
char confstr[80] = "", *tmp = NULL;
|
||||
struct ast_channel *tempchan = NULL, *lastchan = NULL,*ichan = NULL;
|
||||
struct ast_frame *f;
|
||||
char *desired_group;
|
||||
int input=0,search_group=0;
|
||||
|
||||
if (chan->_state != AST_STATE_UP)
|
||||
ast_answer(chan);
|
||||
|
||||
desired_group = ast_strdupa(data);
|
||||
if(!ast_strlen_zero(desired_group)) {
|
||||
ast_verb(3, "Scanning for group %s\n", desired_group);
|
||||
search_group = 1;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (ast_waitfor(chan, 100) < 0)
|
||||
break;
|
||||
|
||||
f = ast_read(chan);
|
||||
if (!f)
|
||||
break;
|
||||
if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*')) {
|
||||
ast_frfree(f);
|
||||
break;
|
||||
}
|
||||
ast_frfree(f);
|
||||
ichan = NULL;
|
||||
if(input) {
|
||||
ichan = get_zap_channel_locked(input);
|
||||
input = 0;
|
||||
}
|
||||
|
||||
tempchan = ichan ? ichan : ast_channel_walk_locked(tempchan);
|
||||
|
||||
if ( !tempchan && !lastchan )
|
||||
break;
|
||||
|
||||
if (tempchan && search_group) {
|
||||
const char *mygroup;
|
||||
if((mygroup = pbx_builtin_getvar_helper(tempchan, "GROUP")) && (!strcmp(mygroup, desired_group))) {
|
||||
ast_verb(3, "Found Matching Channel %s in group %s\n", tempchan->name, desired_group);
|
||||
} else {
|
||||
ast_channel_unlock(tempchan);
|
||||
lastchan = tempchan;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (tempchan && (!strcmp(tempchan->tech->type, "Zap")) && (tempchan != chan) ) {
|
||||
ast_verb(3, "Zap channel %s is in-use, monitoring...\n", tempchan->name);
|
||||
ast_copy_string(confstr, tempchan->name, sizeof(confstr));
|
||||
ast_channel_unlock(tempchan);
|
||||
if ((tmp = strchr(confstr,'-'))) {
|
||||
*tmp = '\0';
|
||||
}
|
||||
confno = atoi(strchr(confstr,'/') + 1);
|
||||
ast_stopstream(chan);
|
||||
ast_say_number(chan, confno, AST_DIGIT_ANY, chan->language, (char *) NULL);
|
||||
res = conf_run(chan, confno, confflags);
|
||||
if (res<0) break;
|
||||
input = res;
|
||||
} else if (tempchan)
|
||||
ast_channel_unlock(tempchan);
|
||||
lastchan = tempchan;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
return ast_unregister_application(app);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ((ast_register_application(app, conf_exec, synopsis, descrip)) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Scan Zap channels application");
|
||||
|
||||
287
trunk/apps/enter.h
Normal file
287
trunk/apps/enter.h
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* U-law 8-bit audio data
|
||||
*
|
||||
* Source: enter.raw
|
||||
*
|
||||
* Copyright (C) 1999, Mark Spencer and Linux Support Services
|
||||
*
|
||||
* Distributed under the terms of the GNU General Public License
|
||||
*
|
||||
*/
|
||||
|
||||
static unsigned char enter[] = {
|
||||
0xba, 0xba, 0xb0, 0xa6, 0xa9, 0xb8, 0xfe, 0x46, 0x42, 0x46,
|
||||
0x4a, 0xfe, 0xac, 0xa2, 0x9f, 0x9f, 0xa8, 0xb8, 0x3b, 0x29,
|
||||
0x35, 0x4a, 0xfe, 0xc1, 0xad, 0xa2, 0xad, 0xc5, 0x4e, 0x68,
|
||||
0x68, 0xe7, 0xb8, 0xb0, 0xb2, 0xc1, 0xc1, 0xb0, 0xae, 0xcd,
|
||||
0xfe, 0xfe, 0xcd, 0xcd, 0xfe, 0x68, 0xd3, 0xb2, 0xae, 0xab,
|
||||
0xb2, 0xfe, 0x35, 0x31, 0xdb, 0xac, 0xab, 0xaf, 0xab, 0xaa,
|
||||
0xb4, 0x68, 0x3b, 0x39, 0x3f, 0x68, 0xb4, 0xa8, 0xa8, 0xb0,
|
||||
0xbc, 0xbc, 0xc5, 0x3f, 0x31, 0x37, 0xfe, 0xc1, 0xbc, 0xb0,
|
||||
0xa5, 0xa2, 0xa8, 0xaf, 0xbe, 0x3b, 0x28, 0x26, 0x3d, 0xbc,
|
||||
0xb0, 0xae, 0xa2, 0x9f, 0xa2, 0xfe, 0x29, 0x24, 0x29, 0x4a,
|
||||
0xc5, 0xaa, 0xa8, 0xa9, 0xa8, 0xa5, 0xa7, 0xdb, 0x2c, 0x27,
|
||||
0x2d, 0x4a, 0xfe, 0xdb, 0xb2, 0xa2, 0x9f, 0x9f, 0xae, 0xe7,
|
||||
0x2c, 0x22, 0x2b, 0xfe, 0xba, 0xb0, 0xaa, 0x9f, 0xa3, 0xb0,
|
||||
0x5c, 0x33, 0x33, 0x39, 0x5c, 0xdb, 0xc1, 0xb4, 0xb0, 0xaa,
|
||||
0xad, 0xba, 0x54, 0x46, 0xfe, 0xe7, 0xfe, 0x54, 0xe7, 0xaf,
|
||||
0xa6, 0xa7, 0xb0, 0xfe, 0x46, 0x39, 0x5c, 0xe7, 0xdb, 0xfe,
|
||||
0xba, 0xac, 0xa8, 0xc5, 0x46, 0x33, 0x54, 0xc5, 0xae, 0xad,
|
||||
0xb2, 0xc1, 0xcd, 0xc1, 0xbc, 0xfe, 0x3f, 0x37, 0xfe, 0xb4,
|
||||
0xb6, 0xcd, 0xdb, 0xc1, 0xb0, 0xb6, 0xcd, 0x4e, 0x39, 0x37,
|
||||
0xfe, 0xb0, 0xab, 0xa9, 0xa9, 0xa9, 0xb0, 0x5c, 0x29, 0x25,
|
||||
0x31, 0xfe, 0xc1, 0xb4, 0xae, 0xab, 0xab, 0xb2, 0xcd, 0x3b,
|
||||
0x2a, 0x2c, 0x54, 0xb4, 0xb4, 0xba, 0xb2, 0xa3, 0x9f, 0xa8,
|
||||
0xfe, 0x33, 0x27, 0x2a, 0x39, 0xfe, 0xc1, 0xbe, 0xb0, 0xa2,
|
||||
0x9f, 0xb0, 0x33, 0x22, 0x25, 0x46, 0xc1, 0xb8, 0xb0, 0xab,
|
||||
0xa8, 0xa8, 0xb0, 0xbe, 0x42, 0x2c, 0x2e, 0x4a, 0xfe, 0x5c,
|
||||
0xfe, 0xb4, 0xa8, 0xa8, 0xba, 0xfe, 0x4a, 0x39, 0x39, 0x46,
|
||||
0xfe, 0xbc, 0xaf, 0xa5, 0xa5, 0xae, 0x68, 0x37, 0x4a, 0xfe,
|
||||
0xfe, 0x4a, 0x4a, 0xd3, 0xb0, 0xb0, 0xc1, 0x5c, 0x46, 0x46,
|
||||
0xd3, 0xb6, 0xbe, 0x54, 0x54, 0xc9, 0xab, 0xae, 0xc5, 0x46,
|
||||
0x4a, 0xfe, 0xcd, 0xc9, 0xcd, 0xe7, 0xe7, 0xc9, 0xb4, 0xc5,
|
||||
0x4a, 0x2c, 0x37, 0xc1, 0xb0, 0xb2, 0xb4, 0xb2, 0xb6, 0xdb,
|
||||
0xfe, 0x4a, 0x46, 0x3f, 0x68, 0xba, 0xb2, 0xba, 0xc5, 0xb6,
|
||||
0xb2, 0xcd, 0x33, 0x2e, 0x39, 0x68, 0xfe, 0xe7, 0xba, 0xaf,
|
||||
0xa7, 0xa7, 0xad, 0xe7, 0x2d, 0x25, 0x2f, 0xd3, 0xbe, 0xcd,
|
||||
0xc5, 0xac, 0xa6, 0xac, 0xfe, 0x3b, 0x2c, 0x2d, 0x3d, 0xc1,
|
||||
0xb4, 0xbe, 0xcd, 0xaf, 0xa5, 0xa8, 0xe7, 0x31, 0x2f, 0x39,
|
||||
0x46, 0x5c, 0xdb, 0xbc, 0xba, 0xaf, 0xa9, 0xad, 0xfe, 0x2f,
|
||||
0x2d, 0xba, 0xad, 0xba, 0xfe, 0x3d, 0x42, 0x5c, 0xc9, 0xc1,
|
||||
0xcd, 0xfe, 0xc1, 0xae, 0xa6, 0xcd, 0x33, 0x25, 0x3b, 0xdb,
|
||||
0xb0, 0xb6, 0xb8, 0xb6, 0xb4, 0xb8, 0xba, 0xfe, 0x3d, 0x37,
|
||||
0xfe, 0xba, 0xc1, 0x54, 0x54, 0xd3, 0xb0, 0xb4, 0xe7, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xd3, 0xb6, 0xa9, 0xa7, 0xba,
|
||||
0x3d, 0x35, 0xfe, 0xc1, 0xcd, 0x4a, 0x54, 0xbe, 0xb2, 0xb8,
|
||||
0xfe, 0x46, 0x3b, 0xfe, 0xba, 0xab, 0xc5, 0x46, 0x3b, 0xbc,
|
||||
0xaa, 0xab, 0xd3, 0x68, 0xfe, 0xd3, 0xcd, 0xdb, 0x54, 0x3d,
|
||||
0x4a, 0xbc, 0xac, 0xb4, 0x3f, 0x2e, 0x3d, 0xba, 0xb0, 0xb8,
|
||||
0xba, 0xb6, 0xba, 0xcd, 0xfe, 0xfe, 0x5c, 0x54, 0xc9, 0xb4,
|
||||
0xbe, 0x54, 0x54, 0xcd, 0xb6, 0xc9, 0x46, 0x54, 0xcd, 0xc5,
|
||||
0xdb, 0xfe, 0xfe, 0xc1, 0xae, 0xa9, 0xac, 0xfe, 0x35, 0x2e,
|
||||
0xfe, 0xba, 0xc1, 0x5c, 0xfe, 0xb6, 0xaa, 0xb0, 0xe7, 0x35,
|
||||
0x2e, 0x39, 0xc1, 0xac, 0xb0, 0xfe, 0xfe, 0xbc, 0xa6, 0xac,
|
||||
0xc1, 0x42, 0x46, 0x54, 0xfe, 0xfe, 0xfe, 0xfe, 0xc9, 0xae,
|
||||
0xa9, 0xb0, 0x54, 0x35, 0x37, 0xfe, 0xd3, 0xd3, 0xb8, 0xae,
|
||||
0xab, 0xb6, 0xe7, 0xfe, 0xfe, 0x68, 0xfe, 0xfe, 0xfe, 0x4e,
|
||||
0xfe, 0xb0, 0xac, 0xb8, 0xfe, 0xfe, 0xc1, 0xb6, 0xc5, 0x46,
|
||||
0x3d, 0xe7, 0xb4, 0xa7, 0xab, 0xbc, 0x3f, 0x37, 0x54, 0xba,
|
||||
0xcd, 0x54, 0x42, 0xc5, 0xae, 0xac, 0xc9, 0x46, 0x3d, 0x54,
|
||||
0xba, 0xb0, 0xb0, 0xfe, 0x5c, 0xcd, 0xb0, 0xb0, 0xc9, 0x54,
|
||||
0x54, 0xfe, 0xfe, 0xfe, 0xfe, 0xe7, 0xcd, 0xc1, 0xba, 0xc5,
|
||||
0xfe, 0x42, 0x46, 0xfe, 0xc5, 0xba, 0xb2, 0xa7, 0xa7, 0xb0,
|
||||
0xfe, 0x3d, 0x4a, 0x5c, 0xfe, 0xfe, 0xfe, 0xe7, 0xbc, 0xb0,
|
||||
0xae, 0xc5, 0x4e, 0x39, 0xfe, 0xc5, 0xbe, 0xfe, 0x54, 0xc9,
|
||||
0xa9, 0xa2, 0xa5, 0xbc, 0x3b, 0x2f, 0x35, 0xfe, 0xc9, 0xfe,
|
||||
0xfe, 0xc5, 0xa9, 0xa6, 0xb0, 0x54, 0x31, 0x31, 0x3f, 0xd3,
|
||||
0xbc, 0xc1, 0xcd, 0xb8, 0xae, 0xa8, 0xb4, 0xd3, 0x54, 0x4e,
|
||||
0x5c, 0x54, 0xfe, 0xdb, 0xba, 0xb4, 0xb4, 0xba, 0xcd, 0x5c,
|
||||
0x3d, 0x3f, 0x54, 0xfe, 0xcd, 0xaf, 0xa8, 0xac, 0xc5, 0xfe,
|
||||
0xfe, 0xe7, 0xdb, 0xfe, 0xfe, 0xfe, 0xe7, 0xb8, 0xaf, 0xb0,
|
||||
0xe7, 0x42, 0x4a, 0xcd, 0xbc, 0xdb, 0x46, 0x68, 0xcd, 0xb0,
|
||||
0xab, 0xbc, 0xfe, 0x3d, 0x46, 0xfe, 0xb8, 0xbc, 0xd3, 0xd3,
|
||||
0xb6, 0xb0, 0xb6, 0x5c, 0x3b, 0x35, 0x54, 0xdb, 0xba, 0xb4,
|
||||
0xc1, 0xc9, 0xc1, 0xba, 0xc9, 0x5c, 0x3d, 0x46, 0xfe, 0xcd,
|
||||
0xc5, 0xb8, 0xae, 0xaf, 0xb4, 0xd3, 0x54, 0x3d, 0x35, 0x46,
|
||||
0xfe, 0xdb, 0xbc, 0xb2, 0xa9, 0xab, 0xba, 0x3f, 0x31, 0x39,
|
||||
0xfe, 0xe7, 0xdb, 0xcd, 0xb8, 0xae, 0xab, 0xac, 0xe7, 0x3d,
|
||||
0x2d, 0x3f, 0xfe, 0xdb, 0xfe, 0xfe, 0xbc, 0xaa, 0xa8, 0xb0,
|
||||
0xfe, 0x31, 0x2d, 0x3d, 0xdb, 0xc5, 0xcd, 0xc9, 0xb4, 0xa8,
|
||||
0xad, 0xc5, 0x46, 0x39, 0x3f, 0x5c, 0xfe, 0xd3, 0xc5, 0xc1,
|
||||
0xb6, 0xb0, 0xbc, 0x68, 0x46, 0x4e, 0xe7, 0xfe, 0x5c, 0xfe,
|
||||
0xc1, 0xaf, 0xb0, 0xb8, 0xe7, 0x5c, 0x5c, 0xfe, 0xe7, 0xfe,
|
||||
0xfe, 0xe7, 0xb0, 0xab, 0xb2, 0x4a, 0x37, 0x3f, 0xcd, 0xbe,
|
||||
0xc1, 0xe7, 0xe7, 0xd3, 0xb6, 0xb4, 0xc9, 0x3b, 0x33, 0x4a,
|
||||
0xba, 0xb4, 0xc5, 0xfe, 0xc9, 0xb6, 0xb4, 0xcd, 0xfe, 0x3b,
|
||||
0x3b, 0xfe, 0xc1, 0xb6, 0xc5, 0xc5, 0xb8, 0xb0, 0xba, 0x4a,
|
||||
0x31, 0x35, 0x68, 0xcd, 0xc5, 0xba, 0xb4, 0xb0, 0xb0, 0xba,
|
||||
0x5c, 0x35, 0x2f, 0x4e, 0xd3, 0xc1, 0xdb, 0xd3, 0xb4, 0xa9,
|
||||
0xab, 0xcd, 0x3b, 0x2f, 0x35, 0xfe, 0xd3, 0xd3, 0xdb, 0xbc,
|
||||
0xad, 0xa4, 0xb0, 0xfe, 0x2d, 0x2f, 0x3f, 0xe7, 0xe7, 0xe7,
|
||||
0xcd, 0xb4, 0xaf, 0xad, 0xc5, 0x3d, 0x31, 0x3d, 0xe7, 0xd3,
|
||||
0xe7, 0xe7, 0xc1, 0xaf, 0xad, 0xb6, 0xfe, 0x4a, 0x42, 0x54,
|
||||
0xfe, 0x68, 0xfe, 0xd3, 0xb2, 0xae, 0xb4, 0xfe, 0x42, 0x4e,
|
||||
0xcd, 0xc5, 0xcd, 0xdb, 0xc9, 0xb4, 0xb0, 0xb6, 0xfe, 0x3b,
|
||||
0x42, 0xe7, 0xb0, 0xb8, 0xcd, 0xfe, 0xc9, 0xb6, 0xb8, 0xfe,
|
||||
0x42, 0x3d, 0xfe, 0xc1, 0xb0, 0xba, 0xd3, 0xfe, 0xc1, 0xb0,
|
||||
0xb6, 0xfe, 0x3b, 0x3f, 0xe7, 0xba, 0xb8, 0xbc, 0xc5, 0xc1,
|
||||
0xc1, 0xcd, 0xfe, 0x3b, 0x37, 0xfe, 0xc1, 0xb4, 0xb6, 0xb8,
|
||||
0xb6, 0xb8, 0xc5, 0x5c, 0x3f, 0x46, 0xfe, 0xcd, 0xc5, 0xcd,
|
||||
0xcd, 0xc1, 0xb2, 0xb2, 0xfe, 0x3f, 0x35, 0x54, 0xdb, 0xc1,
|
||||
0xcd, 0xcd, 0xbc, 0xaf, 0xac, 0xb6, 0x54, 0x35, 0x31, 0x68,
|
||||
0xba, 0xb8, 0xcd, 0xdb, 0xc9, 0xb2, 0xb4, 0xc9, 0x46, 0x39,
|
||||
0x42, 0xdb, 0xbc, 0xbc, 0xcd, 0xcd, 0xbe, 0xb2, 0xb8, 0xe7,
|
||||
0x54, 0x46, 0xfe, 0xfe, 0xdb, 0xc9, 0xc5, 0xbe, 0xbe, 0xc9,
|
||||
0xfe, 0x5c, 0x5c, 0xfe, 0xd3, 0xcd, 0xcd, 0xc5, 0xb6, 0xb2,
|
||||
0xc5, 0x68, 0x4e, 0xfe, 0xc5, 0xc1, 0xcd, 0x68, 0x5c, 0xe7,
|
||||
0xb8, 0xb6, 0xd3, 0x4a, 0x46, 0xfe, 0xbc, 0xb8, 0xc1, 0xe7,
|
||||
0xe7, 0xc1, 0xb4, 0xbe, 0xfe, 0x3f, 0x3f, 0xfe, 0xba, 0xb2,
|
||||
0xba, 0xe7, 0xfe, 0xcd, 0xcd, 0xfe, 0x4e, 0x46, 0xfe, 0xc5,
|
||||
0xb8, 0xb2, 0xba, 0xc1, 0xcd, 0xd3, 0xe7, 0xfe, 0x5c, 0x5c,
|
||||
0xfe, 0xe7, 0xc5, 0xbe, 0xb6, 0xba, 0xc5, 0xfe, 0x3f, 0x3f,
|
||||
0x54, 0xfe, 0xd3, 0xc1, 0xbc, 0xb6, 0xb0, 0xb0, 0xd3, 0x54,
|
||||
0x39, 0x46, 0xfe, 0xc1, 0xcd, 0xe7, 0xe7, 0xc5, 0xb8, 0xb4,
|
||||
0xd3, 0x54, 0x37, 0x42, 0xdb, 0xbe, 0xc1, 0xd3, 0xcd, 0xb8,
|
||||
0xb0, 0xb0, 0xcd, 0x4a, 0x3b, 0x42, 0xe7, 0xc5, 0xbe, 0xcd,
|
||||
0xe7, 0xd3, 0xc5, 0xcd, 0xfe, 0x54, 0x54, 0x68, 0xe7, 0xc5,
|
||||
0xc1, 0xc1, 0xcd, 0xcd, 0xc9, 0xc9, 0xcd, 0xe7, 0xfe, 0xfe,
|
||||
0xfe, 0xe7, 0xc5, 0xbe, 0xc1, 0xfe, 0x5c, 0x5c, 0xfe, 0xcd,
|
||||
0xcd, 0xcd, 0xdb, 0xd3, 0xc1, 0xbc, 0xbe, 0xfe, 0x4e, 0x54,
|
||||
0xcd, 0xb6, 0xb8, 0xd3, 0x5c, 0x5c, 0xfe, 0xc5, 0xc9, 0xfe,
|
||||
0x46, 0x4a, 0xe7, 0xb4, 0xb6, 0xc5, 0xfe, 0xe7, 0xcd, 0xc9,
|
||||
0xdb, 0xfe, 0x4e, 0x68, 0xd3, 0xb6, 0xb2, 0xbc, 0xfe, 0x68,
|
||||
0xfe, 0xfe, 0x68, 0x54, 0x68, 0xe7, 0xc5, 0xbc, 0xb8, 0xbe,
|
||||
0xcd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xd3, 0xd3, 0xcd,
|
||||
0xc1, 0xb8, 0xbc, 0xdb, 0x4e, 0x42, 0x4a, 0xfe, 0xc9, 0xc1,
|
||||
0xcd, 0xd3, 0xcd, 0xba, 0xb8, 0xcd, 0x46, 0x3b, 0xfe, 0xc9,
|
||||
0xba, 0xcd, 0xe7, 0xfe, 0xd3, 0xc1, 0xba, 0xdb, 0x54, 0x3d,
|
||||
0x68, 0xd3, 0xbc, 0xcd, 0xfe, 0xfe, 0xc5, 0xbe, 0xc1, 0xe7,
|
||||
0x54, 0x4a, 0xfe, 0xc9, 0xc1, 0xcd, 0xfe, 0xfe, 0xd3, 0xd3,
|
||||
0xd3, 0xfe, 0xe7, 0xe7, 0xe7, 0xdb, 0xd3, 0xe7, 0xe7, 0xe7,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xcd, 0xc9, 0xdb, 0xfe, 0xfe, 0xdb,
|
||||
0xbe, 0xc9, 0xfe, 0x5c, 0xfe, 0xc9, 0xbc, 0xbe, 0xdb, 0x68,
|
||||
0x5c, 0xdb, 0xc5, 0xd3, 0x54, 0x46, 0xfe, 0xbc, 0xb2, 0xb8,
|
||||
0xdb, 0x68, 0x68, 0xe7, 0xcd, 0xdb, 0x5c, 0x54, 0xfe, 0xc1,
|
||||
0xb8, 0xc1, 0xe7, 0xfe, 0xfe, 0xe7, 0xe7, 0xfe, 0xfe, 0xfe,
|
||||
0xd3, 0xc5, 0xc1, 0xc5, 0xcd, 0xd3, 0xe7, 0xfe, 0x54, 0x4e,
|
||||
0xfe, 0xd3, 0xcd, 0xd3, 0xd3, 0xc5, 0xc1, 0xc1, 0xe7, 0x5c,
|
||||
0x4e, 0x5c, 0xd3, 0xc1, 0xcd, 0xfe, 0xfe, 0xcd, 0xba, 0xba,
|
||||
0xe7, 0x4a, 0x4a, 0x68, 0xcd, 0xc5, 0xcd, 0xfe, 0xfe, 0xcd,
|
||||
0xb8, 0xc1, 0xe7, 0x4e, 0x5c, 0xe7, 0xc1, 0xc9, 0xdb, 0xfe,
|
||||
0xe7, 0xc9, 0xc5, 0xd3, 0xfe, 0x68, 0xfe, 0xdb, 0xd3, 0xe7,
|
||||
0xfe, 0xfe, 0xcd, 0xc9, 0xcd, 0xd3, 0xd3, 0xd3, 0xcd, 0xe7,
|
||||
0xfe, 0xfe, 0xe7, 0xc5, 0xc5, 0xe7, 0x68, 0x68, 0xe7, 0xc1,
|
||||
0xc5, 0xfe, 0x5c, 0xfe, 0xd3, 0xc1, 0xd3, 0xfe, 0x68, 0xe7,
|
||||
0xc5, 0xb6, 0xc5, 0xe7, 0x68, 0xfe, 0xcd, 0xc5, 0xe7, 0xfe,
|
||||
0x54, 0xfe, 0xc9, 0xc5, 0xdb, 0xfe, 0xfe, 0xfe, 0xd3, 0xd3,
|
||||
0xfe, 0xfe, 0xfe, 0xcd, 0xc1, 0xc1, 0xc9, 0xd3, 0xd3, 0xe7,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xe7, 0xd3, 0xdb, 0xe7, 0xe7, 0xd3,
|
||||
0xcd, 0xd3, 0xfe, 0xfe, 0xfe, 0xcd, 0xc5, 0xd3, 0xe7, 0xe7,
|
||||
0xc9, 0xbc, 0xbe, 0xe7, 0x68, 0x4a, 0xfe, 0xdb, 0xcd, 0xfe,
|
||||
0xfe, 0xfe, 0xcd, 0xc1, 0xc9, 0xfe, 0x54, 0x5c, 0xe7, 0xc9,
|
||||
0xc5, 0xe7, 0xfe, 0xfe, 0xcd, 0xc5, 0xc5, 0xe7, 0xfe, 0xfe,
|
||||
0xfe, 0xe7, 0xe7, 0xfe, 0xfe, 0xdb, 0xd3, 0xd3, 0xdb, 0xe7,
|
||||
0xfe, 0xfe, 0xe7, 0xe7, 0xdb, 0xd3, 0xc9, 0xd3, 0xe7, 0xfe,
|
||||
0xfe, 0xd3, 0xd3, 0xdb, 0xfe, 0xfe, 0xfe, 0xd3, 0xcd, 0xcd,
|
||||
0xfe, 0xfe, 0xe7, 0xc9, 0xc5, 0xd3, 0xfe, 0xfe, 0xfe, 0xcd,
|
||||
0xc9, 0xd3, 0xfe, 0xfe, 0xfe, 0xdb, 0xc9, 0xcd, 0xe7, 0xfe,
|
||||
0xe7, 0xcd, 0xcd, 0xe7, 0xfe, 0xfe, 0xe7, 0xd3, 0xc5, 0xcd,
|
||||
0xe7, 0xfe, 0xfe, 0xfe, 0xdb, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xe7, 0xcd, 0xcd, 0xd3, 0xe7, 0xe7, 0xe7, 0xe7, 0xfe, 0xfe,
|
||||
0xe7, 0xe7, 0xdb, 0xc9, 0xc1, 0xc5, 0xfe, 0x5c, 0x68, 0xfe,
|
||||
0xd3, 0xdb, 0xe7, 0xe7, 0xe7, 0xd3, 0xc5, 0xcd, 0xe7, 0x68,
|
||||
0xfe, 0xe7, 0xcd, 0xd3, 0xe7, 0xfe, 0xe7, 0xcd, 0xc1, 0xc1,
|
||||
0xdb, 0xfe, 0x54, 0xfe, 0xe7, 0xcd, 0xe7, 0xfe, 0xe7, 0xd3,
|
||||
0xcd, 0xd3, 0xe7, 0xfe, 0xfe, 0xfe, 0xcd, 0xc5, 0xcd, 0xfe,
|
||||
0xfe, 0xe7, 0xcd, 0xd3, 0xdb, 0xe7, 0xfe, 0xfe, 0xfe, 0xe7,
|
||||
0xd3, 0xd3, 0xe7, 0xfe, 0xe7, 0xe7, 0xe7, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xdb, 0xc5, 0xc1, 0xd3, 0xfe, 0xfe, 0xfe, 0xd3, 0xc9,
|
||||
0xcd, 0xe7, 0xfe, 0xfe, 0xd3, 0xcd, 0xdb, 0xfe, 0x5c, 0xfe,
|
||||
0xcd, 0xc9, 0xd3, 0xfe, 0xfe, 0xfe, 0xd3, 0xc9, 0xcd, 0xfe,
|
||||
0x68, 0xfe, 0xd3, 0xc1, 0xc1, 0xdb, 0xfe, 0xfe, 0xe7, 0xe7,
|
||||
0xfe, 0xfe, 0x68, 0xfe, 0xe7, 0xc5, 0xc9, 0xdb, 0xfe, 0xfe,
|
||||
0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xdb, 0xc5, 0xc5,
|
||||
0xd3, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xe7, 0xe7, 0xfe, 0xfe,
|
||||
0xc9, 0xc1, 0xc5, 0xfe, 0x54, 0x5c, 0xfe, 0xcd, 0xc5, 0xcd,
|
||||
0xfe, 0xfe, 0xdb, 0xc5, 0xc9, 0xfe, 0x5c, 0x68, 0xfe, 0xcd,
|
||||
0xcd, 0xfe, 0xfe, 0xfe, 0xe7, 0xc5, 0xc1, 0xd3, 0xfe, 0xfe,
|
||||
0xdb, 0xc9, 0xc5, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xfe,
|
||||
0xfe, 0xfe, 0xe7, 0xcd, 0xcd, 0xdb, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xe7, 0xd3, 0xcd, 0xd3, 0xfe, 0xfe, 0xdb, 0xcd, 0xd3, 0xe7,
|
||||
0xfe, 0xfe, 0xfe, 0xdb, 0xcd, 0xd3, 0xe7, 0xfe, 0xd3, 0xc5,
|
||||
0xc9, 0xfe, 0x5c, 0x54, 0xfe, 0xcd, 0xc1, 0xcd, 0xe7, 0xfe,
|
||||
0xfe, 0xd3, 0xcd, 0xfe, 0x54, 0x5c, 0xe7, 0xc1, 0xc1, 0xd3,
|
||||
0xfe, 0xfe, 0xe7, 0xd3, 0xd3, 0xe7, 0xfe, 0xfe, 0xfe, 0xcd,
|
||||
0xc5, 0xcd, 0xd3, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xe7, 0xd3, 0xcd, 0xc9, 0xcd, 0xe7, 0xfe, 0xfe, 0xfe, 0xdb,
|
||||
0xc9, 0xcd, 0xe7, 0xfe, 0xe7, 0xc9, 0xc5, 0xdb, 0xfe, 0x5c,
|
||||
0xfe, 0xe7, 0xcd, 0xcd, 0xe7, 0xfe, 0xe7, 0xc5, 0xc1, 0xd3,
|
||||
0xfe, 0x5c, 0xfe, 0xcd, 0xc5, 0xcd, 0xe7, 0xfe, 0xfe, 0xe7,
|
||||
0xd3, 0xe7, 0xfe, 0xfe, 0xe7, 0xcd, 0xcd, 0xdb, 0xfe, 0xfe,
|
||||
0xfe, 0xe7, 0xe7, 0xe7, 0xe7, 0xfe, 0xe7, 0xdb, 0xcd, 0xd3,
|
||||
0xd3, 0xdb, 0xfe, 0xfe, 0xfe, 0xfe, 0xdb, 0xd3, 0xdb, 0xe7,
|
||||
0xe7, 0xdb, 0xd3, 0xe7, 0xfe, 0xfe, 0xfe, 0xe7, 0xc9, 0xc5,
|
||||
0xcd, 0xe7, 0xfe, 0xdb, 0xd3, 0xe7, 0xfe, 0x68, 0xfe, 0xe7,
|
||||
0xcd, 0xcd, 0xd3, 0xfe, 0xfe, 0xe7, 0xdb, 0xe7, 0xfe, 0x68,
|
||||
0xfe, 0xdb, 0xfe, 0x68, 0xbe, 0xb2, 0xae, 0xab, 0xb2, 0xfe,
|
||||
0x2f, 0x31, 0xdb, 0xac, 0xad, 0xaf, 0xab, 0xab, 0xb4, 0x68,
|
||||
0x37, 0x39, 0x3f, 0xe7, 0xb4, 0xa8, 0xaa, 0xb0, 0xbc, 0xbc,
|
||||
0xc5, 0x3f, 0x31, 0x3d, 0xfe, 0xc1, 0xb8, 0xb0, 0xa5, 0xa2,
|
||||
0xa8, 0xaf, 0xdb, 0x3b, 0x28, 0x2a, 0x3d, 0xbc, 0xb0, 0xaa,
|
||||
0xa2, 0x9f, 0xab, 0xfe, 0x29, 0x24, 0x29, 0x4a, 0xb4, 0xaa,
|
||||
0xa8, 0xa9, 0xa8, 0xa5, 0xac, 0xdb, 0x2c, 0x27, 0x35, 0x4a,
|
||||
0xfe, 0xcd, 0xb2, 0xa2, 0x9f, 0x9f, 0xae, 0x4e, 0x2c, 0x22,
|
||||
0x33, 0xfe, 0xba, 0xb0, 0xa6, 0x9f, 0xa3, 0xbc, 0x5c, 0x33,
|
||||
0x31, 0x39, 0x5c, 0xcd, 0xc1, 0xb4, 0xad, 0xaa, 0xad, 0xcd,
|
||||
0x54, 0x46, 0xfe, 0xe7, 0xfe, 0x54, 0xc5, 0xaf, 0xa6, 0xa9,
|
||||
0xb0, 0xfe, 0x3d, 0x39, 0x5c, 0xdb, 0xdb, 0xfe, 0xba, 0xac,
|
||||
0xa8, 0xc5, 0x39, 0x33, 0x54, 0xb8, 0xae, 0xad, 0xb8, 0xc1,
|
||||
0xcd, 0xbe, 0xbc, 0xfe, 0x39, 0x37, 0xfe, 0xb4, 0xba, 0xcd,
|
||||
0xdb, 0xb8, 0xb0, 0xb6, 0xfe, 0x4e, 0x39, 0x3d, 0xfe, 0xb0,
|
||||
0xaa, 0xa9, 0xa9, 0xaa, 0xb0, 0x5c, 0x29, 0x28, 0x31, 0xfe,
|
||||
0xba, 0xb4, 0xae, 0xab, 0xab, 0xb2, 0xfe, 0x3b, 0x2a, 0x2f,
|
||||
0x54, 0xb4, 0xb4, 0xba, 0xb2, 0xa3, 0x9f, 0xa8, 0xfe, 0x2c,
|
||||
0x27, 0x2a, 0x46, 0xfe, 0xc1, 0xbc, 0xb0, 0xa2, 0xa2, 0xb0,
|
||||
0x33, 0x22, 0x2b, 0x46, 0xc1, 0xb4, 0xb0, 0xab, 0xa8, 0xa8,
|
||||
0xb0, 0xdb, 0x42, 0x2c, 0x33, 0x4a, 0xfe, 0x5c, 0xdb, 0xb4,
|
||||
0xa8, 0xad, 0xba, 0xfe, 0x46, 0x39, 0x39, 0x4a, 0xfe, 0xbc,
|
||||
0xab, 0xa5, 0xa5, 0xb8, 0x68, 0x37, 0x4a, 0xe7, 0xfe, 0x4a,
|
||||
0x5c, 0xd3, 0xb0, 0xb2, 0xc1, 0x5c, 0x42, 0x46, 0xd3, 0xb4,
|
||||
0xbe, 0x54, 0x54, 0xb6, 0xab, 0xae, 0xe7, 0x46, 0x4a, 0xfe,
|
||||
0xcd, 0xc9, 0xd3, 0xe7, 0xe7, 0xbe, 0xb4, 0xc5, 0x37, 0x2c,
|
||||
0x37, 0xc1, 0xb0, 0xb2, 0xb4, 0xb2, 0xb6, 0xdb, 0x54, 0x4a,
|
||||
0x46, 0x42, 0x68, 0xba, 0xb2, 0xba, 0xc5, 0xb6, 0xb6, 0xcd,
|
||||
0x33, 0x2f, 0x39, 0x68, 0xfe, 0xe7, 0xba, 0xac, 0xa7, 0xa7,
|
||||
0xb2, 0xe7, 0x2d, 0x25, 0x2f, 0xd3, 0xbe, 0xd3, 0xc5, 0xac,
|
||||
0xa6, 0xac, 0xfe, 0x33, 0x2c, 0x2d, 0x54, 0xc1, 0xb4, 0xcd,
|
||||
0xcd, 0xaf, 0xa4, 0xa8, 0xe7, 0x31, 0x31, 0x39, 0x46, 0xfe,
|
||||
0xdb, 0xbc, 0xb6, 0xaf, 0xa9, 0xb2, 0xfe, 0x2f, 0xfe, 0xba,
|
||||
0xad, 0xba, 0x4e, 0x3d, 0x42, 0xfe, 0xc9, 0xc1, 0xe7, 0xfe,
|
||||
0xc1, 0xa9, 0xa6, 0xcd, 0x2a, 0x25, 0x3b, 0xbc, 0xb0, 0xb6,
|
||||
0xb8, 0xb4, 0xb4, 0xb8, 0xc1, 0xfe, 0x3d, 0x3d, 0xfe, 0xba,
|
||||
0xd3, 0x54, 0x54, 0xbe, 0xb0, 0xb4, 0xe7, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xc5, 0xb6, 0xa9, 0xaa, 0xba, 0x3d, 0x39,
|
||||
0xfe, 0xc1, 0xfe, 0x4a, 0x54, 0xbe, 0xb2, 0xb8, 0xfe, 0x3d,
|
||||
0x3b, 0xfe, 0xb0, 0xab, 0xc5, 0x39, 0x3b, 0xbc, 0xa7, 0xab,
|
||||
0xd3, 0x68, 0xfe, 0xd3, 0xcd, 0xfe, 0x54, 0x3d, 0xfe, 0xbc,
|
||||
0xac, 0xc9, 0x3f, 0x2e, 0xfe, 0xba, 0xb0, 0xba, 0xba, 0xb6,
|
||||
0xba, 0xd3, 0xfe, 0xfe, 0x5c, 0x54, 0xc9, 0xb4, 0xbe, 0x54,
|
||||
0x68, 0xcd, 0xb6, 0xfe, 0x46, 0x54, 0xcd, 0xc5, 0xdb, 0xfe,
|
||||
0xe7, 0xc1, 0xae, 0xa8, 0xac, 0xfe, 0x2e, 0x2e, 0xfe, 0xb6,
|
||||
0xc1, 0x5c, 0xe7, 0xb6, 0xaa, 0xb0, 0x54, 0x35, 0x2e, 0x4a,
|
||||
0xc1, 0xac, 0xbc, 0xfe, 0xfe, 0xaf, 0xa6, 0xac, 0xfe, 0x42,
|
||||
0x46, 0x5c, 0xfe, 0xfe, 0xfe, 0xe7, 0xc9, 0xae, 0xa9, 0xb0,
|
||||
0x54, 0x31, 0x37, 0xfe, 0xd3, 0xd3, 0xb8, 0xac, 0xab, 0xb6,
|
||||
0xe7, 0xfe, 0xfe, 0x68, 0xfe, 0xfe, 0xfe, 0x54, 0xfe, 0xb0,
|
||||
0xae, 0xb8, 0xfe, 0xe7, 0xc1, 0xb6, 0xe7, 0x46, 0x3d, 0xe7,
|
||||
0xae, 0xa7, 0xab, 0xdb, 0x3f, 0x37, 0xfe, 0xba, 0xcd, 0x3f,
|
||||
0x42, 0xc5, 0xab, 0xac, 0xc9, 0x46, 0x3d, 0x54, 0xba, 0xad,
|
||||
0xb0, 0xfe, 0x68, 0xcd, 0xb0, 0xb0, 0xc9, 0x54, 0x54, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xe7, 0xcd, 0xbe, 0xba, 0xc5, 0x68, 0x42,
|
||||
0x46, 0xe7, 0xc5, 0xba, 0xaf, 0xa7, 0xa7, 0xbc, 0xfe, 0x3d,
|
||||
0x4a, 0x68, 0xfe, 0xfe, 0xfe, 0xe7, 0xbc, 0xaf, 0xae, 0xc5,
|
||||
0x3d, 0x39, 0xfe, 0xbc, 0xbe, 0xfe, 0x68, 0xc9, 0xa9, 0xa2,
|
||||
0xaa, 0xbc, 0x3b, 0x2d, 0x35, 0xfe, 0xcd, 0xfe, 0xfe, 0xb4,
|
||||
0xa9, 0xa6, 0xbc, 0x54, 0x31, 0x31, 0x54, 0xd3, 0xbc, 0xc5,
|
||||
0xcd, 0xb8, 0xab, 0xa8, 0xb4, 0xfe, 0x54, 0x4e, 0x68, 0x54,
|
||||
0xfe, 0xc9, 0xba, 0xb4, 0xb4, 0xba, 0xcd, 0x5c, 0x3b, 0x3f,
|
||||
0x54, 0xfe, 0xcd, 0xaf, 0xa8, 0xac, 0xc5, 0x68, 0xfe, 0xe7,
|
||||
0xdb, 0xfe, 0xfe, 0xfe, 0xcd, 0xb8, 0xaf, 0xb6, 0xe7, 0x42,
|
||||
0x5c, 0xcd, 0xbc, 0xfe, 0x46, 0x68, 0xba, 0xb0, 0xab, 0xbc,
|
||||
0x54, 0x3d, 0x46, 0xc9, 0xb8, 0xbc, 0xdb, 0xd3, 0xb6, 0xb0,
|
||||
0xb6, 0x5c, 0x37, 0x35, 0x54, 0xc9, 0xba, 0xb4, 0xc1, 0xc9,
|
||||
0xc1, 0xba, 0xe7, 0x5c, 0x3d, 0x54, 0xfe, 0xcd, 0xc5, 0xb8,
|
||||
0xae, 0xaf, 0xb4, 0xd3, 0x54, 0x3b, 0x35, 0x46, 0xfe, 0xdb,
|
||||
0xbc, 0xaf, 0xa9, 0xab, 0xd3, 0x3f, 0x31, 0x3f, 0xfe, 0xe7,
|
||||
0xdb, 0xcd, 0xb8, 0xae, 0xaa, 0xac, 0xe7, 0x33, 0x2d, 0x3f,
|
||||
0xd3, 0xdb, 0xfe, 0xfe, 0xbc, 0xaa, 0xa9, 0xb0, 0xfe, 0x31,
|
||||
0x2f, 0x3d, 0xdb, 0xc5, 0xcd, 0xc9, 0xae, 0xa8, 0xad, 0xfe,
|
||||
0x46, 0x39, 0x46, 0x5c, 0xfe, 0xcd, 0xc5, 0xc1, 0xb6, 0xb0,
|
||||
0xbc, 0x68, 0x42, 0x4e, 0xe7, 0xfe, 0x5c, 0xfe, 0xb6, 0xaf,
|
||||
0xb0, 0xc5, 0xe7, 0x5c, 0x5c, 0xfe, 0xe7, 0xfe, 0x68, 0xe7,
|
||||
0xb0, 0xac, 0xb2, 0x4a, 0x35, 0x3f, 0xcd, 0xbc, 0xc1, 0xe7,
|
||||
0xe7, 0xd3, 0xb6, 0xb4, 0xfe, 0x3b, 0x33, 0xfe, 0xba, 0xb4,
|
||||
0xd3, 0xfe, 0xc9, 0xb4, 0xb4, 0xcd, 0x4a, 0x3b, 0x3b, 0xfe,
|
||||
0xb8, 0xb6, 0xc5, 0xc5, 0xb8, 0xb0, 0xcd, 0x4a, 0x31, 0x3b,
|
||||
0x68, 0xcd, 0xc1, 0xba, 0xb4, 0xb0, 0xb0, 0xba, 0x5c, 0x2f,
|
||||
0x2f, 0x4e, 0xc9, 0xc1, 0xdb, 0xc9, 0xb4 };
|
||||
207
trunk/apps/leave.h
Normal file
207
trunk/apps/leave.h
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* U-law 8-bit audio data
|
||||
*
|
||||
* Source: leave.raw
|
||||
*
|
||||
* Copyright (C) 1999, Mark Spencer and Linux Support Services
|
||||
*
|
||||
* Distributed under the terms of the GNU General Public License
|
||||
*
|
||||
*/
|
||||
|
||||
static unsigned char leave[] = {
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xc1, 0x3d,
|
||||
0x42, 0x46, 0x3f, 0x3f, 0x46, 0x3f, 0x4e, 0xba, 0xbe, 0xbe,
|
||||
0xbc, 0xba, 0xbe, 0xc5, 0xb6, 0x2e, 0x2c, 0x33, 0x2f, 0x2e,
|
||||
0x2f, 0x33, 0x2b, 0x54, 0xac, 0xb0, 0xb0, 0xad, 0xaf, 0xb0,
|
||||
0xae, 0xcd, 0x3b, 0x2f, 0x31, 0x2e, 0x2f, 0x31, 0x2e, 0x46,
|
||||
0xc5, 0xaf, 0xb0, 0xaf, 0xae, 0xaf, 0xaf, 0xb0, 0xfe, 0x2d,
|
||||
0x31, 0x31, 0x2e, 0x31, 0x2f, 0x31, 0xfe, 0xae, 0xaf, 0xaf,
|
||||
0xae, 0xb0, 0xae, 0xaf, 0xfe, 0xdb, 0x2e, 0x2e, 0x31, 0x31,
|
||||
0x2d, 0x2e, 0xdb, 0x68, 0xaf, 0xad, 0xb0, 0xb0, 0xae, 0xaf,
|
||||
0x5c, 0xe7, 0x39, 0x2d, 0x31, 0x31, 0x31, 0x2d, 0xfe, 0xfe,
|
||||
0x68, 0xad, 0xaf, 0xb0, 0xaf, 0xac, 0xbc, 0xfe, 0xd3, 0x2f,
|
||||
0x2e, 0x33, 0x31, 0x2d, 0x4e, 0xdb, 0xfe, 0xfe, 0xac, 0xaf,
|
||||
0xb0, 0xac, 0xb6, 0x68, 0xe7, 0xdb, 0x2e, 0x2f, 0x35, 0x2f,
|
||||
0x31, 0xe7, 0xe7, 0x68, 0xad, 0xac, 0xb0, 0xae, 0xac, 0xfe,
|
||||
0xfe, 0xdb, 0xfe, 0x2d, 0x33, 0x31, 0x2e, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xbc, 0xaf, 0xb0, 0xad, 0xfe, 0xfe, 0xfe, 0xe7, 0x5c,
|
||||
0x2e, 0x33, 0x2e, 0x35, 0xe7, 0xfe, 0xfe, 0xfe, 0xad, 0xb0,
|
||||
0xaf, 0xc1, 0xfe, 0xe7, 0xfe, 0xe7, 0x3d, 0x31, 0x2f, 0x37,
|
||||
0xe7, 0xfe, 0xfe, 0xe7, 0xfe, 0xaf, 0xad, 0xbe, 0xfe, 0xdb,
|
||||
0xfe, 0xfe, 0xdb, 0x35, 0x2d, 0x39, 0xdb, 0xfe, 0xfe, 0xdb,
|
||||
0xfe, 0xfe, 0xad, 0xaf, 0xfe, 0xfe, 0xe7, 0x68, 0xfe, 0xd3,
|
||||
0x2e, 0x2c, 0xdb, 0xdb, 0x2c, 0x35, 0xd3, 0x68, 0xaf, 0xad,
|
||||
0xb0, 0xb0, 0xad, 0xba, 0x68, 0xe7, 0xe7, 0x2e, 0x2f, 0x33,
|
||||
0x31, 0x2d, 0xdb, 0xd3, 0x5c, 0xae, 0xaa, 0xe7, 0x68, 0xaa,
|
||||
0xe7, 0xfe, 0xdb, 0xe7, 0xfe, 0xe7, 0xd3, 0x2d, 0xfe, 0xdb,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xc5, 0xfe, 0xe7, 0xe7,
|
||||
0xfe, 0xfe, 0xe7, 0xe7, 0x3b, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xc5, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xe7, 0xfe, 0x3b,
|
||||
0xdb, 0xfe, 0xfe, 0xfe, 0xe7, 0xfe, 0xfe, 0xb0, 0xfe, 0xfe,
|
||||
0xe7, 0xfe, 0xfe, 0xfe, 0xdb, 0x2e, 0x5c, 0xdb, 0xfe, 0xfe,
|
||||
0xe7, 0xe7, 0x68, 0xb0, 0xbe, 0x68, 0xe7, 0xe7, 0xfe, 0xfe,
|
||||
0xdb, 0x39, 0x2f, 0xdb, 0xfe, 0xfe, 0xe7, 0xe7, 0xfe, 0xbe,
|
||||
0xaf, 0xe7, 0x68, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0x33, 0x33,
|
||||
0xdb, 0xfe, 0xfe, 0xdb, 0xe7, 0xfe, 0xb0, 0xb0, 0xfe, 0xfe,
|
||||
0xe7, 0xfe, 0xfe, 0xfe, 0x35, 0x33, 0xe7, 0xe7, 0xfe, 0xe7,
|
||||
0xe7, 0xfe, 0xb0, 0xb2, 0xb0, 0xfe, 0xfe, 0xe7, 0xfe, 0xe7,
|
||||
0x46, 0x35, 0x35, 0x3f, 0xe7, 0xfe, 0xe7, 0xfe, 0xb2, 0xb0,
|
||||
0xb2, 0xb0, 0xfe, 0xfe, 0xfe, 0xfe, 0x42, 0x35, 0x37, 0x33,
|
||||
0xe7, 0xfe, 0xfe, 0xfe, 0xb8, 0xb0, 0xb6, 0xb0, 0xba, 0xfe,
|
||||
0xfe, 0xe7, 0xe7, 0x33, 0x39, 0x39, 0x33, 0xe7, 0xdb, 0xfe,
|
||||
0xe7, 0xb0, 0xb4, 0xb6, 0xb0, 0xcd, 0xfe, 0xe7, 0xe7, 0x33,
|
||||
0x39, 0x3b, 0x33, 0x46, 0xd3, 0xfe, 0xfe, 0xb0, 0xb2, 0xb6,
|
||||
0xb4, 0xb0, 0xfe, 0xfe, 0xdb, 0x35, 0x37, 0x39, 0x39, 0x35,
|
||||
0x37, 0xdb, 0x68, 0xcd, 0xb2, 0xb6, 0xb6, 0xb4, 0xb4, 0x68,
|
||||
0xe7, 0x42, 0x37, 0x3b, 0x3b, 0x39, 0x37, 0xdb, 0xfe, 0xcd,
|
||||
0xb2, 0xb6, 0xb6, 0xb6, 0xb2, 0xb4, 0xfe, 0x54, 0x37, 0x3b,
|
||||
0x39, 0x3b, 0x3b, 0x39, 0xe7, 0xfe, 0xb6, 0xb6, 0xb6, 0xb4,
|
||||
0xb6, 0xb6, 0xbc, 0xfe, 0x3f, 0x3b, 0x3b, 0x39, 0x3b, 0x3b,
|
||||
0x39, 0xe7, 0xb6, 0xb8, 0xb8, 0xb6, 0xb8, 0xb8, 0xb4, 0xfe,
|
||||
0x3b, 0x3d, 0x3d, 0x3b, 0x39, 0x3d, 0x3b, 0x39, 0xbe, 0xb8,
|
||||
0xba, 0xb8, 0xb6, 0xb8, 0xba, 0xb4, 0xfe, 0x39, 0x3f, 0x3d,
|
||||
0x3b, 0x3d, 0x3f, 0x39, 0xdb, 0xb4, 0xba, 0xb8, 0xb6, 0xb8,
|
||||
0xbc, 0xb4, 0xba, 0x39, 0x42, 0x3f, 0x3d, 0x3d, 0x3f, 0x3f,
|
||||
0x3b, 0xb8, 0xb6, 0xbc, 0xb8, 0xb8, 0xba, 0xbc, 0xb8, 0xe7,
|
||||
0x3d, 0x42, 0x3f, 0x3d, 0x3f, 0x42, 0x3d, 0xfe, 0xb8, 0xbc,
|
||||
0xbc, 0xba, 0xba, 0xbc, 0xba, 0xe7, 0x3d, 0x3f, 0x42, 0x3f,
|
||||
0x3f, 0x42, 0x42, 0xfe, 0xfe, 0xbc, 0xbc, 0xbe, 0xbc, 0xbe,
|
||||
0xbc, 0xc5, 0xe7, 0x68, 0x42, 0x46, 0x42, 0x46, 0x42, 0x46,
|
||||
0xfe, 0xfe, 0xbc, 0xbe, 0xbe, 0xbe, 0xbc, 0xc5, 0xfe, 0xdb,
|
||||
0x46, 0x46, 0x4a, 0x4a, 0x46, 0x46, 0xe7, 0xfe, 0xd3, 0xbe,
|
||||
0xc9, 0xc9, 0xc5, 0xc5, 0xe7, 0xdb, 0xd3, 0x4a, 0x4e, 0x54,
|
||||
0x4e, 0x4e, 0xfe, 0x5c, 0x54, 0xd3, 0xcd, 0xd3, 0xd3, 0xcd,
|
||||
0xd3, 0xd3, 0xcd, 0xfe, 0x5c, 0x68, 0x5c, 0x5c, 0x5c, 0x68,
|
||||
0x5c, 0x5c, 0xcd, 0xcd, 0xd3, 0xcd, 0xdb, 0xe7, 0xe7, 0xdb,
|
||||
0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xe7,
|
||||
0xfe, 0x5c, 0x5c, 0xfe, 0xfe, 0xfe, 0xfe, 0x46, 0x35, 0x35,
|
||||
0x37, 0x39, 0x3b, 0x39, 0x35, 0x33, 0x35, 0x5c, 0xd3, 0xcd,
|
||||
0xdb, 0xfe, 0xfe, 0xd3, 0xb0, 0xb0, 0xb0, 0xb4, 0xb4, 0xb6,
|
||||
0xb2, 0xb0, 0xb0, 0xb6, 0xcd, 0x5c, 0x68, 0xfe, 0xfe, 0xfe,
|
||||
0x3b, 0x33, 0x35, 0x37, 0x39, 0x3b, 0x39, 0x37, 0x35, 0x35,
|
||||
0x3f, 0xdb, 0xcd, 0xcd, 0xdb, 0xfe, 0xe7, 0xc5, 0xb0, 0xb0,
|
||||
0xb2, 0xb6, 0xb6, 0xb6, 0xb2, 0xb0, 0xb0, 0xcd, 0x5c, 0x5c,
|
||||
0xfe, 0xe7, 0xe7, 0xfe, 0x35, 0x35, 0x35, 0x39, 0x3b, 0x3b,
|
||||
0x39, 0x35, 0x33, 0x39, 0xdb, 0xcd, 0xd3, 0xe7, 0xfe, 0xfe,
|
||||
0xba, 0xb0, 0xb0, 0xb2, 0xb4, 0xb6, 0xb6, 0xb4, 0xb2, 0xb0,
|
||||
0xb4, 0xc9, 0x5c, 0x68, 0xfe, 0xfe, 0x5c, 0x3b, 0x35, 0x37,
|
||||
0x39, 0x3b, 0x3b, 0x3b, 0x39, 0x37, 0x37, 0x3d, 0xe7, 0xcd,
|
||||
0xdb, 0xfe, 0xe7, 0xbe, 0xb2, 0xb2, 0xb4, 0xb4, 0xb6, 0xb6,
|
||||
0xb6, 0xb4, 0xb0, 0xb0, 0xc5, 0x5c, 0x5c, 0xfe, 0xe7, 0xe7,
|
||||
0x4e, 0x35, 0x35, 0x37, 0x3b, 0x3b, 0x3b, 0x39, 0x37, 0x37,
|
||||
0x3b, 0xe7, 0xc9, 0xcd, 0xe7, 0xfe, 0xd3, 0xb4, 0xb2, 0xb2,
|
||||
0xb4, 0xb6, 0xb6, 0xb6, 0xb6, 0xb4, 0xb2, 0xb4, 0xc1, 0x68,
|
||||
0x68, 0xfe, 0xfe, 0x42, 0x39, 0x37, 0x39, 0x3b, 0x3b, 0x3b,
|
||||
0x3b, 0x3b, 0x39, 0x37, 0x3b, 0xfe, 0xd3, 0xdb, 0xfe, 0xcd,
|
||||
0xb4, 0xb2, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6, 0xb6, 0xb4, 0xb2,
|
||||
0xb2, 0xc1, 0x5c, 0x5c, 0xfe, 0xfe, 0xfe, 0x3d, 0x37, 0x37,
|
||||
0x39, 0x3b, 0x3b, 0x3b, 0x3b, 0x37, 0x37, 0x39, 0xfe, 0xcd,
|
||||
0xd3, 0xfe, 0xfe, 0xc1, 0xb2, 0xb2, 0xb4, 0xb6, 0xb6, 0xb6,
|
||||
0xb6, 0xb6, 0xb6, 0xb4, 0xb4, 0xbc, 0x68, 0xfe, 0xfe, 0xfe,
|
||||
0x3b, 0x39, 0x39, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x39,
|
||||
0x39, 0x3b, 0xfe, 0xdb, 0xe7, 0xfe, 0xbc, 0xb6, 0xb6, 0xb6,
|
||||
0xb8, 0xb6, 0xb6, 0xb6, 0xb8, 0xb6, 0xb4, 0xb4, 0xbc, 0xfe,
|
||||
0x68, 0xfe, 0xe7, 0x5c, 0x3b, 0x39, 0x39, 0x3b, 0x3b, 0x3b,
|
||||
0x3d, 0x3d, 0x3b, 0x39, 0x3b, 0x68, 0xdb, 0xdb, 0xfe, 0xe7,
|
||||
0xb8, 0xb6, 0xb6, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb6,
|
||||
0xb4, 0xb6, 0xdb, 0x68, 0xfe, 0xfe, 0x46, 0x3b, 0x3b, 0x3b,
|
||||
0x3d, 0x3d, 0x3b, 0x3d, 0x3d, 0x3d, 0x3d, 0x3b, 0x3b, 0x5c,
|
||||
0xdb, 0xdb, 0xc9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
|
||||
0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xbc, 0xcd, 0xfe, 0xfe, 0x3d,
|
||||
0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3b, 0x3d, 0x3d, 0x3d, 0x3d,
|
||||
0x3b, 0x3d, 0x46, 0xfe, 0xe7, 0xe7, 0xc5, 0xb8, 0xb8, 0xb8,
|
||||
0xba, 0xba, 0xb8, 0xb8, 0xba, 0xba, 0xb8, 0xb8, 0xb8, 0xcd,
|
||||
0xfe, 0xfe, 0x68, 0x3f, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d,
|
||||
0x3d, 0x3d, 0x3f, 0x3f, 0x3d, 0x3b, 0x4a, 0xfe, 0xdb, 0xbc,
|
||||
0xb8, 0xba, 0xba, 0xba, 0xba, 0xb8, 0xb8, 0xb8, 0xba, 0xba,
|
||||
0xba, 0xba, 0xba, 0xc5, 0xfe, 0x54, 0x3f, 0x3f, 0x3f, 0x3f,
|
||||
0x3f, 0x3f, 0x3d, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x42,
|
||||
0xfe, 0xe7, 0xdb, 0xbc, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
|
||||
0xba, 0xba, 0xbc, 0xba, 0xba, 0xba, 0xc5, 0xfe, 0xfe, 0x4e,
|
||||
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x42,
|
||||
0x42, 0x42, 0x3f, 0x46, 0xfe, 0xcd, 0xb8, 0xba, 0xbc, 0xbc,
|
||||
0xbc, 0xba, 0xba, 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xba, 0xb8,
|
||||
0xbe, 0xfe, 0x42, 0x3d, 0x3f, 0x42, 0x42, 0x42, 0x3f, 0x3f,
|
||||
0x3f, 0x42, 0x42, 0x42, 0x42, 0x3f, 0x3f, 0x68, 0xdb, 0xc5,
|
||||
0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xba, 0xba, 0xba, 0xbc, 0xbc,
|
||||
0xbc, 0xbc, 0xba, 0xc1, 0xfe, 0xfe, 0x3f, 0x42, 0x46, 0x46,
|
||||
0x46, 0x42, 0x42, 0x42, 0x42, 0x42, 0x46, 0x46, 0x42, 0x3f,
|
||||
0x42, 0x68, 0xbe, 0xba, 0xbc, 0xbe, 0xbe, 0xbe, 0xbc, 0xbc,
|
||||
0xbc, 0xbc, 0xbe, 0xc1, 0xbe, 0xbc, 0xba, 0xbe, 0x68, 0x3f,
|
||||
0x42, 0x46, 0x4a, 0x4a, 0x46, 0x42, 0x42, 0x42, 0x46, 0x46,
|
||||
0x46, 0x46, 0x42, 0x42, 0x68, 0xd3, 0xbc, 0xbc, 0xbe, 0xc1,
|
||||
0xc1, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xc1, 0xc1, 0xbe, 0xbe,
|
||||
0xc1, 0xfe, 0x4e, 0x42, 0x46, 0x4a, 0x4a, 0x4a, 0x46, 0x46,
|
||||
0x46, 0x46, 0x4a, 0x4a, 0x4a, 0x46, 0x46, 0x68, 0xdb, 0xbe,
|
||||
0xbe, 0xc1, 0xc5, 0xc1, 0xc1, 0xbe, 0xbe, 0xbe, 0xbe, 0xc1,
|
||||
0xc5, 0xc5, 0xbe, 0xbc, 0xc1, 0x4e, 0x46, 0x46, 0x4a, 0x4e,
|
||||
0x4e, 0x4a, 0x46, 0x46, 0x46, 0x4a, 0x4a, 0x4e, 0x4a, 0x46,
|
||||
0x46, 0xfe, 0xbe, 0xbe, 0xc1, 0xc9, 0xc5, 0xc5, 0xc1, 0xc1,
|
||||
0xc1, 0xc1, 0xc5, 0xc5, 0xc5, 0xc5, 0xbe, 0xc1, 0xfe, 0x4a,
|
||||
0x4a, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4a, 0x4a, 0x4a, 0x4e,
|
||||
0x54, 0x4e, 0x4a, 0x4a, 0x4e, 0xcd, 0xc1, 0xc5, 0xc5, 0xc9,
|
||||
0xc5, 0xc5, 0xc5, 0xc5, 0xc9, 0xcd, 0xcd, 0xcd, 0xcd, 0xc9,
|
||||
0xc9, 0xd3, 0x68, 0x54, 0x5c, 0x68, 0x68, 0x68, 0x5c, 0x5c,
|
||||
0x5c, 0x5c, 0x5c, 0x68, 0x68, 0x5c, 0x54, 0x5c, 0xdb, 0xcd,
|
||||
0xcd, 0xdb, 0xdb, 0xdb, 0xdb, 0xd3, 0xd3, 0xe7, 0xe7, 0xe7,
|
||||
0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xe7, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
|
||||
0xfe, 0xfe, 0xfe };
|
||||
BIN
trunk/apps/rpt_flow.pdf
Normal file
BIN
trunk/apps/rpt_flow.pdf
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user