Asterisk - The Open Source Telephony Project GIT-master-754dea3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
app_audiosocket.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2019, CyCore Systems, Inc
5 *
6 * Seán C McCord <scm@cycoresys.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19/*! \file
20 *
21 * \brief AudioSocket application -- transmit and receive audio through a TCP socket
22 *
23 * \author Seán C McCord <scm@cycoresys.com>
24 *
25 * \ingroup applications
26 */
27
28/*** MODULEINFO
29 <depend>res_audiosocket</depend>
30 <support_level>extended</support_level>
31 ***/
32
33#include "asterisk.h"
34#include "errno.h"
35#include <uuid/uuid.h>
36
37#include "asterisk/file.h"
38#include "asterisk/module.h"
39#include "asterisk/channel.h"
40#include "asterisk/app.h"
41#include "asterisk/pbx.h"
43#include "asterisk/utils.h"
45
46#define AST_MODULE "app_audiosocket"
47#define AUDIOSOCKET_CONFIG "audiosocket.conf"
48#define MAX_WAIT_TIMEOUT_MSEC 2000
49
50/*** DOCUMENTATION
51 <application name="AudioSocket" language="en_US">
52 <since>
53 <version>18.0.0</version>
54 </since>
55 <synopsis>
56 Transmit and receive PCM audio between a channel and a TCP socket server.
57 </synopsis>
58 <syntax>
59 <parameter name="uuid" required="true">
60 <para>UUID is the universally-unique identifier of the call for the audio socket service. This ID must conform to the string form of a standard UUID.</para>
61 </parameter>
62 <parameter name="service" required="true">
63 <para>Service is the name or IP address and port number of the audio socket service to which this call should be connected. This should be in the form host:port, such as myserver:9019. IPv6 addresses can be specified in square brackets, like [::1]:9019</para>
64 </parameter>
65 </syntax>
66 <description>
67 <para>Connects to the given TCP server, then transmits channel audio as 16-bit, 8KHz mono PCM over that socket (other codecs available via the channel driver interface). In turn, PCM audio is received from the socket and sent to the channel. Only audio frames and DTMF frames will be transmitted.</para>
68 <para>Protocol is specified at https://docs.asterisk.org/Configuration/Channel-Drivers/AudioSocket/</para>
69 <para>This application does not automatically answer and should generally be preceded by an application such as Answer() or Progress().</para>
70 </description>
71 </application>
72 ***/
73
74static const char app[] = "AudioSocket";
75
76static int audiosocket_run(struct ast_channel *chan, const char *id, const int svc, const char *server);
77
78static int audiosocket_exec(struct ast_channel *chan, const char *data)
79{
80 char *parse;
81 struct ast_format *readFormat, *writeFormat;
82 const char *chanName;
83 int res;
84
86 AST_APP_ARG(idStr);
87 AST_APP_ARG(server);
88 );
89
90 int s = 0;
91 uuid_t uu;
92
93
94 chanName = ast_channel_name(chan);
95
96 /* Parse and validate arguments */
97 parse = ast_strdupa(data);
99 if (ast_strlen_zero(args.idStr)) {
100 ast_log(LOG_ERROR, "UUID is required\n");
101 return -1;
102 }
103 if (uuid_parse(args.idStr, uu)) {
104 ast_log(LOG_ERROR, "Failed to parse UUID '%s'\n", args.idStr);
105 return -1;
106 }
107 if ((s = ast_audiosocket_connect(args.server, chan)) < 0) {
108 /* The res module will already output a log message, so another is not needed */
109 return -1;
110 }
111
112 /* Save current channel audio format and force to linear PCM. */
113
114 writeFormat = ao2_bump(ast_channel_writeformat(chan));
115 readFormat = ao2_bump(ast_channel_readformat(chan));
116
118 ast_log(LOG_ERROR, "Failed to set write format to SLINEAR for channel '%s'\n", chanName);
119 res = -1;
120 goto end;
121 }
122
124 ast_log(LOG_ERROR, "Failed to set read format to SLINEAR for channel '%s'\n", chanName);
125 res = -1;
126 goto end;
127 }
128
129 /* Only a requested hangup or socket closure from the remote end will
130 return a 0 value (normal exit). All other events that disrupt an
131 active connection are treated as errors for now (non-zero). */
132
133 res = audiosocket_run(chan, args.idStr, s, args.server);
134
135end:
136
137 /* Restore previous formats and close the connection */
138 if (ast_set_write_format(chan, writeFormat)) {
139 ast_log(LOG_ERROR, "Failed to restore write format for channel '%s'\n", chanName);
140 }
141 if (ast_set_read_format(chan, readFormat)) {
142 ast_log(LOG_ERROR, "Failed to restore read format for channel '%s'\n", chanName);
143 }
144 ao2_ref(writeFormat, -1);
145 ao2_ref(readFormat, -1);
146 close(s);
147
148 return res;
149}
150
151static int audiosocket_run(struct ast_channel *chan, const char *id, int svc, const char *server)
152{
153 const char *chanName;
154 struct ast_channel *targetChan;
155 int ms = MAX_WAIT_TIMEOUT_MSEC;
156 int outfd = -1;
157 struct ast_frame *f;
158 int hangup;
159
160 if (!chan || ast_channel_state(chan) != AST_STATE_UP) {
161 ast_log(LOG_ERROR, "Channel is %s\n", chan ? "not answered" : "missing");
162 return -1;
163 }
164
165 if (ast_audiosocket_init(svc, id)) {
166 ast_log(LOG_ERROR, "Failed to initialize AudioSocket\n");
167 return -1;
168 }
169
170 chanName = ast_channel_name(chan);
171
172 while (1) {
173 /* Timeout is hard-coded currently, could be made into an
174 argument if needed, but 2 seconds seems like a realistic
175 time range to give. */
176 targetChan = ast_waitfor_nandfds(&chan, 1, &svc, 1, NULL, &outfd, &ms);
178
179 if (targetChan) {
180 /* Receive frame from connected channel. */
181 f = ast_read(chan);
182 if (!f) {
183 ast_log(LOG_WARNING, "Failed to receive frame from channel '%s' connected to AudioSocket server '%s'", chanName, server);
184 return -1;
185 }
186
188 /* Send audio frame or DTMF frame to audiosocket */
189 if (ast_audiosocket_send_frame(svc, f)) {
190 ast_log(LOG_WARNING, "Failed to forward frame from channel '%s' to AudioSocket server '%s'\n",
191 chanName, server);
192 ast_frfree(f);
193 return -1;
194 }
195 }
196 ast_frfree(f);
197
198 } else if (outfd >= 0) {
199 /* Receive audio frame from audiosocket. */
201 if (hangup) {
202 /* Graceful termination, no frame to free. */
203 return 0;
204 }
205 if (!f) {
206 ast_log(LOG_WARNING, "Failed to receive frame from AudioSocket server '%s'"
207 " connected to channel '%s'\n", server, chanName);
208 return -1;
209 }
210 /* Send audio frame to connected channel. */
211 if (ast_write(chan, f)) {
212 ast_log(LOG_WARNING, "Failed to forward frame from AudioSocket server '%s' to channel '%s'\n", server, chanName);
213 ast_frfree(f);
214 return -1;
215 }
216 ast_frfree(f);
217
218 } else {
219 /* Neither the channel nor audio socket had activity
220 before timeout. Assume connection was lost. */
221 ast_log(LOG_ERROR, "Reached timeout after %d ms of no activity on AudioSocket connection between '%s' and '%s'\n", MAX_WAIT_TIMEOUT_MSEC, chanName, server);
222 return -1;
223 }
224 }
225 return 0;
226}
227
228static int unload_module(void)
229{
231}
232
233static int load_module(void)
234{
236}
237
241 "AudioSocket Application",
242 .support_level = AST_MODULE_SUPPORT_EXTENDED,
243 .load = load_module,
244 .unload = unload_module,
245 .load_pri = AST_MODPRI_CHANNEL_DRIVER,
246 .requires = "res_audiosocket",
static int audiosocket_run(struct ast_channel *chan, const char *id, const int svc, const char *server)
static const char app[]
static int audiosocket_exec(struct ast_channel *chan, const char *data)
#define MAX_WAIT_TIMEOUT_MSEC
static int load_module(void)
static int unload_module(void)
Asterisk main include file. File version handling, generic pbx functions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_log
Definition: astobj2.c:42
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition: astobj2.h:480
static int hangup(void *data)
Definition: chan_pjsip.c:2520
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
struct ast_channel * ast_waitfor_nandfds(struct ast_channel **c, int n, int *fds, int nfds, int *exception, int *outfd, int *ms)
Waits for activity on a group of channels.
Definition: channel.c:3016
int ast_write(struct ast_channel *chan, struct ast_frame *frame)
Write a frame to a channel This function writes the given frame to the indicated channel.
Definition: channel.c:5161
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition: channel.c:4274
int ast_set_read_format(struct ast_channel *chan, struct ast_format *format)
Sets read format on channel chan.
Definition: channel.c:5779
struct ast_format * ast_channel_writeformat(struct ast_channel *chan)
int ast_set_write_format(struct ast_channel *chan, struct ast_format *format)
Sets write format on channel chan.
Definition: channel.c:5820
struct ast_format * ast_channel_readformat(struct ast_channel *chan)
ast_channel_state
ast_channel states
Definition: channelstate.h:35
@ AST_STATE_UP
Definition: channelstate.h:42
char * end
Definition: eagi_proxy.c:73
Generic File Format Support. Should be included by clients of the file handling routines....
Media Format Cache API.
struct ast_format * ast_format_slin
Built-in cached signed linear 8kHz format.
Definition: format_cache.c:41
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define AST_FRAME_DTMF
#define ast_frfree(fr)
@ AST_FRAME_VOICE
#define LOG_ERROR
#define LOG_WARNING
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:331
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODPRI_CHANNEL_DRIVER
Definition: module.h:341
@ AST_MODULE_SUPPORT_EXTENDED
Definition: module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
Core PBX routines and definitions.
AudioSocket support functions.
const int ast_audiosocket_init(const int svc, const char *id)
Send the initial message to an AudioSocket server.
const int ast_audiosocket_send_frame(const int svc, const struct ast_frame *f)
Send an Asterisk audio frame to an AudioSocket server.
struct ast_frame * ast_audiosocket_receive_frame_with_hangup(const int svc, int *const hangup)
Receive an Asterisk frame from an AudioSocket server.
const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
Send the initial message to an AudioSocket server.
#define NULL
Definition: resample.c:96
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
Main Channel structure associated with a channel.
Definition of a media format.
Definition: format.c:43
Data structure associated with a single frame of data.
enum ast_frame_type frametype
const char * args
Utility functions.