]>
Commit | Line | Data |
---|---|---|
d7e66fb6 | 1 | /* |
2 | * Asterisk -- A telephony toolkit for Linux. | |
3 | * | |
4 | * Trivial application to receive a TIFF FAX file | |
5 | * | |
6 | * Copyright (C) 2003, Steve Underwood | |
7 | * | |
8 | * Steve Underwood <steveu@coppice.org> | |
9 | * | |
10 | * This program is free software, distributed under the terms of | |
11 | * the GNU General Public License | |
12 | */ | |
13 | ||
14 | #include <string.h> | |
15 | #include <stdlib.h> | |
16 | #include <stdio.h> | |
17 | #include <inttypes.h> | |
18 | #include <pthread.h> | |
19 | #include <errno.h> | |
20 | #include <tiffio.h> | |
21 | ||
22 | #include <spandsp.h> | |
23 | ||
24 | #include "asterisk.h" | |
25 | ||
26 | ASTERISK_FILE_VERSION(__FILE__, "$Revision$") | |
27 | ||
28 | #include "asterisk/lock.h" | |
29 | #include "asterisk/file.h" | |
30 | #include "asterisk/logger.h" | |
31 | #include "asterisk/channel.h" | |
32 | #include "asterisk/pbx.h" | |
33 | #include "asterisk/module.h" | |
34 | #include "asterisk/translate.h" | |
35 | #include "asterisk/dsp.h" | |
36 | #include "asterisk/manager.h" | |
37 | ||
38 | static char *tdesc = "Trivial FAX Receive Application"; | |
39 | ||
40 | static char *app = "RxFAX"; | |
41 | ||
42 | static char *synopsis = "Receive a FAX to a file"; | |
43 | ||
44 | static char *descrip = | |
45 | " RxFAX(filename[|caller][|debug]): Receives a FAX from the channel into the\n" | |
46 | "given filename. If the file exists it will be overwritten. The file\n" | |
47 | "should be in TIFF/F format.\n" | |
48 | "The \"caller\" option makes the application behave as a calling machine,\n" | |
49 | "rather than the answering machine. The default behaviour is to behave as\n" | |
50 | "an answering machine.\n" | |
51 | "Uses LOCALSTATIONID to identify itself to the remote end.\n" | |
52 | " LOCALHEADERINFO to generate a header line on each page.\n" | |
53 | "Sets REMOTESTATIONID to the sender CSID.\n" | |
54 | " FAXPAGES to the number of pages received.\n" | |
55 | " FAXBITRATE to the transmition rate.\n" | |
56 | " FAXRESOLUTION to the resolution.\n" | |
57 | "Returns -1 when the user hangs up.\n" | |
58 | "Returns 0 otherwise.\n"; | |
59 | ||
60 | STANDARD_LOCAL_USER; | |
61 | ||
62 | LOCAL_USER_DECL; | |
63 | ||
64 | #define MAX_BLOCK_SIZE 240 | |
65 | ||
66 | static void span_message(int level, const char *msg) | |
67 | { | |
68 | int ast_level; | |
69 | ||
70 | if (level == SPAN_LOG_WARNING) | |
71 | ast_level = __LOG_WARNING; | |
72 | else if (level == SPAN_LOG_WARNING) | |
73 | ast_level = __LOG_WARNING; | |
74 | else | |
75 | ast_level = __LOG_DEBUG; | |
76 | ast_log(ast_level, __FILE__, __LINE__, __PRETTY_FUNCTION__, msg); | |
77 | } | |
78 | /*- End of function --------------------------------------------------------*/ | |
79 | ||
80 | static void t30_flush(t30_state_t *s, int which) | |
81 | { | |
82 | //TODO: | |
83 | } | |
84 | /*- End of function --------------------------------------------------------*/ | |
85 | ||
86 | static void phase_e_handler(t30_state_t *s, void *user_data, int result) | |
87 | { | |
88 | struct ast_channel *chan; | |
89 | t30_stats_t t; | |
90 | char local_ident[21]; | |
91 | char far_ident[21]; | |
92 | char buf[11]; | |
93 | ||
94 | chan = (struct ast_channel *) user_data; | |
95 | if (result == T30_ERR_OK) | |
96 | { | |
97 | t30_get_transfer_statistics(s, &t); | |
98 | t30_get_far_ident(s, far_ident); | |
99 | t30_get_local_ident(s, local_ident); | |
100 | ast_log(LOG_DEBUG, "==============================================================================\n"); | |
101 | ast_log(LOG_DEBUG, "Fax successfully received.\n"); | |
102 | ast_log(LOG_DEBUG, "Remote station id: %s\n", far_ident); | |
103 | ast_log(LOG_DEBUG, "Local station id: %s\n", local_ident); | |
104 | ast_log(LOG_DEBUG, "Pages transferred: %i\n", t.pages_transferred); | |
105 | ast_log(LOG_DEBUG, "Image resolution: %i x %i\n", t.column_resolution, t.row_resolution); | |
106 | ast_log(LOG_DEBUG, "Transfer Rate: %i\n", t.bit_rate); | |
107 | ast_log(LOG_DEBUG, "==============================================================================\n"); | |
108 | manager_event(EVENT_FLAG_CALL, | |
109 | "FaxReceived", "Channel: %s\nExten: %s\nCallerID: %s\nRemoteStationID: %s\nLocalStationID: %s\nPagesTransferred: %i\nResolution: %i\nTransferRate: %i\nFileName: %s\n", | |
110 | chan->name, | |
111 | chan->exten, | |
112 | (chan->cid.cid_num) ? chan->cid.cid_num : "", | |
113 | far_ident, | |
114 | local_ident, | |
115 | t.pages_transferred, | |
116 | t.row_resolution, | |
117 | t.bit_rate, | |
118 | s->rx_file); | |
119 | pbx_builtin_setvar_helper(chan, "REMOTESTATIONID", far_ident); | |
120 | snprintf(buf, sizeof(buf), "%i", t.pages_transferred); | |
121 | pbx_builtin_setvar_helper(chan, "FAXPAGES", buf); | |
122 | snprintf(buf, sizeof(buf), "%i", t.row_resolution); | |
123 | pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", buf); | |
124 | snprintf(buf, sizeof(buf), "%i", t.bit_rate); | |
125 | pbx_builtin_setvar_helper(chan, "FAXBITRATE", buf); | |
126 | } | |
127 | else | |
128 | { | |
129 | ast_log(LOG_DEBUG, "==============================================================================\n"); | |
130 | ast_log(LOG_DEBUG, "Fax receive not successful - result (%d) %s.\n", result, t30_completion_code_to_str(result)); | |
131 | ast_log(LOG_DEBUG, "==============================================================================\n"); | |
132 | } | |
133 | } | |
134 | /*- End of function --------------------------------------------------------*/ | |
135 | ||
136 | static void phase_d_handler(t30_state_t *s, void *user_data, int result) | |
137 | { | |
138 | struct ast_channel *chan; | |
139 | t30_stats_t t; | |
140 | ||
141 | chan = (struct ast_channel *) user_data; | |
142 | if (result) | |
143 | { | |
144 | t30_get_transfer_statistics(s, &t); | |
145 | ast_log(LOG_DEBUG, "==============================================================================\n"); | |
146 | ast_log(LOG_DEBUG, "Pages transferred: %i\n", t.pages_transferred); | |
147 | ast_log(LOG_DEBUG, "Image size: %i x %i\n", t.columns, t.rows); | |
148 | ast_log(LOG_DEBUG, "Image resolution %i x %i\n", t.column_resolution, t.row_resolution); | |
149 | ast_log(LOG_DEBUG, "Transfer Rate: %i\n", t.bit_rate); | |
150 | ast_log(LOG_DEBUG, "Bad rows %i\n", t.bad_rows); | |
151 | ast_log(LOG_DEBUG, "Longest bad row run %i\n", t.longest_bad_row_run); | |
152 | ast_log(LOG_DEBUG, "Compression type %i\n", t.encoding); | |
153 | ast_log(LOG_DEBUG, "Image size (bytes) %i\n", t.image_size); | |
154 | ast_log(LOG_DEBUG, "==============================================================================\n"); | |
155 | } | |
156 | } | |
157 | /*- End of function --------------------------------------------------------*/ | |
158 | ||
159 | static int rxfax_exec(struct ast_channel *chan, void *data) | |
160 | { | |
161 | int res = 0; | |
162 | char template_file[256]; | |
163 | char target_file[256]; | |
164 | char *s; | |
165 | char *t; | |
166 | char *v; | |
167 | char *x; | |
168 | int option; | |
169 | int len; | |
170 | int i; | |
171 | t30_state_t fax; | |
172 | int calling_party; | |
173 | int verbose; | |
174 | int samples; | |
175 | ||
176 | struct localuser *u; | |
177 | struct ast_frame *inf = NULL; | |
178 | struct ast_frame outf; | |
179 | ||
180 | int original_read_fmt; | |
181 | int original_write_fmt; | |
182 | ||
183 | uint8_t __buf[sizeof(uint16_t)*MAX_BLOCK_SIZE + 2*AST_FRIENDLY_OFFSET]; | |
184 | uint8_t *buf = __buf + AST_FRIENDLY_OFFSET; | |
185 | ||
186 | if (chan == NULL) | |
187 | { | |
188 | ast_log(LOG_WARNING, "Fax receive channel is NULL. Giving up.\n"); | |
189 | return -1; | |
190 | } | |
191 | ||
192 | span_set_message_handler(span_message); | |
193 | ||
194 | /* The next few lines of code parse out the filename and header from the input string */ | |
195 | if (data == NULL) | |
196 | { | |
197 | /* No data implies no filename or anything is present */ | |
198 | ast_log(LOG_WARNING, "Rxfax requires an argument (filename)\n"); | |
199 | return -1; | |
200 | } | |
201 | ||
202 | calling_party = FALSE; | |
203 | verbose = FALSE; | |
204 | target_file[0] = '\0'; | |
205 | ||
206 | for (option = 0, v = s = data; v; option++, s++) | |
207 | { | |
208 | t = s; | |
209 | v = strchr(s, '|'); | |
210 | s = (v) ? v : s + strlen(s); | |
211 | strncpy((char *) buf, t, s - t); | |
212 | buf[s - t] = '\0'; | |
213 | if (option == 0) | |
214 | { | |
215 | /* The first option is always the file name */ | |
216 | len = s - t; | |
217 | if (len > 255) | |
218 | len = 255; | |
219 | strncpy(target_file, t, len); | |
220 | target_file[len] = '\0'; | |
221 | /* Allow the use of %d in the file name for a wild card of sorts, to | |
222 | create a new file with the specified name scheme */ | |
223 | if ((x = strchr(target_file, '%')) && x[1] == 'd') | |
224 | { | |
225 | strcpy(template_file, target_file); | |
226 | i = 0; | |
227 | do | |
228 | { | |
229 | snprintf(target_file, 256, template_file, 1); | |
230 | i++; | |
231 | } | |
232 | while (ast_fileexists(target_file, "", chan->language) != -1); | |
233 | } | |
234 | } | |
235 | else if (strncmp("caller", t, s - t) == 0) | |
236 | { | |
237 | calling_party = TRUE; | |
238 | } | |
239 | else if (strncmp("debug", t, s - t) == 0) | |
240 | { | |
241 | verbose = TRUE; | |
242 | } | |
243 | } | |
244 | ||
245 | /* Done parsing */ | |
246 | ||
247 | LOCAL_USER_ADD(u); | |
248 | ||
249 | if (chan->_state != AST_STATE_UP) | |
250 | { | |
251 | /* Shouldn't need this, but checking to see if channel is already answered | |
252 | * Theoretically asterisk should already have answered before running the app */ | |
253 | res = ast_answer(chan); | |
254 | } | |
255 | ||
256 | if (!res) | |
257 | { | |
258 | original_read_fmt = chan->readformat; | |
259 | if (original_read_fmt != AST_FORMAT_SLINEAR) | |
260 | { | |
261 | res = ast_set_read_format(chan, AST_FORMAT_SLINEAR); | |
262 | if (res < 0) | |
263 | { | |
264 | ast_log(LOG_WARNING, "Unable to set to linear read mode, giving up\n"); | |
265 | return -1; | |
266 | } | |
267 | } | |
268 | original_write_fmt = chan->writeformat; | |
269 | if (original_write_fmt != AST_FORMAT_SLINEAR) | |
270 | { | |
271 | res = ast_set_write_format(chan, AST_FORMAT_SLINEAR); | |
272 | if (res < 0) | |
273 | { | |
274 | ast_log(LOG_WARNING, "Unable to set to linear write mode, giving up\n"); | |
275 | res = ast_set_read_format(chan, original_read_fmt); | |
276 | if (res) | |
277 | ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name); | |
278 | return -1; | |
279 | } | |
280 | } | |
281 | fax_init(&fax, calling_party, NULL); | |
282 | if (verbose) | |
283 | fax.logging.level = SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW; | |
284 | x = pbx_builtin_getvar_helper(chan, "LOCALSTATIONID"); | |
285 | if (x && x[0]) | |
286 | t30_set_local_ident(&fax, x); | |
287 | x = pbx_builtin_getvar_helper(chan, "LOCALHEADERINFO"); | |
288 | if (x && x[0]) | |
289 | t30_set_header_info(&fax, x); | |
290 | t30_set_rx_file(&fax, target_file, -1); | |
291 | //t30_set_phase_b_handler(&fax, phase_b_handler, chan); | |
292 | t30_set_phase_d_handler(&fax, phase_d_handler, chan); | |
293 | t30_set_phase_e_handler(&fax, phase_e_handler, chan); | |
294 | while (ast_waitfor(chan, -1) > -1) | |
295 | { | |
296 | inf = ast_read(chan); | |
297 | if (inf == NULL) | |
298 | { | |
299 | res = -1; | |
300 | break; | |
301 | } | |
302 | if (inf->frametype == AST_FRAME_VOICE) | |
303 | { | |
304 | if (fax_rx(&fax, inf->data, inf->samples)) | |
305 | break; | |
306 | samples = (inf->samples <= MAX_BLOCK_SIZE) ? inf->samples : MAX_BLOCK_SIZE; | |
307 | len = fax_tx(&fax, (int16_t *) &buf[AST_FRIENDLY_OFFSET], samples); | |
308 | if (len) | |
309 | { | |
310 | memset(&outf, 0, sizeof(outf)); | |
311 | outf.frametype = AST_FRAME_VOICE; | |
312 | outf.subclass = AST_FORMAT_SLINEAR; | |
313 | outf.datalen = len*sizeof(int16_t); | |
314 | outf.samples = len; | |
315 | outf.data = &buf[AST_FRIENDLY_OFFSET]; | |
316 | outf.offset = AST_FRIENDLY_OFFSET; | |
317 | outf.src = "RxFAX"; | |
318 | if (ast_write(chan, &outf) < 0) | |
319 | { | |
320 | ast_log(LOG_WARNING, "Unable to write frame to channel; %s\n", strerror(errno)); | |
321 | break; | |
322 | } | |
323 | } | |
324 | } | |
325 | ast_frfree(inf); | |
326 | } | |
327 | if (inf == NULL) | |
328 | { | |
329 | ast_log(LOG_DEBUG, "Got hangup\n"); | |
330 | res = -1; | |
331 | } | |
332 | if (original_read_fmt != AST_FORMAT_SLINEAR) | |
333 | { | |
334 | res = ast_set_read_format(chan, original_read_fmt); | |
335 | if (res) | |
336 | ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name); | |
337 | } | |
338 | if (original_write_fmt != AST_FORMAT_SLINEAR) | |
339 | { | |
340 | res = ast_set_write_format(chan, original_write_fmt); | |
341 | if (res) | |
342 | ast_log(LOG_WARNING, "Unable to restore write format on '%s'\n", chan->name); | |
343 | } | |
344 | fax_release(&fax); | |
345 | } | |
346 | else | |
347 | { | |
348 | ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name); | |
349 | } | |
350 | LOCAL_USER_REMOVE(u); | |
351 | return res; | |
352 | } | |
353 | /*- End of function --------------------------------------------------------*/ | |
354 | ||
355 | int unload_module(void) | |
356 | { | |
357 | STANDARD_HANGUP_LOCALUSERS; | |
358 | return ast_unregister_application(app); | |
359 | } | |
360 | /*- End of function --------------------------------------------------------*/ | |
361 | ||
362 | int load_module(void) | |
363 | { | |
364 | return ast_register_application(app, rxfax_exec, synopsis, descrip); | |
365 | } | |
366 | ||
367 | char *description(void) | |
368 | { | |
369 | return tdesc; | |
370 | } | |
371 | /*- End of function --------------------------------------------------------*/ | |
372 | ||
373 | int usecount(void) | |
374 | { | |
375 | int res; | |
376 | STANDARD_USECOUNT(res); | |
377 | return res; | |
378 | } | |
379 | /*- End of function --------------------------------------------------------*/ | |
380 | ||
381 | char *key(void) | |
382 | { | |
383 | return ASTERISK_GPL_KEY; | |
384 | } | |
385 | /*- End of function --------------------------------------------------------*/ | |
386 | /*- End of file ------------------------------------------------------------*/ |