├── .gitignore
├── LICENSE
├── Makefile
├── README.md
└── mod_amd.c
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | *.so
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | MODNAME = mod_amd.so
2 | MODOBJ = mod_amd.o
3 | MODCFLAGS = -Wall -Werror
4 | # MODLDFLAGS = -lssl
5 |
6 | CC = gcc
7 | CFLAGS = -fPIC -g -ggdb `pkg-config --cflags freeswitch` $(MODCFLAGS)
8 | LDFLAGS = `pkg-config --libs freeswitch` $(MODLDFLAGS)
9 |
10 | .PHONY: all
11 | all: $(MODNAME)
12 |
13 | $(MODNAME): $(MODOBJ)
14 | @$(CC) -shared -o $@ $(MODOBJ) $(LDFLAGS)
15 |
16 | .c.o: $<
17 | @$(CC) $(CFLAGS) -o $@ -c $<
18 |
19 | .PHONY: clean
20 | clean:
21 | rm -f $(MODNAME) $(MODOBJ)
22 |
23 | .PHONY: install
24 | install: $(MODNAME)
25 | install -d $(DESTDIR)/usr/lib/freeswitch/mod
26 | install $(MODNAME) $(DESTDIR)/usr/lib/freeswitch/mod
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Asterisk app_amd for FreeSWITCH
2 | ===============================
3 |
4 | This is an implementation of Asterisk's answering machine detection (voice
5 | activity detection) for FreeSWITCH.
6 |
7 | Currently, in limited testing, we are able to get satisfactory results in
8 | determining what is a human and what is a machine, but there is much more to
9 | do:
10 |
11 | * Emit events when a decision is made (Not sure, Machine, or Human).
12 | * Make sure that we are unlocking and cleaning up where necessary.
13 |
14 | Building
15 | --------
16 |
17 | To build this module, all you need to do is type `make`, but because it relies
18 | on `pkg-config` and FreeSWITCH, you need to point `pkg-config` to where
19 | FreeSWITCH is installed before building:
20 |
21 | ```
22 | host$ export PKG_CONFIG_PATH=/usr/local/freeswitch/lib/pkgconfig/
23 | host$ make
24 | ```
25 |
26 | Sample Configuration
27 | --------------------
28 | Just put a file like this in your freeswitch installation, in **conf/autoload_configs/amd.conf.xml**
29 | ```xml
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | ```
44 |
--------------------------------------------------------------------------------
/mod_amd.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #define AMD_PARAMS (2)
4 | #define AMD_SYNTAX " "
5 |
6 | SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_amd_shutdown);
7 | SWITCH_MODULE_LOAD_FUNCTION(mod_amd_load);
8 | SWITCH_MODULE_DEFINITION(mod_amd, mod_amd_load, mod_amd_shutdown, NULL);
9 | SWITCH_STANDARD_APP(amd_start_function);
10 |
11 | static struct {
12 | uint32_t initial_silence;
13 | uint32_t greeting;
14 | uint32_t after_greeting_silence;
15 | uint32_t total_analysis_time;
16 | uint32_t minimum_word_length;
17 | uint32_t between_words_silence;
18 | uint32_t maximum_number_of_words;
19 | uint32_t silence_threshold;
20 | uint32_t maximum_word_length;
21 | } globals;
22 |
23 | static switch_xml_config_item_t instructions[] = {
24 | SWITCH_CONFIG_ITEM(
25 | "initial_silence",
26 | SWITCH_CONFIG_INT,
27 | CONFIG_RELOADABLE,
28 | &globals.initial_silence,
29 | (void *) 2500,
30 | NULL, NULL, NULL),
31 |
32 | SWITCH_CONFIG_ITEM(
33 | "greeting",
34 | SWITCH_CONFIG_INT,
35 | CONFIG_RELOADABLE,
36 | &globals.greeting,
37 | (void *) 1500,
38 | NULL, NULL, NULL),
39 |
40 | SWITCH_CONFIG_ITEM(
41 | "after_greeting_silence",
42 | SWITCH_CONFIG_INT,
43 | CONFIG_RELOADABLE,
44 | &globals.after_greeting_silence,
45 | (void *) 800,
46 | NULL, NULL, NULL),
47 |
48 | SWITCH_CONFIG_ITEM(
49 | "total_analysis_time",
50 | SWITCH_CONFIG_INT,
51 | CONFIG_RELOADABLE,
52 | &globals.total_analysis_time,
53 | (void *) 5000,
54 | NULL, NULL, NULL),
55 |
56 | SWITCH_CONFIG_ITEM(
57 | "min_word_length",
58 | SWITCH_CONFIG_INT,
59 | CONFIG_RELOADABLE,
60 | &globals.minimum_word_length,
61 | (void *) 100,
62 | NULL, NULL, NULL),
63 |
64 | SWITCH_CONFIG_ITEM(
65 | "between_words_silence",
66 | SWITCH_CONFIG_INT,
67 | CONFIG_RELOADABLE,
68 | &globals.between_words_silence,
69 | (void *) 50,
70 | NULL, NULL, NULL),
71 |
72 | SWITCH_CONFIG_ITEM(
73 | "maximum_number_of_words",
74 | SWITCH_CONFIG_INT,
75 | CONFIG_RELOADABLE,
76 | &globals.maximum_number_of_words,
77 | (void *) 3,
78 | NULL, NULL, NULL),
79 |
80 | SWITCH_CONFIG_ITEM(
81 | "maximum_word_length",
82 | SWITCH_CONFIG_INT,
83 | CONFIG_RELOADABLE,
84 | &globals.maximum_word_length,
85 | (void *)5000,
86 | NULL, NULL, NULL),
87 |
88 | SWITCH_CONFIG_ITEM(
89 | "silence_threshold",
90 | SWITCH_CONFIG_INT,
91 | CONFIG_RELOADABLE,
92 | &globals.silence_threshold,
93 | (void *) 256,
94 | NULL, NULL, NULL),
95 |
96 | SWITCH_CONFIG_ITEM_END()
97 | };
98 |
99 | static switch_status_t do_config(switch_bool_t reload)
100 | {
101 | memset(&globals, 0, sizeof(globals));
102 |
103 | if (switch_xml_config_parse_module_settings("amd.conf", reload, instructions) != SWITCH_STATUS_SUCCESS) {
104 | return SWITCH_STATUS_FALSE;
105 | }
106 |
107 | return SWITCH_STATUS_SUCCESS;
108 | }
109 |
110 | SWITCH_MODULE_LOAD_FUNCTION(mod_amd_load)
111 | {
112 | switch_application_interface_t *app_interface;
113 |
114 | *module_interface = switch_loadable_module_create_module_interface(pool, modname);
115 |
116 | do_config(SWITCH_FALSE);
117 |
118 | SWITCH_ADD_APP(
119 | app_interface,
120 | "amd",
121 | "Voice activity detection (blocking)",
122 | "Asterisk's AMD (Blocking)",
123 | amd_start_function,
124 | NULL,
125 | SAF_NONE);
126 |
127 | return SWITCH_STATUS_SUCCESS;
128 | }
129 |
130 | SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_amd_shutdown)
131 | {
132 | switch_xml_config_cleanup(instructions);
133 |
134 | return SWITCH_STATUS_SUCCESS;
135 | }
136 |
137 | typedef enum {
138 | SILENCE,
139 | VOICED
140 | } amd_frame_classifier;
141 |
142 | typedef enum {
143 | VAD_STATE_IN_WORD,
144 | VAD_STATE_IN_SILENCE,
145 | } amd_vad_state_t;
146 |
147 | typedef struct {
148 | const switch_core_session_t *session;
149 | switch_channel_t *channel;
150 | amd_vad_state_t state;
151 | uint32_t frame_ms;
152 |
153 | uint32_t silence_duration;
154 | uint32_t voice_duration;
155 | uint32_t words;
156 |
157 | uint32_t in_initial_silence:1;
158 | uint32_t in_greeting:1;
159 | } amd_vad_t;
160 |
161 | static amd_frame_classifier classify_frame(const switch_frame_t *f, const switch_codec_implementation_t *codec)
162 | {
163 | int16_t *audio = f->data;
164 | uint32_t score, count, j;
165 | double energy;
166 | int divisor;
167 |
168 | divisor = codec->actual_samples_per_second / 8000;
169 |
170 | for (energy = 0, j = 0, count = 0; count < f->samples; count++) {
171 | energy += abs(audio[j++]);
172 | j += codec->number_of_channels;
173 | }
174 |
175 | score = (uint32_t) (energy / (f->samples / divisor));
176 |
177 | if (score >= globals.silence_threshold) {
178 | return VOICED;
179 | }
180 |
181 | return SILENCE;
182 | }
183 |
184 | static switch_bool_t amd_handle_silence_frame(amd_vad_t *vad, const switch_frame_t *f)
185 | {
186 | vad->silence_duration += vad->frame_ms;
187 |
188 | if (vad->silence_duration >= globals.between_words_silence) {
189 | if (vad->state != VAD_STATE_IN_SILENCE) {
190 | switch_log_printf(
191 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
192 | SWITCH_LOG_DEBUG,
193 | "AMD: Changed state to VAD_STATE_IN_SILENCE\n");
194 | }
195 |
196 | vad->state = VAD_STATE_IN_SILENCE;
197 | vad->voice_duration = 0;
198 | }
199 |
200 | if (vad->in_initial_silence && vad->silence_duration >= globals.initial_silence) {
201 | switch_log_printf(
202 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
203 | SWITCH_LOG_DEBUG,
204 | "AMD: MACHINE (silence_duration: %d, initial_silence: %d)\n",
205 | vad->silence_duration,
206 | globals.initial_silence);
207 |
208 | switch_channel_set_variable(vad->channel, "amd_result", "MACHINE");
209 | switch_channel_set_variable(vad->channel, "amd_cause", "INITIALSILENCE");
210 | return SWITCH_TRUE;
211 | }
212 |
213 | if (vad->silence_duration >= globals.after_greeting_silence && vad->in_greeting) {
214 | switch_log_printf(
215 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
216 | SWITCH_LOG_DEBUG,
217 | "AMD: HUMAN (silence_duration: %d, after_greeting_silence: %d)\n",
218 | vad->silence_duration,
219 | globals.after_greeting_silence);
220 |
221 | switch_channel_set_variable(vad->channel, "amd_result", "HUMAN");
222 | switch_channel_set_variable(vad->channel, "amd_cause", "HUMAN");
223 | return SWITCH_TRUE;
224 | }
225 |
226 | return SWITCH_FALSE;
227 | }
228 |
229 | static switch_bool_t amd_handle_voiced_frame(amd_vad_t *vad, const switch_frame_t *f)
230 | {
231 | vad->voice_duration += vad->frame_ms;
232 |
233 | if (vad->voice_duration >= globals.minimum_word_length && vad->state == VAD_STATE_IN_SILENCE) {
234 | vad->words++;
235 |
236 | switch_log_printf(
237 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
238 | SWITCH_LOG_DEBUG,
239 | "AMD: Word detected (words: %d)\n",
240 | vad->words);
241 |
242 | vad->state = VAD_STATE_IN_WORD;
243 | }
244 |
245 | if (vad->voice_duration >= globals.maximum_word_length) {
246 | switch_log_printf(
247 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
248 | SWITCH_LOG_DEBUG,
249 | "AMD: MACHINE (voice_duration: %d, maximum_word_length: %d)\n",
250 | vad->voice_duration,
251 | globals.maximum_word_length);
252 |
253 | switch_channel_set_variable(vad->channel, "amd_result", "MACHINE");
254 | switch_channel_set_variable(vad->channel, "amd_cause", "MAXWORDLENGTH");
255 | return SWITCH_TRUE;
256 | }
257 |
258 | if (vad->words >= globals.maximum_number_of_words) {
259 | switch_log_printf(
260 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
261 | SWITCH_LOG_DEBUG,
262 | "AMD: MACHINE (words: %d, maximum_number_of_words: %d)\n",
263 | vad->words,
264 | globals.maximum_number_of_words);
265 |
266 | switch_channel_set_variable(vad->channel, "amd_result", "MACHINE");
267 | switch_channel_set_variable(vad->channel, "amd_cause", "MAXWORDS");
268 | return SWITCH_TRUE;
269 | }
270 |
271 | if (vad->in_greeting && vad->voice_duration >= globals.greeting) {
272 | switch_log_printf(
273 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
274 | SWITCH_LOG_DEBUG,
275 | "AMD: MACHINE (voice_duration: %d, greeting: %d)\n",
276 | vad->voice_duration,
277 | globals.greeting);
278 |
279 | switch_channel_set_variable(vad->channel, "amd_result", "MACHINE");
280 | switch_channel_set_variable(vad->channel, "amd_cause", "LONGGREETING");
281 | return SWITCH_TRUE;
282 | }
283 |
284 | if (vad->voice_duration >= globals.minimum_word_length) {
285 | if (vad->silence_duration) {
286 | switch_log_printf(
287 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
288 | SWITCH_LOG_DEBUG,
289 | "AMD: Detected Talk, previous silence duration: %dms\n",
290 | vad->silence_duration);
291 | }
292 |
293 | vad->silence_duration = 0;
294 | }
295 |
296 | if (vad->voice_duration >= globals.minimum_word_length && !vad->in_greeting) {
297 | if (vad->silence_duration) {
298 | switch_log_printf(
299 | SWITCH_CHANNEL_SESSION_LOG(vad->session),
300 | SWITCH_LOG_DEBUG,
301 | "AMD: Before Greeting Time (silence_duration: %d, voice_duration: %d)\n",
302 | vad->silence_duration,
303 | vad->voice_duration);
304 | }
305 |
306 | vad->in_initial_silence = 0;
307 | vad->in_greeting = 1;
308 | }
309 |
310 | return SWITCH_FALSE;
311 | }
312 |
313 | SWITCH_STANDARD_APP(amd_start_function)
314 | {
315 | switch_channel_t *channel = switch_core_session_get_channel(session);
316 | switch_codec_t raw_codec = { 0 };
317 | switch_codec_implementation_t read_impl = { 0 };
318 | switch_frame_t *read_frame;
319 | switch_status_t status;
320 | uint32_t timeout_ms = globals.total_analysis_time;
321 | int32_t sample_count_limit;
322 | switch_bool_t complete = SWITCH_FALSE;
323 |
324 | amd_vad_t vad = { 0 };
325 |
326 | if (!session) {
327 | return;
328 | }
329 |
330 | vad.channel = channel;
331 | vad.session = session;
332 | vad.state = VAD_STATE_IN_WORD;
333 | vad.silence_duration = 0;
334 | vad.voice_duration = 0;
335 | vad.frame_ms = 0;
336 | vad.in_initial_silence = 1;
337 | vad.in_greeting = 0;
338 | vad.words = 0;
339 |
340 | switch_core_session_get_read_impl(session, &read_impl);
341 |
342 | if (timeout_ms) {
343 | sample_count_limit = (read_impl.actual_samples_per_second / 1000) * timeout_ms;
344 | }
345 |
346 | /*
347 | * We are creating a new L16 (raw 16-bit samples) codec for the read end
348 | * of our channel. We'll use this to process the audio coming off of the
349 | * channel so that we always know what we are dealing with.
350 | */
351 | status = switch_core_codec_init(
352 | &raw_codec,
353 | "L16",
354 | NULL,
355 | NULL,
356 | read_impl.actual_samples_per_second,
357 | read_impl.microseconds_per_packet / 1000,
358 | 1,
359 | SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
360 | NULL,
361 | switch_core_session_get_pool(session));
362 |
363 | if (status != SWITCH_STATUS_SUCCESS) {
364 | switch_log_printf(
365 | SWITCH_CHANNEL_SESSION_LOG(session),
366 | SWITCH_LOG_ERROR,
367 | "Unable to initialize L16 (raw) codec.\n");
368 | return;
369 | }
370 |
371 | switch_core_session_set_read_codec(session, &raw_codec);
372 |
373 | while (switch_channel_ready(channel)) {
374 | status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);
375 |
376 | if (!SWITCH_READ_ACCEPTABLE(status)) {
377 | break;
378 | }
379 |
380 | if (read_frame->samples == 0) {
381 | continue;
382 | }
383 |
384 | vad.frame_ms = 1000 / (read_impl.actual_samples_per_second / read_frame->samples);
385 |
386 | if (sample_count_limit) {
387 | sample_count_limit -= raw_codec.implementation->samples_per_packet;
388 | if (sample_count_limit <= 0) {
389 | switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "AMD: Timeout\n");
390 |
391 | switch_channel_set_variable(channel, "amd_result", "NOTSURE");
392 | switch_channel_set_variable(channel, "amd_cause", "TOOLONG");
393 | break;
394 | }
395 | }
396 |
397 | switch (classify_frame(read_frame, &read_impl)) {
398 | case SILENCE:
399 | switch_log_printf(
400 | SWITCH_CHANNEL_SESSION_LOG(session),
401 | SWITCH_LOG_DEBUG,
402 | "AMD: Silence\n");
403 |
404 | if (amd_handle_silence_frame(&vad, read_frame)) {
405 | complete = SWITCH_TRUE;
406 | }
407 | break;
408 | case VOICED:
409 | default:
410 | switch_log_printf(
411 | SWITCH_CHANNEL_SESSION_LOG(session),
412 | SWITCH_LOG_DEBUG,
413 | "AMD: Voiced\n");
414 |
415 | if (amd_handle_voiced_frame(&vad, read_frame)) {
416 | complete = SWITCH_TRUE;
417 | }
418 | break;
419 | }
420 |
421 | if (complete) {
422 | break;
423 | }
424 | }
425 |
426 | switch_core_session_reset(session, SWITCH_FALSE, SWITCH_TRUE);
427 | switch_core_codec_destroy(&raw_codec);
428 | }
429 |
--------------------------------------------------------------------------------