1 /*
2 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3 * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
4 *
5 * Version: MPL 1.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18 *
19 * The Initial Developer of the Original Code is
20 * Anthony Minessale II <anthm@freeswitch.org>
21 * Portions created by the Initial Developer are Copyright (C)
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 *
26 * Anthony Minessale II <anthm@freeswitch.org>
27 *
28 *
29 * mod_snapshot.c -- record a sliding window of audio and take snapshots to disk
30 *
31 */
32 #include <switch.h>
33
34 /* Prototypes */
35 SWITCH_MODULE_RUNTIME_FUNCTION(mod_snapshot_runtime);
36 SWITCH_MODULE_LOAD_FUNCTION(mod_snapshot_load);
37
38 /* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime)
39 * Defines a switch_loadable_module_function_table_t and a static const char[] modname
40 */
41 SWITCH_MODULE_DEFINITION(mod_snapshot, mod_snapshot_load, NULL, NULL);
42
43 struct cap_cb {
44 switch_buffer_t *buffer;
45 switch_mutex_t *mutex;
46 char *base;
47 };
48
capture_callback(switch_media_bug_t * bug,void * user_data,switch_abc_type_t type)49 static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
50 {
51 switch_core_session_t *session = switch_core_media_bug_get_session(bug);
52 switch_channel_t *channel = switch_core_session_get_channel(session);
53 struct cap_cb *cb = (struct cap_cb *) user_data;
54
55 switch (type) {
56 case SWITCH_ABC_TYPE_INIT:
57 break;
58 case SWITCH_ABC_TYPE_CLOSE:
59 {
60 if (cb->buffer) {
61 switch_buffer_destroy(&cb->buffer);
62 }
63 switch_channel_set_private(channel, "snapshot", NULL);
64 }
65
66 break;
67 case SWITCH_ABC_TYPE_READ:
68
69 if (cb->buffer) {
70 uint8_t data[SWITCH_RECOMMENDED_BUFFER_SIZE];
71 switch_frame_t frame = { 0 };
72
73 frame.data = data;
74 frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE;
75
76 if (switch_mutex_trylock(cb->mutex) == SWITCH_STATUS_SUCCESS) {
77 while (switch_core_media_bug_read(bug, &frame, SWITCH_TRUE) == SWITCH_STATUS_SUCCESS && !switch_test_flag((&frame), SFF_CNG)) {
78 if (frame.datalen)
79 switch_buffer_slide_write(cb->buffer, frame.data, frame.datalen);
80 }
81 switch_mutex_unlock(cb->mutex);
82 }
83
84 }
85 break;
86 case SWITCH_ABC_TYPE_WRITE:
87 default:
88 break;
89 }
90
91 return SWITCH_TRUE;
92 }
93
start_capture(switch_core_session_t * session,unsigned int seconds,switch_media_bug_flag_t flags,const char * base)94 static switch_status_t start_capture(switch_core_session_t *session, unsigned int seconds, switch_media_bug_flag_t flags, const char *base)
95 {
96 switch_channel_t *channel = switch_core_session_get_channel(session);
97 switch_media_bug_t *bug;
98 switch_status_t status;
99 switch_codec_implementation_t read_impl = { 0 };
100 struct cap_cb *cb;
101 switch_size_t bytes;
102 switch_bind_flag_t bind_flags = 0;
103
104 if (switch_channel_get_private(channel, "snapshot")) {
105 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Already Running.\n");
106 return SWITCH_STATUS_FALSE;
107 }
108
109 if (seconds < 5) {
110 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Must be at least 5 seconds!\n");
111 return SWITCH_STATUS_FALSE;
112 }
113
114 switch_core_session_get_read_impl(session, &read_impl);
115
116 if (switch_channel_pre_answer(channel) != SWITCH_STATUS_SUCCESS) {
117 return SWITCH_STATUS_FALSE;
118 }
119
120 cb = switch_core_session_alloc(session, sizeof(*cb));
121 cb->base = switch_core_session_strdup(session, base);
122
123 bytes = read_impl.samples_per_second * seconds * 2;
124
125 switch_buffer_create_dynamic(&cb->buffer, bytes, bytes, bytes);
126 switch_mutex_init(&cb->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
127
128 if ((status = switch_core_media_bug_add(session, "snapshot", NULL, capture_callback, cb, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
129 return status;
130 }
131
132 bind_flags = SBF_DIAL_ALEG | SBF_EXEC_ALEG | SBF_EXEC_SAME;
133 switch_ivr_bind_dtmf_meta_session(session, 7, bind_flags, "snapshot::snap");
134
135 switch_channel_set_private(channel, "snapshot", bug);
136
137 return SWITCH_STATUS_SUCCESS;
138 }
139
do_snap(switch_core_session_t * session)140 static switch_status_t do_snap(switch_core_session_t *session)
141 {
142 switch_channel_t *channel = switch_core_session_get_channel(session);
143 switch_media_bug_t *bug = switch_channel_get_private(channel, "snapshot");
144 char *file;
145 switch_file_handle_t fh = { 0 };
146 switch_codec_implementation_t read_impl = { 0 };
147 switch_size_t bytes_read;
148 int16_t pdata[4096] = { 0 };
149
150 if (bug) {
151 switch_time_exp_t tm;
152 switch_size_t retsize;
153 char date[80] = "";
154 struct cap_cb *cb = (struct cap_cb *) switch_core_media_bug_get_user_data(bug);
155
156 if (!cb) {
157 return SWITCH_STATUS_FALSE;
158 }
159
160 switch_time_exp_lt(&tm, switch_time_make(switch_epoch_time_now(NULL), 0));
161 switch_strftime(date, &retsize, sizeof(date), "%Y_%m_%d_%H_%M_%S", &tm);
162
163 file = switch_core_session_sprintf(session, "%s%s%s_%s.wav", SWITCH_GLOBAL_dirs.sounds_dir, SWITCH_PATH_SEPARATOR, cb->base, date);
164
165 switch_core_session_get_read_impl(session, &read_impl);
166 fh.channels = 0;
167 fh.native_rate = read_impl.actual_samples_per_second;
168
169 if (switch_core_file_open(&fh,
170 file,
171 0,
172 read_impl.actual_samples_per_second, SWITCH_FILE_FLAG_WRITE | SWITCH_FILE_DATA_SHORT, NULL) != SWITCH_STATUS_SUCCESS) {
173 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error opening %s\n", file);
174 switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
175 switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE);
176 return SWITCH_STATUS_FALSE;
177 }
178
179 switch_mutex_lock(cb->mutex);
180 while ((bytes_read = switch_buffer_read(cb->buffer, pdata, sizeof(pdata)))) {
181 switch_size_t samples = bytes_read / 2;
182
183 if (switch_core_file_write(&fh, pdata, &samples) != SWITCH_STATUS_SUCCESS) {
184 break;
185 }
186 }
187 switch_mutex_unlock(cb->mutex);
188 switch_core_file_close(&fh);
189 switch_core_set_variable("file", file);
190 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Wrote %s\n", file);
191 return SWITCH_STATUS_SUCCESS;
192 }
193
194 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "%s Bug is not attached.\n", switch_channel_get_name(channel));
195 return SWITCH_STATUS_FALSE;
196
197 }
198
199 #define SNAP_SYNTAX "start <sec> <read|write>"
SWITCH_STANDARD_APP(snapshot_app_function)200 SWITCH_STANDARD_APP(snapshot_app_function)
201 {
202 char *argv[4] = { 0 };
203 int argc = 0;
204 char *lbuf = NULL;
205 switch_media_bug_flag_t flags = SMBF_READ_STREAM | SMBF_WRITE_STREAM | SMBF_READ_PING;
206
207 if (!zstr(data) && (lbuf = switch_core_session_strdup(session, data))) {
208 argc = switch_separate_string(lbuf, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
209 }
210
211 if (argc < 1) {
212 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Usage: %s\n", SNAP_SYNTAX);
213 return;
214 }
215
216 if (!strcasecmp(argv[0], "start")) {
217 char *sec = argv[1];
218 char *fl = argv[2];
219 const char *base = argv[3];
220 int seconds = 5;
221
222 if (sec) {
223 int tmp = atoi(sec);
224 if (tmp > 5) {
225 seconds = tmp;
226 }
227 }
228
229 if (fl) {
230 flags = SMBF_READ_PING;
231 if (switch_stristr("read", fl)) {
232 flags |= SMBF_READ_STREAM;
233 }
234 if (switch_stristr("write", fl)) {
235 flags |= SMBF_WRITE_STREAM;
236 }
237 }
238
239 if (!base) {
240 base = "mod_snapshot";
241 }
242
243 start_capture(session, seconds, flags, base);
244
245 }
246
247 if (!strcasecmp(argv[0], "snap")) {
248 do_snap(session);
249 return;
250 }
251 }
252
253
254 #define SNAP_API_SYNTAX "<uuid> snap|start [<sec> read|write <base>]"
SWITCH_STANDARD_API(snapshot_function)255 SWITCH_STANDARD_API(snapshot_function)
256 {
257 char *mycmd = NULL, *argv[5] = { 0 };
258 int argc = 0;
259 switch_status_t status = SWITCH_STATUS_FALSE;
260
261 if (!zstr(cmd) && (mycmd = strdup(cmd))) {
262 argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
263 }
264
265 if (zstr(cmd) || argc < 2 || zstr(argv[0])) {
266 stream->write_function(stream, "-USAGE: %s\n", SNAP_API_SYNTAX);
267 goto done;
268 } else {
269 switch_core_session_t *lsession = NULL;
270
271 if ((lsession = switch_core_session_locate(argv[0]))) {
272 if (!strcasecmp(argv[1], "snap")) {
273 status = do_snap(lsession);
274 } else if (!strcasecmp(argv[1], "start")) {
275 char *sec = argv[2];
276 char *fl = argv[3];
277 const char *base = argv[4];
278 int seconds = 5;
279 switch_media_bug_flag_t flags = SMBF_READ_STREAM | SMBF_WRITE_STREAM | SMBF_READ_PING;
280
281 if (sec) {
282 int tmp = atoi(sec);
283 if (tmp > 5) {
284 seconds = tmp;
285 }
286 }
287
288 if (fl) {
289 flags = SMBF_READ_PING;
290 if (switch_stristr("read", fl)) {
291 flags |= SMBF_READ_STREAM;
292 }
293 if (switch_stristr("write", fl)) {
294 flags |= SMBF_WRITE_STREAM;
295 }
296 }
297
298 if (!base) {
299 base = "mod_snapshot";
300 }
301
302 status = start_capture(lsession, seconds, flags, base);
303 }
304
305 switch_core_session_rwunlock(lsession);
306 }
307 }
308
309 if (status == SWITCH_STATUS_SUCCESS) {
310 stream->write_function(stream, "+OK Success\n");
311 } else {
312 stream->write_function(stream, "-ERR Operation Failed\n");
313 }
314
315 done:
316
317 switch_safe_free(mycmd);
318 return SWITCH_STATUS_SUCCESS;
319 }
320
321 /* Macro expands to: switch_status_t mod_snapshot_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */
SWITCH_MODULE_LOAD_FUNCTION(mod_snapshot_load)322 SWITCH_MODULE_LOAD_FUNCTION(mod_snapshot_load)
323 {
324 switch_api_interface_t *api_interface;
325 switch_application_interface_t *app_interface;
326
327 /* connect my internal structure to the blank pointer passed to me */
328 *module_interface = switch_loadable_module_create_module_interface(pool, modname);
329
330 SWITCH_ADD_API(api_interface, "uuid_snapshot", "Snapshot API", snapshot_function, SNAP_API_SYNTAX);
331 SWITCH_ADD_APP(app_interface, "snapshot", "", "", snapshot_app_function, SNAP_SYNTAX, SAF_MEDIA_TAP);
332 switch_console_set_complete("add uuid_snapshot ::console::list_uuid");
333
334 /* indicate that the module should continue to be loaded */
335 return SWITCH_STATUS_SUCCESS;
336 }
337
338 /* For Emacs:
339 * Local Variables:
340 * mode:c
341 * indent-tabs-mode:t
342 * tab-width:4
343 * c-basic-offset:4
344 * End:
345 * For VIM:
346 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet
347 */
348