2013-08-04 23:49:33 -04:00
/*
* FreeSWITCH Modular Media Switching Software Library / Soft - Switch Application
* Copyright ( C ) 2005 - 2011 , Anthony Minessale II < anthm @ freeswitch . org >
*
* Version : MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 ( the " License " ) ; you may not use this file except in compliance with
* the License . You may obtain a copy of the License at
* http : //www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an " AS IS " basis ,
* WITHOUT WARRANTY OF ANY KIND , either express or implied . See the License
* for the specific language governing rights and limitations under the
* License .
*
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Oreka Recording Module
*
* The Initial Developer of the Original Code is
* Moises Silva < moises . silva @ gmail . com >
* Portions created by the Initial Developer are Copyright ( C )
* the Initial Developer . All Rights Reserved .
*
* Contributor ( s ) :
* Moises Silva < moises . silva @ gmail . com >
*
2013-08-05 01:30:44 -04:00
* mod_bert - - Naive BERT tester
2013-08-04 23:49:33 -04:00
*
*/
# include <switch.h>
SWITCH_MODULE_LOAD_FUNCTION ( mod_bert_load ) ;
SWITCH_MODULE_SHUTDOWN_FUNCTION ( mod_bert_shutdown ) ;
SWITCH_MODULE_DEFINITION ( mod_bert , mod_bert_load , mod_bert_shutdown , NULL ) ;
2014-04-14 01:28:38 -04:00
# define G711_ULAW_IDLE_OCTET 0xFF
2013-08-13 17:35:35 -04:00
/* http://en.wikipedia.org/wiki/Digital_milliwatt */
unsigned char ulaw_digital_milliwatt [ 8 ] = { 0x1e , 0x0b , 0x0b , 0x1e , 0x9e , 0x8b , 0x8b , 0x9e } ;
typedef struct {
uint32_t processed_samples ;
uint32_t err_samples ;
uint32_t window_ms ;
uint32_t window_samples ;
2013-08-16 17:39:18 -04:00
uint32_t stats_sync_lost_cnt ;
2014-04-14 16:23:05 -04:00
uint32_t stats_cng_cnt ;
2013-08-13 17:35:35 -04:00
uint8_t sequence_sample ;
uint8_t predicted_sample ;
float max_err ;
float max_err_hit ;
float max_err_ever ;
uint8_t in_sync ;
uint8_t hangup_on_error ;
uint8_t milliwatt_index ;
uint8_t milliwatt_prediction_index ;
switch_time_t timeout ;
FILE * input_debug_f ;
FILE * output_debug_f ;
2013-08-19 15:43:26 -04:00
switch_timer_t timer ;
2013-08-13 17:35:35 -04:00
} bert_t ;
# define bert_increase_milliwatt_index(index) \
do { \
if ( ( index ) = = ( switch_arraylen ( ulaw_digital_milliwatt ) - 1 ) ) { \
( index ) = 0 ; \
} else { \
( index ) = ( ( index ) + 1 ) ; \
} \
} while ( 0 ) ;
2013-08-06 12:29:26 -04:00
2013-08-16 16:30:10 -04:00
# define bert_close_debug_streams(bert, session) \
do { \
int rc = 0 ; \
if ( bert . input_debug_f ) { \
rc = fclose ( bert . input_debug_f ) ; \
if ( rc ) { \
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Failed to close BERT input debug file! \n " ) ; \
} \
bert . input_debug_f = NULL ; \
} \
if ( bert . output_debug_f ) { \
rc = fclose ( bert . output_debug_f ) ; \
if ( rc ) { \
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Failed to close BERT output debug file! \n " ) ; \
} \
bert . output_debug_f = NULL ; \
} \
} while ( 0 ) ;
2013-08-16 17:39:18 -04:00
# define BERT_STATS_VAR_SYNC_LOST "bert_stats_sync_lost"
# define BERT_STATS_VAR_SYNC_LOST_CNT "bert_stats_sync_lost_count"
2013-08-27 21:07:13 -04:00
# define BERT_EVENT_TIMEOUT "mod_bert::timeout"
# define BERT_EVENT_LOST_SYNC "mod_bert::lost_sync"
2014-04-14 17:18:08 -04:00
# define BERT_EVENT_IN_SYNC "mod_bert::in_sync"
2013-08-27 21:07:13 -04:00
2013-08-05 01:30:44 -04:00
# define BERT_DEFAULT_WINDOW_MS 1000
# define BERT_DEFAULT_MAX_ERR 10.0
# define BERT_DEFAULT_TIMEOUT_MS 10000
SWITCH_STANDARD_APP ( bert_test_function )
2013-08-04 23:49:33 -04:00
{
switch_status_t status ;
switch_frame_t * read_frame = NULL , write_frame = { 0 } ;
switch_codec_implementation_t read_impl = { 0 } ;
switch_channel_t * channel = NULL ;
2013-08-27 21:07:13 -04:00
switch_event_t * event = NULL ;
2013-08-05 01:30:44 -04:00
const char * var = NULL ;
2013-08-04 23:49:33 -04:00
int i = 0 ;
2013-08-05 01:30:44 -04:00
int synced = 0 ;
int32_t timeout_ms = 0 ;
2013-08-19 15:43:26 -04:00
int32_t interval = 20 ;
int32_t samples = 0 ;
uint8_t * write_samples = NULL ;
uint8_t * read_samples = NULL ;
2013-09-03 09:41:51 -04:00
const char * timer_name = NULL ;
2013-08-13 17:35:35 -04:00
bert_t bert = { 0 } ;
memset ( & bert , 0 , sizeof ( bert ) ) ;
2013-08-04 23:49:33 -04:00
channel = switch_core_session_get_channel ( session ) ;
switch_channel_answer ( channel ) ;
switch_core_session_get_read_impl ( session , & read_impl ) ;
2013-08-13 17:35:35 -04:00
if ( read_impl . ianacode ! = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " This application only works when using ulaw codec \n " ) ;
goto done ;
}
2013-08-05 01:30:44 -04:00
bert . window_ms = BERT_DEFAULT_WINDOW_MS ;
bert . window_samples = switch_samples_per_packet ( read_impl . samples_per_second , bert . window_ms ) ;
bert . max_err = BERT_DEFAULT_MAX_ERR ;
timeout_ms = BERT_DEFAULT_TIMEOUT_MS ;
/* check if there are user-defined overrides */
if ( ( var = switch_channel_get_variable ( channel , " bert_window_ms " ) ) ) {
int tmp = atoi ( var ) ;
if ( tmp > 0 ) {
bert . window_ms = tmp ;
bert . window_samples = switch_samples_per_packet ( read_impl . samples_per_second , bert . window_ms ) ;
}
}
if ( ( var = switch_channel_get_variable ( channel , " bert_timeout_ms " ) ) ) {
int tmp = atoi ( var ) ;
if ( tmp > 0 ) {
timeout_ms = tmp ;
}
}
if ( ( var = switch_channel_get_variable ( channel , " bert_max_err " ) ) ) {
double tmp = atoi ( var ) ;
if ( tmp > 0 ) {
bert . max_err = ( float ) tmp ;
}
}
if ( ( var = switch_channel_get_variable ( channel , " bert_hangup_on_error " ) ) ) {
if ( switch_true ( var ) ) {
bert . hangup_on_error = 1 ;
}
}
2013-08-13 17:35:35 -04:00
if ( ( var = switch_channel_get_variable ( channel , " bert_debug_io_file " ) ) ) {
char debug_file [ 1024 ] ;
snprintf ( debug_file , sizeof ( debug_file ) , " %s.in " , var ) ;
bert . input_debug_f = fopen ( debug_file , " w " ) ;
if ( ! bert . input_debug_f ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Failed to open input debug file %s \n " , debug_file ) ;
}
snprintf ( debug_file , sizeof ( debug_file ) , " %s.out " , var ) ;
bert . output_debug_f = fopen ( debug_file , " w " ) ;
if ( ! bert . output_debug_f ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Failed to open output debug file %s \n " , debug_file ) ;
}
}
2013-09-03 09:41:51 -04:00
if ( ( var = switch_channel_get_variable ( channel , " bert_timer_name " ) ) ) {
timer_name = var ;
}
2013-08-05 01:30:44 -04:00
2013-08-19 15:43:26 -04:00
/* Setup the timer, so we can send audio at correct time frames even if we do not receive audio */
2013-09-03 09:41:51 -04:00
if ( timer_name ) {
interval = read_impl . microseconds_per_packet / 1000 ;
samples = switch_samples_per_packet ( read_impl . samples_per_second , interval ) ;
2014-04-14 17:18:08 -04:00
if ( switch_core_timer_init ( & bert . timer , timer_name , interval , samples , switch_core_session_get_pool ( session ) ) = = SWITCH_STATUS_SUCCESS ) {
2013-09-03 09:41:51 -04:00
switch_log_printf ( SWITCH_CHANNEL_LOG , SWITCH_LOG_DEBUG , " Setup timer success interval: %u samples: %u \n " , interval , samples ) ;
} else {
switch_log_printf ( SWITCH_CHANNEL_LOG , SWITCH_LOG_ERROR , " Timer Setup Failed. BERT cannot start! \n " ) ;
goto done ;
}
switch_core_timer_sync ( & bert . timer ) ;
2013-08-19 15:43:26 -04:00
}
2013-08-05 01:30:44 -04:00
bert . timeout = ( switch_micro_time_now ( ) + ( timeout_ms * 1000 ) ) ;
2013-08-04 23:49:33 -04:00
write_frame . codec = switch_core_session_get_read_codec ( session ) ;
write_frame . data = switch_core_session_alloc ( session , SWITCH_RECOMMENDED_BUFFER_SIZE ) ;
write_frame . buflen = SWITCH_RECOMMENDED_BUFFER_SIZE ;
2013-08-13 17:35:35 -04:00
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_DEBUG , " BERT Test Window=%ums/%u, MaxErr=%f%%, Timeout=%dms \n " , bert . window_ms , bert . window_samples , bert . max_err , timeout_ms ) ;
2013-08-05 01:30:44 -04:00
if ( bert . window_samples < = 0 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Failed to compute BERT window samples! \n " ) ;
goto done ;
}
2013-08-16 17:39:18 -04:00
switch_channel_set_variable ( channel , BERT_STATS_VAR_SYNC_LOST_CNT , " 0 " ) ;
switch_channel_set_variable ( channel , BERT_STATS_VAR_SYNC_LOST , " false " ) ;
2013-08-19 15:43:26 -04:00
write_samples = write_frame . data ;
2013-09-03 09:41:51 -04:00
for ( ; ; ) {
if ( ! switch_channel_ready ( channel ) ) {
break ;
}
switch_ivr_parse_all_events ( session ) ;
if ( timer_name ) {
if ( switch_core_timer_next ( & bert . timer ) ! = SWITCH_STATUS_SUCCESS ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Failed to step on timer! \n " ) ;
break ;
}
}
2013-08-19 15:43:26 -04:00
/* Write our frame before anything else happens */
for ( i = 0 ; i < read_impl . samples_per_packet ; i + + ) {
/* Calculate our next sequence sample to write */
bert . sequence_sample = ulaw_digital_milliwatt [ bert . milliwatt_index ] ;
//switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "[%d] 0x%X\n", bert.milliwatt_index, bert.sequence_sample);
bert_increase_milliwatt_index ( bert . milliwatt_index ) ;
//switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "[%d] 0x%X\n", bert.milliwatt_index, bert.sequence_sample);
write_samples [ i ] = bert . sequence_sample ;
}
write_frame . datalen = read_impl . samples_per_packet ;
write_frame . samples = read_impl . samples_per_packet ;
2013-09-03 09:41:51 -04:00
if ( timer_name ) {
write_frame . timestamp = bert . timer . samplecount ;
} else {
/* playback() does not set write_frame.timestamp unless a timer is used, what's the catch? */
}
2013-08-19 15:43:26 -04:00
if ( bert . output_debug_f ) {
fwrite ( write_frame . data , write_frame . datalen , 1 , bert . output_debug_f ) ;
}
status = switch_core_session_write_frame ( session , & write_frame , SWITCH_IO_FLAG_NONE , 0 ) ;
if ( ! SWITCH_READ_ACCEPTABLE ( status ) ) {
break ;
}
2013-08-13 17:35:35 -04:00
2013-09-03 09:41:51 -04:00
/* Proceed to read and process the read frame ...
* Note core_session_read_frame is a blocking operation , we should probably do reathing in another thread like playback ( ) does using switch_core_service_session ( ) */
2013-08-04 23:49:33 -04:00
status = switch_core_session_read_frame ( session , & read_frame , SWITCH_IO_FLAG_NONE , 0 ) ;
if ( ! SWITCH_READ_ACCEPTABLE ( status ) ) {
break ;
}
2013-08-13 17:35:35 -04:00
if ( bert . timeout & & ! synced ) {
switch_time_t now = switch_micro_time_now ( ) ;
if ( now > = bert . timeout ) {
2013-08-27 21:07:13 -04:00
bert . timeout = 0 ;
2013-08-16 16:30:10 -04:00
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_WARNING , " BERT Timeout (read_samples=%d, read_bytes=%d, expected_samples=%d, session=%s) \n " ,
2013-08-13 17:35:35 -04:00
read_frame - > samples , read_frame - > datalen , read_impl . samples_per_packet , switch_core_session_get_uuid ( session ) ) ;
2013-08-27 21:07:13 -04:00
if ( switch_event_create_subclass ( & event , SWITCH_EVENT_CUSTOM , BERT_EVENT_TIMEOUT ) = = SWITCH_STATUS_SUCCESS ) {
switch_channel_event_set_basic_data ( channel , event ) ;
switch_event_fire ( & event ) ;
}
2013-08-13 17:35:35 -04:00
if ( bert . hangup_on_error ) {
switch_channel_hangup ( channel , SWITCH_CAUSE_MEDIA_TIMEOUT ) ;
}
}
}
2014-04-14 01:28:38 -04:00
/* Treat CNG as silence */
if ( switch_test_flag ( read_frame , SFF_CNG ) ) {
read_frame - > samples = read_impl . samples_per_packet ;
read_frame - > datalen = read_impl . samples_per_packet ;
memset ( read_frame - > data , G711_ULAW_IDLE_OCTET , read_frame - > datalen ) ;
2014-04-14 16:23:05 -04:00
bert . stats_cng_cnt + + ;
2013-08-13 17:35:35 -04:00
}
2014-04-14 01:28:38 -04:00
if ( read_frame - > samples ! = read_impl . samples_per_packet | | ! read_frame - > datalen ) {
2013-08-19 15:43:26 -04:00
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Read %d samples, expected %d! \n " , read_frame - > samples , read_impl . samples_per_packet ) ;
2013-08-13 17:35:35 -04:00
continue ;
}
2013-08-04 23:49:33 -04:00
read_samples = read_frame - > data ;
2013-08-16 16:30:10 -04:00
if ( bert . input_debug_f ) {
size_t ret = fwrite ( read_frame - > data , read_frame - > datalen , 1 , bert . input_debug_f ) ;
if ( ret ! = 1 ) {
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_ERROR , " Failed to write to BERT input debug file! \n " ) ;
2013-08-13 17:35:35 -04:00
}
}
/* BERT Sync Loop */
for ( i = 0 ; i < read_frame - > samples ; i + + ) {
2013-08-05 01:30:44 -04:00
if ( bert . window_samples = = bert . processed_samples ) {
2013-08-16 17:39:18 -04:00
float err = 0.0 ;
/* If the channel is going down, then it is expected we'll have errors, ignore them and bail out */
if ( ! switch_channel_ready ( channel ) ) {
bert_close_debug_streams ( bert , session ) ;
break ;
}
2013-08-05 01:30:44 -04:00
/* Calculate error rate */
2013-08-16 17:39:18 -04:00
err = ( ( float ) ( ( float ) bert . err_samples / ( float ) bert . processed_samples ) * 100.0 ) ;
2013-08-05 01:30:44 -04:00
if ( err > bert . max_err ) {
if ( bert . in_sync ) {
bert . in_sync = 0 ;
2013-08-16 17:39:18 -04:00
bert . stats_sync_lost_cnt + + ;
2014-04-14 16:23:05 -04:00
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_WARNING , " BERT Sync Lost: %f%% loss (count=%u, cng_count=%d, err_samples=%u, session=%s) \n " ,
err , bert . stats_sync_lost_cnt , bert . stats_cng_cnt , bert . err_samples , switch_core_session_get_uuid ( session ) ) ;
2013-08-16 17:39:18 -04:00
switch_channel_set_variable_printf ( channel , BERT_STATS_VAR_SYNC_LOST_CNT , " %u " , bert . stats_sync_lost_cnt ) ;
switch_channel_set_variable ( channel , BERT_STATS_VAR_SYNC_LOST , " true " ) ;
2013-08-27 21:07:13 -04:00
if ( switch_event_create_subclass ( & event , SWITCH_EVENT_CUSTOM , BERT_EVENT_LOST_SYNC ) = = SWITCH_STATUS_SUCCESS ) {
switch_channel_event_set_basic_data ( channel , event ) ;
switch_event_fire ( & event ) ;
}
2013-08-05 01:30:44 -04:00
if ( bert . hangup_on_error ) {
switch_channel_hangup ( channel , SWITCH_CAUSE_MEDIA_TIMEOUT ) ;
2013-08-16 16:30:10 -04:00
bert_close_debug_streams ( bert , session ) ;
2013-08-05 01:30:44 -04:00
}
}
} else if ( ! bert . in_sync ) {
bert . in_sync = 1 ;
synced = 1 ;
2013-08-06 12:29:26 -04:00
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_INFO , " BERT Sync Success \n " ) ;
2014-04-14 16:23:05 -04:00
bert . stats_cng_cnt = 0 ;
2013-08-05 01:30:44 -04:00
bert . timeout = 0 ;
2014-04-14 17:18:08 -04:00
if ( switch_event_create_subclass ( & event , SWITCH_EVENT_CUSTOM , BERT_EVENT_IN_SYNC ) = = SWITCH_STATUS_SUCCESS ) {
switch_channel_event_set_basic_data ( channel , event ) ;
switch_event_fire ( & event ) ;
}
2013-08-05 01:30:44 -04:00
}
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_DEBUG10 , " Err=%f%% (%u/%u) \n " , err , bert . err_samples , bert . processed_samples ) ;
if ( synced & & err > bert . max_err_hit ) {
bert . max_err_hit = err ;
2013-08-04 23:49:33 -04:00
}
2013-08-05 01:30:44 -04:00
if ( err > bert . max_err_ever ) {
bert . max_err_ever = err ;
2013-08-04 23:49:33 -04:00
}
2013-08-05 01:30:44 -04:00
bert . processed_samples = 0 ;
bert . err_samples = 0 ;
2013-08-04 23:49:33 -04:00
}
2013-08-13 17:35:35 -04:00
2013-08-05 01:30:44 -04:00
if ( bert . predicted_sample ! = read_samples [ i ] ) {
bert . err_samples + + ;
2013-08-13 17:35:35 -04:00
if ( ! bert . in_sync ) {
/* If we're not in sync, we must reset the index on error to start the pattern detection again */
bert . milliwatt_prediction_index = 0 ;
}
2013-08-05 01:30:44 -04:00
}
2013-08-13 17:35:35 -04:00
/* Try to guess what the next sample will be in the milliwatt sequence */
bert . predicted_sample = ulaw_digital_milliwatt [ bert . milliwatt_prediction_index ] ;
bert_increase_milliwatt_index ( bert . milliwatt_prediction_index ) ;
2013-08-05 01:30:44 -04:00
bert . processed_samples + + ;
2013-08-04 23:49:33 -04:00
}
}
2013-08-05 01:30:44 -04:00
done :
2013-08-16 16:30:10 -04:00
bert_close_debug_streams ( bert , session ) ;
2013-08-19 15:43:26 -04:00
if ( bert . timer . interval ) {
switch_core_timer_destroy ( & bert . timer ) ;
}
2013-08-05 01:30:44 -04:00
switch_log_printf ( SWITCH_CHANNEL_SESSION_LOG ( session ) , SWITCH_LOG_DEBUG , " BERT Test Completed. MaxErr=%f%% \n " , synced ? bert . max_err_hit : bert . max_err_ever ) ;
2013-08-04 23:49:33 -04:00
}
SWITCH_MODULE_LOAD_FUNCTION ( mod_bert_load )
{
switch_application_interface_t * app_interface = NULL ;
* module_interface = switch_loadable_module_create_module_interface ( pool , modname ) ;
2013-08-05 01:30:44 -04:00
SWITCH_ADD_APP ( app_interface , " bert_test " , " Start BERT Test " , " Start BERT Test " , bert_test_function , " " , SAF_NONE ) ;
2013-08-04 23:49:33 -04:00
return SWITCH_STATUS_SUCCESS ;
}
SWITCH_MODULE_SHUTDOWN_FUNCTION ( mod_bert_shutdown )
{
return SWITCH_STATUS_UNLOAD ;
}
/* For Emacs:
* Local Variables :
* mode : c
* indent - tabs - mode : nil
* tab - width : 4
* c - basic - offset : 4
* End :
* For VIM :
* vim : set softtabstop = 4 shiftwidth = 4 tabstop = 4 :
*/