3a2a0f37c869cd07e10f81515630a47ff9c2036e
[asterisk/asterisk.git] / apps / app_record.c
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 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31
32 #include "asterisk.h"
33
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35
36 #include "asterisk/lock.h"
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/module.h"
42 #include "asterisk/translate.h"
43 #include "asterisk/dsp.h"
44 #include "asterisk/utils.h"
45 #include "asterisk/options.h"
46
47
48 static char *app = "Record";
49
50 static char *synopsis = "Record to a file";
51
52 static char *descrip = 
53 "  Record(filename.format|silence[|maxduration][|options])\n\n"
54 "Records from the channel into a given filename. If the file exists it will\n"
55 "be overwritten.\n"
56 "- 'format' is the format of the file type to be recorded (wav, gsm, etc).\n"
57 "- 'silence' is the number of seconds of silence to allow before returning.\n"
58 "- 'maxduration' is the maximum recording duration in seconds. If missing\n"
59 "or 0 there is no maximum.\n"
60 "- 'options' may contain any of the following letters:\n"
61 "     'a' : append to existing recording rather than replacing\n"
62 "     'n' : do not answer, but record anyway if line not yet answered\n"
63 "     'q' : quiet (do not play a beep tone)\n"
64 "     's' : skip recording if the line is not yet answered\n"
65 "     't' : use alternate '*' terminator key instead of default '#'\n"
66 "\n"
67 "If filename contains '%d', these characters will be replaced with a number\n"
68 "incremented by one each time the file is recorded. \n\n"
69 "Use 'show file formats' to see the available formats on your system\n\n"
70 "User can press '#' to terminate the recording and continue to the next priority.\n\n"
71 "If the user should hangup during a recording, all data will be lost and the\n"
72 "application will teminate. \n";
73
74 LOCAL_USER_DECL;
75
76 static int record_exec(struct ast_channel *chan, void *data)
77 {
78         int res = 0;
79         int count = 0;
80         int percentflag = 0;
81         char *filename, *ext = NULL, *silstr, *maxstr, *options;
82         char *vdata, *p;
83         int i = 0;
84         char tmp[256];
85
86         struct ast_filestream *s = '\0';
87         struct localuser *u;
88         struct ast_frame *f = NULL;
89         
90         struct ast_dsp *sildet = NULL;          /* silence detector dsp */
91         int totalsilence = 0;
92         int dspsilence = 0;
93         int silence = 0;                /* amount of silence to allow */
94         int gotsilence = 0;             /* did we timeout for silence? */
95         int maxduration = 0;            /* max duration of recording in milliseconds */
96         int gottimeout = 0;             /* did we timeout for maxduration exceeded? */
97         int option_skip = 0;
98         int option_noanswer = 0;
99         int option_append = 0;
100         int terminator = '#';
101         int option_quiet = 0;
102         int rfmt = 0;
103         int flags;
104         int waitres;
105         struct ast_silence_generator *silgen = NULL;
106         
107         /* The next few lines of code parse out the filename and header from the input string */
108         if (ast_strlen_zero(data)) { /* no data implies no filename or anything is present */
109                 ast_log(LOG_WARNING, "Record requires an argument (filename)\n");
110                 return -1;
111         }
112
113         LOCAL_USER_ADD(u);
114
115         /* Yay for strsep being easy */
116         vdata = ast_strdupa(data);
117
118         p = vdata;
119         filename = strsep(&p, "|");
120         silstr = strsep(&p, "|");
121         maxstr = strsep(&p, "|");       
122         options = strsep(&p, "|");
123         
124         if (filename) {
125                 if (strstr(filename, "%d"))
126                         percentflag = 1;
127                 ext = strrchr(filename, '.'); /* to support filename with a . in the filename, not format */
128                 if (!ext)
129                         ext = strchr(filename, ':');
130                 if (ext) {
131                         *ext = '\0';
132                         ext++;
133                 }
134         }
135         if (!ext) {
136                 ast_log(LOG_WARNING, "No extension specified to filename!\n");
137                 LOCAL_USER_REMOVE(u);
138                 return -1;
139         }
140         if (silstr) {
141                 if ((sscanf(silstr, "%d", &i) == 1) && (i > -1)) {
142                         silence = i * 1000;
143                 } else if (!ast_strlen_zero(silstr)) {
144                         ast_log(LOG_WARNING, "'%s' is not a valid silence duration\n", silstr);
145                 }
146         }
147         
148         if (maxstr) {
149                 if ((sscanf(maxstr, "%d", &i) == 1) && (i > -1))
150                         /* Convert duration to milliseconds */
151                         maxduration = i * 1000;
152                 else if (!ast_strlen_zero(maxstr))
153                         ast_log(LOG_WARNING, "'%s' is not a valid maximum duration\n", maxstr);
154         }
155         if (options) {
156                 /* Retain backwards compatibility with old style options */
157                 if (!strcasecmp(options, "skip"))
158                         option_skip = 1;
159                 else if (!strcasecmp(options, "noanswer"))
160                         option_noanswer = 1;
161                 else {
162                         if (strchr(options, 's'))
163                                 option_skip = 1;
164                         if (strchr(options, 'n'))
165                                 option_noanswer = 1;
166                         if (strchr(options, 'a'))
167                                 option_append = 1;
168                         if (strchr(options, 't'))
169                                 terminator = '*';
170                         if (strchr(options, 'q'))
171                                 option_quiet = 1;
172                 }
173         }
174         
175         /* done parsing */
176         
177         /* these are to allow the use of the %d in the config file for a wild card of sort to
178           create a new file with the inputed name scheme */
179         if (percentflag) {
180                 do {
181                         snprintf(tmp, sizeof(tmp), filename, count);
182                         count++;
183                 } while ( ast_fileexists(tmp, ext, chan->language) != -1 );
184                 pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
185         } else
186                 strncpy(tmp, filename, sizeof(tmp)-1);
187         /* end of routine mentioned */
188         
189         
190         
191         if (chan->_state != AST_STATE_UP) {
192                 if (option_skip) {
193                         /* At the user's option, skip if the line is not up */
194                         LOCAL_USER_REMOVE(u);
195                         return 0;
196                 } else if (!option_noanswer) {
197                         /* Otherwise answer unless we're supposed to record while on-hook */
198                         res = ast_answer(chan);
199                 }
200         }
201         
202         if (res) {
203                 ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name);
204                 goto out;
205         }
206         
207         if (!option_quiet) {
208                 /* Some code to play a nice little beep to signify the start of the record operation */
209                 res = ast_streamfile(chan, "beep", chan->language);
210                 if (!res) {
211                         res = ast_waitstream(chan, "");
212                 } else {
213                         ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", chan->name);
214                 }
215                 ast_stopstream(chan);
216         }
217                 
218         /* The end of beep code.  Now the recording starts */
219                 
220         if (silence > 0) {
221                 rfmt = chan->readformat;
222                 res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
223                 if (res < 0) {
224                         ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
225                         LOCAL_USER_REMOVE(u);
226                         return -1;
227                 }
228                 sildet = ast_dsp_new();
229                 if (!sildet) {
230                         ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
231                         LOCAL_USER_REMOVE(u);
232                         return -1;
233                 }
234                 ast_dsp_set_threshold(sildet, 256);
235         } 
236                 
237                 
238         flags = option_append ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
239         s = ast_writefile( tmp, ext, NULL, flags , 0, 0644);
240                 
241         if (!s) {
242                 ast_log(LOG_WARNING, "Could not create file %s\n", filename);
243                 goto out;
244         }
245
246         if (ast_opt_transmit_silence)
247                 silgen = ast_channel_start_silence_generator(chan);
248         
249         /* Request a video update */
250         ast_indicate(chan, AST_CONTROL_VIDUPDATE);
251         
252         if (maxduration <= 0)
253                 maxduration = -1;
254         
255         while ((waitres = ast_waitfor(chan, maxduration)) > -1) {
256                 if (maxduration > 0) {
257                         if (waitres == 0) {
258                                 gottimeout = 1;
259                                 break;
260                         }
261                         maxduration = waitres;
262                 }
263                 
264                 f = ast_read(chan);
265                 if (!f) {
266                         res = -1;
267                         break;
268                 }
269                 if (f->frametype == AST_FRAME_VOICE) {
270                         res = ast_writestream(s, f);
271                         
272                         if (res) {
273                                 ast_log(LOG_WARNING, "Problem writing frame\n");
274                                 ast_frfree(f);
275                                 break;
276                         }
277                         
278                         if (silence > 0) {
279                                 dspsilence = 0;
280                                 ast_dsp_silence(sildet, f, &dspsilence);
281                                 if (dspsilence) {
282                                         totalsilence = dspsilence;
283                                 } else {
284                                         totalsilence = 0;
285                                 }
286                                 if (totalsilence > silence) {
287                                         /* Ended happily with silence */
288                                         ast_frfree(f);
289                                         gotsilence = 1;
290                                         break;
291                                 }
292                         }
293                 } else if (f->frametype == AST_FRAME_VIDEO) {
294                         res = ast_writestream(s, f);
295                         
296                         if (res) {
297                                 ast_log(LOG_WARNING, "Problem writing frame\n");
298                                 ast_frfree(f);
299                                 break;
300                         }
301                 } else if ((f->frametype == AST_FRAME_DTMF) &&
302                     (f->subclass == terminator)) {
303                         ast_frfree(f);
304                         break;
305                 }
306                 ast_frfree(f);
307         }
308         if (!f) {
309                 ast_log(LOG_DEBUG, "Got hangup\n");
310                 res = -1;
311         }
312                         
313         if (gotsilence) {
314                 ast_stream_rewind(s, silence-1000);
315                 ast_truncstream(s);
316         } else if (!gottimeout) {
317                 /* Strip off the last 1/4 second of it */
318                 ast_stream_rewind(s, 250);
319                 ast_truncstream(s);
320         }
321         ast_closestream(s);
322
323         if (silgen)
324                 ast_channel_stop_silence_generator(chan, silgen);
325         
326  out:
327         if ((silence > 0) && rfmt) {
328                 res = ast_set_read_format(chan, rfmt);
329                 if (res)
330                         ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name);
331                 if (sildet)
332                         ast_dsp_free(sildet);
333         }
334
335         LOCAL_USER_REMOVE(u);
336
337         return res;
338 }
339
340 static int unload_module(void *mod)
341 {
342         int res;
343
344         res = ast_unregister_application(app);
345         
346         STANDARD_HANGUP_LOCALUSERS;
347
348         return res;     
349 }
350
351 static int load_module(void *mod)
352 {
353         return ast_register_application(app, record_exec, synopsis, descrip);
354 }
355
356 static const char *description(void)
357 {
358         return "Trivial Record Application";
359 }
360
361 static const char *key(void)
362 {
363         return ASTERISK_GPL_KEY;
364 }
365
366 STD_MOD1;