feat: AudioSocket channel, application, and ARI support.
[asterisk/asterisk.git] / apps / app_audiosocket.c
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/res_audiosocket.h"
42 #include "asterisk/utils.h"
43 #include "asterisk/format_cache.h"
44
45 #define AST_MODULE "app_audiosocket"
46 #define AUDIOSOCKET_CONFIG "audiosocket.conf"
47 #define MAX_CONNECT_TIMEOUT_MSEC 2000
48
49 /*** DOCUMENTATION
50         <application name="AudioSocket" language="en_US">
51                 <synopsis>
52                         Transmit and receive audio between channel and TCP socket
53                 </synopsis>
54                 <syntax>
55                         <parameter name="uuid" required="true">
56                                 <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>
57                         </parameter>
58                         <parameter name="service" required="true">
59                                 <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 </para>
60                         </parameter>
61                 </syntax>
62                 <description>
63                         <para>Connects to the given TCP service, then transmits channel audio over that socket.  In turn, audio is received from the socket and sent to the channel.  Only audio frames will be transmitted.</para>
64                         <para>Protocol is specified at https://wiki.asterisk.org/wiki/display/AST/AudioSocket</para>
65                         <para>This application does not automatically answer and should generally be preceeded by an application such as Answer() or Progress().</para>
66                 </description>
67         </application>
68  ***/
69
70 static const char app[] = "AudioSocket";
71
72 static int audiosocket_run(struct ast_channel *chan, const char *id, const int svc);
73
74 static int audiosocket_exec(struct ast_channel *chan, const char *data)
75 {
76         char *parse;
77         struct ast_format *readFormat, *writeFormat;
78         const char *chanName;
79         int res;
80
81         AST_DECLARE_APP_ARGS(args,
82                 AST_APP_ARG(idStr);
83                 AST_APP_ARG(server);
84         );
85
86         int s = 0;
87         uuid_t uu;
88
89
90         chanName = ast_channel_name(chan);
91
92         /* Parse and validate arguments */
93         parse = ast_strdupa(data);
94         AST_STANDARD_APP_ARGS(args, parse);
95         if (ast_strlen_zero(args.idStr)) {
96                 ast_log(LOG_ERROR, "UUID is required\n");
97                 return -1;
98         }
99         if (uuid_parse(args.idStr, uu)) {
100                 ast_log(LOG_ERROR, "Failed to parse UUID '%s'\n", args.idStr);
101                 return -1;
102         }
103         if ((s = ast_audiosocket_connect(args.server, chan)) < 0) {
104                 /* The res module will already output a log message, so another is not needed */
105                 return -1;
106         }
107
108         writeFormat = ao2_bump(ast_channel_writeformat(chan));
109         readFormat = ao2_bump(ast_channel_readformat(chan));
110
111         if (ast_set_write_format(chan, ast_format_slin)) {
112                 ast_log(LOG_ERROR, "Failed to set write format to SLINEAR for channel %s\n", chanName);
113                 ao2_ref(writeFormat, -1);
114                 ao2_ref(readFormat, -1);
115                 close(s);
116                 return -1;
117         }
118         if (ast_set_read_format(chan, ast_format_slin)) {
119                 ast_log(LOG_ERROR, "Failed to set read format to SLINEAR for channel %s\n", chanName);
120
121                 /* Attempt to restore previous write format even though it is likely to
122                  * fail, since setting the read format did.
123                  */
124                 if (ast_set_write_format(chan, writeFormat)) {
125                         ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", chanName);
126                 }
127                 ao2_ref(writeFormat, -1);
128                 ao2_ref(readFormat, -1);
129                 close(s);
130                 return -1;
131         }
132
133         res = audiosocket_run(chan, args.idStr, s);
134         /* On non-zero return, report failure */
135         if (res) {
136                 /* Restore previous formats and close the connection */
137                 if (ast_set_write_format(chan, writeFormat)) {
138                         ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", chanName);
139                 }
140                 if (ast_set_read_format(chan, readFormat)) {
141                         ast_log(LOG_ERROR, "Failed to restore read format for channel %s\n", chanName);
142                 }
143                 ao2_ref(writeFormat, -1);
144                 ao2_ref(readFormat, -1);
145                 close(s);
146                 return res;
147         }
148         close(s);
149
150         if (ast_set_write_format(chan, writeFormat)) {
151                 ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", chanName);
152         }
153         if (ast_set_read_format(chan, readFormat)) {
154                 ast_log(LOG_ERROR, "Failed to restore read format for channel %s\n", chanName);
155         }
156         ao2_ref(writeFormat, -1);
157         ao2_ref(readFormat, -1);
158
159         return 0;
160 }
161
162 static int audiosocket_run(struct ast_channel *chan, const char *id, int svc)
163 {
164         const char *chanName;
165         struct ast_channel *targetChan;
166         int ms = 0;
167         int outfd = -1;
168         struct ast_frame *f;
169
170         if (!chan || ast_channel_state(chan) != AST_STATE_UP) {
171                 ast_log(LOG_ERROR, "Channel is %s\n", chan ? "not answered" : "missing");
172                 return -1;
173         }
174
175         if (ast_audiosocket_init(svc, id)) {
176                 ast_log(LOG_ERROR, "Failed to intialize AudioSocket\n");
177                 return -1;
178         }
179
180         chanName = ast_channel_name(chan);
181
182         while (1) {
183
184                 targetChan = ast_waitfor_nandfds(&chan, 1, &svc, 1, NULL, &outfd, &ms);
185                 if (targetChan) {
186                         f = ast_read(chan);
187                         if (!f) {
188                                 return -1;
189                         }
190
191                         if (f->frametype == AST_FRAME_VOICE) {
192                                 /* Send audio frame to audiosocket */
193                                 if (ast_audiosocket_send_frame(svc, f)) {
194                                         ast_log(LOG_ERROR, "Failed to forward channel frame from %s to AudioSocket\n",
195                                                 chanName);
196                                         ast_frfree(f);
197                                         return -1;
198                                 }
199                         }
200                         ast_frfree(f);
201                 }
202
203                 if (outfd >= 0) {
204                         f = ast_audiosocket_receive_frame(svc);
205                         if (!f) {
206                                 ast_log(LOG_ERROR, "Failed to receive frame from AudioSocket message for"
207                                         "channel %s\n", chanName);
208                                 return -1;
209                         }
210                         if (ast_write(chan, f)) {
211                                 ast_log(LOG_WARNING, "Failed to forward frame to channel %s\n", chanName);
212                                 ast_frfree(f);
213                                 return -1;
214                         }
215                         ast_frfree(f);
216                 }
217         }
218         return 0;
219 }
220
221 static int unload_module(void)
222 {
223         return ast_unregister_application(app);
224 }
225
226 static int load_module(void)
227 {
228         return ast_register_application_xml(app, audiosocket_exec);
229 }
230
231 AST_MODULE_INFO(
232         ASTERISK_GPL_KEY,
233         AST_MODFLAG_LOAD_ORDER,
234         "AudioSocket Application",
235         .support_level = AST_MODULE_SUPPORT_EXTENDED,
236         .load = load_module,
237         .unload = unload_module,
238         .load_pri = AST_MODPRI_CHANNEL_DRIVER,
239         .requires = "res_audiosocket",
240 );