#pragma GCC diagnostic ignored "-Wwrite-strings" #include #include #include #include #include #include #include #include #define THIS_FILE "APP" #define SIP_DOMAIN "example.com" #define SIP_USER "mike" #define SIP_PASSWD "secret" // global helper vars int app_exiting = 0; // struct for app configuration settings struct app_config { char *sip_domain; char *sip_user; char *sip_password; char *phone_number; char *tts; char *tts_file; int record_call; char *record_file; int repetition_limit; int silent_mode; } app_cfg; // helper for logging messages to console (disabled if silent mode is active) static void log_message(char *message) { if (!app_cfg.silent_mode) { fprintf(stderr, message); } } // clean application exit static void app_exit(char *message) { log_message(message); if (!app_exiting) { app_exiting = 1; log_message("Stopping application ... "); // hangup open calls and stop pjsua pjsua_call_hangup_all(); pjsua_destroy(); log_message("Done.\n"); exit(0); } } // display error and exit application static void error_exit(const char *title, pj_status_t status) { if (!app_exiting) { app_exiting = 1; pjsua_perror("SIP Call", title, status); // hangup open calls and stop pjsua pjsua_call_hangup_all(); pjsua_destroy(); exit(1); } } ///////////////////////////////////////////////////////////////////////////////// // key piece of code ///////////////////////////////////////////////////////////////////////////////// struct call_data { pj_pool_t *pool; pjmedia_conf *conf; pjmedia_port *cport; pjmedia_port *null; pjmedia_port *writer; pjmedia_port *player; pjmedia_master_port *m; unsigned int call_slot; unsigned int writer_slot; unsigned int player_slot; }; static void call_media_init(pjsua_call_id call_id){ log_message("RUNNING... call_media_init\n"); pj_pool_t *pool; struct call_data *cd; pj_status_t status; pool = pjsua_pool_create("mycall", 4000, 4000); cd = PJ_POOL_ZALLOC_T(pool, struct call_data); cd->pool = pool; pjsua_call_set_user_data(call_id, (void*)cd); pjsua_media_config media_cfg; pjsua_media_config_default(&media_cfg); status = pjmedia_conf_create( cd->pool, media_cfg.max_media_ports, //max media ports media_cfg.clock_rate, media_cfg.channel_count, media_cfg.clock_rate * media_cfg.channel_count * media_cfg.audio_frame_ptime / 1000, //mconf_cfg.samples_per_frame, 16, //mconf_cfg.bits_per_sample, PJMEDIA_CONF_NO_DEVICE | PJMEDIA_CONF_NO_MIC, //options &cd->conf //pointer to conference bridge instance ); if (status != PJ_SUCCESS) {pjsua_perror(THIS_FILE, "STATUS ERROR: ", status);} cd->cport = pjmedia_conf_get_master_port(cd->conf); status = pjmedia_null_port_create( cd->pool, media_cfg.clock_rate, media_cfg.channel_count, media_cfg.clock_rate * media_cfg.channel_count * media_cfg.audio_frame_ptime / 1000, //mconf_cfg.samples_per_frame, 16, //mconf_cfg.bits_per_sample, &cd->null); if (status != PJ_SUCCESS) {pjsua_perror(THIS_FILE, "STATUS ERROR: ", status);} status = pjmedia_master_port_create(cd->pool, cd->null, cd->cport, 0, &cd->m); if (status != PJ_SUCCESS) {pjsua_perror(THIS_FILE, "STATUS ERROR: ", status);} status = pjmedia_master_port_start(cd->m); if (status != PJ_SUCCESS) {pjsua_perror(THIS_FILE, "STATUS ERROR: ", status);} //todo(mike) handle errors, see pjsua_aud.c /* wav writer */ status = pjmedia_wav_writer_port_create( cd->pool, "testingtesting.wav", //path media_cfg.clock_rate, media_cfg.channel_count, media_cfg.clock_rate * media_cfg.channel_count * media_cfg.audio_frame_ptime / 1000, //mconf_cfg.samples_per_frame, 16, //mconf_cfg.bits_per_sample, 0, //options 0, //buf_size defaults to 4kb if set to 0 &cd->writer //yes this should be a pjmedia_port ** ); if (status != PJ_SUCCESS) {pjsua_perror(THIS_FILE, "STATUS ERROR: ", status);} status = pjmedia_conf_add_port(cd->conf, cd->pool, cd->writer, NULL, &cd->writer_slot); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "STATUS ERROR: ", status); pjmedia_port_destroy(cd->writer); } pjmedia_conf_connect_port(cd->conf, cd->call_slot, cd->writer_slot, 0); /* wav player */ status = pjmedia_wav_player_port_create( cd->pool, "message.wav", media_cfg.audio_frame_ptime, 0, 0, &cd->player //yes this should be a pjmedia_port ** ); if (status != PJ_SUCCESS) {pjsua_perror(THIS_FILE, "STATUS ERROR: ", status);} status = pjmedia_conf_add_port(cd->conf, cd->pool, cd->player, NULL, &cd->player_slot); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "STATUS ERROR: ", status); pjmedia_port_destroy(cd->player); } pjmedia_conf_connect_port(cd->conf, cd->player_slot, cd->call_slot, 0); //uncomment to loop back remote audio (also doesn't work) //pjmedia_conf_connect_port(cd->conf, cd->call_slot, cd->call_slot, 0); } static void call_media_deinit(pjsua_call_id call_id){ log_message("RUNNING... call_media_deinit\n"); struct call_data *cd; cd = (struct call_data*) pjsua_call_get_user_data(call_id); if (!cd) return; pjmedia_master_port_stop(cd->m); pjmedia_master_port_destroy(cd->m, PJ_FALSE); pjmedia_conf_destroy(cd->conf); pjmedia_port_destroy(cd->null); pjmedia_port_destroy(cd->writer); log_message("Called call_media_deinit.\n"); pjsua_call_set_user_data(call_id, NULL); } ///////////////////////////////////////////////////////////////////////////////// // on_ functions ///////////////////////////////////////////////////////////////////////////////// static void on_call_state(pjsua_call_id call_id, pjsip_event *e){ log_message("RUNNING... on_call_state\n"); pjsua_call_info call_info; pjsua_call_get_info(call_id, &call_info); PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id, (int)call_info.state_text.slen, call_info.state_text.ptr)); if (call_info.state == PJSIP_INV_STATE_CALLING) { log_message("Call state is CALLING.\n"); call_media_init(call_id); } if (call_info.state == PJSIP_INV_STATE_CONFIRMED) { log_message("Call state is CONFIRMED.\n"); } if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { log_message("Call state is DISCONNECTED.\n"); call_media_deinit(call_id); } } static void on_call_media_state(pjsua_call_id call_id){ log_message("RUNNING... on_call_media_state\n"); pjsua_call_info call_info; pjsua_call_get_info(call_id, &call_info); if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) { log_message("Media state is ACTIVE.\n"); //call_media_init(call_id); //doesn't work here either! } } static void on_stream_created(pjsua_call_id call_id, pjmedia_stream *strm, unsigned stream_idx, pjmedia_port **p_port){ log_message("RUNNING... on_stream_created\n"); struct call_data *cd; cd = (struct call_data*) pjsua_call_get_user_data(call_id); if (!cd) return; pjmedia_conf_add_port(cd->conf, cd->pool, *p_port, NULL, &cd->call_slot); } static void on_stream_destroyed(pjsua_call_id call_id, pjmedia_stream *strm, unsigned stream_idx){ log_message("RUNNING... on_stream_destroyed\n"); struct call_data *cd; cd = (struct call_data*) pjsua_call_get_user_data(call_id); if (!cd) return; pjmedia_conf_remove_port(cd->conf, cd->call_slot); } ///////////////////////////////////////////////////////////////////////////////// // main() ///////////////////////////////////////////////////////////////////////////////// //argv[1] may contain URL to call. int main(int argc, char *argv[]){ pjsua_acc_id acc_id; app_cfg.tts_file = "message.wav"; app_cfg.record_call = 1; app_cfg.record_file = "recording.wav"; app_cfg.repetition_limit = 250; app_cfg.silent_mode = 0; pj_status_t status; /* Create pjsua first! */ status = pjsua_create(); if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status); /* If argument is specified, it's got to be a valid SIP URL */ if (argc > 1) { status = pjsua_verify_url(argv[1]); if (status != PJ_SUCCESS) error_exit("Invalid URL in argv", status); } else { PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", status)); } /* Init pjsua */ { pjsua_config cfg; pjsua_logging_config log_cfg; pjsua_config_default(&cfg); //see pjsua.h //cfg.cb.on_incoming_call = &on_incoming_call; cfg.cb.on_call_media_state = &on_call_media_state; cfg.cb.on_call_state = &on_call_state; cfg.cb.on_stream_created = &on_stream_created; cfg.cb.on_stream_destroyed = &on_stream_destroyed; pjsua_logging_config_default(&log_cfg); log_cfg.console_level = 4; pjsua_media_config media_cfg; pjsua_media_config_default(&media_cfg); //media_cfg.snd_play_latency = 160; //default 2000 //media_cfg.snd_rec_latency = 160; //default 2000 //media_cfg.clock_rate = 16000; //media_cfg.snd_clock_rate = 16000; media_cfg.quality = 10; media_cfg.ec_tail_len = 0; //media_cfg.ptime = 100; //100 milliseconds //default seems to be 20? status = pjsua_init(&cfg, &log_cfg, NULL); if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status); } /* Add UDP transport. */ { pjsua_transport_config cfg; pjsua_transport_config_default(&cfg); cfg.port = 5060; status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL); if (status != PJ_SUCCESS) error_exit("Error creating transport", status); } /* Initialization is done, now start pjsua */ status = pjsua_start(); if (status != PJ_SUCCESS) error_exit("Error starting pjsua", status); // pjsua_set_null_snd_dev() now not used, because instead using pjsua_set_no_snd_dev() (see below) // disable sound - use null sound device //status = pjsua_set_null_snd_dev(); //if (status != PJ_SUCCESS) error_exit("Error disabling audio", status); pjmedia_port* mainconfbridgeport; mainconfbridgeport = pjsua_set_no_snd_dev(); /* Register to SIP server by creating SIP account. */ { pjsua_acc_config cfg; pjsua_acc_config_default(&cfg); cfg.id = pj_str("sip:" SIP_USER "@" SIP_DOMAIN); status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id); if (status != PJ_SUCCESS) error_exit("Error adding account", status); } /* If URL is specified, make call to the URL. */ if (argc > 1) { pj_str_t uri = pj_str(argv[1]); status = pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, NULL); if (status != PJ_SUCCESS) error_exit("Error making call", status); } //Wait until user press "q" to quit. for (;;) { char option[10]; puts("Press 'h' to hangup all calls, 'q' to quit"); if (fgets(option, sizeof(option), stdin) == NULL) { puts("EOF while reading stdin, will quit now.."); break; } if (option[0] == 'q') break; if (option[0] == 'h') pjsua_call_hangup_all(); } /* Destroy pjsua */ pjsua_destroy(); return 0; }