1 /*
2  * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3  * Copyright (C) 2008, Eric des Courtis <eric.des.courtis@benbria.com>
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  * Eric des Courtis <eric.des.courtis@benbria.com>
18  * Copyright (C) Benbria. All Rights Reserved.
19  *
20  * Contributor(s):
21  *
22  * Eric des Courtis <eric.des.courtis@benbria.com>
23  *
24  * Special thanks to the following companies for their help:
25  * 	- JohnnyVoIP
26  * 	- Magor Communications Corporation
27  *
28  * Special thanks to the following people for their help:
29  * 	- The FreeSWITCH Team
30  * 	- Matt Battig
31  * 	- Dean Swan
32  * 	- Lucas Cornelisse
33  * 	- Kevin Green
34  *
35  * mod_vmd.c -- Voicemail Detection Module
36  *
37  * This module detects voicemail beeps at any frequency in O(1) time.
38  *
39  */
40 
41 #include <switch.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <math.h>
46 #ifdef WIN32
47 #include <float.h>
48 #define ISNAN(x) (!!(_isnan(x)))
49 #else
50 #define ISNAN(x) (isnan(x))
51 #endif
52 
53 /*! Number of points for beep detection. */
54 #define POINTS 32
55 
56 /*! Number of valid points required for beep detection. */
57 #define VALID 22
58 
59 /*! Maximum number of invalid points to declare beep has stopped. */
60 #define MAX_CHIRP 22
61 
62 /*! Minimum time for a beep. */
63 #define MIN_TIME 8000
64 
65 /*! Minimum amplitude of the signal. */
66 #define MIN_AMPL 0.10
67 
68 /*! Minimum beep frequency. */
69 #define MIN_FREQ (600)
70 
71 /*! Maximum beep frequency. */
72 #define MAX_FREQ (1100)
73 
74 /*! \brief Helper for amplitude calculation
75  *
76  *  The function is defined as \f$\psi{(x)} = {x^2_1} - {x_2} {x_0}\f$
77  *
78  *  @author Eric des Courtis
79  *  @param x An array of 3 or more samples.
80  *  @return The value of \f$\psi{(x)}\f$.
81  */
82 #define PSI(x) (x[1]*x[1]-x[2]*x[0])
83 
84 /*! Sample rate NOTE: this should be dynamic in the future. */
85 #define F (8000)
86 
87 /*! \brief Conversion of frequency to Hz
88  *
89  * \f$F = \frac{f}{{2}{\pi}}\f$
90  */
91 #define TO_HZ(f) ((F * f) / (2.0 * M_PI))
92 
93 /* Number of points in discreet energy separation. */
94 #define P (5)
95 
96 /* Maximum signed value of int16_t
97  * DEPRECATED */
98 #define ADJUST (32768)
99 /* Same as above times two
100  * DEPRECATED */
101 #define ADJUST_MAX (65536)
102 
103 /*! Discreet energy separation tolerance to error. */
104 #define TOLERANCE (0.20)
105 
106 /*! Maximum value within tolerance. */
107 #define TOLERANCE_T(m) (m + (m * TOLERANCE))
108 
109 /*! Minimum value within tolerance. */
110 #define TOLERANCE_B(m) (m - (m * TOLERANCE))
111 
112 /*! Syntax of the API call. */
113 #define VMD_SYNTAX "<uuid> <command>"
114 
115 /*! Number of expected parameters in api call. */
116 #define VMD_PARAMS 2
117 
118 /*! FreeSWITCH CUSTOM event type. */
119 #define VMD_EVENT_BEEP "vmd::beep"
120 
121 /* Prototypes */
122 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_vmd_shutdown);
123 SWITCH_STANDARD_API(vmd_api_main);
124 
125 SWITCH_MODULE_LOAD_FUNCTION(mod_vmd_load);
126 SWITCH_MODULE_DEFINITION(mod_vmd, mod_vmd_load, NULL, NULL);
127 SWITCH_STANDARD_APP(vmd_start_function);
128 
129 /*! Type that holds state information about the beep. */
130 typedef enum vmd_state {
131 	BEEP_DETECTED, BEEP_NOT_DETECTED
132 } vmd_state_t;
133 
134 /*! Type that holds data for 5 points of discreet energy separation */
135 typedef struct vmd_point {
136 	double freq;
137 	double ampl;
138 } vmd_point_t;
139 
140 /*! Type that holds codec information. */
141 typedef struct vmd_codec_info {
142 	/*! The sampling rate of the audio stream. */
143 	int rate;
144 	/*! The number of channels. */
145 	int channels;
146 } vmd_codec_info_t;
147 
148 /*! Type that holds session information pertinent to the vmd module. */
149 typedef struct vmd_session_info {
150 	/*! State of the session. */
151 	vmd_state_t state;
152 	/*! Snapshot of DESA samples. */
153 	vmd_point_t points[POINTS+1];
154 	/*! Internal FreeSWITCH session. */
155 	switch_core_session_t *session;
156 	/*! Codec information for the session. */
157 	vmd_codec_info_t vmd_codec;
158 	/*! Current position in the snapshot. */
159 	unsigned int pos;
160 	/*! Frequency aproximation of a detected beep. */
161 	double beep_freq;
162 	/*! A count of how long a distinct beep was detected
163 	 *  by the discreet energy separation algorithm. */
164 	switch_size_t timestamp;
165 	/*! The MIN_TIME to use for this call */
166 	int minTime;
167 } vmd_session_info_t;
168 
169 static switch_bool_t process_data(vmd_session_info_t *vmd_info, switch_frame_t *frame);
170 static switch_bool_t vmd_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type);
171 static double freq_estimator(double *x);
172 static double ampl_estimator(double *x);
173 static void convert_pts(int16_t *i_pts, double *d_pts, int16_t max);
174 static void find_beep(vmd_session_info_t *vmd_info, switch_frame_t *frame);
175 static double median(double *m, int n);
176 
177 /*
178 #define PRINT(a) do{ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, a); }while(0)
179 #define PRINT2(a, b) do{ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, a, b); }while(0)
180 */
181 
182 /*! \brief The callback function that is called when new audio data becomes available
183  *
184  * @author Eric des Courtis
185  * @param bug A reference to the media bug.
186  * @param user_data The session information for this call.
187  * @param type The switch callback type.
188  * @return The success or failure of the function.
189  */
vmd_callback(switch_media_bug_t * bug,void * user_data,switch_abc_type_t type)190 static switch_bool_t vmd_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
191 {
192 	vmd_session_info_t *vmd_info;
193 	switch_codec_t *read_codec;
194 	switch_frame_t *frame;
195 
196 	vmd_info = (vmd_session_info_t *) user_data;
197 	if (vmd_info == NULL) {
198 		return SWITCH_FALSE;
199 	}
200 
201 	switch (type) {
202 
203 	case SWITCH_ABC_TYPE_INIT:
204 		read_codec = switch_core_session_get_read_codec(vmd_info->session);
205 		vmd_info->vmd_codec.rate = read_codec->implementation->samples_per_second;
206 		vmd_info->vmd_codec.channels = read_codec->implementation->number_of_channels;
207 		break;
208 
209 	case SWITCH_ABC_TYPE_READ_REPLACE:
210 		frame = switch_core_media_bug_get_read_replace_frame(bug);
211 		return process_data(vmd_info, frame);
212 
213 	default:
214 		break;
215 	}
216 
217 	return SWITCH_TRUE;
218 }
219 
220 /*! \brief Process and convert data to be used by the find_beep() function
221  *
222  * @author Eric des Courtis
223  * @param vmd_info The session information associated with the call.
224  * @param frame The audio data.
225  * @return The success or failure of the function.
226  */
process_data(vmd_session_info_t * vmd_info,switch_frame_t * frame)227 static switch_bool_t process_data(vmd_session_info_t *vmd_info, switch_frame_t *frame)
228 {
229 	uint32_t i;
230 	unsigned int j;
231 	double pts[P];
232 	int16_t *data;
233 	int16_t max;
234 
235 	//len = frame->samples * sizeof(int16_t);
236 	data = (int16_t *) frame->data;
237 
238 	for (max = (int16_t) abs(data[0]), i = 1; i < frame->samples; i++) {
239 		if ((int16_t) abs(data[i]) > max) {
240 			max = (int16_t) abs(data[i]);
241 		}
242 	}
243 
244 /*
245     if (vmd_info->data_len != len){
246 	vmd_info->data_len = len;
247 	if (vmd_info->data != NULL) free(vmd_info->data);
248 	vmd_info->data = (int16_t *)malloc(len);
249 	if (vmd_info->data == NULL) return SWITCH_FALSE;
250     }
251 
252     (void)memcpy(vmd_info->data, data, len);
253     for(i = 2; i < frame->samples; i++){
254 	vmd_info->data[i] =
255 	    0.0947997 * data[i]
256 	    -
257 	    0.0947997 * data[i - 2]
258 	    -
259 	    1.4083405 * vmd_info->data[i - 1]
260 	    +
261 	    0.8104005 * vmd_info->data[i - 2];
262     }
263 */
264 
265 	for (i = 0, j = vmd_info->pos; i < frame->samples; j++, j %= POINTS, i += 5) {
266 		/*      convert_pts(vmd_info->data + i, pts); */
267 		convert_pts(data + i, pts, max);
268 		vmd_info->points[j].freq = TO_HZ(freq_estimator(pts));
269 		vmd_info->points[j].ampl = ampl_estimator(pts);
270 		vmd_info->pos = j % POINTS;
271 		find_beep(vmd_info, frame);
272 	}
273 
274 	return SWITCH_TRUE;
275 }
276 
277 /*! \brief Find voicemail beep in the audio stream
278  *
279  * @author Eric des Courtis
280  * @param vmd_info The session information associated with the call.
281  * @param frame The audio data.
282  * @return The success or failure of the function.
283  */
find_beep(vmd_session_info_t * vmd_info,switch_frame_t * frame)284 static void find_beep(vmd_session_info_t *vmd_info, switch_frame_t *frame)
285 {
286 	int i;
287 	int c;
288 	double m[POINTS];
289 	double med;
290 	unsigned int j = (vmd_info->pos + 1) % POINTS;
291 	unsigned int k = j;
292 	switch_event_t *event;
293 	switch_status_t status;
294 	switch_event_t *event_copy;
295 	switch_channel_t *channel = switch_core_session_get_channel(vmd_info->session);
296 
297 	switch (vmd_info->state) {
298 	case BEEP_DETECTED:
299 		for (c = 0, i = 0; i < POINTS; j++, j %= POINTS, i++) {
300 			vmd_info->timestamp++;
301 			if (vmd_info->points[j].freq < TOLERANCE_T(vmd_info->beep_freq) && vmd_info->points[j].freq > TOLERANCE_B(vmd_info->beep_freq)) {
302 				c++;
303 				vmd_info->beep_freq = (vmd_info->beep_freq * 0.95) + (vmd_info->points[j].freq * 0.05);
304 			}
305 		}
306 
307 		if (c < (POINTS - MAX_CHIRP)) {
308 			vmd_info->state = BEEP_NOT_DETECTED;
309 			if (vmd_info->timestamp < (switch_size_t) vmd_info->minTime) {
310 				break;
311 			}
312 
313 			status = switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, VMD_EVENT_BEEP);
314 			if (status != SWITCH_STATUS_SUCCESS) {
315 				return;
316 			}
317 
318 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Beep-Status", "stop");
319 			switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Beep-Time", "%d", (int) vmd_info->timestamp / POINTS);
320 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Unique-ID", switch_core_session_get_uuid(vmd_info->session));
321 			switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Frequency", "%6.4lf", vmd_info->beep_freq);
322 			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-command", "vmd");
323 
324 			if ((switch_event_dup(&event_copy, event)) != SWITCH_STATUS_SUCCESS) {
325 				return;
326 			}
327 
328 			switch_core_session_queue_event(vmd_info->session, &event);
329 			switch_event_fire(&event_copy);
330 
331 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(vmd_info->session), SWITCH_LOG_INFO, "<<< VMD - Beep Detected >>>\n");
332 			switch_channel_set_variable(channel, "vmd_detect", "TRUE");
333 
334 			vmd_info->timestamp = 0;
335 		}
336 
337 		break;
338 
339 	case BEEP_NOT_DETECTED:
340 
341 		for (i = 0; i < POINTS; k++, k %= POINTS, i++) {
342 			m[i] = vmd_info->points[k].freq;
343 			if (ISNAN(m[i])) {
344 				m[i] = 0.0;
345 			}
346 		}
347 
348 		med = median(m, POINTS);
349 		if (ISNAN(med)) {
350 			for (i = 0; i < POINTS; i++) {
351 				if (!ISNAN(m[i])) {
352 					med = m[i];
353 					break;
354 				}
355 			}
356 		}
357 
358 		for (c = 0, i = 0; i < POINTS; j++, j %= POINTS, i++) {
359 			if (vmd_info->points[j].freq < TOLERANCE_T(med) && vmd_info->points[j].freq > TOLERANCE_B(med)) {
360 				if (vmd_info->points[j].ampl > MIN_AMPL && vmd_info->points[j].freq > MIN_FREQ && vmd_info->points[j].freq < MAX_FREQ) {
361 					c++;
362 				}
363 			}
364 		}
365 
366 		if (c >= VALID) {
367 			vmd_info->state = BEEP_DETECTED;
368 			vmd_info->beep_freq = med;
369 			vmd_info->timestamp = 0;
370 		}
371 
372 		break;
373 	}
374 }
375 
376 /*! \brief Find the median of an array of doubles
377  *
378  * @param m Array of frequency samples.
379  * @param n Number of samples in the array.
380  * @return The median.
381  */
median(double * m,int n)382 static double median(double *m, int n)
383 {
384 	int i;
385 	int less;
386 	int greater;
387 	int equal;
388 	double min;
389 	double max;
390 	double guess;
391 	double maxltguess;
392 	double mingtguess;
393 
394 	min = max = m[0];
395 
396 	for (i = 1; i < n; i++) {
397 		if (m[i] < min)
398 			min = m[i];
399 		if (m[i] > max)
400 			max = m[i];
401 	}
402 
403 	for (;;) {
404 		guess = (min + max) / 2;
405 		less = 0;
406 		greater = 0;
407 		equal = 0;
408 		maxltguess = min;
409 		mingtguess = max;
410 
411 		for (i = 0; i < n; i++) {
412 			if (m[i] < guess) {
413 				less++;
414 				if (m[i] > maxltguess) {
415 					maxltguess = m[i];
416 				}
417 			} else if (m[i] > guess) {
418 				greater++;
419 				if (m[i] < mingtguess) {
420 					mingtguess = m[i];
421 				}
422 			} else {
423 				equal++;
424 			}
425 		}
426 
427 		if (less <= (n + 1) / 2 && greater <= (n + 1) / 2) {
428 			break;
429 		} else if (less > greater) {
430 			max = maxltguess;
431 		} else {
432 			min = mingtguess;
433 		}
434 	}
435 
436 	if (less >= (n + 1) / 2) {
437 		return maxltguess;
438 	} else if (less + equal >= (n + 1) / 2) {
439 		return guess;
440 	}
441 
442 	return mingtguess;
443 }
444 
445 /*! \brief Convert many points for Signed L16 to relative floating point
446  *
447  * @author Eric des Courtis
448  * @param i_pts Array of P 16 bit integer audio samples.
449  * @param d_pts Array of P double floating point audio samples.
450  * @param max The maximum value in the entire audio frame.
451  * @return Nothing.
452  */
convert_pts(int16_t * i_pts,double * d_pts,int16_t max)453 static void convert_pts(int16_t *i_pts, double *d_pts, int16_t max)
454 {
455 	int i;
456 	for (i = 0; i < P; i++) {
457 		/*! Signed L16 to relative floating point conversion */
458 		d_pts[i] = ((((double) (i_pts[i]) + (double) max) / (double) (2 * max)) - 0.5) * 2.0;
459 	}
460 }
461 
462 /*! \brief Amplitude estimator for DESA-2
463  *
464  *  The function is defined as \f$A = \sqrt{\frac{\psi{(x)}}{\sin{\Omega^2}}}\f$
465  *
466  *  @author Eric des Courtis
467  *  @param x An array of 5 evenly spaced audio samples \f$x_0, x_1, x_2, x_3, x_4\f$.
468  *  @return The estimated amplitude.
469  */
ampl_estimator(double * x)470 double ampl_estimator(double *x)
471 {
472 	double freq_sq;
473 
474 	freq_sq = freq_estimator(x);
475 	freq_sq *= freq_sq;
476 
477 	return sqrt(PSI(x) / sin(freq_sq));
478 }
479 
480 /*! \brief The DESA-2 algorithm
481  *
482  *  The function is defined as \f$f = \frac{1}{2}\arccos{\frac{{{x^2_2} -
483  *  {x_0}{x_4}} - {{x^2_1} -
484  *  {x_0}{x_2}} - {{x^2_3} -
485  *  {x_2}{x_4}}}
486  *  {{2}({x^2_2} - {x_1}{x_3})}}\f$
487  *
488  * @author Eric des Courtis
489  * @param x An array for 5 evenly spaced audio samples \f$x_0, x_1, x_2, x_3, x_4\f$.
490  * @return A frequency estimate.
491  */
freq_estimator(double * x)492 double freq_estimator(double *x)
493 {
494 	return 0.5 * acos((((x[2] * x[2]) - (x[0] * x[4]))
495 					   - ((x[1] * x[1]) - (x[0] * x[2]))
496 					   - ((x[3] * x[3]) - (x[2] * x[4])))
497 					  / (2.0 * ((x[2] * x[2]) - (x[1] * x[3])))
498 
499 		);
500 }
501 
502 /*! \brief FreeSWITCH module loading function
503  *
504  * @author Eric des Courtis
505  * @return Load success or failure.
506  */
SWITCH_MODULE_LOAD_FUNCTION(mod_vmd_load)507 SWITCH_MODULE_LOAD_FUNCTION(mod_vmd_load)
508 {
509 	switch_application_interface_t *app_interface;
510 	switch_api_interface_t *api_interface;
511 
512 	if (switch_event_reserve_subclass(VMD_EVENT_BEEP) != SWITCH_STATUS_SUCCESS) {
513 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", VMD_EVENT_BEEP);
514 		return SWITCH_STATUS_TERM;
515 	}
516 
517 	/* connect my internal structure to the blank pointer passed to me */
518 	*module_interface = switch_loadable_module_create_module_interface(pool, modname);
519 
520 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Voicemail detection enabled\n");
521 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "mod_vmd deprecated.  Please migrate to mod_avmd\n");
522 
523 	SWITCH_ADD_APP(app_interface, "vmd", "Detect beeps", "Detect voicemail beeps", vmd_start_function, "[start] [stop]", SAF_NONE);
524 
525 	SWITCH_ADD_API(api_interface, "vmd", "Detected voicemail beeps", vmd_api_main, VMD_SYNTAX);
526 
527 	/* indicate that the module should continue to be loaded */
528 	return SWITCH_STATUS_SUCCESS;
529 }
530 
531 /*! \brief FreeSWITCH application handler function.
532  *  This handles calls made from applications such as LUA and the dialplan
533  *
534  * @author Eric des Courtis
535  * @return Success or failure of the function.
536  */
SWITCH_STANDARD_APP(vmd_start_function)537 SWITCH_STANDARD_APP(vmd_start_function)
538 {
539 	switch_media_bug_t *bug;
540 	switch_status_t status;
541 	switch_channel_t *channel;
542 	vmd_session_info_t *vmd_info;
543 	int i;
544 	const char *minTimeString;
545 	int mintime = 0;
546 
547 	if (session == NULL)
548 		return;
549 
550 	channel = switch_core_session_get_channel(session);
551 
552 	/* Is this channel already set? */
553 	bug = (switch_media_bug_t *) switch_channel_get_private(channel, "_vmd_");
554 	/* If yes */
555 	if (bug != NULL) {
556 		/* If we have a stop remove audio bug */
557 		if (strcasecmp(data, "stop") == 0) {
558 			switch_channel_set_private(channel, "_vmd_", NULL);
559 			switch_core_media_bug_remove(session, &bug);
560 			return;
561 		}
562 
563 		/* We have already started */
564 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Cannot run 2 at once on the same channel!\n");
565 
566 		return;
567 	}
568 
569 	vmd_info = (vmd_session_info_t *) switch_core_session_alloc(session, sizeof(vmd_session_info_t)
570 		);
571 
572 	vmd_info->state = BEEP_NOT_DETECTED;
573 	vmd_info->session = session;
574 	vmd_info->pos = 0;
575 	/*
576 	   vmd_info->data = NULL;
577 	   vmd_info->data_len = 0;
578 	 */
579 	for (i = 0; i < POINTS; i++) {
580 		vmd_info->points[i].freq = 0.0;
581 		vmd_info->points[i].ampl = 0.0;
582 	}
583 
584 	status = switch_core_media_bug_add(session, "vmd", NULL, vmd_callback, vmd_info, 0, SMBF_READ_REPLACE, &bug);
585 
586 	if (status != SWITCH_STATUS_SUCCESS) {
587 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Failure hooking to stream\n");
588 		return;
589 	}
590 
591 	switch_channel_set_private(channel, "_vmd_", bug);
592 
593 	if ((minTimeString = switch_channel_get_variable(channel, "vmd_min_time")) && (mintime = atoi(minTimeString))) {
594 		vmd_info->minTime = mintime;
595 	} else {
596 		vmd_info->minTime = MIN_TIME;
597 	}
598 
599 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "MIN_TIME for call: %d\n", vmd_info->minTime);
600 }
601 
602 /*! \brief Called when the module shuts down
603  *
604  * @author Eric des Courtis
605  * @return The success or failure of the function.
606  */
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_vmd_shutdown)607 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_vmd_shutdown)
608 {
609 
610 	switch_event_free_subclass(VMD_EVENT_BEEP);
611 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Voicemail detection disabled\n");
612 
613 	return SWITCH_STATUS_SUCCESS;
614 }
615 
616 /*! \brief FreeSWITCH API handler function.
617  *  This function handles API calls such as the ones from mod_event_socket and in some cases
618  *  scripts such as LUA scripts.
619  *
620  *  @author Eric des Courtis
621  *  @return The success or failure of the function.
622  */
SWITCH_STANDARD_API(vmd_api_main)623 SWITCH_STANDARD_API(vmd_api_main)
624 {
625 	switch_core_session_t *vmd_session = NULL;
626 	switch_media_bug_t *bug;
627 	vmd_session_info_t *vmd_info;
628 	switch_channel_t *channel;
629 	switch_status_t status;
630 	int argc;
631 	char *argv[VMD_PARAMS];
632 	char *ccmd = NULL;
633 	char *uuid;
634 	char *command;
635 	int i;
636 
637 	/* No command? Display usage */
638 	if (zstr(cmd)) {
639 		stream->write_function(stream, "-USAGE: %s\n", VMD_SYNTAX);
640 		return SWITCH_STATUS_SUCCESS;
641 	}
642 
643 	/* Duplicated contents of original string */
644 	ccmd = strdup(cmd);
645 	/* Separate the arguments */
646 	argc = switch_separate_string(ccmd, ' ', argv, VMD_PARAMS);
647 
648 	/* If we don't have the expected number of parameters
649 	 * display usage */
650 	if (argc != VMD_PARAMS) {
651 		stream->write_function(stream, "-USAGE: %s\n", VMD_SYNTAX);
652 		goto end;
653 	}
654 
655 	uuid = argv[0];
656 	command = argv[1];
657 
658 	/* using uuid locate a reference to the FreeSWITCH session */
659 	vmd_session = switch_core_session_locate(uuid);
660 
661 	/* If the session was not found exit */
662 	if (vmd_session == NULL) {
663 		stream->write_function(stream, "-USAGE: %s\n", VMD_SYNTAX);
664 		goto end;
665 	}
666 
667 	/* Get current channel of the session to tag the session
668 	 * This indicates that our module is present */
669 	channel = switch_core_session_get_channel(vmd_session);
670 
671 	/* Is this channel already set? */
672 	bug = (switch_media_bug_t *) switch_channel_get_private(channel, "_vmd_");
673 	/* If yes */
674 	if (bug != NULL) {
675 		/* If we have a stop remove audio bug */
676 		if (strcasecmp(command, "stop") == 0) {
677 			switch_channel_set_private(channel, "_vmd_", NULL);
678 			switch_core_media_bug_remove(vmd_session, &bug);
679 			switch_safe_free(ccmd);
680 			stream->write_function(stream, "+OK\n");
681 			goto end;
682 		}
683 
684 		/* We have already started */
685 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Cannot run 2 at once on the same channel!\n");
686 		goto end;
687 	}
688 
689 	/* If we don't see the expected start exit */
690 	if (strcasecmp(command, "start") != 0) {
691 		stream->write_function(stream, "-USAGE: %s\n", VMD_SYNTAX);
692 		goto end;
693 	}
694 
695 	/* Allocate memory attached to this FreeSWITCH session for
696 	 * use in the callback routine and to store state information */
697 	vmd_info = (vmd_session_info_t *) switch_core_session_alloc(vmd_session, sizeof(vmd_session_info_t)
698 		);
699 
700 	/* Set initial values and states */
701 	vmd_info->state = BEEP_NOT_DETECTED;
702 	vmd_info->session = vmd_session;
703 	vmd_info->pos = 0;
704 /*
705     vmd_info->data = NULL;
706     vmd_info->data_len = 0;
707 */
708 
709 	for (i = 0; i < POINTS; i++) {
710 		vmd_info->points[i].freq = 0.0;
711 		vmd_info->points[i].ampl = 0.0;
712 	}
713 
714 	/* Add a media bug that allows me to intercept the
715 	 * reading leg of the audio stream */
716 	status = switch_core_media_bug_add(vmd_session, "vmd", NULL, vmd_callback, vmd_info, 0, SMBF_READ_REPLACE, &bug);
717 
718 	/* If adding a media bug fails exit */
719 	if (status != SWITCH_STATUS_SUCCESS) {
720 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Failure hooking to stream\n");
721 		goto end;
722 	}
723 
724 	/* Set the vmd tag to detect an existing vmd media bug */
725 	switch_channel_set_private(channel, "_vmd_", bug);
726 
727 	/* Everything went according to plan! Notify the user */
728 	stream->write_function(stream, "+OK\n");
729 
730 
731   end:
732 
733 	if (vmd_session) {
734 		switch_core_session_rwunlock(vmd_session);
735 	}
736 
737 	switch_safe_free(ccmd);
738 
739 	return SWITCH_STATUS_SUCCESS;
740 }
741 
742 
743 /* For Emacs:
744  * Local Variables:
745  * mode:c
746  * indent-tabs-mode:t
747  * tab-width:4
748  * c-basic-offset:4
749  * End:
750  * For VIM:
751  * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
752  */
753