Asterisk - The Open Source Telephony Project  18.5.0
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 <[email protected]>
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 <[email protected]>
24  *
25  * \ingroup applications
26  */
27 
28 /*** MODULEINFO
29  <support_level>core</support_level>
30  ***/
31 
32 #include "asterisk.h"
33 
34 #include "asterisk/file.h"
35 #include "asterisk/pbx.h"
36 #include "asterisk/module.h"
37 #include "asterisk/app.h"
38 #include "asterisk/channel.h"
39 #include "asterisk/dsp.h" /* use dsp routines for silence detection */
40 #include "asterisk/format_cache.h"
41 #include "asterisk/paths.h"
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="o">
71  <para>Exit when 0 is pressed, setting the variable <variable>RECORD_STATUS</variable>
72  to <literal>OPERATOR</literal> instead of <literal>DTMF</literal></para>
73  </option>
74  <option name="q">
75  <para>quiet (do not play a beep tone).</para>
76  </option>
77  <option name="s">
78  <para>skip recording if the line is not yet answered.</para>
79  </option>
80  <option name="t">
81  <para>use alternate '*' terminator key (DTMF) instead of default '#'</para>
82  </option>
83  <option name="u">
84  <para>Don't truncate recorded silence.</para>
85  </option>
86  <option name="x">
87  <para>Ignore all terminator keys (DTMF) and keep recording until hangup.</para>
88  </option>
89  <option name="k">
90  <para>Keep recorded file upon hangup.</para>
91  </option>
92  <option name="y">
93  <para>Terminate recording if *any* DTMF digit is received.</para>
94  </option>
95  </optionlist>
96  </parameter>
97  </syntax>
98  <description>
99  <para>If filename contains <literal>%d</literal>, these characters will be replaced with a number
100  incremented by one each time the file is recorded.
101  Use <astcli>core show file formats</astcli> to see the available formats on your system
102  User can press <literal>#</literal> to terminate the recording and continue to the next priority.
103  If the user hangs up during a recording, all data will be lost and the application will terminate.</para>
104  <variablelist>
105  <variable name="RECORDED_FILE">
106  <para>Will be set to the final filename of the recording, without an extension.</para>
107  </variable>
108  <variable name="RECORD_STATUS">
109  <para>This is the final status of the command</para>
110  <value name="DTMF">A terminating DTMF was received ('#' or '*', depending upon option 't')</value>
111  <value name="SILENCE">The maximum silence occurred in the recording.</value>
112  <value name="SKIP">The line was not yet answered and the 's' option was specified.</value>
113  <value name="TIMEOUT">The maximum length was reached.</value>
114  <value name="HANGUP">The channel was hung up.</value>
115  <value name="ERROR">An unrecoverable error occurred, which resulted in a WARNING to the logs.</value>
116  </variable>
117  </variablelist>
118  </description>
119  </application>
120 
121  ***/
122 
123 #define OPERATOR_KEY '0'
124 
125 static char *app = "Record";
126 
127 enum {
128  OPTION_APPEND = (1 << 0),
129  OPTION_NOANSWER = (1 << 1),
130  OPTION_QUIET = (1 << 2),
131  OPTION_SKIP = (1 << 3),
134  OPTION_KEEP = (1 << 6),
137  OPTION_NO_TRUNCATE = (1 << 9),
138 };
139 
144 };
145 
157 });
158 
159 /*!
160  * \internal
161  * \brief Used to determine what action to take when DTMF is received while recording
162  * \since 13.0.0
163  *
164  * \param chan channel being recorded
165  * \param flags option flags in use by the record application
166  * \param dtmf_integer the integer value of the DTMF key received
167  * \param terminator key currently set to be pressed for normal termination
168  *
169  * \returns One of enum dtmf_response
170  */
172  struct ast_flags *flags, int dtmf_integer, int terminator)
173 {
174  if ((dtmf_integer == OPERATOR_KEY) &&
176  return RESPONSE_OPERATOR;
177  }
178 
179  if ((dtmf_integer == terminator) ||
181  return RESPONSE_DTMF;
182  }
183 
184  return RESPONSE_NO_MATCH;
185 }
186 
187 static int create_destination_directory(const char *path)
188 {
189  int res;
190  char directory[PATH_MAX], *file_sep;
191 
192  if (!(file_sep = strrchr(path, '/'))) {
193  /* No directory to create */
194  return 0;
195  }
196 
197  /* Overwrite temporarily */
198  *file_sep = '\0';
199 
200  /* Absolute path? */
201  if (path[0] == '/') {
202  res = ast_mkdir(path, 0777);
203  *file_sep = '/';
204  return res;
205  }
206 
207  /* Relative path */
208  res = snprintf(directory, sizeof(directory), "%s/sounds/%s",
210 
211  *file_sep = '/';
212 
213  if (res >= sizeof(directory)) {
214  /* We truncated, so we fail */
215  return -1;
216  }
217 
218  return ast_mkdir(directory, 0777);
219 }
220 
221 static int record_exec(struct ast_channel *chan, const char *data)
222 {
223  int res = 0;
224  char *ext = NULL, *opts[0];
225  char *parse;
226  int i = 0;
227  char tmp[PATH_MAX];
228 
229  struct ast_filestream *s = NULL;
230  struct ast_frame *f = NULL;
231 
232  struct ast_dsp *sildet = NULL; /* silence detector dsp */
233  int totalsilence = 0;
234  int dspsilence = 0;
235  int silence = 0; /* amount of silence to allow */
236  int gotsilence = 0; /* did we timeout for silence? */
237  int truncate_silence = 1; /* truncate on complete silence recording */
238  int maxduration = 0; /* max duration of recording in milliseconds */
239  int gottimeout = 0; /* did we timeout for maxduration exceeded? */
240  int terminator = '#';
241  RAII_VAR(struct ast_format *, rfmt, NULL, ao2_cleanup);
242  int ioflags;
243  struct ast_silence_generator *silgen = NULL;
244  struct ast_flags flags = { 0, };
246  AST_APP_ARG(filename);
247  AST_APP_ARG(silence);
248  AST_APP_ARG(maxduration);
250  );
251  int ms;
252  struct timeval start;
253  const char *status_response = "ERROR";
254 
255  /* The next few lines of code parse out the filename and header from the input string */
256  if (ast_strlen_zero(data)) { /* no data implies no filename or anything is present */
257  ast_log(LOG_WARNING, "Record requires an argument (filename)\n");
258  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
259  return -1;
260  }
261 
262  parse = ast_strdupa(data);
263  AST_STANDARD_APP_ARGS(args, parse);
264  if (args.argc == 4)
265  ast_app_parse_options(app_opts, &flags, opts, args.options);
266 
267  if (!ast_strlen_zero(args.filename)) {
268  ext = strrchr(args.filename, '.'); /* to support filename with a . in the filename, not format */
269  if (!ext)
270  ext = strchr(args.filename, ':');
271  if (ext) {
272  *ext = '\0';
273  ext++;
274  }
275  }
276  if (!ext) {
277  ast_log(LOG_WARNING, "No extension specified to filename!\n");
278  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
279  return -1;
280  }
281  if (args.silence) {
282  if ((sscanf(args.silence, "%30d", &i) == 1) && (i > -1)) {
283  silence = i * 1000;
284  } else if (!ast_strlen_zero(args.silence)) {
285  ast_log(LOG_WARNING, "'%s' is not a valid silence duration\n", args.silence);
286  }
287  }
288 
289  if (ast_test_flag(&flags, OPTION_NO_TRUNCATE))
290  truncate_silence = 0;
291 
292  if (args.maxduration) {
293  if ((sscanf(args.maxduration, "%30d", &i) == 1) && (i > -1))
294  /* Convert duration to milliseconds */
295  maxduration = i * 1000;
296  else if (!ast_strlen_zero(args.maxduration))
297  ast_log(LOG_WARNING, "'%s' is not a valid maximum duration\n", args.maxduration);
298  }
299 
301  terminator = '*';
303  terminator = '\0';
304 
305  /*
306  If a '%d' is specified as part of the filename, we replace that token with
307  sequentially incrementing numbers until we find a unique filename.
308  */
309  if (strchr(args.filename, '%')) {
310  size_t src, dst, count = 0;
311  size_t src_len = strlen(args.filename);
312  size_t dst_len = sizeof(tmp) - 1;
313 
314  do {
315  for (src = 0, dst = 0; src < src_len && dst < dst_len; src++) {
316  if (!strncmp(&args.filename[src], "%d", 2)) {
317  int s = snprintf(&tmp[dst], PATH_MAX - dst, "%zu", count);
318  if (s >= PATH_MAX - dst) {
319  /* We truncated, so we need to bail */
320  ast_log(LOG_WARNING, "Failed to create unique filename from template: %s\n", args.filename);
321  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
322  return -1;
323  }
324  dst += s;
325  src++;
326  } else {
327  tmp[dst] = args.filename[src];
328  tmp[++dst] = '\0';
329  }
330  }
331  count++;
332  } while (ast_fileexists(tmp, ext, ast_channel_language(chan)) > 0);
333  } else
334  ast_copy_string(tmp, args.filename, sizeof(tmp));
335 
336  pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
337 
338  if (ast_channel_state(chan) != AST_STATE_UP) {
339  if (ast_test_flag(&flags, OPTION_SKIP)) {
340  /* At the user's option, skip if the line is not up */
341  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "SKIP");
342  return 0;
343  } else if (!ast_test_flag(&flags, OPTION_NOANSWER)) {
344  /* Otherwise answer unless we're supposed to record while on-hook */
345  res = ast_answer(chan);
346  }
347  }
348 
349  if (res) {
350  ast_log(LOG_WARNING, "Could not answer channel '%s'\n", ast_channel_name(chan));
351  status_response = "ERROR";
352  goto out;
353  }
354 
355  if (!ast_test_flag(&flags, OPTION_QUIET)) {
356  /* Some code to play a nice little beep to signify the start of the record operation */
357  res = ast_streamfile(chan, "beep", ast_channel_language(chan));
358  if (!res) {
359  res = ast_waitstream(chan, "");
360  } else {
361  ast_log(LOG_WARNING, "ast_streamfile(beep) failed on %s\n", ast_channel_name(chan));
362  res = 0;
363  }
364  ast_stopstream(chan);
365  }
366 
367  /* The end of beep code. Now the recording starts */
368 
369  if (silence > 0) {
370  rfmt = ao2_bump(ast_channel_readformat(chan));
372  if (res < 0) {
373  ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
374  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
375  return -1;
376  }
377  sildet = ast_dsp_new();
378  if (!sildet) {
379  ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
380  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
381  return -1;
382  }
384  }
385 
386  if (create_destination_directory(tmp)) {
387  ast_log(LOG_WARNING, "Could not create directory for file %s\n", args.filename);
388  status_response = "ERROR";
389  goto out;
390  }
391 
392  ioflags = ast_test_flag(&flags, OPTION_APPEND) ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
393  s = ast_writefile(tmp, ext, NULL, ioflags, 0, AST_FILE_MODE);
394 
395  if (!s) {
396  ast_log(LOG_WARNING, "Could not create file %s\n", args.filename);
397  status_response = "ERROR";
398  goto out;
399  }
400 
403 
404  /* Request a video update */
406 
407  if (maxduration <= 0)
408  maxduration = -1;
409 
410  start = ast_tvnow();
411  while ((ms = ast_remaining_ms(start, maxduration))) {
412  ms = ast_waitfor(chan, ms);
413  if (ms < 0) {
414  break;
415  }
416 
417  if (maxduration > 0 && ms == 0) {
418  break;
419  }
420 
421  f = ast_read(chan);
422  if (!f) {
423  res = -1;
424  break;
425  }
426  if (f->frametype == AST_FRAME_VOICE) {
427  res = ast_writestream(s, f);
428 
429  if (res) {
430  ast_log(LOG_WARNING, "Problem writing frame\n");
431  ast_frfree(f);
432  status_response = "ERROR";
433  break;
434  }
435 
436  if (silence > 0) {
437  dspsilence = 0;
438  ast_dsp_silence(sildet, f, &dspsilence);
439  if (dspsilence) {
440  totalsilence = dspsilence;
441  } else {
442  totalsilence = 0;
443  }
444  if (totalsilence > silence) {
445  /* Ended happily with silence */
446  ast_frfree(f);
447  gotsilence = 1;
448  status_response = "SILENCE";
449  break;
450  }
451  }
452  } else if (f->frametype == AST_FRAME_VIDEO) {
453  res = ast_writestream(s, f);
454 
455  if (res) {
456  ast_log(LOG_WARNING, "Problem writing frame\n");
457  status_response = "ERROR";
458  ast_frfree(f);
459  break;
460  }
461  } else if (f->frametype == AST_FRAME_DTMF) {
462  enum dtmf_response rc =
463  record_dtmf_response(chan, &flags, f->subclass.integer, terminator);
464  switch(rc) {
465  case RESPONSE_NO_MATCH:
466  break;
467  case RESPONSE_OPERATOR:
468  status_response = "OPERATOR";
469  ast_debug(1, "Got OPERATOR\n");
470  break;
471  case RESPONSE_DTMF:
472  status_response = "DTMF";
473  ast_debug(1, "Got DTMF\n");
474  break;
475  }
476  if (rc != RESPONSE_NO_MATCH) {
477  ast_frfree(f);
478  break;
479  }
480  }
481  ast_frfree(f);
482  }
483 
484  if (maxduration > 0 && !ms) {
485  gottimeout = 1;
486  status_response = "TIMEOUT";
487  }
488 
489  if (!f) {
490  ast_debug(1, "Got hangup\n");
491  res = -1;
492  status_response = "HANGUP";
493  if (!ast_test_flag(&flags, OPTION_KEEP)) {
494  ast_filedelete(args.filename, NULL);
495  }
496  }
497 
498  if (gotsilence && truncate_silence) {
499  ast_stream_rewind(s, silence - 1000);
500  ast_truncstream(s);
501  } else if (!gottimeout && f) {
502  /*
503  * Strip off the last 1/4 second of it, if we didn't end because of a timeout,
504  * or a hangup. This must mean we ended because of a DTMF tone and while this
505  * 1/4 second stripping is very old code the most likely explanation is that it
506  * relates to stripping a partial DTMF tone.
507  */
508  ast_stream_rewind(s, 250);
509  ast_truncstream(s);
510  }
511  ast_closestream(s);
512 
513  if (silgen)
515 
516 out:
517  if ((silence > 0) && rfmt) {
518  res = ast_set_read_format(chan, rfmt);
519  if (res) {
520  ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", ast_channel_name(chan));
521  }
522  }
523 
524  if (sildet) {
525  ast_dsp_free(sildet);
526  }
527 
528  pbx_builtin_setvar_helper(chan, "RECORD_STATUS", status_response);
529 
530  return res;
531 }
532 
533 static int unload_module(void)
534 {
536 }
537 
538 static int load_module(void)
539 {
541 }
542 
543 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Trivial Record Application");
Main Channel structure associated with a channel.
#define AST_MODULE_INFO_STANDARD(keystr, desc)
Definition: module.h:567
int ast_streamfile(struct ast_channel *c, const char *filename, const char *preflang)
Streams a file.
Definition: file.c:1250
Asterisk main include file. File version handling, generic pbx functions.
dtmf_response
Definition: app_record.c:140
#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:4322
void ast_dsp_free(struct ast_dsp *dsp)
Definition: dsp.c:1770
Convenient Signal Processing routines.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the &#39;standard&#39; argument separation process for an application.
#define LOG_WARNING
Definition: logger.h:274
struct ast_dsp * ast_dsp_new(void)
Allocates a new dsp, assumes 8khz for internal sample rate.
Definition: dsp.c:1745
static int tmp()
Definition: bt_open.c:389
unsigned int flags
Definition: utils.h:200
struct ast_frame * ast_read(struct ast_channel *chan)
Reads a frame.
Definition: channel.c:4302
ast_channel_state
ast_channel states
Definition: channelstate.h:35
Definition of a media format.
Definition: format.c:43
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:150
#define ast_opt_transmit_silence
Definition: options.h:124
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
const char * args
#define NULL
Definition: resample.c:96
int ast_filedelete(const char *filename, const char *fmt)
Deletes a file.
Definition: file.c:1098
#define AST_FRAME_DTMF
const char * ext
Definition: http.c:147
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
#define AST_FILE_MODE
Definition: asterisk.h:32
struct ast_frame_subclass subclass
int totalsilence
Definition: dsp.c:409
#define ast_strlen_zero(foo)
Definition: strings.h:52
struct ast_format * ast_channel_readformat(struct ast_channel *chan)
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
#define ao2_bump(obj)
Definition: astobj2.h:491
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:452
#define ast_log
Definition: astobj2.c:42
#define OPERATOR_KEY
Definition: app_record.c:123
General Asterisk PBX channel definitions.
Asterisk file paths, configured in asterisk.conf.
int ast_set_read_format(struct ast_channel *chan, struct ast_format *format)
Sets read format on channel chan.
Definition: channel.c:5849
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:911
static const struct ast_app_option app_opts[128]
Definition: app_record.c:157
Definition: dsp.c:405
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
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: main/app.c:2906
Core PBX routines and definitions.
void ast_dsp_set_threshold(struct ast_dsp *dsp, int threshold)
Set the minimum average magnitude threshold to determine talking by the DSP.
Definition: dsp.c:1775
struct ast_silence_generator * ast_channel_start_silence_generator(struct ast_channel *chan)
Starts a silence generator on the given channel.
Definition: channel.c:8266
const char * ast_config_AST_DATA_DIR
Definition: options.c:158
int ast_remaining_ms(struct timeval start, int max_ms)
Calculate remaining milliseconds given a starting timestamp and upper bound.
Definition: main/utils.c:2033
int ast_stream_rewind(struct ast_filestream *fs, off_t ms)
Rewind stream ms.
Definition: file.c:1063
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:1361
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:8312
static void parse(struct mgcp_request *req)
Definition: chan_mgcp.c:1872
static int unload_module(void)
Definition: app_record.c:533
int ast_closestream(struct ast_filestream *f)
Closes a stream.
Definition: file.c:1068
Structure used to handle boolean flags.
Definition: utils.h:199
int ast_truncstream(struct ast_filestream *fs)
Trunc stream at current location.
Definition: file.c:1043
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...
int ast_dsp_silence(struct ast_dsp *dsp, struct ast_frame *f, int *totalsilence)
Process the audio frame for silence.
Definition: dsp.c:1483
static int load_module(void)
Definition: app_record.c:538
This structure is allocated by file.c in one chunk, together with buf_size and desc_size bytes of mem...
Definition: mod_format.h:101
FILE * out
Definition: utils/frame.c:33
int ast_waitfor(struct ast_channel *chan, int ms)
Wait for input on a channel.
Definition: channel.c:3171
#define ao2_cleanup(obj)
Definition: astobj2.h:1958
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
const char * ast_channel_name(const struct ast_channel *chan)
int ast_writestream(struct ast_filestream *fs, struct ast_frame *f)
Writes a frame to a stream.
Definition: file.c:209
int ast_waitstream(struct ast_channel *c, const char *breakon)
Waits for a stream to stop or digit to be pressed.
Definition: file.c:1776
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
Definition: file.c:1086
#define ast_frfree(fr)
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:2814
Data structure associated with a single frame of data.
const char * ast_channel_language(const struct ast_channel *chan)
static int record_exec(struct ast_channel *chan, const char *data)
Definition: app_record.c:221
enum ast_frame_type frametype
#define PATH_MAX
Definition: asterisk.h:40
static struct test_options options
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
struct ast_format * ast_format_slin
Built-in cached signed linear 8kHz format.
Definition: format_cache.c:41
int ast_dsp_get_threshold_from_settings(enum threshold which)
Get silence threshold from dsp.conf.
Definition: dsp.c:1996
Asterisk module definitions.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application&#39;s arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
static char * app
Definition: app_record.c:125
static int create_destination_directory(const char *path)
Definition: app_record.c:187
int ast_stopstream(struct ast_channel *c)
Stops a stream.
Definition: file.c:187
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:626
static enum dtmf_response record_dtmf_response(struct ast_channel *chan, struct ast_flags *flags, int dtmf_integer, int terminator)
Definition: app_record.c:171
Media Format Cache API.
#define AST_APP_ARG(name)
Define an application argument.
int ast_mkdir(const char *path, int mode)
Recursively create directory path.
Definition: main/utils.c:2231