Wed Jan 8 2020 09:49:40

Asterisk developer's documentation


app_record.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Matthew Fredrickson <creslin@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 Trivial application to record a sound file
22  *
23  * \author Matthew Fredrickson <creslin@digium.com>
24  *
25  * \ingroup applications
26  */
27 
28 /*** MODULEINFO
29  <support_level>core</support_level>
30  ***/
31 
32 #include "asterisk.h"
33 
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 375993 $")
35 
36 #include "asterisk/file.h"
37 #include "asterisk/pbx.h"
38 #include "asterisk/module.h"
39 #include "asterisk/app.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/dsp.h" /* use dsp routines for silence detection */
42 
43 /*** DOCUMENTATION
44  <application name="Record" language="en_US">
45  <synopsis>
46  Record to a file.
47  </synopsis>
48  <syntax>
49  <parameter name="filename" required="true" argsep=".">
50  <argument name="filename" required="true" />
51  <argument name="format" required="true">
52  <para>Is the format of the file type to be recorded (wav, gsm, etc).</para>
53  </argument>
54  </parameter>
55  <parameter name="silence">
56  <para>Is the number of seconds of silence to allow before returning.</para>
57  </parameter>
58  <parameter name="maxduration">
59  <para>Is the maximum recording duration in seconds. If missing
60  or 0 there is no maximum.</para>
61  </parameter>
62  <parameter name="options">
63  <optionlist>
64  <option name="a">
65  <para>Append to existing recording rather than replacing.</para>
66  </option>
67  <option name="n">
68  <para>Do not answer, but record anyway if line not yet answered.</para>
69  </option>
70  <option name="q">
71  <para>quiet (do not play a beep tone).</para>
72  </option>
73  <option name="s">
74  <para>skip recording if the line is not yet answered.</para>
75  </option>
76  <option name="t">
77  <para>use alternate '*' terminator key (DTMF) instead of default '#'</para>
78  </option>
79  <option name="x">
80  <para>Ignore all terminator keys (DTMF) and keep recording until hangup.</para>
81  </option>
82  <option name="k">
83  <para>Keep recorded file upon hangup.</para>
84  </option>
85  <option name="y">
86  <para>Terminate recording if *any* DTMF digit is received.</para>
87  </option>
88  </optionlist>
89  </parameter>
90  </syntax>
91  <description>
92  <para>If filename contains <literal>%d</literal>, these characters will be replaced with a number
93  incremented by one each time the file is recorded.
94  Use <astcli>core show file formats</astcli> to see the available formats on your system
95  User can press <literal>#</literal> to terminate the recording and continue to the next priority.
96  If the user hangs up during a recording, all data will be lost and the application will terminate.</para>
97  <variablelist>
98  <variable name="RECORDED_FILE">
99  <para>Will be set to the final filename of the recording.</para>
100  </variable>
101  <variable name="RECORD_STATUS">
102  <para>This is the final status of the command</para>
103  <value name="DTMF">A terminating DTMF was received ('#' or '*', depending upon option 't')</value>
104  <value name="SILENCE">The maximum silence occurred in the recording.</value>
105  <value name="SKIP">The line was not yet answered and the 's' option was specified.</value>
106  <value name="TIMEOUT">The maximum length was reached.</value>
107  <value name="HANGUP">The channel was hung up.</value>
108  <value name="ERROR">An unrecoverable error occurred, which resulted in a WARNING to the logs.</value>
109  </variable>
110  </variablelist>
111  </description>
112  </application>
113 
114  ***/
115 
116 static char *app = "Record";
117 
118 enum {
119  OPTION_APPEND = (1 << 0),
120  OPTION_NOANSWER = (1 << 1),
121  OPTION_QUIET = (1 << 2),
122  OPTION_SKIP = (1 << 3),
125  OPTION_KEEP = (1 << 6),
126  FLAG_HAS_PERCENT = (1 << 7),
128 };
129 
139 });
140 
141 static int record_exec(struct ast_channel *chan, const char *data)
142 {
143  int res = 0;
144  int count = 0;
145  char *ext = NULL, *opts[0];
146  char *parse, *dir, *file;
147  int i = 0;
148  char tmp[256];
149 
150  struct ast_filestream *s = NULL;
151  struct ast_frame *f = NULL;
152 
153  struct ast_dsp *sildet = NULL; /* silence detector dsp */
154  int totalsilence = 0;
155  int dspsilence = 0;
156  int silence = 0; /* amount of silence to allow */
157  int gotsilence = 0; /* did we timeout for silence? */
158  int maxduration = 0; /* max duration of recording in milliseconds */
159  int gottimeout = 0; /* did we timeout for maxduration exceeded? */
160  int terminator = '#';
161  int rfmt = 0;
162  int ioflags;
163  struct ast_silence_generator *silgen = NULL;
164  struct ast_flags flags = { 0, };
166  AST_APP_ARG(filename);
167  AST_APP_ARG(silence);
168  AST_APP_ARG(maxduration);
169  AST_APP_ARG(options);
170  );
171  int ms;
172  struct timeval start;
173 
174  /* The next few lines of code parse out the filename and header from the input string */
175  if (ast_strlen_zero(data)) { /* no data implies no filename or anything is present */
176  ast_log(LOG_WARNING, "Record requires an argument (filename)\n");
177  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
178  return -1;
179  }
180 
181  parse = ast_strdupa(data);
182  AST_STANDARD_APP_ARGS(args, parse);
183  if (args.argc == 4)
184  ast_app_parse_options(app_opts, &flags, opts, args.options);
185 
186  if (!ast_strlen_zero(args.filename)) {
187  if (strstr(args.filename, "%d"))
189  ext = strrchr(args.filename, '.'); /* to support filename with a . in the filename, not format */
190  if (!ext)
191  ext = strchr(args.filename, ':');
192  if (ext) {
193  *ext = '\0';
194  ext++;
195  }
196  }
197  if (!ext) {
198  ast_log(LOG_WARNING, "No extension specified to filename!\n");
199  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
200  return -1;
201  }
202  if (args.silence) {
203  if ((sscanf(args.silence, "%30d", &i) == 1) && (i > -1)) {
204  silence = i * 1000;
205  } else if (!ast_strlen_zero(args.silence)) {
206  ast_log(LOG_WARNING, "'%s' is not a valid silence duration\n", args.silence);
207  }
208  }
209 
210  if (args.maxduration) {
211  if ((sscanf(args.maxduration, "%30d", &i) == 1) && (i > -1))
212  /* Convert duration to milliseconds */
213  maxduration = i * 1000;
214  else if (!ast_strlen_zero(args.maxduration))
215  ast_log(LOG_WARNING, "'%s' is not a valid maximum duration\n", args.maxduration);
216  }
217 
219  terminator = '*';
221  terminator = '\0';
222 
223  /* done parsing */
224 
225  /* these are to allow the use of the %d in the config file for a wild card of sort to
226  create a new file with the inputed name scheme */
227  if (ast_test_flag(&flags, FLAG_HAS_PERCENT)) {
228  AST_DECLARE_APP_ARGS(fname,
229  AST_APP_ARG(piece)[100];
230  );
231  char *tmp2 = ast_strdupa(args.filename);
232  char countstring[15];
233  int idx;
234 
235  /* Separate each piece out by the format specifier */
236  AST_NONSTANDARD_APP_ARGS(fname, tmp2, '%');
237  do {
238  int tmplen;
239  /* First piece has no leading percent, so it's copied verbatim */
240  ast_copy_string(tmp, fname.piece[0], sizeof(tmp));
241  tmplen = strlen(tmp);
242  for (idx = 1; idx < fname.argc; idx++) {
243  if (fname.piece[idx][0] == 'd') {
244  /* Substitute the count */
245  snprintf(countstring, sizeof(countstring), "%d", count);
246  ast_copy_string(tmp + tmplen, countstring, sizeof(tmp) - tmplen);
247  tmplen += strlen(countstring);
248  } else if (tmplen + 2 < sizeof(tmp)) {
249  /* Unknown format specifier - just copy it verbatim */
250  tmp[tmplen++] = '%';
251  tmp[tmplen++] = fname.piece[idx][0];
252  }
253  /* Copy the remaining portion of the piece */
254  ast_copy_string(tmp + tmplen, &(fname.piece[idx][1]), sizeof(tmp) - tmplen);
255  }
256  count++;
257  } while (ast_fileexists(tmp, ext, chan->language) > 0);
258  pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
259  } else
260  ast_copy_string(tmp, args.filename, sizeof(tmp));
261  /* end of routine mentioned */
262 
263  if (chan->_state != AST_STATE_UP) {
264  if (ast_test_flag(&flags, OPTION_SKIP)) {
265  /* At the user's option, skip if the line is not up */
266  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "SKIP");
267  return 0;
268  } else if (!ast_test_flag(&flags, OPTION_NOANSWER)) {
269  /* Otherwise answer unless we're supposed to record while on-hook */
270  res = ast_answer(chan);
271  }
272  }
273 
274  if (res) {
275  ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name);
276  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
277  goto out;
278  }
279 
280  if (!ast_test_flag(&flags, OPTION_QUIET)) {
281  /* Some code to play a nice little beep to signify the start of the record operation */
282  res = ast_streamfile(chan, "beep", chan->language);
283  if (!res) {
284  res = ast_waitstream(chan, "");
285  } else {
286  ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", chan->name);
287  }
288  ast_stopstream(chan);
289  }
290 
291  /* The end of beep code. Now the recording starts */
292 
293  if (silence > 0) {
294  rfmt = chan->readformat;
296  if (res < 0) {
297  ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
298  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
299  return -1;
300  }
301  sildet = ast_dsp_new();
302  if (!sildet) {
303  ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
304  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
305  return -1;
306  }
308  }
309 
310  /* Create the directory if it does not exist. */
311  dir = ast_strdupa(tmp);
312  if ((file = strrchr(dir, '/')))
313  *file++ = '\0';
314  ast_mkdir (dir, 0777);
315 
316  ioflags = ast_test_flag(&flags, OPTION_APPEND) ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
317  s = ast_writefile(tmp, ext, NULL, ioflags, 0, AST_FILE_MODE);
318 
319  if (!s) {
320  ast_log(LOG_WARNING, "Could not create file %s\n", args.filename);
321  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
322  goto out;
323  }
324 
327 
328  /* Request a video update */
330 
331  if (maxduration <= 0)
332  maxduration = -1;
333 
334  start = ast_tvnow();
335  while ((ms = ast_remaining_ms(start, maxduration))) {
336  ms = ast_waitfor(chan, ms);
337  if (ms < 0) {
338  break;
339  }
340 
341  if (maxduration > 0 && ms == 0) {
342  break;
343  }
344 
345  f = ast_read(chan);
346  if (!f) {
347  res = -1;
348  break;
349  }
350  if (f->frametype == AST_FRAME_VOICE) {
351  res = ast_writestream(s, f);
352 
353  if (res) {
354  ast_log(LOG_WARNING, "Problem writing frame\n");
355  ast_frfree(f);
356  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
357  break;
358  }
359 
360  if (silence > 0) {
361  dspsilence = 0;
362  ast_dsp_silence(sildet, f, &dspsilence);
363  if (dspsilence) {
364  totalsilence = dspsilence;
365  } else {
366  totalsilence = 0;
367  }
368  if (totalsilence > silence) {
369  /* Ended happily with silence */
370  ast_frfree(f);
371  gotsilence = 1;
372  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "SILENCE");
373  break;
374  }
375  }
376  } else if (f->frametype == AST_FRAME_VIDEO) {
377  res = ast_writestream(s, f);
378 
379  if (res) {
380  ast_log(LOG_WARNING, "Problem writing frame\n");
381  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
382  ast_frfree(f);
383  break;
384  }
385  } else if ((f->frametype == AST_FRAME_DTMF) &&
386  ((f->subclass.integer == terminator) ||
387  (ast_test_flag(&flags, OPTION_ANY_TERMINATE)))) {
388  ast_frfree(f);
389  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "DTMF");
390  break;
391  }
392  ast_frfree(f);
393  }
394 
395  if (maxduration > 0 && !ms) {
396  gottimeout = 1;
397  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "TIMEOUT");
398  }
399 
400  if (!f) {
401  ast_debug(1, "Got hangup\n");
402  res = -1;
403  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "HANGUP");
404  if (!ast_test_flag(&flags, OPTION_KEEP)) {
405  ast_filedelete(args.filename, NULL);
406  }
407  }
408 
409  if (gotsilence) {
410  ast_stream_rewind(s, silence - 1000);
411  ast_truncstream(s);
412  } else if (!gottimeout) {
413  /* Strip off the last 1/4 second of it */
414  ast_stream_rewind(s, 250);
415  ast_truncstream(s);
416  }
417  ast_closestream(s);
418 
419  if (silgen)
421 
422 out:
423  if ((silence > 0) && rfmt) {
424  res = ast_set_read_format(chan, rfmt);
425  if (res) {
426  ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name);
427  }
428  }
429 
430  if (sildet) {
431  ast_dsp_free(sildet);
432  }
433  return res;
434 }
435 
436 static int unload_module(void)
437 {
438  return ast_unregister_application(app);
439 }
440 
441 static int load_module(void)
442 {
444 }
445 
446 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Trivial Record Application");
union ast_frame_subclass subclass
Definition: frame.h:146
Main Channel structure associated with a channel.
Definition: channel.h:742
#define AST_MODULE_INFO_STANDARD(keystr, desc)
Definition: module.h:396
int ast_streamfile(struct ast_channel *c, const char *filename, const char *preflang)
Streams a file.
Definition: file.c:946
Asterisk main include file. File version handling, generic pbx functions.
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
Definition: app.h:712
#define ast_test_flag(p, flag)
Definition: utils.h:63
int ast_indicate(struct ast_channel *chan, int condition)
Indicates condition of channel.
Definition: channel.c:4393
void ast_dsp_free(struct ast_dsp *dsp)
Definition: dsp.c:1650
Convenient Signal Processing routines.
#define ast_set_flag(p, flag)
Definition: utils.h:70
#define LOG_WARNING
Definition: logger.h:144
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: app.c:2101
struct ast_dsp * ast_dsp_new(void)
Definition: dsp.c:1607
#define AST_FRAME_DTMF
Definition: frame.h:128
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application&#39;s arguments.
Definition: app.h:572
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition: channel.c:4383
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:142
#define ast_opt_transmit_silence
Definition: options.h:120
int ast_filedelete(const char *filename, const char *fmt)
Deletes a file.
Definition: file.c:931
const char * ext
Definition: http.c:112
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx.c:7705
#define AST_FILE_MODE
Definition: asterisk.h:36
int totalsilence
Definition: dsp.c:393
int ast_set_read_format(struct ast_channel *chan, format_t format)
Sets read format on channel chan Set read format for channel to whichever component of &quot;format&quot; is be...
Definition: channel.c:5301
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:236
General Asterisk PBX channel definitions.
Definition: dsp.c:390
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:63
Core PBX routines and definitions.
void ast_dsp_set_threshold(struct ast_dsp *dsp, int threshold)
Set threshold value for silence.
Definition: dsp.c:1655
struct ast_silence_generator * ast_channel_start_silence_generator(struct ast_channel *chan)
Starts a silence generator on the given channel.
Definition: channel.c:8309
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: utils.h:663
static struct @350 args
int ast_remaining_ms(struct timeval start, int max_ms)
Calculate remaining milliseconds given a starting timestamp and upper bound.
Definition: utils.c:1615
int ast_stream_rewind(struct ast_filestream *fs, off_t ms)
Rewind stream ms.
Definition: file.c:899
enum ast_channel_state _state
Definition: channel.h:839
const ast_string_field name
Definition: channel.h:787
struct ast_filestream * ast_writefile(const char *filename, const char *type, const char *comment, int flags, int check, mode_t mode)
Starts writing a file.
Definition: file.c:1049
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
void ast_channel_stop_silence_generator(struct ast_channel *chan, struct ast_silence_generator *state)
Stops a previously-started silence generator on the given channel.
Definition: channel.c:8355
static void parse(struct mgcp_request *req)
Definition: chan_mgcp.c:1858
static int unload_module(void)
Definition: app_record.c:436
int ast_closestream(struct ast_filestream *f)
Closes a stream.
Definition: file.c:904
static struct ast_format f[]
Definition: format_g726.c:181
Structure used to handle boolean flags.
Definition: utils.h:200
int ast_truncstream(struct ast_filestream *fs)
Trunc stream at current location.
Definition: file.c:884
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name...
Definition: pbx.c:10546
int ast_dsp_silence(struct ast_dsp *dsp, struct ast_frame *f, int *totalsilence)
Return non-zero if this is silence. Updates &quot;totalsilence&quot; with the total number of seconds of silenc...
Definition: dsp.c:1355
static int load_module(void)
Definition: app_record.c:441
This structure is allocated by file.c in one chunk, together with buf_size and desc_size bytes of mem...
Definition: mod_format.h:100
#define AST_FORMAT_SLINEAR
Definition: frame.h:254
int ast_waitfor(struct ast_channel *chan, int ms)
Wait for input on a channel.
Definition: channel.c:3539
format_t readformat
Definition: channel.h:853
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:223
int ast_writestream(struct ast_filestream *fs, struct ast_frame *f)
Writes a frame to a stream.
Definition: file.c:150
int ast_waitstream(struct ast_channel *c, const char *breakon)
Waits for a stream to stop or digit to be pressed.
Definition: file.c:1343
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
Definition: file.c:919
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:3086
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
Data structure associated with a single frame of data.
Definition: frame.h:142
static int record_exec(struct ast_channel *chan, const char *data)
Definition: app_record.c:141
#define AST_APP_ARG(name)
Define an application argument.
Definition: app.h:555
enum ast_frame_type frametype
Definition: frame.h:144
#define ast_frfree(fr)
Definition: frame.h:583
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the &#39;standard&#39; argument separation process for an application.
Definition: app.h:604
static struct ast_app_option app_opts[128]
Definition: app_record.c:139
#define AST_NONSTANDARD_APP_ARGS(args, parse, sep)
Performs the &#39;nonstandard&#39; argument separation process for an application.
Definition: app.h:619
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:38
int ast_dsp_get_threshold_from_settings(enum threshold which)
Get silence threshold from dsp.conf.
Definition: dsp.c:1880
Asterisk module definitions.
static char * app
Definition: app_record.c:116
const ast_string_field language
Definition: channel.h:787
int ast_stopstream(struct ast_channel *c)
Stops a stream.
Definition: file.c:128
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:437
#define ASTERISK_FILE_VERSION(file, version)
Register/unregister a source code file with the core.
Definition: asterisk.h:180
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
Definition: app.h:721
int ast_mkdir(const char *path, int mode)
Recursively create directory path.
Definition: utils.c:2151