Asterisk - The Open Source Telephony Project GIT-master-590b490
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 case AST_FRAME_CNG:
264 return 0;
265 default: {
266 char frame_type[32];
268 ast_log(LOG_WARNING, "Unsupported frame type %s (%d) for AudioSocket\n", frame_type, f->frametype);
269 return 0;
270 }
271 }
272
273 if (write(svc, buf, 3 + datalen) != 3 + datalen) {
274 ast_log(LOG_WARNING, "Failed to write data to AudioSocket because: %s\n", strerror(errno));
275 return -1;
276 }
277 }
278
279 return 0;
280}
281
286
288 int *const hangup)
289{
290 int i = 0, n = 0, ret = 0;
291 struct ast_frame f = {
293 .src = "AudioSocket",
294 .mallocd = AST_MALLOCD_DATA,
295 };
296 uint8_t header[3];
297 uint8_t *kind = &header[0];
298 uint16_t *length = (uint16_t *) &header[1];
299 uint8_t *data;
300
301 if (hangup) {
302 *hangup = 0;
303 }
304
305 while (i < 3) {
306 n = read(svc, header + i, 3 - i);
307 if (n == -1) {
308 if (errno == EAGAIN || errno == EWOULDBLOCK) {
309 int poll_result = ast_wait_for_input(svc, 5);
310
311 if (poll_result == 1) {
312 continue;
313 } else if (poll_result == 0) {
314 ast_debug(1, "Poll timed out while waiting for header data\n");
315 continue;
316 } else {
317 ast_log(LOG_WARNING, "Poll error: %s\n", strerror(errno));
318 }
319 }
320
321 ast_log(LOG_ERROR, "Failed to read header from AudioSocket because: %s\n", strerror(errno));
322 return NULL;
323 }
324 if (n == 0) {
325 break;
326 }
327 i += n;
328 }
329
330 if (n == 0 || *kind == AST_AUDIOSOCKET_KIND_HANGUP) {
331 /* Socket closure or requested hangup. */
332 if (hangup) {
333 *hangup = 1;
334 }
335 return NULL;
336 }
337
338 switch (*kind) {
341 break;
344 break;
347 break;
350 break;
353 break;
356 break;
359 break;
362 break;
365 break;
366 default:
367 ast_log(LOG_ERROR, "Received AudioSocket message other than hangup or audio, refer to protocol specification for valid message types\n");
368 return NULL;
369 }
370
371 /* Swap endianess of length if needed. */
372 *length = ntohs(*length);
373 if (*length < 1) {
374 ast_log(LOG_ERROR, "Invalid message length received from AudioSocket server. \n");
375 return NULL;
376 }
377
378 data = ast_malloc(*length);
379 if (!data) {
380 ast_log(LOG_ERROR, "Failed to allocate for data from AudioSocket\n");
381 return NULL;
382 }
383
384 ret = 0;
385 n = 0;
386 i = 0;
387 while (i < *length) {
388 n = read(svc, data + i, *length - i);
389 if (n == -1) {
390 if (errno == EAGAIN || errno == EWOULDBLOCK) {
391 int poll_result = ast_wait_for_input(svc, 5);
392
393 if (poll_result == 1) {
394 continue;
395 } else if (poll_result == 0) {
396 ast_log(LOG_WARNING, "Poll timed out while waiting for data\n");
397 } else {
398 ast_log(LOG_WARNING, "Poll error: %s\n", strerror(errno));
399 }
400 }
401
402 ast_log(LOG_ERROR, "Failed to read payload from AudioSocket: %s\n", strerror(errno));
403 ret = -1;
404 break;
405 }
406 if (n == 0) {
407 ast_log(LOG_ERROR, "Insufficient payload read from AudioSocket\n");
408 ret = -1;
409 break;
410 }
411 i += n;
412 }
413
414 if (ret != 0) {
415 ast_free(data);
416 return NULL;
417 }
418
419 f.data.ptr = data;
420 f.datalen = *length;
421 f.samples = *length / 2;
422
423 /* The frame steals data, so it doesn't need to be freed here */
424 return ast_frisolate(&f);
425}
426
427static int load_module(void)
428{
429 ast_verb(5, "Loading AudioSocket Support module\n");
431}
432
433static int unload_module(void)
434{
435 ast_verb(5, "Unloading AudioSocket Support module\n");
437}
438
440 .support_level = AST_MODULE_SUPPORT_EXTENDED,
441 .load = load_module,
442 .unload = unload_module,
443 .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...
frame_type
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.
char * ast_frame_type2str(enum ast_frame_type frame_type, char *ftype, size_t len)
Copy the discription of a frame type into the provided string.
Definition main/frame.c:671
#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::@235 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:1732
Universally unique identifier support.