Asterisk - The Open Source Telephony Project GIT-master-27fb039
Loading...
Searching...
No Matches
res_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 support for Asterisk
22 *
23 * \author Seán C McCord <scm@cycoresys.com>
24 *
25 */
26
27/*** MODULEINFO
28 <support_level>extended</support_level>
29 ***/
30
31#include "asterisk.h"
32#include "errno.h"
33#include <uuid/uuid.h>
34#include <arpa/inet.h> /* For byte-order conversion. */
35
36#include "asterisk/file.h"
38#include "asterisk/channel.h"
39#include "asterisk/module.h"
40#include "asterisk/uuid.h"
42
43#define MODULE_DESCRIPTION "AudioSocket support functions for Asterisk"
44
45#define MAX_CONNECT_TIMEOUT_MSEC 2000
46
47/*!
48 * \internal
49 * \brief Attempt to complete the audiosocket connection.
50 *
51 * \param server Url that we are trying to connect to.
52 * \param addr Address that host was resolved to.
53 * \param netsockfd File descriptor of socket.
54 *
55 * \retval 0 when connection is succesful.
56 * \retval 1 when there is an error.
57 */
58static int handle_audiosocket_connection(const char *server,
59 const struct ast_sockaddr addr, const int netsockfd)
60{
61 struct pollfd pfds[1];
62 int res, conresult;
63 socklen_t reslen;
64
65 reslen = sizeof(conresult);
66
67 pfds[0].fd = netsockfd;
68 pfds[0].events = POLLOUT;
69
70 while ((res = ast_poll(pfds, 1, MAX_CONNECT_TIMEOUT_MSEC)) != 1) {
71 if (errno != EINTR) {
72 if (!res) {
73 ast_log(LOG_WARNING, "AudioSocket connection to '%s' timed"
74 "out after MAX_CONNECT_TIMEOUT_MSEC (%d) milliseconds.\n",
76 } else {
77 ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", server,
78 strerror(errno));
79 }
80
81 return -1;
82 }
83 }
84
85 if (getsockopt(pfds[0].fd, SOL_SOCKET, SO_ERROR, &conresult, &reslen) < 0) {
86 ast_log(LOG_WARNING, "Connection to '%s' failed with error: %s\n",
87 ast_sockaddr_stringify(&addr), strerror(errno));
88 return -1;
89 }
90
91 if (conresult) {
92 ast_log(LOG_WARNING, "Connecting to '%s' failed for url '%s': %s\n",
93 ast_sockaddr_stringify(&addr), server, strerror(conresult));
94 return -1;
95 }
96
97 return 0;
98}
99
100const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
101{
102 int s = -1;
103 struct ast_sockaddr *addrs = NULL;
104 int num_addrs = 0, i = 0;
105
106 if (chan && ast_autoservice_start(chan) < 0) {
107 ast_log(LOG_WARNING, "Failed to start autoservice for channel "
108 "'%s'\n", ast_channel_name(chan));
109 goto end;
110 }
111
112 if (ast_strlen_zero(server)) {
113 ast_log(LOG_ERROR, "No AudioSocket server provided\n");
114 goto end;
115 }
116
117 if (!(num_addrs = ast_sockaddr_resolve(&addrs, server, PARSE_PORT_REQUIRE,
118 AST_AF_UNSPEC))) {
119 ast_log(LOG_ERROR, "Failed to resolve AudioSocket service using '%s' - "
120 "requires a valid hostname and port\n", server);
121 goto end;
122 }
123
124 /* Connect to AudioSocket service */
125 for (i = 0; i < num_addrs; i++) {
126
127 if (!ast_sockaddr_port(&addrs[i])) {
128 /* If there's no port, other addresses should have the
129 * same problem. Stop here.
130 */
131 ast_log(LOG_ERROR, "No port provided for '%s'\n",
132 ast_sockaddr_stringify(&addrs[i]));
133 s = -1;
134 goto end;
135 }
136
137 if ((s = ast_socket_nonblock(addrs[i].ss.ss_family, SOCK_STREAM,
138 IPPROTO_TCP)) < 0) {
139 ast_log(LOG_WARNING, "Unable to create socket: '%s'\n", strerror(errno));
140 continue;
141 }
142
143 /*
144 * Disable Nagle's algorithm by setting the TCP_NODELAY socket option.
145 * This reduces latency by preventing delays caused by packet buffering.
146 */
147 if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &(int){1}, sizeof(int)) < 0) {
148 ast_log(LOG_ERROR, "Failed to set TCP_NODELAY on AudioSocket: %s\n", strerror(errno));
149 }
150
151 if (ast_connect(s, &addrs[i]) && errno == EINPROGRESS) {
152
153 if (handle_audiosocket_connection(server, addrs[i], s)) {
154 close(s);
155 continue;
156 }
157
158 } else {
159 ast_log(LOG_ERROR, "Connection to '%s' failed with unexpected error: %s\n",
160 ast_sockaddr_stringify(&addrs[i]), strerror(errno));
161 close(s);
162 s = -1;
163 }
164
165 break;
166 }
167
168end:
169 if (addrs) {
170 ast_free(addrs);
171 }
172
173 if (chan && ast_autoservice_stop(chan) < 0) {
174 ast_log(LOG_WARNING, "Failed to stop autoservice for channel '%s'\n",
175 ast_channel_name(chan));
176 close(s);
177 return -1;
178 }
179
180 if (i == num_addrs) {
181 ast_log(LOG_ERROR, "Failed to connect to AudioSocket service\n");
182 close(s);
183 return -1;
184 }
185
186 return s;
187}
188
189const int ast_audiosocket_init(const int svc, const char *id)
190{
191 uuid_t uu;
192 int ret = 0;
193 uint8_t buf[3 + 16];
194
195 if (ast_strlen_zero(id)) {
196 ast_log(LOG_ERROR, "No UUID for AudioSocket\n");
197 return -1;
198 }
199
200 if (uuid_parse(id, uu)) {
201 ast_log(LOG_ERROR, "Failed to parse UUID '%s'\n", id);
202 return -1;
203 }
204
206 buf[1] = 0x00;
207 buf[2] = 0x10;
208 memcpy(buf + 3, uu, 16);
209
210 if (write(svc, buf, 3 + 16) != 3 + 16) {
211 ast_log(LOG_WARNING, "Failed to write data to AudioSocket because: %s\n", strerror(errno));
212 ret = -1;
213 }
214
215 return ret;
216}
217
218const int ast_audiosocket_send_frame(const int svc, const struct ast_frame *f)
219{
220 int datalen = f->datalen;
221 if (f->frametype == AST_FRAME_DTMF) {
222 datalen = 1;
223 }
224
225 {
226 uint8_t buf[3 + datalen];
227 uint16_t *length = (uint16_t *) &buf[1];
228
229 /* Audio format is 16-bit, 8kHz signed linear mono for dialplan app,
230 depends on agreed upon audio codec for channel driver interface. */
231 switch (f->frametype) {
232 case AST_FRAME_VOICE:
251 } else {
253 }
254
255 *length = htons(datalen);
256 memcpy(&buf[3], f->data.ptr, datalen);
257 break;
258 case AST_FRAME_DTMF:
260 buf[3] = (uint8_t) f->subclass.integer;
261 *length = htons(1);
262 break;
263 default:
264 ast_log(LOG_ERROR, "Unsupported frame type %d for AudioSocket\n", f->frametype);
265 return -1;
266 }
267
268 if (write(svc, buf, 3 + datalen) != 3 + datalen) {
269 ast_log(LOG_WARNING, "Failed to write data to AudioSocket because: %s\n", strerror(errno));
270 return -1;
271 }
272 }
273
274 return 0;
275}
276
281
283 int *const hangup)
284{
285 int i = 0, n = 0, ret = 0;
286 struct ast_frame f = {
288 .src = "AudioSocket",
289 .mallocd = AST_MALLOCD_DATA,
290 };
291 uint8_t header[3];
292 uint8_t *kind = &header[0];
293 uint16_t *length = (uint16_t *) &header[1];
294 uint8_t *data;
295
296 if (hangup) {
297 *hangup = 0;
298 }
299
300 while (i < 3) {
301 n = read(svc, header, 3);
302 if (n == -1) {
303 if (errno == EAGAIN || errno == EWOULDBLOCK) {
304 int poll_result = ast_wait_for_input(svc, 5);
305
306 if (poll_result == 1) {
307 continue;
308 } else if (poll_result == 0) {
309 ast_debug(1, "Poll timed out while waiting for header data\n");
310 continue;
311 } else {
312 ast_log(LOG_WARNING, "Poll error: %s\n", strerror(errno));
313 }
314 }
315
316 ast_log(LOG_ERROR, "Failed to read header from AudioSocket because: %s\n", strerror(errno));
317 return NULL;
318 }
319 if (n == 0) {
320 break;
321 }
322 i += n;
323 }
324
325 if (n == 0 || *kind == AST_AUDIOSOCKET_KIND_HANGUP) {
326 /* Socket closure or requested hangup. */
327 if (hangup) {
328 *hangup = 1;
329 }
330 return NULL;
331 }
332
333 switch (*kind) {
336 break;
339 break;
342 break;
345 break;
348 break;
351 break;
354 break;
357 break;
360 break;
361 default:
362 ast_log(LOG_ERROR, "Received AudioSocket message other than hangup or audio, refer to protocol specification for valid message types\n");
363 return NULL;
364 }
365
366 /* Swap endianess of length if needed. */
367 *length = ntohs(*length);
368 if (*length < 1) {
369 ast_log(LOG_ERROR, "Invalid message length received from AudioSocket server. \n");
370 return NULL;
371 }
372
373 data = ast_malloc(*length);
374 if (!data) {
375 ast_log(LOG_ERROR, "Failed to allocate for data from AudioSocket\n");
376 return NULL;
377 }
378
379 ret = 0;
380 n = 0;
381 i = 0;
382 while (i < *length) {
383 n = read(svc, data + i, *length - i);
384 if (n == -1) {
385 if (errno == EAGAIN || errno == EWOULDBLOCK) {
386 int poll_result = ast_wait_for_input(svc, 5);
387
388 if (poll_result == 1) {
389 continue;
390 } else if (poll_result == 0) {
391 ast_log(LOG_WARNING, "Poll timed out while waiting for data\n");
392 } else {
393 ast_log(LOG_WARNING, "Poll error: %s\n", strerror(errno));
394 }
395 }
396
397 ast_log(LOG_ERROR, "Failed to read payload from AudioSocket: %s\n", strerror(errno));
398 ret = -1;
399 break;
400 }
401 if (n == 0) {
402 ast_log(LOG_ERROR, "Insufficient payload read from AudioSocket\n");
403 ret = -1;
404 break;
405 }
406 i += n;
407 }
408
409 if (ret != 0) {
410 ast_free(data);
411 return NULL;
412 }
413
414 f.data.ptr = data;
415 f.datalen = *length;
416 f.samples = *length / 2;
417
418 /* The frame steals data, so it doesn't need to be freed here */
419 return ast_frisolate(&f);
420}
421
422static int load_module(void)
423{
424 ast_verb(5, "Loading AudioSocket Support module\n");
426}
427
428static int unload_module(void)
429{
430 ast_verb(5, "Unloading AudioSocket Support module\n");
432}
433
435 .support_level = AST_MODULE_SUPPORT_EXTENDED,
436 .load = load_module,
437 .unload = unload_module,
438 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition astmm.h:180
#define ast_malloc(len)
A wrapper for malloc()
Definition astmm.h:191
#define ast_log
Definition astobj2.c:42
static int hangup(void *data)
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
char * end
Definition eagi_proxy.c:73
char buf[BUFSIZE]
Definition eagi_proxy.c:66
Generic File Format Support. Should be included by clients of the file handling routines....
enum ast_format_cmp_res ast_format_cmp(const struct ast_format *format1, const struct ast_format *format2)
Compare two formats.
Definition format.c:201
@ AST_FORMAT_CMP_EQUAL
Definition format.h:36
Media Format Cache API.
struct ast_format * ast_format_slin44
Built-in cached signed linear 44kHz format.
struct ast_format * ast_format_slin24
Built-in cached signed linear 24kHz format.
struct ast_format * ast_format_slin32
Built-in cached signed linear 32kHz format.
struct ast_format * ast_format_slin192
Built-in cached signed linear 192kHz format.
struct ast_format * ast_format_slin16
Built-in cached signed linear 16kHz format.
struct ast_format * ast_format_slin96
Built-in cached signed linear 96kHz format.
struct ast_format * ast_format_slin48
Built-in cached signed linear 48kHz format.
struct ast_format * ast_format_slin
Built-in cached signed linear 8kHz format.
struct ast_format * ast_format_slin12
Built-in cached signed linear 12kHz format.
#define AST_FRAME_DTMF
#define ast_frisolate(fr)
Makes a frame independent of any static storage.
#define AST_MALLOCD_DATA
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define ast_verb(level,...)
#define LOG_WARNING
int errno
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition module.h:331
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition module.h:330
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition module.h:557
@ AST_MODPRI_CHANNEL_DEPEND
Definition module.h:340
@ AST_MODULE_SUPPORT_EXTENDED
Definition module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition module.h:70
@ AST_AF_UNSPEC
Definition netsock2.h:54
int ast_connect(int sockfd, const struct ast_sockaddr *addr)
Wrapper around connect(2) that uses struct ast_sockaddr.
Definition netsock2.c:595
static char * ast_sockaddr_stringify(const struct ast_sockaddr *addr)
Wrapper around ast_sockaddr_stringify_fmt() with default format.
Definition netsock2.h:256
#define ast_sockaddr_port(addr)
Get the port number of a socket address.
Definition netsock2.h:517
int ast_sockaddr_resolve(struct ast_sockaddr **addrs, const char *str, int flags, int family)
Parses a string with an IPv4 or IPv6 address and place results into an array.
Definition netsock2.c:280
#define ast_poll(a, b, c)
Definition poll-compat.h:88
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(const int svc)
Receive an Asterisk frame from 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 MAX_CONNECT_TIMEOUT_MSEC
static int load_module(void)
static int unload_module(void)
static int handle_audiosocket_connection(const char *server, const struct ast_sockaddr addr, const int netsockfd)
AudioSocket support functions.
@ AST_AUDIOSOCKET_KIND_AUDIO_SLIN16
Messages contains audio data, format: slin16, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_AUDIO
Messages contains audio data, format: slin, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_AUDIO_SLIN48
Messages contains audio data, format: slin48, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_HANGUP
Message indicates the channel should be hung up, direction: Sent only.
@ AST_AUDIOSOCKET_KIND_AUDIO_SLIN96
Messages contains audio data, format: slin96, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_AUDIO_SLIN192
Messages contains audio data, format: slin192, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_UUID
Message contains the connection's UUID, direction: Received only.
@ AST_AUDIOSOCKET_KIND_AUDIO_SLIN24
Messages contains audio data, format: slin24, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_AUDIO_SLIN44
Messages contains audio data, format: slin44, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_DTMF
Message contains a DTMF digit, direction: Received only.
@ AST_AUDIOSOCKET_KIND_AUDIO_SLIN12
Messages contains audio data, format: slin12, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_AUDIO_SLIN32
Messages contains audio data, format: slin32, direction: Sent and received.
#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.
struct ast_format * format
Data structure associated with a single frame of data.
struct ast_frame_subclass subclass
enum ast_frame_type frametype
union ast_frame::@239 data
Socket address structure.
Definition netsock2.h:97
struct sockaddr_storage ss
Definition netsock2.h:98
#define ast_socket_nonblock(domain, type, protocol)
Create a non-blocking socket.
Definition utils.h:1113
int ast_wait_for_input(int fd, int ms)
Definition utils.c:1734
Universally unique identifier support.