Asterisk - The Open Source Telephony Project  21.4.1
app_stream_echo.c
Go to the documentation of this file.
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 ?
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;
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 = {
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;
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 
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);
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 ?
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 
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");
Main Channel structure associated with a channel.
enum ast_media_type ast_format_get_type(const struct ast_format *format)
Get the media type of a format.
Definition: format.c:354
Asterisk main include file. File version handling, generic pbx functions.
int ast_channel_is_multistream(struct ast_channel *chan)
Determine if a channel is multi-stream capable.
struct ast_stream_topology * ast_channel_get_stream_topology(const struct ast_channel *chan)
Retrieve the topology of streams on a channel.
enum ast_media_type ast_stream_get_type(const struct ast_stream *stream)
Get the media type of a stream.
Definition: stream.c:316
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
const char * ast_codec_media_type2str(enum ast_media_type type)
Conversion function to take a media type and turn it into a string.
Definition: codec.c:348
enum ast_media_type ast_media_type_from_str(const char *media_type_str)
Conversion function to take a media string and convert it to a media type.
Definition: codec.c:364
Set when the stream has been removed/declined.
Definition: stream.h:78
struct ast_frame * ast_read_stream(struct ast_channel *chan)
Reads a frame, but does not filter to just the default streams.
Definition: channel.c:4262
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
int ast_channel_request_stream_topology_change(struct ast_channel *chan, struct ast_stream_topology *topology, void *change_source)
Request that the stream topology of a channel change.
Definition: channel.c:10966
struct ast_stream * ast_stream_topology_get_stream(const struct ast_stream_topology *topology, unsigned int position)
Get a specific stream from the topology.
Definition: stream.c:788
int ast_stream_topology_append_stream(struct ast_stream_topology *topology, struct ast_stream *stream)
Append a stream to the topology.
Definition: stream.c:748
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
int ast_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame *frame)
Write a frame to a stream This function writes the given frame to the indicated stream on the channel...
Definition: channel.c:5149
struct ast_frame_subclass subclass
Media Stream API.
General Asterisk PBX channel definitions.
enum ast_media_type type
The type of media the stream is handling.
Definition: stream.c:85
struct ast_stream * ast_channel_get_default_stream(struct ast_channel *chan, enum ast_media_type type)
Retrieve the default stream of a specific media type on a channel.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
Conversion utility functions.
Set when the stream is sending and receiving media.
Definition: stream.h:82
struct ast_stream_topology * ast_stream_topology_alloc(void)
Create a stream topology.
Definition: stream.c:650
Set when the stream is sending media only.
Definition: stream.h:86
void ast_stream_set_state(struct ast_stream *stream, enum ast_stream_state state)
Set the state of a stream.
Definition: stream.c:380
int ast_str_to_uint(const char *str, unsigned int *res)
Convert the given string to an unsigned integer.
Definition: conversions.c:56
int ast_stream_topology_get_count(const struct ast_stream_topology *topology)
Get the number of streams in a topology.
Definition: stream.c:765
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
struct timeval delivery
void ast_stream_free(struct ast_stream *stream)
Destroy a media stream representation.
Definition: stream.c:292
int ast_waitfor(struct ast_channel *chan, int ms)
Wait for input on a channel.
Definition: channel.c:3162
struct ast_stream * ast_stream_clone(const struct ast_stream *stream, const char *name)
Create a deep clone of an existing stream.
Definition: stream.c:257
Data structure associated with a single frame of data.
ast_media_type
Types of media.
Definition: codec.h:30
enum ast_frame_type frametype
struct ast_format * format
void ast_stream_topology_free(struct ast_stream_topology *topology)
Unreference and destroy a stream topology.
Definition: stream.c:743
int ast_stream_get_position(const struct ast_stream *stream)
Get the position of the stream in the topology.
Definition: stream.c:500
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
enum ast_stream_state ast_stream_get_state(const struct ast_stream *stream)
Get the current state of a stream.
Definition: stream.c:373
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
#define AST_APP_ARG(name)
Define an application argument.