An Example of a Simple Application Written in C/C++
To illustrate the huge learning curve and code maintenance required to write applications in low level languages such as C or C++, below is the source code from a simple application written in C that has similar functionality the the 50 line example given for the Telecom Engine:
//////////////////////////////////////////////////////////////////////////////////////// // // example.c - Simple voting app. Play menu, get DTMF, display result.. // // Main entry point for this application. Links in the required modules for the // application, i.e. call-control, switching and Prosody. // //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// // // Standard C Includes // //////////////////////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <stdlib.h> ////////////////////////////////////// // // V6 API Header File Include // Statements // ////////////////////////////////////// #include "acu_type.h" #include "res_lib.h" #include "cl_lib.h" #include "sw_lib.h" #include "smdrvr.h" #include "smrtp.h" #include "iptel_lib.h" #include "mg_lib.h" #include "eventset.h" ////////////////////////////////////// // SDK Header File Include // Statements ////////////////////////////////////// #include "acu_bin.h" #include "acu_os_port.h" #include "switching.h" #include "call_control.h" #include "prosody_common.h" #include "prosody.h" #include "card.h" #include "smbesp.h" // Basic Speech Processing functionallity #include "smhlib.h" // High-level Speech functionallity #include "smwavlib.h" // Wav file replay/record funtions #include "eventset.h" #include "settings.h" #include "vmp_wrapper.h" ACU_INT setup_display( void ); void ShutdownSystem( void ); void setup_prosody( void ); void free_prosody_cs( void ); //global head of a linked list of card_data structs ACU_QQ_HDR CARD_LIST; ACU_INT main( void ) { ACU_ERR result = 0; ACU_UINT this_card = 0; CARD_DATA* card_data = NULL; ACU_SNAPSHOT_PARMS snapshot_parms; INIT_ACU_STRUCT( &snapshot_parms ); //create the conn_calls_cs critical section result = init_call_cs(); if( result != 0 ) { printf("failed to create conn_call_cs critical section\n"); return result; } //setup all the H100 resources for the system result = init_h100_resources(); if( result != 0 ) { printf("h100 resource setup failed\n"); return result; } result = setup_display(); if( result != 0 ) { printf("Error: Could not set-up display\n"); return result; } //allocate the global critical section setup_prosody(); //first determine the number of cards in the system and their serial numbers result = acu_get_system_snapshot( &snapshot_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to get system snapshot" ); return result; } //////////////////////////////////////////////////// // // For each card we have detected carry out the following: // 1. allocate memory for the card_data struct // 2. Start the card - includes starting call control and prosody activity // 3. Add the card data struct to the CARDS linked list // ///////////////////////////////////////////////////// for( this_card = 0; this_card < snapshot_parms.count; this_card++ ) { card_data = acu_os_alloc( sizeof(CARD_DATA) ); if( card_data == NULL ) { acu_os_printf_at(ERROR_COLUMN, ERROR_ROW, "Error : Unable to alloc memory for card_Data"); return ERR_ACU_OS_NO_MEMORY; } StartCard(card_data, snapshot_parms.serial_no[this_card]); //add the card to the global linked list of cards acu_qq_add_tail( &CARD_LIST, &card_data->node ,card_data ); } acu_os_printf_at( 1, 1, "System initialised and Ready" ); //Wait here for a key to be pressed to indicate the application should be shutdown acu_os_get_next_keypress(); //Function which shutdowns the system in an orderly fashion ShutdownSystem(); //free the critical section free_call_cs(); //free the prosody resource cs free_prosody_cs(); free_h100_resources(); acu_os_uninitialise_screen(); return result; } ////////////////////////////////////////////////////////////////////// // // setup_display // // Set-up the on screen display. // // Returns 0 for success, or a negative value indicating the error. // ////////////////////////////////////////////////////////////////////// ACU_INT setup_display( void ) { ACU_ERR result = 0; result = acu_os_initialise_screen(); if ( result != 0 ) { printf("Error: Could not initialise screen\n"); } else { // Initialised screen, now clear it: acu_os_clear_scr(); // Set-up the data for our display acu_os_printf_at( CONTESTANT_COLUMN, VOTE_ROW, "Gareth Gates"); acu_os_printf_at( CONTESTANT_COLUMN, VOTE_ROW + 1, "Darius"); acu_os_printf_at( CONTESTANT_COLUMN, VOTE_ROW + 2, "So Solid Crew"); acu_os_printf_at( CONTESTANT_COLUMN, VOTE_ROW + 3, "Will Young"); acu_os_printf_at( CONTESTANT_COLUMN, VOTE_ROW + 4, "Celiene Dion"); acu_os_printf_at( VOTE_COLUMN, VOTE_ROW, "0"); acu_os_printf_at( VOTE_COLUMN, VOTE_ROW + 1, "0"); acu_os_printf_at( VOTE_COLUMN, VOTE_ROW + 2, "0"); acu_os_printf_at( VOTE_COLUMN, VOTE_ROW + 3, "0"); acu_os_printf_at( VOTE_COLUMN, VOTE_ROW + 4, "0"); acu_os_printf_at( ACTIVE_CALL_COLUMN, ACTIVE_CALL_ROW, "Active calls:"); acu_os_printf_at( ACTIVE_CALL_COUNT_COLUMN, ACTIVE_CALL_ROW, "0"); } return result; } ////////////////////////////////////////////////////////////////////// // // ShutdownSystem // // For each card in the system calls the ShutdownCard function. Once the // card has been shutdown frees memory associated with the card. // ////////////////////////////////////////////////////////////////////// void ShutdownSystem( void ) { CARD_DATA* card_data = NULL; //obtain the head of the card linked list card_data = acu_qq_get_head( &CARD_LIST ); /*Logic for the loop below: while the card data pointer is not equal to NULL: (if the pointer is equal to NULL we have removed all cards from the system and should exit the loop) Shutdown the card Get the next card from the linked list*/ while( card_data != NULL ) { ShutdownCard( card_data ); acu_os_free( card_data ); card_data = acu_qq_get_head( &CARD_LIST ); } //destroy the critical section protecting the vote array destroy_vote_cs(); } ////////////////////////////////////////////////////////////////////// // // setup_prosody // // This function allocates a critical section which is used to access // the linked list of prosody resources // ////////////////////////////////////////////////////////////////////// void setup_prosody( void ) { free_resource_lock = acu_os_create_critical_section(); if( free_resource_lock == NULL ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to create free_resource_lock critical section"); exit( 1 ); } vote_lock = acu_os_create_critical_section(); if( vote_lock == NULL ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to create free_resource_lock critical section"); exit( 1 ); } } //free the gloabl critical section void free_prosody_cs( void ) { acu_os_destroy_critical_section( free_resource_lock ); } //////////////////////////////////////////////////////////////////////////////// // // // // The following contains all of the functions for handlgin call control on a card // i.e. // - opening an incoming call // - handling the events generated by these calls // //////////////////////////////////////////////////////////////////////////////// #define EVENT_HANDLER_WAIT 500 // Length of time we should wait for a call-control event #define MAX_TIMESLOTS 32 //maximum number of timeslots #define MAX_PORTS 1 //the number of ports to be used for this application ////////////////////////////////////////////////////////////////////// // // init_call_control // // This function determines the number of ports on a card. Then // opens the port for calls and spawns a port_call_control_thread // // Return 0 for success - a negative value indicating an error // ////////////////////////////////////////////////////////////////////// ACU_INT init_call_control( CARD_DATA* card_data ) { ACU_ERR result = 0; ACU_UINT this_port = 0; ACU_INT first_ip_port = 0; ACU_INT max_ip_channels_port_port = 0; CARD_INFO_PARMS card_info_parms; INIT_ACU_STRUCT( &card_info_parms ); card_info_parms.card_id = card_data->card_id; //obtain information about the card result = call_get_card_info( &card_info_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to get card information %d", result ); return result; } //record the number of ports on the card card_data->num_of_ports = card_info_parms.ports; /* For each port on this card carry out the following: 1. Open the port 2. Spawn a thread which controls the call control for the port 3. Then add the port_data struct to the tail PORTS linked list */ for( this_port = 0; this_port < card_data->num_of_ports; this_port++ ) { ACU_ERR result = 0; ACU_OS_THREAD* thread_id = NULL; OPEN_PORT_PARMS open_port; PORT_INFO_PARMS port_info; PORT_DATA* port_data = NULL; INIT_ACU_STRUCT( &open_port ); INIT_ACU_STRUCT( &port_info ); //allocate memory for the port_data struct port_data = acu_os_alloc( sizeof(PORT_DATA) ); open_port.port_ix = this_port; open_port.card_id = card_data->card_id; //open the port so we get a port_id which can be used with the API result = call_open_port( &open_port ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to open port %d", result ); return result; } //record the port_id, port_ix and terminate flag values port_data->port_id = open_port.port_id; port_data->port_ix = this_port; port_data->terminate = ACU_OS_FALSE; port_data->call_direction = IN_CALL; //specify the direction of calls on this port port_data->num_calls_to_open = 0;//specify the number of calls to open on this port - 0 meaning open all possible calls port_info.port_id = port_data->port_id; result = call_port_info ( &port_info ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Unable to obtain port info for port <%x> on card <%x>", port_data->port_id, card_data->card_id ); return result; } port_data->port_type = port_info.port_type; //check if this is an IP Port if( port_data->port_type == ACU_PORT_CAP_IP ) { if( first_ip_port == 0 ) { //check if there is another port on the card must be an IP Port so we will have to half the number of channels we are using if( card_data->num_of_ports - (this_port+1) ) { max_ip_channels_port_port = port_info.channel_count / 2; } else { //there is only one IP Port it will use all the channels on this card max_ip_channels_port_port = port_info.channel_count; } } //work out if the port is H323 or SIP port_data->ip_port_type = call_type( port_data->port_id ); //increment this counter as we have dealt with the first ip port now first_ip_port++; } if( port_info.port_type == ACU_PORT_CAP_IP ) { port_data->num_calls_to_open = max_ip_channels_port_port; } /* Spawn a thread which handles the call control for this port */ thread_id = acu_os_create_thread(port_call_control_thread, port_data ); if( thread_id == NULL) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to start port call control thread" ); } else { //record the thread_id of the port_call_control_thread we have jus spawned port_data->thread_id = thread_id; //add the port_data struct to the tail of the PORTS linked list acu_qq_add_tail( &card_data->PORTS, &port_data->node, port_data ); } } return result; } ////////////////////////////////////////////////////////////////////// // // port_call_control_thread // // // This is the call-control thread that is responsible for handling // all of the call-control events that occur on a port. // The thread determines which call that an event is for and performs // the appropriate action, e.g. accepting an incoming call. // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// ACU_INT port_call_control_thread( void* param ) { ACU_ERR result = 0; ACU_INT calls_active = 0; // Number of calls currently active in the system ACU_INT timeslot = 0; ACU_INT connected_calls = 0; // The number of connected calls in the system ACU_SDK_WAIT_OBJECT* queue_wait_object = NULL; STATE_XPARMS event_parms; ACU_QUEUE_WAIT_OBJECT_PARMS queue_wo_parms; CALL_DATA *call_data = NULL; // Call data of current call //conver the argument into something meaningful PORT_DATA* port_data = (PORT_DATA*)param; INIT_ACU_STRUCT( &event_parms ); INIT_ACU_STRUCT( &queue_wo_parms ); /* Allocate an event queue for the port */ result = allocate_port_event_queue( port_data ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to allocate port event queue" ); return result; } /* Set the previously allocate port event queue to be the default event queue for this port */ result = set_default_port_queue( port_data ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to set default port queue" ); return result; } /* Obtain an operating system specific wait object for the event queue we have just allocated */ queue_wo_parms.queue_id = port_data->queue_id; result = acu_get_event_queue_wait_object( &queue_wo_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN , ERROR_ROW, "Error: Unable to obtain a port queue wait object" ); return result; } /* Convert the OS specific wait object to an ACU_SDK_WAIT_OBJECT */ queue_wait_object = acu_os_convert_wait_object( queue_wo_parms.wait_object ); if( queue_wait_object == NULL ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW , "Error: Unable to convert wait objects" ); } /* Open all calls on the port */ //pass in the direction of the calls and the number of calls calls_active = open_calls_on_port( port_data ); /* Now wait for events on the port until there are no more active calls */ while( calls_active != 0 ) { //wait here until an event occurs on the port result = acu_os_wait_for_wait_object( queue_wait_object, EVENT_HANDLER_WAIT ); if( result == ERR_ACU_OS_NO_ERROR ) { //wait object has been signalled - there is an event on the queue that needs to be collected call_data = get_call_data_from_queue( port_data ); //if call_data is NULL we have not been able to obtain the call_data token if(call_data == NULL ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to get call_data struct from event queue " ); //fatal error - cannot recover from this exit( 1 ); } event_parms.handle = call_data->handle; //obtain the actual event that has occured result = call_event( &event_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Call event failed <%d>", result ); exit( 1 ); } else { //if the handle is zero (which isn't valid) break and return to the top of the loop if( event_parms.handle == 0 ) { continue; } switch( event_parms.state ) { case EV_WAIT_FOR_OUTGOING: acu_os_printf_at( ERROR_COLUMN, ERROR_ROW -3, "wait out" ); break; case EV_OUTGOING_PROCEEDING: // The out going call is proceeding acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "proceed"); break; case EV_OUTGOING_RINGING: // Outgoing call got ring tone acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "ringing"); break; case EV_WAIT_FOR_INCOMING: //wait for an incoming call acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "wait in"); break; case EV_INCOMING_CALL_DET: //an incoming call has been detected acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "in det"); result = get_call_details( call_data ); if ( result != 0 ) { // Failed getting details, clear call disconnect_call( call_data ); } else { // Got the call's details now we'll call "alert_call" to // determine what to do next: result = alert_call( call_data, port_data ); if ( result != 0 ) { // Failed alerting call, clear call disconnect_call( call_data ); } } break; case EV_WAIT_FOR_ACCEPT: //wait for accept acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "accept"); //accept the call result = accept_call( call_data ); if ( result != 0 ) { // Couldn't accept call, clear call: disconnect_call( call_data ); } break; case EV_DETAILS: acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "details"); // More call details have arrived (usually CLI/DDI digits) // Get the call's details: result = get_call_details( call_data ); if ( result != 0 ) { // Failed getting details, clear call disconnect_call( call_data ); } else { if( port_data->call_direction == IN_CALL ) { // Got the call's details now we'll call "alert_call" to // determine what to do next: result = alert_call( call_data, port_data ); if ( result != 0 ) { // Failed alerting call, clear call disconnect_call( call_data ); } } } break; case EV_CALL_CONNECTED: acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "connect"); ////////////////////////////////////////////////////////// // // This call has moved to the connected state, so get the // call's details and connect to a free Prosody resource // thread. // ////////////////////////////////////////////////////////// result = get_call_details( call_data ); if ( result != 0 ) { // If we cannot get the details then we'll be unable to // connect to the Prosody resources, so clear the call disconnect_call( call_data ); } else { // Incremented the connected calls' count: ++connected_calls; call_data->connected = ACU_OS_TRUE; // Connect to a Prosody resource: result = start_prosody( call_data, port_data ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to associate call with Prosody Resource" ); disconnect_call( call_data ); } else { // Display the new count acu_os_printf_at( ACTIVE_CALL_COUNT_COLUMN, ACTIVE_CALL_ROW, "%d", connected_calls); } } break; case EV_REMOTE_DISCONNECT: // Far-end user has hung-up. acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "rem disc"); // Now we can initiate call clearing: disconnect_call( call_data ); break; case EV_IDLE: acu_os_printf_at( ERROR_COLUMN, ERROR_ROW - 3, "idle "); //disconnect our call from the prosody resource stop_prosody( call_data ); //one less active call --calls_active; //release the call release_call( call_data ); if( call_data->connected == ACU_OS_TRUE ) { //this call was connected so one less connected call now --connected_calls; //display the new connected call count acu_os_printf_at( ACTIVE_CALL_COUNT_COLUMN, ACTIVE_CALL_ROW, "%d", connected_calls ); } //check the state of the terminate flag to see if the call should be reopened or not if ( port_data->terminate == ACU_OS_FALSE ) { // Record the last call's timeslot: timeslot = call_data->ts; //need to remove the call from the linked list acu_qq_extract( &port_data->CALLS, &call_data->node ); //clear the call data struct here memset( call_data, 0, sizeof( CALL_DATA )); //assign the timeslot that the previous call was on call_data->ts = timeslot; if( port_data->call_direction == IN_CALL ) { result = open_call_incoming( call_data, port_data ); } if( port_data->call_direction == OUT_CALL ) { result = open_call_outgoing( call_data, port_data ); } if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Couldn't open call %d %d %d ", port_data->port_id, call_data->handle, result ); exit( 1 ); } ++calls_active; acu_qq_add_tail( &port_data->CALLS, &call_data->node, call_data ); } else { //need to remove the call from the linked list acu_qq_extract( &port_data->CALLS, &call_data->node ); // Application is terminating, so we want to free the memory we allocated // for this call: acu_os_free(call_data); } break; default: acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Unknown event occurred: ignored <%X> H:%X>", event_parms.state, event_parms.handle); break; }//end switch }//end else }//end if }//end while return result; } ////////////////////////////////////////////////////////////////////// // // open_calls_on_port // // This function opens all calls avaliable by checking valid_vector // on a port. // // Return 'active_calls' - the number of calls succesfully opened // ////////////////////////////////////////////////////////////////////// ACU_INT open_calls_on_port( PORT_DATA* port_data ) { ACU_ERR result = 0; ACU_INT this_ts = 0; ACU_INT active_calls = 0; ACU_INT max_ts = MAX_TIMESLOTS; ACU_LONG mask = 1; CALL_DATA* call_data = NULL; PORT_INFO_PARMS port_info_parms; INIT_ACU_STRUCT(&port_info_parms); port_info_parms.port_id = port_data->port_id; result = call_port_info(&port_info_parms); if(result != 0) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to obtain valid vector info <%d>",result ); } else { if( port_info_parms.port_type == ACU_PORT_CAP_IP ) { max_ts = port_data->num_calls_to_open; } for(this_ts = 0; this_ts < max_ts; this_ts++) { //check the valid_vector to see if this is a valid timeslot on which to make a call //only if this is a CCS/CAS port not valid for VoIP if( port_data->port_type != ACU_PORT_CAP_IP ) { if( !( port_info_parms.valid_vector & mask ) ) { //invalid timeslot adjust the mask and go back to the top of the loop mask <<= 1L; continue; } } result = 0; call_data = acu_os_alloc( sizeof(CALL_DATA) ); if( port_data->port_type == ACU_PORT_CAP_IP ) { //populate the IP Props here call_data->ts = -1; call_data->ip = ACU_OS_TRUE; } else { call_data->ts = this_ts; } if( port_data->call_direction == IN_CALL ) { result = open_call_incoming( call_data, port_data ); } if( port_data->call_direction == OUT_CALL ) { result = open_call_outgoing( call_data, port_data ); } if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not open call on port %d ts %d result %d", port_data->port_ix, call_data->ts, result ); return result; } if ( port_data->port_type != ACU_PORT_CAP_IP ) { // Need to adjust timeslot mask: mask <<= 1L; } //add the call to the CALLS linked list for this port acu_qq_add_tail( &port_data->CALLS, &call_data->node, call_data ); //one more active call ++active_calls; if( active_calls == port_data->num_calls_to_open ) { return active_calls; } } } if( result == 0) { return active_calls; } return result; } ////////////////////////////////////////////////////////////////////// // // open_call_outgoing // // This function is a wrapper around the 'call_openin' function. // If this function succeds the call will enter the wait for incoming // state // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// ACU_INT open_call_outgoing( CALL_DATA* call_data, PORT_DATA* port_data ) { ACU_ERR result = 0; OUT_XPARMS open_parms; INIT_ACU_STRUCT( &open_parms ); open_parms.net = port_data->port_id; //port_id of the port this call will take place on open_parms.ts = call_data->ts; //timeslot the call will take place on open_parms.queue_id = port_data->queue_id; //queue that any call events will occur on for this call handle open_parms.app_context_token = (ACU_ACT)call_data; //application context token that will be returned when an event occurs on this call handle open_parms.cnf = CNF_REM_DISC; //flag which allows the remote disconnect event to be generated strcpy( open_parms.destination_addr, "1234" ); //Number we are calling result = call_openout( &open_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error; Unable to open incoming call <%d>", result ); return result; } //record the handle this call has been allocated call_data->handle = open_parms.handle; return result; } ////////////////////////////////////////////////////////////////////// // // open_call_incoming // // This function is a wrapper around the 'call_openin' function. // If this function succeds the call will enter the wait for incoming // state // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// ACU_INT open_call_incoming( CALL_DATA* call_data, PORT_DATA* port_data ) { ACU_ERR result = 0; IN_XPARMS open_parms; INIT_ACU_STRUCT( &open_parms ); open_parms.net = port_data->port_id; open_parms.ts = call_data->ts; open_parms.queue_id = port_data->queue_id; //queue that any call events will occur on for this call handle open_parms.app_context_token = (ACU_ACT)call_data; //application context token that will be returned when an event occurs on this call handle open_parms.cnf = CNF_REM_DISC; //set the flag so the EV_REMOTE_DISCONNECT event can be generated result = call_openin( &open_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error; Unable to open incoming call <%d>", result ); return result; } //record the handle this call has been allocated call_data->handle = open_parms.handle; return result; } ////////////////////////////////////////////////////////////////////// // // allocate_port_event_queue // // This function allocates an event queue for a port // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// ACU_INT allocate_port_event_queue( PORT_DATA* port_data ) { ACU_ERR result = 0; ACU_ALLOC_EVENT_QUEUE_PARMS alloc_q_parms; INIT_ACU_STRUCT(&alloc_q_parms); result = acu_allocate_event_queue(&alloc_q_parms); if(result != 0) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to create port event queue" ); return result; } else { //record the queue_id that has been allocated by the API port_data->queue_id = alloc_q_parms.queue_id; return result; } } ////////////////////////////////////////////////////////////////////// // // free_port_event_queue // // This function frees an event queue previously allocated for a port // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// ACU_INT free_port_event_queue( PORT_DATA* port_data ) { ACU_ERR result = 0; ACU_FREE_EVENT_QUEUE_PARMS free_q_parms; INIT_ACU_STRUCT( &free_q_parms ); free_q_parms.queue_id = port_data->queue_id; result = acu_free_event_queue( &free_q_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to free port event queue <%d>", result ); return result; } return result; } ////////////////////////////////////////////////////////////////////// // // set_default_port_queue // // This function sets the default event queue for a port // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// ACU_INT set_default_port_queue( PORT_DATA* port_data ) { ACU_ERR result = 0; ACU_QUEUE_PARMS queue_parms; INIT_ACU_STRUCT( &queue_parms ); queue_parms.queue_id = port_data->queue_id; queue_parms.resource_id = port_data->port_id; //set the default event queue for call control events for this port result = call_set_port_default_handle_event_queue(&queue_parms); if(result != 0) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to set default port queue" ); return result; } return result; } ////////////////////////////////////////////////////////////////////// // // get_call_data_from_queue // // This function gets the call_data token from associated with the // event which has just occured on the port event queue // // Return valid CALL_DATA if successfull, otherwise return NULL // ////////////////////////////////////////////////////////////////////// CALL_DATA* get_call_data_from_queue( PORT_DATA* port_data ) { ACU_ERR result = 0; ACU_EVENT_QUEUE_PARMS event_queue_parms; CALL_DATA* call_data = NULL; INIT_ACU_STRUCT( &event_queue_parms ); event_queue_parms.queue_id = port_data->queue_id; //get the event from the event queue result = acu_get_event_from_queue( &event_queue_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to get event from queue <%d> ", result ); //return NULL if there has been an error return call_data; } //dereference the context token to a call_data struct call_data = (CALL_DATA*)event_queue_parms.context; //return the call_data struct we have got from the event queue return call_data; } ////////////////////////////////////////////////////////////////////// // // get_call_details // // Get the call details of the specified call. If the 'valid' flag // is set, then the call's "call_io_data" structure is set-up. // If there 'destination_addr' field is set-up for CCS/CAS calls, // then "num_digits" field is set-up with its length. // ////////////////////////////////////////////////////////////////////// ACU_INT get_call_details( CALL_DATA *call_data ) { DETAIL_XPARMS call_info; ACU_INT result = 0; INIT_ACU_STRUCT( &call_info ); call_info.handle = call_data->handle; call_info.timeout = 0; // Return immediatley from API call result = call_details( &call_info ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not get call's details H:%X <%d>", call_data->handle, result); } else { // Got some details, see if we can do anything with them: if ( call_info.valid == 1 ) { // This means that the timeslot value can be used, so set-up // the call's stream & timeslot: call_data->ts = call_info.ts; call_data->stream = call_info.stream; } // This is a CCS or CAS call, so check the destination address: if ( call_info.destination_addr[0] != '\0' ) { // Number isn't null, so get it's length call_data->num_digits = strlen( call_info.destination_addr ); } // Finally, check if "sending_complete" is set. This means that the call has // sent all the digits that it's going to, so we may as well accept the call: if ( call_info.sending_complete != 0 ) { // We've received all the digits, so adjust num_digits to DDI_DIGITS, // this will cause the call to be accepted immediatley. call_data->num_digits = DDI_DIGITS; } } return result; } ////////////////////////////////////////////////////////////////////// // // alert_call // // This function is a wrapper around the call_incoming_ringing. As // "call_incoming_ringing" stops anymore digits being recieved we // need to determine if we've received enough information for us to // accept the call. This is mainly used by CAS protocol as they // do not support en bloc sending. // // Return if the function succeeds, otherwise return a negative error // ////////////////////////////////////////////////////////////////////// ACU_INT alert_call( CALL_DATA *call_data, PORT_DATA* port_data ) { ACU_ERR result = 0; INCOMING_RINGING_XPARMS ringing_parms; INIT_ACU_STRUCT( &ringing_parms ); /////////////////////////////////////////////////////////////////////// // // This is a CCS/CAS call, check the number of digits we've recieved // NOTE: The value of DDI_DIGITS is an arbitrary value, in "real-life" // circumstances this limit would be set by the switch provider. // /////////////////////////////////////////////////////////////////////// if( !call_data->ip ) { if ( call_data->num_digits >= DDI_DIGITS ) { // We have enough digits, so stop anymore being sent: result = call_incoming_ringing( call_data->handle ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Failed at call_incoming_ringing <%d>", result); } } } else { ringing_parms.handle = call_data->handle; if( COMPANDING == COMPANDING_ALAW ) { ringing_parms.unique_xparms.sig_iptel.media_settings.tdm_encoding = TDM_ALAW; //if this is a SIP port enable early media support if( port_data->ip_port_type == S_SIP ) { ringing_parms.unique_xparms.sig_iptel.protocol_specific.sig_sip.send_early_media = 1; } } else if (COMPANDING == COMPANDING_MULAW ) { ringing_parms.unique_xparms.sig_iptel.media_settings.tdm_encoding = TDM_ULAW; } result = xcall_incoming_ringing( &ringing_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Failed to call xcall_incoming_ringing <%d>", result ); } } return result; } ////////////////////////////////////////////////////////////////////// // // disconnect_call // // This function is a wrapper around the call_disconnect API call. // When this function is called the specified call begins to clear // down. // ////////////////////////////////////////////////////////////////////// ACU_INT disconnect_call( CALL_DATA *call_data ) { ACU_ERR result = 0; CAUSE_XPARMS disconnect_parms; INIT_ACU_STRUCT( &disconnect_parms ); disconnect_parms.handle = call_data->handle; result = call_disconnect( &disconnect_parms ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not disconnect call H:%X <%d>", call_data->handle, result); } return result; } ////////////////////////////////////////////////////////////////////// // // accept_call // // This function is a wrapper around the 'call_accept' function. // If this function succeeds the call will move to the connected // state. // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// ACU_INT accept_call( CALL_DATA *call_data ) { ACU_ERR result = 0; ACCEPT_XPARMS accept_parms; INIT_ACU_STRUCT( &accept_parms ); accept_parms.handle = call_data->handle; result = xcall_accept( &accept_parms ); if ( result != 0 ) { // Couldn't accept the call, so clear it down: acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not accept call <%d>", result); } return result; } ////////////////////////////////////////////////////////////////////// // // stop_call_control_on_port // // This function sets the terminate flag for the port // Then clears all the calls on the port - then waits for all the // calls to clear down and then destroys the thread // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// void stop_call_control_on_port( PORT_DATA* port_data ) { //first set the terminate flag port_data->terminate = ACU_OS_TRUE; //the above will catch calls that are in the connected state and make sure they are not reopened but we need to call call_disconnect() //on all calls currently open on the port close_all_calls_on_port( port_data ); //wait for the thread to terminate acu_os_wait_for_thread( port_data->thread_id ); //destroy the thread data acu_os_destroy_thread( port_data->thread_id ); } ////////////////////////////////////////////////////////////////////// // // close_all_calls_on_port // // This function disconnects all calls currently active on the port // It recursivly gets the head of the call linked list until it is // empty indicated by card_data == NULL // Then call disconnect_call for each call_data item returned from // the linked list // // Return 0 if successfull, otherwise return a negative error value // ////////////////////////////////////////////////////////////////////// void close_all_calls_on_port( PORT_DATA* port_data ) { //need to get each thing from the head of the linked list and call disconnect_call() CALL_DATA* call_data = NULL; //get the head of the linked list call_data = acu_qq_get_head( &port_data->CALLS ); //disconnect each call in the linked list until all calls have been removed indicated by call_data == NULL while( call_data != NULL ) { disconnect_call( call_data ); call_data = acu_qq_get_head( &port_data->CALLS ); } } ////////////////////////////////////////////////////////////////////// // // release_call // // This function is a wrapper around the call_release API call, and // should only be called when a call has fully cleared down, i.e. // has progress to the IDLE state. // // If call_release is successful, we move the specified call from // the port's active list to the port's free list. // ////////////////////////////////////////////////////////////////////// ACU_INT release_call( CALL_DATA *call_data ) { ACU_ERR result = 0; CAUSE_XPARMS release_parms; INIT_ACU_STRUCT( &release_parms ); // Clear the structure release_parms.handle = call_data->handle; result = call_release( &release_parms ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not release call H:%X <%d>", call_data->handle, result); } return result; } ////////////////////////////////////////////////////////////////////// // // close_port // // This function is a wrapper around the call_close_port API function. // This function is called once the port thread is terminating and // all calls on the port have terminated // // 0 returned for success otherwise a negative value returned for an error // ////////////////////////////////////////////////////////////////////// ACU_INT close_port( PORT_DATA* port_data ) { ACU_INT result = 0; CLOSE_PORT_PARMS close_port; INIT_ACU_STRUCT( &close_port ); close_port.port_id = port_data->port_id; result = call_close_port( &close_port ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to close port <%d>", result ); return result; } return result; } //////////////////////////////////////////////////////////////////////////////// // // // // The following contains all of the functions for controlling the speech processing // resources on a card // i.e. // - allocating a channel // - controlling playback/record/dtmf detection and more // //////////////////////////////////////////////////////////////////////////////// /* * Prosody resource thread state machine defines: */ #define STATE_PLAY_VOTE_PROMPT 0 // Play a prompt and wait for caller vote: #define STATE_WAIT_FOR_VOTE 1 // Wait for caller to vote: #define STATE_PLAY_HANGUP_PROMPT 2 // Play a prompt to infrom caller to hangup #define STATE_WAIT_HANG_UP 3 // Wait for the caller to hang-up #define STATE_COMPLETE 4 // Caller has hung-up #define STATE_APP_EXIT 5 // Application is terminating #define VALID_DIGIT 0 // A valid digit was recognised #define INVALID_DIGIT 1 // An invalid digit was recognised #define NO_DIGIT 2 // No digit was recognised #define VOTE_PROMPT_FILE "../../wav_files/onetofive.wav" // Name (and path) of the file to play // as vote prompt #define HANGUP_PROMPT_FILE "../../wav_files/hangup.wav" // Name (and path) of the file to play #define REPROMPT_DELAY 10000 // Number of milliseconds to wait before replaying a prompt // (10,000 == 10 second delay). /* * pros_res_man * The structure contains the data required for managing the Prosody resource * once the resource has begun it's main purpose (e.g. playing prompts & detecting digits). */ typedef struct _pros_res_man { tSMChannelId channel; // The resource's channel ACU_SDK_WAIT_OBJECT *kill_me; // Signalled if our resource thread is terminating ACU_SDK_WAIT_OBJECT *prompt; // Signalled if the prompt thread has terminated ACU_SDK_WAIT_OBJECT *digits; // Signalled if we've detected DTMF digits ACU_SDK_WAIT_OBJECT *hungup; // Signalled if our call has hung-up tSMEventId *event; // The Prosody event converted to 'digits' wait object ACU_OS_THREAD *prompt_thread_id; // Thread ID of the pompt SM_FILE_REPLAY_PARMS *prompt_parms; // Parameters used by thread to progress prompt } PROS_RES_MAN; ACU_INT setup_detection( PROS_RES_MAN* pros_res_man ); ACU_INT play_prompt_wait_for_vote( PROS_RES_MAN* pros_res_man ); ACU_INT wait_for_vote( PROS_RES_MAN* pros_res_man ); ACU_INT play_prompt_wait_for_hangup( PROS_RES_MAN *pros_res_man ); ACU_INT wait_for_hangup( PROS_RES_MAN *pros_res_man ); ACU_INT start_prompt( PROS_RES_MAN *pros_res_man, char *filename ); ACU_INT prompt_playing( void *parameters ); ACU_INT stop_prompt( PROS_RES_MAN *pros_res_man ); ACU_INT stars_in_their_eyes( PROS_DATA* pros_data ); ACU_INT get_digit( PROS_RES_MAN *pros_res_man ); ACU_OS_BOOL validate_digit( tSM_INT digit ); void update_votes( ACU_INT contestant ); void remove_detection( PROS_RES_MAN *pros_res_man ); ///////////////////////////////////////////////// // // destroy_vote_cs // // This function destroys the critical section // used to protect the votes array // ///////////////////////////////////////////////// void destroy_vote_cs( void ) { acu_os_destroy_critical_section( vote_lock ); } //////////////////////////////////////////////////////////////////// // // prosody_resource_thread // // This is a Prosody thread that is responsible for handling the // DTMF detection, and wav playback for a single channel. // // NOTE: It is also the responsibility of the thread to indicate to // it's caller that the thread is fully initialised. // // Returns 0 for success, or a negative value indicating the error. // ///////////////////////////////////////////////////////////////////// ACU_INT prosody_resource_thread( void* param ) { ACU_ERR result = 0; PROS_DATA* pros_data = (PROS_DATA*)param; MODULE_DATA* module_data = NULL; ACU_SDK_WAIT_OBJECT *resource_events[2]; ACU_OS_BOOL sig_res_events[2]; ACU_OS_BOOL terminate = ACU_OS_FALSE; CALL_2_PROS_PARMS connect_parms; //setup a local copy of the module data which relates to this Prosody thread module_data = pros_data->module_data; result = init_pros_data( pros_data ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not setup Prosody resource data"); } else { //successfully initialised everything, so inform our caller by adding ourselves to the free prosody resources //set_prosody_resource_free( pros_data ); set_prosody_resource_free( pros_data ); //set-up the wait objects resource_events[0] = pros_data->kill_me; //Event 0 is application terminate resource_events[1] = pros_data->call_connected; //Event 1 is caller connected //Now perform the loop until our thread is told to terminate: while( terminate == ACU_OS_FALSE ) { //Wait until we've had a call associated with out thread or the application is terminated result = acu_os_wait_for_any_wait_object( 2, resource_events, sig_res_events, ACU_OS_INFINITE ); if( result != ERR_ACU_OS_NO_ERROR ) { //an error occured in the wait object function, kill our thread acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not wait on connected wait object <%d>", result ); break; } else if( sig_res_events[0] == ACU_OS_TRUE ) { //Application is terminating so set loop exit condition terminate = ACU_OS_TRUE; } else { //We have been associated with a call ( sig_res_events[1] == ACU_OS_TRUE ) //NOTE: This means that this Prosody resource will be removed from the free resource list //to stop any other call from attempting to use this channel INIT_ACU_SM_STRUCT( &connect_parms ); //setup the information we need to connect the call to our Prosody channel connect_parms.call_card_id = pros_data->call_card; connect_parms.call_stream = pros_data->call_stream; connect_parms.call_ts = pros_data->call_ts; connect_parms.pros_channel = pros_data->my_channel; connect_parms.module_card_id = pros_data->module_data->card_id; //card id the module is based on connect_parms.handle = pros_data->call_handle; if( pros_data->using_vmp ) { result = connect_vmp_call_2_prosody( &connect_parms ); } else { result = connect_call_2_prosody( &connect_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not connect call and Prosody <%d>", result ); terminate = ACU_OS_TRUE; } } //start the main purpose for this application the stars in their eyes voting system result = stars_in_their_eyes( pros_data ); if(result != 0 ) { // Either the application is terminating or something critical failed, // so we need to terminate our thread: terminate = ACU_OS_TRUE; } // We'' only get to here if the application is terminating or the caller // has hung-up. In either case we want to break the switch connection // between the caller and our Prosody channel: // Break the switch connections that we set-up: if( pros_data->using_vmp ) { disconnect_vmp_call_from_prosody( &connect_parms, pros_data ); destroy_vmp_context( pros_data->vmprx, pros_data->vmptx ); } else { disconnect_call_from_prosody( &connect_parms ); } pros_data->using_vmp = 0; pros_data->vmprx = NULL; pros_data->vmptx = NULL; // Now that we've broken the switch connections we can add ourselves to // the free resource list: set_prosody_resource_free( pros_data ); } } } // Free the resources that we allocated // NOTE: The application guarantees that in "normal" termination we'll be // able to free our resources before the thread will be killed. release_prosody_channel( pros_data ); // Release the Prosody channel free_pros_data( pros_data ); // Release the events we used return result; } ////////////////////////////////////////////////////////////////////// // // stars_in_their_eyes // // Main function which carries out the voting system // // Returns 0 for success, or a negative value indicating the error. // ////////////////////////////////////////////////////////////////////// ACU_INT stars_in_their_eyes( PROS_DATA* pros_data ) { ACU_ERR result = 0; ACU_INT next_state = STATE_PLAY_VOTE_PROMPT; ACU_OS_BOOL terminate = ACU_OS_FALSE; PROS_RES_MAN pros_res_man; //Set-up the data required for managing this resource's behaviour: //NOTE: 'digits' is set-up by setup_detection() pros_res_man.channel = pros_data->my_channel; pros_res_man.kill_me = pros_data->kill_me; pros_res_man.hungup = pros_data->call_cleared; pros_res_man.prompt = acu_os_create_wait_object(); if( pros_res_man.prompt == NULL ) { //Fatal Error acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not create prompt event, terminating" ); return result; } //Set-up DTMF detection parameters (and digits event) result = setup_detection( &pros_res_man ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not set-up detection parameters, terminating" ); return result; } //Now everything is set-up, set off the state machine: //NOTE: next state is initialised to STATE_PLAY_VOTE_PROMPT while( terminate == ACU_OS_FALSE ) { switch( next_state ) { case STATE_PLAY_VOTE_PROMPT: //Play a prompt and wait for caller vote next_state = play_prompt_wait_for_vote( &pros_res_man ); break; case STATE_WAIT_FOR_VOTE: //wait for the caller to vote next_state = wait_for_vote( &pros_res_man ); break; case STATE_PLAY_HANGUP_PROMPT: //caller voted but not hungup next_state = play_prompt_wait_for_hangup( &pros_res_man ); break; case STATE_WAIT_HANG_UP: //wait for caller to hangup next_state = wait_for_hangup( &pros_res_man ); break; case STATE_COMPLETE: //caller has hungup //drop through case STATE_APP_EXIT: //Drop Through default: //Application is terminating OR we received and invalid state terminate = ACU_OS_TRUE; break; } } //remove the resources we allocated for detection remove_detection( &pros_res_man ); //Free the resources we allocated acu_os_destroy_wait_object( pros_res_man.prompt ); //Set-up the return code based on what our last state was if( next_state == STATE_COMPLETE ) { //our caller hung up so everything was successfull: result = 0; } else { //Either the thread is terminating or something failed. In either case // set-up a negative return code result = -1; } return result; } //////////////////////////////////////////////////////////////////////////////////////// // // setup_detection // // Setup the detection parameters for the given channel. If the detection // is set-up successfully, the function will create a wait object (from a prosody event) // that is associated with the detection criteria // // Returns 0 for sucess, or a negative value indicating the error // ///////////////////////////////////////////////////////////////////////////////////////// ACU_INT setup_detection( PROS_RES_MAN* pros_res_man ) { ACU_ERR result = 0; SM_LISTEN_FOR_PARMS detection_parms; SM_CHANNEL_SET_EVENT_PARMS event_parms; //Cleat the API structs INIT_ACU_SM_STRUCT( &detection_parms ); INIT_ACU_SM_STRUCT( &event_parms ); //////////////////////////////////////////////////////////////////////////////////// // // A brief description of what the following parameters will setup // // - Tone set 0 is the DTMF tone set // - kSMToneDectionMinDuration64 requires that digits be at least 64 milliseconds // to be regarded as valid // - kSMDTMFToneSetDigitMapping causes the recognised tone to be converted into the // equivalent digit // ///////////////////////////////////////////////////////////////////////////////////// detection_parms.channel = pros_res_man->channel; detection_parms.active_tone_set_id = 0; detection_parms.tone_detection_mode = kSMToneDetectionMinDuration64; detection_parms.map_tones_to_digits = kSMDTMFToneSetDigitMapping; result = sm_listen_for( &detection_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not set-up detection parameters <%d>", result ); } else { //Create a Prosody event that will be signalled for recognition events on this channel pros_res_man->event = acu_os_alloc( sizeof(tSMEventId) ); result = smd_ev_create( pros_res_man->event, //the event to create pros_res_man->channel, //the event's channel kSMEventTypeRecog, //the type of event(recognition) kSMChannelSpecificEvent ); //mapping between event & channel if( result != 0 ) { //Fatal Error acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not create Prosody Event <%d>", result ); exit( -1 ); } else { //Created the event, now we've got set it up //NOTE: The following arguments have the same meaning as those in smd_ev_create() event_parms.channel = pros_res_man->channel; event_parms.event = *(pros_res_man->event); event_parms.event_type = kSMEventTypeRecog; event_parms.issue_events =kSMChannelSpecificEvent; result = sm_channel_set_event( &event_parms ); if( result != 0 ) { //Fatal Error acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not associate channel and event <%d>", result ); exit( -1 ); } else { //Finally we can convert the Prosody event to a wait object that we can use in acu_os_wait_for_wait_object() pros_res_man->digits = acu_os_create_wait_object_from_prosody( *(pros_res_man->event) ); if( pros_res_man->digits == NULL ) { //This is a fatal error, we we won't be able to wait on this event acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not conver Prosody event "); exit( -1 ); } } } } return result; } ////////////////////////////////////////////////////////////////////// // // play_prompt_wait_for_vote // // State machine function called from "stars_in_their_eyes". This // function plays a prompt and waits for the caller to select an // option. // // Returns the next state to move to. // ////////////////////////////////////////////////////////////////////// ACU_INT play_prompt_wait_for_vote( PROS_RES_MAN *pros_res_man ) { ACU_INT result = 0; ACU_OS_BOOL got_digit = NO_DIGIT; // VALID_DIGIT, INVALID_DIGIT, NO_DIGIT ACU_INT next_state = STATE_PLAY_VOTE_PROMPT; // Default next state ACU_INT current_state = STATE_PLAY_VOTE_PROMPT; // State we're currently in ACU_SDK_WAIT_OBJECT *pros_events[4]; // Array of the events we want to wait on ACU_OS_BOOL pros_events_sig[4]; // Array that indicates which of our events // were signalled // Start off the prompt: result = start_prompt( pros_res_man, VOTE_PROMPT_FILE ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not start prompt <%d>, terminating", result); exit(1); } // Set-up our wait object array: pros_events[0] = pros_res_man->kill_me; // Wait object 0 is application termination pros_events[1] = pros_res_man->hungup; // Wait object 1 is caller hung-up pros_events[2] = pros_res_man->digits; // Wait object 2 is digits detected pros_events[3] = pros_res_man->prompt; // Wait object 3 is prompt finished // Make sure there are no stale DTMF events on the line result = sm_discard_recognised( pros_res_man->channel ); /////////////////////////////////////////////////////// // // We'll drop out of this while loop if: // a) The caller has made a valid vote // b) The prompt finished // c) The caller hung-up. // // NOTE: Barge in will only occur if the caller makes // a valid vote. // /////////////////////////////////////////////////////// while ( current_state == next_state ) { ///////////////////////////////////////////////////////////////////////////////// // // Wait for any one of our events to be signalled. When this function returns // the entries in 'pros_events_sig' array will be set to indicate whether // or not the corresponding event was signalled // For example, if 'pros_events[2]' signalled then 'pros_events_sig[2]' would be // set to ACU_OS_TRUE. // ///////////////////////////////////////////////////////////////////////////////// result = acu_os_wait_for_any_wait_object( 4, // Number of events we're waiting on pros_events, // The events we're waiting for pros_events_sig, // The events signalled state ACU_OS_INFINITE); // How long to wait if ( result != ERR_ACU_OS_NO_ERROR ) { // This is a fatal error, as there's a chance that at least one of our // wait objects is invalid, safer to drop out. acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Resource thread failed to wait, terminating"); exit(1); } else { // At least one of our wait objects was signalled, find out which one(s) if ( pros_events_sig[0] == ACU_OS_TRUE ) { // Application is terminating: next_state = STATE_APP_EXIT; } else if ( pros_events_sig[1] == ACU_OS_TRUE ) { // Caller has hung-up, clear down normally next_state = STATE_COMPLETE; } else if ( pros_events_sig[2] == ACU_OS_TRUE ) { // We've detected a digit, collect it and determine if it's valid. got_digit = get_digit( pros_res_man ); if ( got_digit == VALID_DIGIT ) { // Got a valid vote, update state and wait for caller to hang-up: next_state = STATE_PLAY_HANGUP_PROMPT; } else if ( got_digit == INVALID_DIGIT ) { // Got an invalid vote, remain in this state: next_state = STATE_PLAY_VOTE_PROMPT; } else // got_digit == NO_DIGIT { // Did not get a digit. loop round and try again next_state = current_state; } } else { // It must be the prompt thread finishing wait for the caller to vote: next_state = STATE_WAIT_FOR_VOTE; } } } // End while // Whatever happened to drop us out of the while we need to // halt the prompt: if ( next_state != STATE_PLAY_VOTE_PROMPT ) { stop_prompt( pros_res_man ); } return next_state; } ////////////////////////////////////////////////////////////////////// // // wait_for_vote // // State machine function called from "stars_in_their_eyes". This // function is called when a prompt has finished playing but the // caller is yet to make a selection. // // Returns the next state to move to. // ////////////////////////////////////////////////////////////////////// ACU_INT wait_for_vote( PROS_RES_MAN *pros_res_man ) { ACU_ERR result = 0; ACU_OS_BOOL got_digit = ACU_OS_FALSE; // True if we've recieved a valid vote ACU_INT next_state = STATE_WAIT_HANG_UP; // Default next state ACU_INT current_state = STATE_WAIT_FOR_VOTE; ACU_SDK_WAIT_OBJECT *pros_events[3]; // Array of the events we want to wait on ACU_OS_BOOL pros_events_sig[3]; // Array that indicates which of our events // were signalled // Set-up our wait object array: pros_events[0] = pros_res_man->kill_me; // Wait object 0 is application termination pros_events[1] = pros_res_man->hungup; // Wait object 1 is caller hung-up pros_events[2] = pros_res_man->digits; // Wait object 2 is digits detected ///////////////////////////////////////////////////////////////////////////////// // // Wait for any one of our events to be signalled. When this function returns // the entries in 'pros_events_sig' array will be set to indicate whether // or not the corresponding event was signalled // For example, if 'pros_events[2]' signalled then 'pros_events_sig[2]' would be // set to ACU_OS_TRUE. // ///////////////////////////////////////////////////////////////////////////////// result = acu_os_wait_for_any_wait_object( 3, // Number of events we're waiting on pros_events, // The events we're waiting for pros_events_sig, // The events singalled state REPROMPT_DELAY); // How long to wait if ( result == ERR_ACU_OS_SIGNAL ) { // This is a fatal error, as there's a chance that at least one of our // wait objects is invalid, safer to drop out. acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Resource thread failed to wait, terminating"); exit(-1); } else if ( result == ERR_ACU_OS_TIMED_OUT ) { // The caller hasn't entered an option after 10 seconds, so replay prompt: next_state = STATE_PLAY_VOTE_PROMPT; } else { // At least one of our wait objects was signalled, find out which one(s) if ( pros_events_sig[0] == ACU_OS_TRUE ) { // Application is terminating: next_state = STATE_APP_EXIT; } else if ( pros_events_sig[1] == ACU_OS_TRUE ) { // Caller has hung-up, clear down normally next_state = STATE_COMPLETE; } else { // We've detected a digit, collect it and determine if // it's valid. If it is then we'll drop out of the loop got_digit = get_digit( pros_res_man ); if ( got_digit == VALID_DIGIT ) { // Got a valid vote, update state and wait for caller to hang-up: next_state = STATE_PLAY_HANGUP_PROMPT; } else if ( got_digit == INVALID_DIGIT ) { // Got an invalid vote, remain in this state: next_state = STATE_PLAY_VOTE_PROMPT; } else // got_digit == NO_DIGIT { // Did not get a digit. loop round and try again next_state = current_state; } } } return next_state; } ////////////////////////////////////////////////////////////////////// // // play_prompt_wait_for_hangup // // State machine function called from "stars_in_their_eyes". This // function is called when the caller has made a vote but not yet // hung-up. Play a prompt to the caller informing them to hang-up. // // Returns the next state to move to. // ////////////////////////////////////////////////////////////////////// ACU_INT play_prompt_wait_for_hangup( PROS_RES_MAN *pros_res_man ) { ACU_ERR result = 0; ACU_INT next_state = STATE_WAIT_HANG_UP; // Default next state ACU_SDK_WAIT_OBJECT *pros_events[3]; // Array of the events we want to wait on ACU_OS_BOOL pros_events_sig[3]; // Array that indicates which of our events // were signalled // Start off the prompt: result = start_prompt( pros_res_man, HANGUP_PROMPT_FILE ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not start prompt <%d>, terminating", result); exit(-1); } // Set-up our wait object array: pros_events[0] = pros_res_man->kill_me; // Wait object 0 is application termination pros_events[1] = pros_res_man->hungup; // Wait object 1 is caller hung-up pros_events[2] = pros_res_man->prompt; // Wait object 2 is prompt finished ///////////////////////////////////////////////////////////////////////////////// // // Wait for any one of our events to be signalled. When this function returns // the entries in 'pros_events_sig' array will be set to indicate whether // or not the corresponding event was signalled // For example, if 'pros_events[2]' signalled then 'pros_events_sig[2]' would be // set to ACU_OS_TRUE. // ///////////////////////////////////////////////////////////////////////////////// result = acu_os_wait_for_any_wait_object( 3, // Number of events we're waiting on pros_events, // The events we're waiting for pros_events_sig, // The events singalled state ACU_OS_INFINITE); // How long to wait if ( result != ERR_ACU_OS_NO_ERROR ) { // This is a fatal error, as there's a chance that at least one of our // wait objects is invalid, safer to drop out. acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Resource thread failed to wait, terminating"); exit(-1); } else { // At least one of our wait objects was signalled, find out which one(s) if ( pros_events_sig[0] == ACU_OS_TRUE ) { // Application is terminating: next_state = STATE_APP_EXIT; } else if ( pros_events_sig[1] == ACU_OS_TRUE ) { // Caller has hung-up, clear down normally next_state = STATE_COMPLETE; } else { // It must be the prompt thread finishing wait for the caller to hang_up: next_state = STATE_WAIT_HANG_UP; } } // Whatever the reason we dropped out of the wait, we need to terminate // the prompt (either prematurely or normally): stop_prompt( pros_res_man ); return next_state; } ////////////////////////////////////////////////////////////////////// // // wait_for_hangup // // State machine function called from "stars_in_their_eyes". This // function is called when the caller has made a vote but has not yet // hung-up // // Returns the next state to move to. // ////////////////////////////////////////////////////////////////////// ACU_INT wait_for_hangup( PROS_RES_MAN *pros_res_man ) { ACU_ERR result = 0; ACU_INT next_state = STATE_COMPLETE; // Default next state ACU_SDK_WAIT_OBJECT *pros_events[2]; // Array of the events we want to wait on ACU_OS_BOOL pros_events_sig[2]; // Array that indicates which of our events // were signalled // Set-up our wait object array: pros_events[0] = pros_res_man->kill_me; // Wait object 0 is application termination pros_events[1] = pros_res_man->hungup; // Wait object 1 is caller hung-up ///////////////////////////////////////////////////////////////////////////////// // // Wait for any one of our events to be signalled. When this function returns // the entries in 'pros_events_sig' array will be set to indicate whether // or not the corresponding event was signalled // For example, if 'pros_events[1]' signalled then 'pros_events_sig[1]' would be // set to ACU_OS_TRUE. // ///////////////////////////////////////////////////////////////////////////////// result = acu_os_wait_for_any_wait_object( 2, // Number of events we're waiting on pros_events, // The events we're waiting for pros_events_sig, // The events singalled state REPROMPT_DELAY); // How long to wait if ( result == ERR_ACU_OS_SIGNAL ) { // This is a fatal error, as there's a chance that at least one of our // wait objects is invalid, safer to drop out. acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Resource thread failed to wait, terminating"); exit(-1); } else if ( result == ERR_ACU_OS_TIMED_OUT ) { // Caller hasn't hung-up after 10 seconds, replay hang-up prompt next_state = STATE_PLAY_HANGUP_PROMPT; } else { // At least one of our wait objects was signalled, find out which one(s) if ( pros_events_sig[0] == ACU_OS_TRUE ) { // Application is terminating: next_state = STATE_APP_EXIT; } else { // Caller has hung-up, clear down normally next_state = STATE_COMPLETE; } } return next_state; } ////////////////////////////////////////////////////////////////////// // // remove_detection // // Free the resources allocated for digit detection. // ////////////////////////////////////////////////////////////////////// void remove_detection( PROS_RES_MAN *pros_res_man ) { ACU_ERR result = 0; SM_CHANNEL_SET_EVENT_PARMS event_parms; // Clear the API structure: INIT_ACU_SM_STRUCT( &event_parms ); // Clear the Prosody event mapping: event_parms.channel = pros_res_man->channel; event_parms.issue_events = kSMChannelNoEvent; event_parms.event = *(pros_res_man->event); event_parms.event_type = kSMEventTypeRecog; result = sm_channel_set_event( &event_parms ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not disassociate channel and event <%d>", result); } // Free the event from the Prosody API: smd_ev_free( *pros_res_man->event ); // Free the memory that we used for the Prosody event: if ( pros_res_man->event != NULL ) { acu_os_free( pros_res_man->event ); } // Now free the event we created from the Prosody event: if ( pros_res_man->digits != NULL ) { acu_os_destroy_wait_object(pros_res_man->digits); } } ////////////////////////////////////////////////////////////////////// // // start_prompt // // This function sets up the required Prosody resources for starting // the specified prompt (in "filename"). If this is successfull the // function spawns a thread that will block until the prompt has // completed. // // Returns 0 for success, or a negative value indicating the error. // ////////////////////////////////////////////////////////////////////// ACU_INT start_prompt( PROS_RES_MAN *pros_res_man, char *filename ) { ACU_ERR result = 0; SM_FILE_REPLAY_PARMS *prompt_parms; // Allocate API structure // NOTE: acu_os_alloc initialises the memory for us prompt_parms = acu_os_alloc( sizeof(SM_FILE_REPLAY_PARMS) ); if ( prompt_parms == NULL ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not create prompt structure"); exit(-1); } // Attempt to start the prompt first: // NOTE: The remaing values are either left as default or are set-up by // the API call sm_replay_wav_start() prompt_parms->replay_parms.channel = pros_res_man->channel; prompt_parms->replay_parms.type = kSMDataFormat8KHzALawPCM; result = sm_replay_wav_start( filename, prompt_parms ); if ( result != 0 ) { // Fatal error: couldn't initiate wav replay acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not initiate wav %s replay <%d>", filename, result); } else { // Initiated prompt, now we can set up the information required to progress // the prompt: pros_res_man->prompt_parms = prompt_parms; // Spawn prompt thread: pros_res_man->prompt_thread_id = acu_os_create_thread( prompt_playing, pros_res_man ); if ( pros_res_man->prompt_thread_id == NULL ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not create prompt thread, terminating"); exit(-1); } } return result; } ////////////////////////////////////////////////////////////////////// // // prompt_playing // // This is a thread function which blocks playing a prompt. When // the prompt is finished, the thread signals its' caller. // // Returns 0 for success, or a negative value indicating the error. // ////////////////////////////////////////////////////////////////////// ACU_INT prompt_playing( void *parameters ) { ACU_ERR result = 0; PROS_RES_MAN *pros_res_man = (PROS_RES_MAN *)parameters; // Covert argument to something meaningful result = sm_replay_file_complete( pros_res_man->prompt_parms ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to complete file replay -> <%d>", result ); return result; } // Prompt finished: result = acu_os_signal_wait_object( pros_res_man->prompt ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not signal prompt"); } return result; } ////////////////////////////////////////////////////////////////////// // // get_digit // // Get a detected digit from the Prosody queue and determines if the // digit is in the range of valid digits. // // Returns VALID_DIGIT if the digit is valid, INVALID_DIGIT if // digit is invalid, NO_DIGIT otherwise // ////////////////////////////////////////////////////////////////////// ACU_INT get_digit( PROS_RES_MAN *pros_res_man ) { ACU_INT result = 0; tSM_INT digit = 0; ACU_OS_BOOL valid = ACU_OS_FALSE; SM_RECOGNISED_PARMS recog_parms; // Clear API structure: INIT_ACU_SM_STRUCT( &recog_parms ); recog_parms.channel = pros_res_man->channel; result = sm_get_recognised( &recog_parms ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not collect digit <%d>", result); } else { if ( recog_parms.type == kSMRecognisedDigit ) { // Got digit determine if it's valid: valid = validate_digit( recog_parms.param0 ); if ( valid ) { // Set-up the digit: digit = recog_parms.param0 - '0'; // Update the stats: update_votes( digit ); result = VALID_DIGIT; } else { result = INVALID_DIGIT; } } else if( recog_parms.type == kSMRecognisedNothing ) { result = NO_DIGIT; } } return result; } ////////////////////////////////////////////////////////////////////// // // stop_prompt // // Determine if there's currently a prompt in progress for the given // resource, if so, terminate it. // // Returns 0 for success, or a negative value indicating the error. // ////////////////////////////////////////////////////////////////////// ACU_INT stop_prompt( PROS_RES_MAN *pros_res_man ) { ACU_ERR result = 0; // Abort the replay job: result = sm_replay_file_stop( pros_res_man->prompt_parms ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not abort prompt <%d>", result); } // Wait for prompt thread to termiante: // NOTE: The thread may already have terminated, in which case the function // will return immdeiatley result = acu_os_wait_for_thread( pros_res_man->prompt_thread_id ); // Free thread resources: acu_os_destroy_thread( pros_res_man->prompt_thread_id ); // Tidy up other resources: result = sm_replay_wav_close( pros_res_man->prompt_parms ); if ( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Could not close wav file <%d>", result); } // Make sure the prompt event is not signalled: acu_os_unsignal_wait_object( pros_res_man->prompt ); // Free the memory allocated for the prompt parameters: acu_os_free( pros_res_man->prompt_parms ); pros_res_man->prompt_parms = NULL; return result; } ////////////////////////////////////////////////////////////////////// // // validate_digit // // Check the given digit against this application's range of valid // values. // // Return ACU_OS_TRUE if digit is valid, ACU_OS_FALSE otherwise. // ////////////////////////////////////////////////////////////////////// ACU_OS_BOOL validate_digit( tSM_INT digit ) { ACU_OS_BOOL valid = ACU_OS_FALSE; // Valid digits for this application are 1.. NUM_CONTESTANTS if ( (digit >= '1') && (digit <= (NUM_CONTESTANTS + '0') ) ) { valid = ACU_OS_TRUE; } return valid; } ////////////////////////////////////////////////////////////////////// // // update_votes // // Update the vote count for the specified contestant. // // NOTE: As this function can be called by any of the Prosody threads // we need to control access to the totals that are being // updated. // ////////////////////////////////////////////////////////////////////// void update_votes( ACU_INT contestant ) { // We need to synchronise access to the count: acu_os_lock_critical_section( vote_lock ); // Update this contestants votes: // NOTE: Because the votes are from 1 to NUM_CONTESTANTS and the array is // indexed from 0 to (NUM_CONTESTANTS - 1) we need to subtract 1 from // the contestant number. ++votes[contestant - 1]; // Now update the display for this contestant acu_os_printf_at( VOTE_COLUMN, VOTE_ROW + (contestant - 1), "%d", votes[contestant - 1]); acu_os_unlock_critical_section( vote_lock ); } ///////////////////////////////////////////////////////////////////// // // setup_prosody_module // // This function sets up a Prosody module first opens the module then // allocates a number of channels and spawns a thread for each of these // channels that have been allocated. // // Return 0 for success - a negative value indicating an error // ////////////////////////////////////////////////////////////////////// ACU_INT setup_prosody_module( MODULE_DATA* module_data , CARD_DATA* card_data) { PROS_DATA* pros_data = NULL; ACU_ERR result = 0; ACU_INT stream = 0; ACU_INT timeslot = 0; ACU_INT channels = CHANNELS_PER_MODULE; ACU_OS_BOOL using_2nd_stream = ACU_OS_FALSE; ACU_OS_THREAD* thread_id; SM_OPEN_MODULE_PARMS open_module; SM_MODULE_INFO_PARMS mod_info; INIT_ACU_SM_STRUCT( &open_module ); open_module.module_ix = module_data->module_ix; open_module.card_id = card_data->card_id; //open the module so we get a module_id result = sm_open_module( &open_module ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: Unable to open Prosody Module <%d>", result); acu_os_free( module_data ); return result; } //record the module_id returned by the API module_data->module_id = open_module.module_id; module_data->card_id = card_data->card_id; //find out the BASE MODULE STREAM HERE..... INIT_ACU_SM_STRUCT( &mod_info ); mod_info.module = module_data->module_id; result = sm_get_module_info( &mod_info ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Failed to obtain module information %d", result ); exit(1); } module_data->min_stream = mod_info.min_stream; // Allocate and setup all of the channels for the specified modules. // For each channel we allocate, start a resource thread and pass in // the required info. //////////////////////////////////////////////////////////////// // // Now we can allocate the Prosody switching resources. // First, we need to determine what our base stream is. We do // this by using our card module index, and multiplying this // value by 2 and adding the result to the base stream value. // This will give us streams on an even offset, which is what we // want as each module has an even & and odd stream (starting // with the even value). For example, Module 3 on a card would // give us: (2 * 2) + 48 = 52 (remember modules are 0 based). // ///////////////////////////////////////////////////////////////// stream = (module_data->module_ix * 2) + module_data->min_stream; for ( timeslot = 0; timeslot < channels; timeslot++ ) { //now check if we need to use the 2nd channel if( timeslot >= TIMESLOTS_PER_STREAM ) { //Need to use the 2nd stream on the module, make sure we are not already using it if( using_2nd_stream == ACU_OS_TRUE ) { return ERR_SM_NO_RESOURCES; //Error code from the Speech API } //adjust the counters stream += 1; //2nd stream channels -= TIMESLOTS_PER_STREAM; //numbers of channels remaining timeslot = 0; //reset timeslot to 0 (1st ts on the 2nd stream } //Now we've got a stream and timeslot, allocate a channel and its data result = allocate_channel( module_data, stream , timeslot, &pros_data ); if( result != 0 ) { acu_os_printf_at( ERROR_COLUMN, ERROR_ROW, "Error: could not allocate channel <%d>", result ); return result; } //Now that a channel has been set-up start it's thread: //NOTE: The Prosody Resource will indicate when it's ready to be used thread_id = acu_os_create_thread( prosody_resource_thread, pros_data ); if( thread_id == NULL ) { acu_os_printf_at( ERROR_COLUMN , ERROR_ROW, "Error: Could not create resource thread for Prosody Channel" ); return ERR_ACU_OS_ERROR; } else { //record the thread_id for the thread just spawned pros_data->thread_id = thread_id; } } return result; } ///////////////////////////////////////////////////////////////// // // free_prosody_resources_on_module // // This function frees all the resources currently on the // free_resource_queue // // ///////////////////////////////////////////////////////////////// void free_prosody_resources_on_module( MODULE_DATA* module_data ) { PROSODY_NODE* pros_node = NULL; PROS_DATA *pros_data = NULL; acu_os_lock_critical_section( free_resource_lock ); //find the first prosody resource on the module indicated by modile_data->module_id pros_node = acu_qq_iterate( &free_pros_resources, find_pros_res_on_module, (void*)&(module_data->module_id), 0 ); acu_os_unlock_critical_section( free_resource_lock ); //make a copy of the pros_data pros_data = pros_node->pros_data; //remove this node from the linked list acu_qq_extract( &free_pros_resources, &pros_node->node ); //free the associated pros_node memory acu_os_free( pros_node ); while( pros_data != NULL ) { // Signal this thread to stop: acu_os_signal_wait_object( pros_data->kill_me ); // Now wait for the thread to terminate: acu_os_wait_for_thread( pros_data->thread_id ); // Now that the thread has terminated, free it's resource: acu_os_destroy_thread( pros_data->thread_id ); // Finally, free the Prosody data: acu_os_free( pros_data ); pros_data = NULL; acu_os_lock_critical_section( free_resource_lock ); pros_node = acu_qq_iterate( &free_pros_resources, find_pros_res_on_module, (void*)&(module_data->module_id), 0 ); acu_os_unlock_critical_section( free_resource_lock ); if( pros_node != NULL ) { //make a copy of the pros_data pros_data = pros_node->pros_data; //remove this node from the linked list acu_qq_extract( &free_pros_resources, &pros_node->node ); //free the associated pros_node data memory acu_os_free( pros_node ); } } } |