app_queue: Cleanup queue_ref / queue_unref routines.
[asterisk/asterisk.git] / apps / app_stream_echo.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2017, Digium, Inc.
5  *
6  * Kevin Harwell <kharwell@digium.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 Stream echo application
22  *
23  * \author Kevin Harwell <kharwell@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 #include "asterisk/app.h"
33 #include "asterisk/conversions.h"
34 #include "asterisk/file.h"
35 #include "asterisk/module.h"
36 #include "asterisk/channel.h"
37 #include "asterisk/stream.h"
38
39 /*** DOCUMENTATION
40         <application name="StreamEcho" language="en_US">
41                 <synopsis>
42                         Echo media, up to 'N' streams of a type, and DTMF back to the calling party
43                 </synopsis>
44                 <syntax>
45                 <parameter name="num" required="false">
46                         <para>The number of streams of a type to echo back. If '0' is specified then
47                         all streams of a type are removed.</para>
48                 </parameter>
49                 <parameter name="type" required="false">
50                         <para>The media type of the stream(s) to add or remove (in the case of "num"
51                         being '0'). This can be set to either "audio" or "video" (default). If "num"
52                         is empty (i.e. not specified) then this parameter is ignored.</para>
53                 </parameter>
54                 </syntax>
55                 <description>
56                         <para>If a "num" (the number of streams) is not given then this simply echos
57                         back any media or DTMF frames (note, however if '#' is detected then the
58                         application exits) read from the calling channel back to itself. This means
59                         for any relevant frame read from a particular stream it is written back out
60                         to the associated write stream in a one to one fashion.
61                         </para>
62                         <para>However if a "num" is specified, and if the calling channel allows it
63                         (a new offer is made requesting the allowance of additional streams) then any
64                         any media received, like before, is echoed back onto each stream. However, in
65                         this case a relevant frame received on a stream of the given "type" is also
66                         echoed back out to the other streams of that same type. It should be noted that
67                         when operating in this mode only the first stream found of the given "type" is
68                         allowed from the original offer. And this first stream found is also the only
69                         stream of that "type" granted read (send/receive) capabilities in the new offer
70                         whereas the additional ones are set to receive only.</para>
71                         <note><para>This does not echo CONTROL, MODEM, or NULL frames.</para></note>
72                 </description>
73         </application>
74  ***/
75
76 static const char app[] = "StreamEcho";
77
78 static int stream_echo_write_error(struct ast_channel *chan, struct ast_frame *frame, int pos)
79 {
80         char frame_type[32];
81         const char *media_type;
82         struct ast_stream *stream;
83
84         ast_frame_type2str(frame->frametype, frame_type, sizeof(frame_type));
85
86         stream = pos < 0 ?
87                 ast_channel_get_default_stream(chan, ast_format_get_type(frame->subclass.format)) :
88                 ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), pos);
89
90         media_type = ast_codec_media_type2str(ast_stream_get_type(stream));
91
92         ast_log(LOG_ERROR, "%s - unable to write frame type '%s' to stream type '%s' at "
93                 "position '%d'\n", ast_channel_name(chan), frame_type, media_type,
94                 ast_stream_get_position(stream));
95
96         return -1;
97 }
98
99 static int stream_echo_write(struct ast_channel *chan, struct ast_frame *frame,
100         enum ast_media_type type, int one_to_one)
101 {
102         int i;
103         int num;
104         struct ast_stream_topology *topology;
105
106         /*
107          * Since this is an echo application if we get a frame in on a stream
108          * we simply want to echo it back out onto the same stream number.
109          */
110         num = ast_channel_is_multistream(chan) ? frame->stream_num : -1;
111         if (ast_write_stream(chan, num, frame)) {
112                 return stream_echo_write_error(chan, frame, num);
113         }
114
115         /*
116          * If the frame's type and given type don't match, or we are operating in
117          * a one to one stream echo mode then there is nothing left to do.
118          *
119          * Note, if the channel is not multi-stream capable then one_to_one will
120          * always be true, so it is safe to also not check for that here too.
121          */
122         if (one_to_one || !frame->subclass.format ||
123             ast_format_get_type(frame->subclass.format) != type) {
124                 return 0;
125         }
126
127         /*
128          * However, if we are operating in a single stream echoed to many stream
129          * mode, and the frame's type matches the given type then we also need to
130          * find the other streams of the same type and write out to those streams
131          * as well.
132          *
133          * If we are here, then it's accepted that whatever stream number the frame
134          * was read from for the given type is the only one set to send/receive,
135          * while the others of the same type are set to receive only. Since we
136          * shouldn't assume any order to the streams, we'll loop back through all
137          * streams in the channel's topology writing only to those of the same type.
138          * And, of course also not the stream which has already been written to.
139          */
140         topology = ast_channel_get_stream_topology(chan);
141
142         for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
143                 struct ast_stream *stream = ast_stream_topology_get_stream(topology, i);
144                 if (num != i && ast_stream_get_type(stream) == type) {
145                         if (ast_write_stream(chan, i, frame)) {
146                                 return stream_echo_write_error(chan, frame, i);
147                         }
148                 }
149         }
150
151         return 0;
152 }
153
154 static int stream_echo_perform(struct ast_channel *chan,
155         struct ast_stream_topology *topology, enum ast_media_type type)
156 {
157         int update_sent = 0;
158         int request_change = topology != NULL;
159         int one_to_one = 1;
160
161         while (ast_waitfor(chan, -1) > -1) {
162                 struct ast_frame *f;
163
164                 if (request_change) {
165                         /* Request a change to the new topology */
166                         if (ast_channel_request_stream_topology_change(chan, topology, NULL)) {
167                                 ast_log(LOG_WARNING, "Request stream topology change not supported "
168                                         "by channel '%s'\n", ast_channel_name(chan));
169                         }
170                         request_change = 0;
171                 }
172
173                 f = ast_read_stream(chan);
174                 if (!f) {
175                         return -1;
176                 }
177
178                 if ((f->frametype == AST_FRAME_DTMF) && (f->subclass.integer == '#')) {
179                         ast_frfree(f);
180                         break;
181                 }
182
183                 f->delivery.tv_sec = 0;
184                 f->delivery.tv_usec = 0;
185
186                 if (f->frametype == AST_FRAME_CONTROL) {
187                         if (f->subclass.integer == AST_CONTROL_VIDUPDATE && !update_sent) {
188                                 if (stream_echo_write(chan, f, type, one_to_one)) {
189                                         ast_frfree(f);
190                                         return -1;
191                                 }
192                                 update_sent = 1;
193                         } else if (f->subclass.integer == AST_CONTROL_SRCCHANGE) {
194                                 update_sent = 0;
195                         } else if (f->subclass.integer == AST_CONTROL_STREAM_TOPOLOGY_CHANGED) {
196                                 update_sent = 0;
197                                 one_to_one = 0; /* Switch writing to one to many */
198                         }
199                 } else if (f->frametype == AST_FRAME_VIDEO && !update_sent){
200                         struct ast_frame frame = {
201                                 .frametype = AST_FRAME_CONTROL,
202                                 .subclass.integer = AST_CONTROL_VIDUPDATE,
203                         };
204                         stream_echo_write(chan, &frame, type, one_to_one);
205                         update_sent = 1;
206                 }
207
208                 if (f->frametype != AST_FRAME_CONTROL &&
209                     f->frametype != AST_FRAME_MODEM &&
210                     f->frametype != AST_FRAME_NULL &&
211                     stream_echo_write(chan, f, type, one_to_one)) {
212                         ast_frfree(f);
213                         return -1;
214                 }
215
216                 ast_frfree(f);
217         }
218
219         return 0;
220 }
221
222 static struct ast_stream_topology *stream_echo_topology_alloc(
223         struct ast_stream_topology *original, unsigned int num, enum ast_media_type type)
224 {
225         int i, n = num;
226         struct ast_stream_topology *res = ast_stream_topology_alloc();
227
228         if (!res) {
229                 return NULL;
230         }
231
232         /*
233          * Clone every stream of a type not matching the given one. If the type
234          * matches clone only the first stream found for the given type. Then for
235          * that stream clone it again up to num - 1 times. Ignore any other streams
236          * of the same matched type in the original topology.
237          *
238          * So for instance if the original stream contains 1 audio stream and 2 video
239          * streams (video stream 'A' and video stream 'B'), num is '3', and the given
240          * type is 'video' then the resulting topology will contain a clone of the
241          * audio stream along with 3 clones of video stream 'A'. Video stream 'B' is
242          * not copied over.
243          */
244         for (i = 0; i < ast_stream_topology_get_count(original); ++i) {
245                 struct ast_stream *stream = ast_stream_topology_get_stream(original, i);
246
247                 if (!n && ast_stream_get_type(stream) == type) {
248                         /* Don't copy any[more] of the given type */
249                         continue;
250                 }
251
252                 if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
253                         /* Don't copy removed/declined streams */
254                         continue;
255                 }
256
257                 do {
258                         stream = ast_stream_clone(stream, NULL);
259
260                         if (!stream || ast_stream_topology_append_stream(res, stream) < 0) {
261                                 ast_stream_free(stream);
262                                 ast_stream_topology_free(res);
263                                 return NULL;
264                         }
265
266                         if (ast_stream_get_type(stream) != type) {
267                                 /* Do not multiply non matching streams */
268                                 break;
269                         }
270
271                         /*
272                          * Since num is not zero yet (i.e. this is first stream found to
273                          * match on the type) and the types match then loop num - 1 times
274                          * cloning the same stream.
275                          */
276                         ast_stream_set_state(stream, n == num ?
277                              AST_STREAM_STATE_SENDRECV : AST_STREAM_STATE_RECVONLY);
278                 } while (--n);
279         }
280
281         return res;
282 }
283
284 static int stream_echo_exec(struct ast_channel *chan, const char *data)
285 {
286         int res;
287         unsigned int num = 0;
288         enum ast_media_type type;
289         char *parse;
290         struct ast_stream_topology *topology;
291
292         AST_DECLARE_APP_ARGS(args,
293                 AST_APP_ARG(num);
294                 AST_APP_ARG(type);
295         );
296
297         parse = ast_strdupa(data);
298         AST_STANDARD_APP_ARGS(args, parse);
299
300         if (ast_strlen_zero(args.num)) {
301                 /*
302                  * If a number is not given then no topology is to be created
303                  * and renegotiated. The app will just echo back each stream
304                  * received to itself.
305                  */
306                 return stream_echo_perform(chan, NULL, AST_MEDIA_TYPE_UNKNOWN);
307         }
308
309         if (ast_str_to_uint(args.num, &num)) {
310                 ast_log(LOG_ERROR, "Failed to parse the first parameter '%s' into a"
311                         " greater than or equal to zero\n", args.num);
312                 return -1;
313         }
314
315         type = ast_strlen_zero(args.type) ? AST_MEDIA_TYPE_VIDEO :
316                 ast_media_type_from_str(args.type);
317
318         topology = stream_echo_topology_alloc(
319                 ast_channel_get_stream_topology(chan), num, type);
320         if (!topology) {
321                 ast_log(LOG_ERROR, "Unable to create '%u' streams of type '%s' to"
322                         " the topology\n", num, ast_codec_media_type2str(type));
323                 return -1;
324         }
325
326         res = stream_echo_perform(chan, topology, type);
327
328         if (ast_channel_get_stream_topology(chan) != topology) {
329                 ast_stream_topology_free(topology);
330         }
331
332         return res;
333 }
334
335 static int unload_module(void)
336 {
337         return ast_unregister_application(app);
338 }
339
340 static int load_module(void)
341 {
342         return ast_register_application_xml(app, stream_echo_exec);
343 }
344
345 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Stream Echo Application");