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