static void cleanup() { if (app.srtp) pjmedia_transport_close(app.srtp); if (app.wav) { pj_ssize_t pos = pjmedia_wav_writer_port_get_pos(app.wav); if (pos >= 0) { unsigned msec; msec = (unsigned)pos / 2 * 1000 / PJMEDIA_PIA_SRATE(&app.wav->info); printf("Written: %dm:%02ds.%03d\n", msec / 1000 / 60, (msec / 1000) % 60, msec % 1000); } pjmedia_port_destroy(app.wav); } if (app.pcap) pj_pcap_close(app.pcap); if (app.codec) { pjmedia_codec_mgr *cmgr; pjmedia_codec_close(app.codec); cmgr = pjmedia_endpt_get_codec_mgr(app.mept); pjmedia_codec_mgr_dealloc_codec(cmgr, app.codec); } if (app.aud_strm) { pjmedia_aud_stream_stop(app.aud_strm); pjmedia_aud_stream_destroy(app.aud_strm); } if (app.mept) pjmedia_endpt_destroy(app.mept); if (app.pool) pj_pool_release(app.pool); pj_caching_pool_destroy(&app.cp); pj_shutdown(); }
static void record(unsigned rec_index, const char *filename) { pj_pool_t *pool = NULL; pjmedia_port *wav = NULL; pjmedia_aud_param param; pjmedia_aud_stream *strm = NULL; char line[10], *dummy; pj_status_t status; if (filename == NULL) filename = WAV_FILE; pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav", 1000, 1000, NULL); status = pjmedia_wav_writer_port_create(pool, filename, 16000, 1, 320, 16, 0, 0, &wav); if (status != PJ_SUCCESS) { app_perror("Error creating WAV file", status); goto on_return; } status = pjmedia_aud_dev_default_param(rec_index, ¶m); if (status != PJ_SUCCESS) { app_perror("pjmedia_aud_dev_default_param()", status); goto on_return; } param.dir = PJMEDIA_DIR_CAPTURE; param.clock_rate = PJMEDIA_PIA_SRATE(&wav->info); param.samples_per_frame = PJMEDIA_PIA_SPF(&wav->info); param.channel_count = PJMEDIA_PIA_CCNT(&wav->info); param.bits_per_sample = PJMEDIA_PIA_BITS(&wav->info); status = pjmedia_aud_stream_create(¶m, &wav_rec_cb, NULL, wav, &strm); if (status != PJ_SUCCESS) { app_perror("Error opening the sound device", status); goto on_return; } status = pjmedia_aud_stream_start(strm); if (status != PJ_SUCCESS) { app_perror("Error starting the sound device", status); goto on_return; } PJ_LOG(3,(THIS_FILE, "Recording started, press ENTER to stop")); dummy = fgets(line, sizeof(line), stdin); on_return: if (strm) { pjmedia_aud_stream_stop(strm); pjmedia_aud_stream_destroy(strm); } if (wav) pjmedia_port_destroy(wav); if (pool) pj_pool_release(pool); }
/* * Find out latency */ static int calculate_latency(pj_pool_t *pool, pjmedia_port *wav) { pjmedia_frame frm; short *buf; unsigned i, samples_per_frame, read, len; unsigned start_pos; pj_status_t status; unsigned lat_sum = 0, lat_cnt = 0, lat_min = 10000, lat_max = 0; samples_per_frame = PJMEDIA_PIA_SPF(&wav->info); frm.buf = pj_pool_alloc(pool, samples_per_frame * 2); frm.size = samples_per_frame * 2; len = pjmedia_wav_player_get_len(wav); buf = pj_pool_alloc(pool, len + samples_per_frame); read = 0; while (read < len/2) { status = pjmedia_port_get_frame(wav, &frm); if (status != PJ_SUCCESS) break; pjmedia_copy_samples(buf+read, (short*)frm.buf, samples_per_frame); read += samples_per_frame; } if (read < 2 * PJMEDIA_PIA_SRATE(&wav->info)) { puts("Error: too short"); return -1; } start_pos = 0; while (start_pos < len/2 - PJMEDIA_PIA_SRATE(&wav->info)) { int max_signal = 0; unsigned max_signal_pos = start_pos; unsigned max_echo_pos = 0; unsigned pos; unsigned lat; /* Get the largest signal in the next 0.7s */ for (i=start_pos; i<start_pos + PJMEDIA_PIA_SRATE(&wav->info) * 700 / 1000; ++i) { if (abs(buf[i]) > max_signal) { max_signal = abs(buf[i]); max_signal_pos = i; } } /* Advance 10ms from max_signal_pos */ pos = max_signal_pos + 10 * PJMEDIA_PIA_SRATE(&wav->info) / 1000; /* Get the largest signal in the next 500ms */ max_signal = 0; max_echo_pos = pos; for (i=pos; i<pos+PJMEDIA_PIA_SRATE(&wav->info)/2; ++i) { if (abs(buf[i]) > max_signal) { max_signal = abs(buf[i]); max_echo_pos = i; } } lat = (max_echo_pos - max_signal_pos) * 1000 / PJMEDIA_PIA_SRATE(&wav->info); #if 0 printf("Latency = %u\n", lat); #endif lat_sum += lat; lat_cnt++; if (lat < lat_min) lat_min = lat; if (lat > lat_max) lat_max = lat; /* Advance next loop */ start_pos += PJMEDIA_PIA_SRATE(&wav->info); } printf("Latency average = %u\n", lat_sum / lat_cnt); printf("Latency minimum = %u\n", lat_min); printf("Latency maximum = %u\n", lat_max); printf("Number of data = %u\n", lat_cnt); return 0; }
PJ_DEF(pj_status_t) pjmedia_stereo_port_create( pj_pool_t *pool, pjmedia_port *dn_port, unsigned channel_count, unsigned options, pjmedia_port **p_port ) { const pj_str_t name = pj_str("stereo"); struct stereo_port *sport; unsigned samples_per_frame; /* Validate arguments. */ PJ_ASSERT_RETURN(pool && dn_port && channel_count && p_port, PJ_EINVAL); /* Only supports 16bit samples per frame */ PJ_ASSERT_RETURN(PJMEDIA_PIA_BITS(&dn_port->info) == 16, PJMEDIA_ENCBITS); /* Validate channel counts */ PJ_ASSERT_RETURN(((PJMEDIA_PIA_CCNT(&dn_port->info)>1 && channel_count==1) || (PJMEDIA_PIA_CCNT(&dn_port->info)==1 && channel_count>1)), PJ_EINVAL); /* Create and initialize port. */ sport = PJ_POOL_ZALLOC_T(pool, struct stereo_port); PJ_ASSERT_RETURN(sport != NULL, PJ_ENOMEM); samples_per_frame = PJMEDIA_PIA_SPF(&dn_port->info) * channel_count / PJMEDIA_PIA_CCNT(&dn_port->info); pjmedia_port_info_init(&sport->base.info, &name, SIGNATURE, PJMEDIA_PIA_SRATE(&dn_port->info), channel_count, PJMEDIA_PIA_BITS(&dn_port->info), samples_per_frame); sport->dn_port = dn_port; sport->options = options; /* We always need buffer for put_frame */ sport->put_buf = (pj_int16_t*) pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info)); /* See if we need buffer for get_frame */ if (PJMEDIA_PIA_CCNT(&dn_port->info) > channel_count) { sport->get_buf = (pj_int16_t*) pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info)); } /* Media port interface */ sport->base.get_frame = &stereo_get_frame; sport->base.put_frame = &stereo_put_frame; sport->base.on_destroy = &stereo_destroy; /* Done */ *p_port = &sport->base; return PJ_SUCCESS; }
int main(int argc, char *argv[]) { pj_caching_pool cp; pj_pool_t *pool; pjmedia_conf *conf; pjmedia_port *player_port1, *player_port2, *writer_port; pjmedia_master_port *master_port; unsigned slot1, slot2; pj_status_t status; char tmp[10]; slot1 = 1; slot2 = 2; pj_ssize_t len; if (argc < 5) { printf("Usage: %s <clock_rate> <player file 1> <player file 2> <mix file>\n", argv[0]); exit(-1); } unsigned clock_rate = atoi(argv[1]); // Initialialize pj_init() ; pj_caching_pool_init(&cp, NULL, 0); pool = pj_pool_create(&cp.factory, "pool", 1000, 1000, NULL); // Create player port pjmedia_wav_player_port_create(pool, argv[2], 20, PJMEDIA_FILE_NO_LOOP, 0, &player_port1); pjmedia_wav_player_port_create(pool, argv[3], 20, PJMEDIA_FILE_NO_LOOP, 0, &player_port2); // Create the bridge pjmedia_conf_create(pool, MAX_WAV+4, clock_rate, 1, clock_rate * PTIME / 1000, 16, PJMEDIA_CONF_NO_DEVICE, &conf); pjmedia_conf_add_port(conf, pool, player_port2, NULL, &slot2); pjmedia_conf_add_port(conf, pool, player_port1, NULL, &slot1); pjmedia_conf_connect_port(conf, slot1, 0, 0); pjmedia_conf_connect_port(conf, slot2, 0, 0); pjmedia_port *port = pjmedia_conf_get_master_port(conf); //Both ports MUST have equal clock rate, samples per frame and channel count pjmedia_wav_writer_port_create(pool, argv[4], PJMEDIA_PIA_SRATE(&port->info), PJMEDIA_PIA_CCNT(&port->info), PJMEDIA_PIA_SPF(&port->info), PJMEDIA_PIA_BITS(&port->info), 0, 0, &writer_port); pjmedia_master_port_create(pool, port, writer_port, 0, &master_port); if (status != PJ_SUCCESS) { return 1; } status = pjmedia_master_port_start(master_port); pj_thread_sleep(5000); /* Shutdown everything */ pjmedia_master_port_destroy(master_port, PJ_TRUE); pj_pool_release(pool); pj_caching_pool_destroy(&cp); pj_shutdown(); return 0; }
/* * main() */ int main(int argc, char *argv[]) { pj_caching_pool cp; pjmedia_endpt *med_endpt; pj_pool_t *pool; pjmedia_port *wav_play; pjmedia_port *wav_rec; pjmedia_port *wav_out; pj_status_t status; pjmedia_echo_state *ec; pjmedia_frame play_frame, rec_frame; unsigned opt = 0; unsigned latency_ms = 25; unsigned tail_ms = TAIL_LENGTH; pj_timestamp t0, t1; int i, repeat=1, interactive=0, c; pj_optind = 0; while ((c=pj_getopt(argc, argv, "d:l:a:r:i")) !=-1) { switch (c) { case 'd': latency_ms = atoi(pj_optarg); if (latency_ms < 25) { puts("Invalid delay"); puts(desc); } break; case 'l': tail_ms = atoi(pj_optarg); break; case 'a': { int alg = atoi(pj_optarg); switch (alg) { case 0: opt = 0; case 1: opt = PJMEDIA_ECHO_SPEEX; break; case 3: opt = PJMEDIA_ECHO_SIMPLE; break; default: puts("Invalid algorithm"); puts(desc); return 1; } } break; case 'r': repeat = atoi(pj_optarg); if (repeat < 1) { puts("Invalid repeat count"); puts(desc); return 1; } break; case 'i': interactive = 1; break; } } if (argc - pj_optind != 3) { puts("Error: missing argument(s)"); puts(desc); return 1; } /* Must init PJLIB first: */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Create memory pool for our file player */ pool = pj_pool_create( &cp.factory, /* pool factory */ "wav", /* pool name. */ 4000, /* init size */ 4000, /* increment size */ NULL /* callback on error */ ); /* Open wav_play */ status = pjmedia_wav_player_port_create(pool, argv[pj_optind], PTIME, PJMEDIA_FILE_NO_LOOP, 0, &wav_play); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening playback WAV file", status); return 1; } /* Open recorded wav */ status = pjmedia_wav_player_port_create(pool, argv[pj_optind+1], PTIME, PJMEDIA_FILE_NO_LOOP, 0, &wav_rec); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening recorded WAV file", status); return 1; } /* play and rec WAVs must have the same clock rate */ if (PJMEDIA_PIA_SRATE(&wav_play->info) != PJMEDIA_PIA_SRATE(&wav_rec->info)) { puts("Error: clock rate mismatch in the WAV files"); return 1; } /* .. and channel count */ if (PJMEDIA_PIA_CCNT(&wav_play->info) != PJMEDIA_PIA_CCNT(&wav_rec->info)) { puts("Error: clock rate mismatch in the WAV files"); return 1; } /* Create output wav */ status = pjmedia_wav_writer_port_create(pool, argv[pj_optind+2], PJMEDIA_PIA_SRATE(&wav_play->info), PJMEDIA_PIA_CCNT(&wav_play->info), PJMEDIA_PIA_SPF(&wav_play->info), PJMEDIA_PIA_BITS(&wav_play->info), 0, 0, &wav_out); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening output WAV file", status); return 1; } /* Create echo canceller */ status = pjmedia_echo_create2(pool, PJMEDIA_PIA_SRATE(&wav_play->info), PJMEDIA_PIA_CCNT(&wav_play->info), PJMEDIA_PIA_SPF(&wav_play->info), tail_ms, latency_ms, opt, &ec); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error creating EC", status); return 1; } /* Processing loop */ play_frame.buf = pj_pool_alloc(pool, PJMEDIA_PIA_SPF(&wav_play->info)<<1); rec_frame.buf = pj_pool_alloc(pool, PJMEDIA_PIA_SPF(&wav_play->info)<<1); pj_get_timestamp(&t0); for (i=0; i < repeat; ++i) { for (;;) { play_frame.size = PJMEDIA_PIA_SPF(&wav_play->info) << 1; status = pjmedia_port_get_frame(wav_play, &play_frame); if (status != PJ_SUCCESS) break; status = pjmedia_echo_playback(ec, (short*)play_frame.buf); rec_frame.size = PJMEDIA_PIA_SPF(&wav_play->info) << 1; status = pjmedia_port_get_frame(wav_rec, &rec_frame); if (status != PJ_SUCCESS) break; status = pjmedia_echo_capture(ec, (short*)rec_frame.buf, 0); //status = pjmedia_echo_cancel(ec, (short*)rec_frame.buf, // (short*)play_frame.buf, 0, NULL); pjmedia_port_put_frame(wav_out, &rec_frame); } pjmedia_wav_player_port_set_pos(wav_play, 0); pjmedia_wav_player_port_set_pos(wav_rec, 0); } pj_get_timestamp(&t1); i = (int)pjmedia_wav_writer_port_get_pos(wav_out) / sizeof(pj_int16_t) * 1000 / (PJMEDIA_PIA_SRATE(&wav_out->info) * PJMEDIA_PIA_CCNT(&wav_out->info)); PJ_LOG(3,(THIS_FILE, "Processed %3d.%03ds audio", i / 1000, i % 1000)); PJ_LOG(3,(THIS_FILE, "Completed in %u msec\n", pj_elapsed_msec(&t0, &t1))); /* Destroy file port(s) */ status = pjmedia_port_destroy( wav_play ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); status = pjmedia_port_destroy( wav_rec ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); status = pjmedia_port_destroy( wav_out ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Destroy ec */ pjmedia_echo_destroy(ec); /* Release application pool */ pj_pool_release( pool ); /* Destroy media endpoint. */ pjmedia_endpt_destroy( med_endpt ); /* Destroy pool factory */ pj_caching_pool_destroy( &cp ); /* Shutdown PJLIB */ pj_shutdown(); if (interactive) { char s[10], *dummy; puts("ENTER to quit"); dummy = fgets(s, sizeof(s), stdin); } /* Done. */ return 0; }
/* * main() */ int main(int argc, char *argv[]) { pj_caching_pool cp; pjmedia_endpt *med_endpt; pj_pool_t *pool; pjmedia_port *file_port; pjmedia_snd_port *snd_port; char tmp[10]; pj_status_t status; if (argc != 2) { puts("Error: filename required"); puts(desc); return 1; } /* Must init PJLIB first: */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Create memory pool for our file player */ pool = pj_pool_create( &cp.factory, /* pool factory */ "wav", /* pool name. */ 4000, /* init size */ 4000, /* increment size */ NULL /* callback on error */ ); /* Create file media port from the WAV file */ status = pjmedia_wav_player_port_create( pool, /* memory pool */ argv[1], /* file to play */ 20, /* ptime. */ 0, /* flags */ 0, /* default buffer */ &file_port/* returned port */ ); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to use WAV file", status); return 1; } /* Create sound player port. */ status = pjmedia_snd_port_create_player( pool, /* pool */ -1, /* use default dev. */ PJMEDIA_PIA_SRATE(&file_port->info),/* clock rate. */ PJMEDIA_PIA_CCNT(&file_port->info),/* # of channels. */ PJMEDIA_PIA_SPF(&file_port->info), /* samples per frame. */ PJMEDIA_PIA_BITS(&file_port->info),/* bits per sample. */ 0, /* options */ &snd_port /* returned port */ ); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to open sound device", status); return 1; } /* Connect file port to the sound player. * Stream playing will commence immediately. */ status = pjmedia_snd_port_connect( snd_port, file_port); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* * File should be playing and looping now, using sound device's thread. */ /* Sleep to allow log messages to flush */ pj_thread_sleep(100); printf("Playing %s..\n", argv[1]); puts(""); puts("Press <ENTER> to stop playing and quit"); if (fgets(tmp, sizeof(tmp), stdin) == NULL) { puts("EOF while reading stdin, will quit now.."); } /* Start deinitialization: */ /* Disconnect sound port from file port */ status = pjmedia_snd_port_disconnect(snd_port); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Without this sleep, Windows/DirectSound will repeteadly * play the last frame during destroy. */ pj_thread_sleep(100); /* Destroy sound device */ status = pjmedia_snd_port_destroy( snd_port ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Destroy file port */ status = pjmedia_port_destroy( file_port ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Release application pool */ pj_pool_release( pool ); /* Destroy media endpoint. */ pjmedia_endpt_destroy( med_endpt ); /* Destroy pool factory */ pj_caching_pool_destroy( &cp ); /* Shutdown PJLIB */ pj_shutdown(); /* Done. */ return 0; }
/* This is the transmission "tick". * This function is called periodically every "tick" milliseconds, and * it will determine whether to transmit packet(s) (or to drop it). */ static void tx_tick(const pj_time_val *t) { struct stream *strm = g_app.tx; static char log_msg[120]; pjmedia_port *port = g_app.tx->port; long pkt_interval; /* packet interval, without jitter */ pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 / PJMEDIA_PIA_SRATE(&port->info); while (PJ_TIME_VAL_GTE(*t, strm->state.tx.next_schedule)) { struct log_entry entry; pj_bool_t drop_this_pkt = PJ_FALSE; int jitter; /* Init log entry */ pj_bzero(&entry, sizeof(entry)); entry.wall_clock = *t; /* * Determine whether to drop this packet */ if (strm->state.tx.cur_lost_burst) { /* We are currently dropping packet */ /* Make it comply to minimum lost burst */ if (strm->state.tx.cur_lost_burst < g_app.cfg.tx_min_lost_burst) { drop_this_pkt = PJ_TRUE; } /* Correlate the next packet loss */ if (!drop_this_pkt && strm->state.tx.cur_lost_burst < g_app.cfg.tx_max_lost_burst && MAX(strm->state.tx.total_lost-LOSS_EXTRA,0) * 100 / MAX(strm->state.tx.total_tx,1) < g_app.cfg.tx_pct_avg_lost ) { strm->state.tx.drop_prob = ((g_app.cfg.tx_pct_loss_corr * strm->state.tx.drop_prob) + ((100-g_app.cfg.tx_pct_loss_corr) * (pj_rand()%100)) ) / 100; if (strm->state.tx.drop_prob >= 100) strm->state.tx.drop_prob = 99; if (strm->state.tx.drop_prob >= 100 - g_app.cfg.tx_pct_avg_lost) drop_this_pkt = PJ_TRUE; } } /* If we're not dropping packet then use randomly distributed loss */ if (!drop_this_pkt && MAX(strm->state.tx.total_lost-LOSS_EXTRA,0) * 100 / MAX(strm->state.tx.total_tx,1) < g_app.cfg.tx_pct_avg_lost) { strm->state.tx.drop_prob = pj_rand() % 100; if (strm->state.tx.drop_prob >= 100 - g_app.cfg.tx_pct_avg_lost) drop_this_pkt = PJ_TRUE; } if (drop_this_pkt) { /* Drop the frame */ pjmedia_transport_simulate_lost(g_app.loop, PJMEDIA_DIR_ENCODING, 100); run_one_frame(g_app.tx_wav, g_app.tx->port, NULL); pjmedia_transport_simulate_lost(g_app.loop, PJMEDIA_DIR_ENCODING, 0); entry.event = EVENT_TX_DROP; entry.log = "** This packet was lost **"; ++strm->state.tx.total_lost; ++strm->state.tx.cur_lost_burst; } else { pjmedia_rtcp_stat stat; pjmedia_jb_state jstate; unsigned last_discard; pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate); last_discard = jstate.discard; run_one_frame(g_app.tx_wav, g_app.tx->port, NULL); pjmedia_stream_get_stat(g_app.rx->strm, &stat); pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate); entry.event = EVENT_TX; entry.jb_state = &jstate; entry.stat = &stat; entry.log = log_msg; if (jstate.discard > last_discard) strcat(log_msg, "** Note: packet was discarded by jitter buffer **"); strm->state.tx.cur_lost_burst = 0; } write_log(&entry, PJ_TRUE); ++strm->state.tx.total_tx; /* Calculate next schedule */ strm->state.tx.next_schedule.sec = 0; strm->state.tx.next_schedule.msec = (strm->state.tx.total_tx + 1) * pkt_interval; /* Apply jitter */ if (g_app.cfg.tx_max_jitter || g_app.cfg.tx_min_jitter) { if (g_app.cfg.tx_max_jitter == g_app.cfg.tx_min_jitter) { /* Fixed jitter */ switch (pj_rand() % 3) { case 0: jitter = 0 - g_app.cfg.tx_min_jitter; break; case 2: jitter = g_app.cfg.tx_min_jitter; break; default: jitter = 0; break; } } else { int jitter_range; jitter_range = (g_app.cfg.tx_max_jitter-g_app.cfg.tx_min_jitter)*2; jitter = pj_rand() % jitter_range; if (jitter < jitter_range/2) { jitter = 0 - g_app.cfg.tx_min_jitter - (jitter/2); } else { jitter = g_app.cfg.tx_min_jitter + (jitter/2); } } } else { jitter = 0; } pj_time_val_normalize(&strm->state.tx.next_schedule); sprintf(log_msg, "** Packet #%u tick is at %d.%03d, %d ms jitter applied **", strm->state.tx.total_tx+1, (int)strm->state.tx.next_schedule.sec, (int)strm->state.tx.next_schedule.msec, jitter); strm->state.tx.next_schedule.msec += jitter; pj_time_val_normalize(&strm->state.tx.next_schedule); } /* while */ }
/* * Create file writer port. */ PJ_DEF(pj_status_t) pjmedia_wav_writer_port_create( pj_pool_t *pool, const char *filename, unsigned sampling_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, unsigned flags, pj_ssize_t buff_size, pjmedia_port **p_port ) { struct file_port *fport; pjmedia_wave_hdr wave_hdr; pj_ssize_t size; pj_str_t name; pj_status_t status; /* Check arguments. */ PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL); /* Only supports 16bits per sample for now. * See flush_buffer(). */ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); /* Create file port instance. */ fport = PJ_POOL_ZALLOC_T(pool, struct file_port); PJ_ASSERT_RETURN(fport != NULL, PJ_ENOMEM); /* Initialize port info. */ pj_strdup2(pool, &name, filename); pjmedia_port_info_init(&fport->base.info, &name, SIGNATURE, sampling_rate, channel_count, bits_per_sample, samples_per_frame); fport->base.get_frame = &file_get_frame; fport->base.put_frame = &file_put_frame; fport->base.on_destroy = &file_on_destroy; if (flags == PJMEDIA_FILE_WRITE_ALAW) { fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_ALAW; fport->bytes_per_sample = 1; } else if (flags == PJMEDIA_FILE_WRITE_ULAW) { fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_ULAW; fport->bytes_per_sample = 1; } else { fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_PCM; fport->bytes_per_sample = 2; } /* Open file in write and read mode. * We need the read mode because we'll modify the WAVE header once * the recording has completed. */ status = pj_file_open(pool, filename, PJ_O_WRONLY, &fport->fd); if (status != PJ_SUCCESS) return status; /* Initialize WAVE header */ pj_bzero(&wave_hdr, sizeof(pjmedia_wave_hdr)); wave_hdr.riff_hdr.riff = PJMEDIA_RIFF_TAG; wave_hdr.riff_hdr.file_len = 0; /* will be filled later */ wave_hdr.riff_hdr.wave = PJMEDIA_WAVE_TAG; wave_hdr.fmt_hdr.fmt = PJMEDIA_FMT_TAG; wave_hdr.fmt_hdr.len = 16; wave_hdr.fmt_hdr.fmt_tag = (pj_uint16_t)fport->fmt_tag; wave_hdr.fmt_hdr.nchan = (pj_int16_t)channel_count; wave_hdr.fmt_hdr.sample_rate = sampling_rate; wave_hdr.fmt_hdr.bytes_per_sec = sampling_rate * channel_count * fport->bytes_per_sample; wave_hdr.fmt_hdr.block_align = (pj_uint16_t) (fport->bytes_per_sample * channel_count); wave_hdr.fmt_hdr.bits_per_sample = (pj_uint16_t) (fport->bytes_per_sample * 8); wave_hdr.data_hdr.data = PJMEDIA_DATA_TAG; wave_hdr.data_hdr.len = 0; /* will be filled later */ /* Convert WAVE header from host byte order to little endian * before writing the header. */ pjmedia_wave_hdr_host_to_file(&wave_hdr); /* Write WAVE header */ if (fport->fmt_tag != PJMEDIA_WAVE_FMT_TAG_PCM) { pjmedia_wave_subchunk fact_chunk; pj_uint32_t tmp = 0; fact_chunk.id = PJMEDIA_FACT_TAG; fact_chunk.len = 4; PJMEDIA_WAVE_NORMALIZE_SUBCHUNK(&fact_chunk); /* Write WAVE header without DATA chunk header */ size = sizeof(pjmedia_wave_hdr) - sizeof(wave_hdr.data_hdr); status = pj_file_write(fport->fd, &wave_hdr, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } /* Write FACT chunk if it stores compressed data */ size = sizeof(fact_chunk); status = pj_file_write(fport->fd, &fact_chunk, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } size = 4; status = pj_file_write(fport->fd, &tmp, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } /* Write DATA chunk header */ size = sizeof(wave_hdr.data_hdr); status = pj_file_write(fport->fd, &wave_hdr.data_hdr, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } } else { size = sizeof(pjmedia_wave_hdr); status = pj_file_write(fport->fd, &wave_hdr, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } } /* Set buffer size. */ if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE; fport->bufsize = buff_size; /* Check that buffer size is greater than bytes per frame */ pj_assert(fport->bufsize >= PJMEDIA_PIA_AVG_FSZ(&fport->base.info)); /* Allocate buffer and set initial write position */ fport->buf = (char*) pj_pool_alloc(pool, fport->bufsize); if (fport->buf == NULL) { pj_file_close(fport->fd); return PJ_ENOMEM; } fport->writepos = fport->buf; /* Done. */ *p_port = &fport->base; PJ_LOG(4,(THIS_FILE, "File writer '%.*s' created: samp.rate=%d, bufsize=%uKB", (int)fport->base.info.name.slen, fport->base.info.name.ptr, PJMEDIA_PIA_SRATE(&fport->base.info), fport->bufsize / 1000)); return PJ_SUCCESS; }
int main(int argc, char *argv[]) { pj_caching_pool cp; pjmedia_endpt *med_endpt; pj_pool_t *pool; pjmedia_port *file_port; pjmedia_port *resample_port; pjmedia_snd_port *snd_port; char tmp[10]; pj_status_t status; int dev_id = -1; int sampling_rate = CLOCK_RATE; int channel_count = NCHANNELS; int samples_per_frame = NSAMPLES; int bits_per_sample = NBITS; //int ptime; //int down_samples; /* Must init PJLIB first: */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Get options */ if (get_snd_options(THIS_FILE, argc, argv, &dev_id, &sampling_rate, &channel_count, &samples_per_frame, &bits_per_sample)) { puts(""); puts(desc); return 1; } if (!argv[pj_optind]) { puts("Error: no file is specified"); puts(desc); return 1; } /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Create memory pool for our file player */ pool = pj_pool_create( &cp.factory, /* pool factory */ "app", /* pool name. */ 4000, /* init size */ 4000, /* increment size */ NULL /* callback on error */ ); /* Create the file port. */ status = pjmedia_wav_player_port_create( pool, argv[pj_optind], 0, 0, 0, &file_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to open file", status); return 1; } /* File must have same number of channels. */ if (PJMEDIA_PIA_CCNT(&file_port->info) != (unsigned)channel_count) { PJ_LOG(3,(THIS_FILE, "Error: file has different number of channels. " "Perhaps you'd need -c option?")); pjmedia_port_destroy(file_port); return 1; } /* Calculate number of samples per frame to be taken from file port */ //ptime = samples_per_frame * 1000 / sampling_rate; /* Create the resample port. */ status = pjmedia_resample_port_create( pool, file_port, sampling_rate, 0, &resample_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create resample port", status); return 1; } /* Create sound player port. */ status = pjmedia_snd_port_create( pool, /* pool */ dev_id, /* device */ dev_id, /* device */ sampling_rate, /* clock rate. */ channel_count, /* # of channels. */ samples_per_frame, /* samples per frame. */ bits_per_sample, /* bits per sample. */ 0, /* options */ &snd_port /* returned port */ ); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to open sound device", status); return 1; } /* Connect resample port to sound device */ status = pjmedia_snd_port_connect( snd_port, resample_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error connecting sound ports", status); return 1; } /* Dump memory usage */ dump_pool_usage(THIS_FILE, &cp); /* * File should be playing and looping now, using sound device's thread. */ /* Sleep to allow log messages to flush */ pj_thread_sleep(100); printf("Playing %s at sampling rate %d (original file sampling rate=%d)\n", argv[pj_optind], sampling_rate, PJMEDIA_PIA_SRATE(&file_port->info)); puts(""); puts("Press <ENTER> to stop playing and quit"); if (fgets(tmp, sizeof(tmp), stdin) == NULL) { puts("EOF while reading stdin, will quit now.."); } /* Start deinitialization: */ /* Destroy sound device */ status = pjmedia_snd_port_destroy( snd_port ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Destroy resample port. * This will destroy all downstream ports (e.g. the file port) */ status = pjmedia_port_destroy( resample_port ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Release application pool */ pj_pool_release( pool ); /* Destroy media endpoint. */ pjmedia_endpt_destroy( med_endpt ); /* Destroy pool factory */ pj_caching_pool_destroy( &cp ); /* Shutdown PJLIB */ pj_shutdown(); /* Done. */ return 0; }
/* * Print stream statistics */ static void print_stream_stat(pjmedia_stream *stream, const pjmedia_codec_param *codec_param) { char duration[80], last_update[80]; char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16]; pjmedia_port *port; pjmedia_rtcp_stat stat; pj_time_val now; pj_gettimeofday(&now); pjmedia_stream_get_stat(stream, &stat); pjmedia_stream_get_port(stream, &port); puts("Stream statistics:"); /* Print duration */ PJ_TIME_VAL_SUB(now, stat.start); sprintf(duration, " Duration: %02ld:%02ld:%02ld.%03ld", now.sec / 3600, (now.sec % 3600) / 60, (now.sec % 60), now.msec); printf(" Info: audio %dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n", PJMEDIA_PIA_SRATE(&port->info), PJMEDIA_PIA_PTIME(&port->info), good_number(bps, (codec_param->info.avg_bps+7)/8), good_number(ipbps, ((codec_param->info.avg_bps+7)/8) + (40 * 1000 / codec_param->setting.frm_per_pkt / codec_param->info.frm_ptime))); if (stat.rx.update_cnt == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, stat.rx.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } printf(" RX stat last update: %s\n" " total %s packets %sB received (%sB +IP hdr)%s\n" " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n" " (msec) min avg max last dev\n" " loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n" " jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n", last_update, good_number(packets, stat.rx.pkt), good_number(bytes, stat.rx.bytes), good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32), "", stat.rx.loss, stat.rx.loss * 100.0 / (stat.rx.pkt + stat.rx.loss), stat.rx.dup, stat.rx.dup * 100.0 / (stat.rx.pkt + stat.rx.loss), stat.rx.reorder, stat.rx.reorder * 100.0 / (stat.rx.pkt + stat.rx.loss), "", stat.rx.loss_period.min / 1000.0, stat.rx.loss_period.mean / 1000.0, stat.rx.loss_period.max / 1000.0, stat.rx.loss_period.last / 1000.0, pj_math_stat_get_stddev(&stat.rx.loss_period) / 1000.0, "", stat.rx.jitter.min / 1000.0, stat.rx.jitter.mean / 1000.0, stat.rx.jitter.max / 1000.0, stat.rx.jitter.last / 1000.0, pj_math_stat_get_stddev(&stat.rx.jitter) / 1000.0, "" ); if (stat.tx.update_cnt == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, stat.tx.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } printf(" TX stat last update: %s\n" " total %s packets %sB sent (%sB +IP hdr)%s\n" " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n" " (msec) min avg max last dev\n" " loss period: %7.3f %7.3f %7.3f %7.3f %7.3f%s\n" " jitter : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n", last_update, good_number(packets, stat.tx.pkt), good_number(bytes, stat.tx.bytes), good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32), "", stat.tx.loss, stat.tx.loss * 100.0 / (stat.tx.pkt + stat.tx.loss), stat.tx.dup, stat.tx.dup * 100.0 / (stat.tx.pkt + stat.tx.loss), stat.tx.reorder, stat.tx.reorder * 100.0 / (stat.tx.pkt + stat.tx.loss), "", stat.tx.loss_period.min / 1000.0, stat.tx.loss_period.mean / 1000.0, stat.tx.loss_period.max / 1000.0, stat.tx.loss_period.last / 1000.0, pj_math_stat_get_stddev(&stat.tx.loss_period) / 1000.0, "", stat.tx.jitter.min / 1000.0, stat.tx.jitter.mean / 1000.0, stat.tx.jitter.max / 1000.0, stat.tx.jitter.last / 1000.0, pj_math_stat_get_stddev(&stat.tx.jitter) / 1000.0, "" ); printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n", stat.rtt.min / 1000.0, stat.rtt.mean / 1000.0, stat.rtt.max / 1000.0, stat.rtt.last / 1000.0, pj_math_stat_get_stddev(&stat.rtt) / 1000.0, "" ); #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) /* RTCP XR Reports */ do { char loss[16], dup[16]; char jitter[80]; char toh[80]; char plc[16], jba[16], jbr[16]; char signal_lvl[16], noise_lvl[16], rerl[16]; char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16]; pjmedia_rtcp_xr_stat xr_stat; if (pjmedia_stream_get_stat_xr(stream, &xr_stat) != PJ_SUCCESS) break; puts("\nExtended reports:"); /* Statistics Summary */ puts(" Statistics Summary"); if (xr_stat.rx.stat_sum.l) sprintf(loss, "%d", xr_stat.rx.stat_sum.lost); else sprintf(loss, "(na)"); if (xr_stat.rx.stat_sum.d) sprintf(dup, "%d", xr_stat.rx.stat_sum.dup); else sprintf(dup, "(na)"); if (xr_stat.rx.stat_sum.j) { unsigned jmin, jmax, jmean, jdev; SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min, port->info.fmt.det.aud.clock_rate); SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max, port->info.fmt.det.aud.clock_rate); SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean, port->info.fmt.det.aud.clock_rate); SAMPLES_TO_USEC(jdev, pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter), port->info.fmt.det.aud.clock_rate); sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); } else sprintf(jitter, "(report not available)"); if (xr_stat.rx.stat_sum.t) { sprintf(toh, "%11d %11d %11d %11d", xr_stat.rx.stat_sum.toh.min, xr_stat.rx.stat_sum.toh.mean, xr_stat.rx.stat_sum.toh.max, pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); } else sprintf(toh, "(report not available)"); if (xr_stat.rx.stat_sum.update.sec == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } printf(" RX last update: %s\n" " begin seq=%d, end seq=%d%s\n" " pkt loss=%s, dup=%s%s\n" " (msec) min avg max dev\n" " jitter : %s\n" " toh : %s\n", last_update, xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq, "", loss, dup, "", jitter, toh ); if (xr_stat.tx.stat_sum.l) sprintf(loss, "%d", xr_stat.tx.stat_sum.lost); else sprintf(loss, "(na)"); if (xr_stat.tx.stat_sum.d) sprintf(dup, "%d", xr_stat.tx.stat_sum.dup); else sprintf(dup, "(na)"); if (xr_stat.tx.stat_sum.j) { unsigned jmin, jmax, jmean, jdev; SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min, port->info.fmt.det.aud.clock_rate); SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max, port->info.fmt.det.aud.clock_rate); SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean, port->info.fmt.det.aud.clock_rate); SAMPLES_TO_USEC(jdev, pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter), port->info.fmt.det.aud.clock_rate); sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); } else sprintf(jitter, "(report not available)"); if (xr_stat.tx.stat_sum.t) { sprintf(toh, "%11d %11d %11d %11d", xr_stat.tx.stat_sum.toh.min, xr_stat.tx.stat_sum.toh.mean, xr_stat.tx.stat_sum.toh.max, pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); } else sprintf(toh, "(report not available)"); if (xr_stat.tx.stat_sum.update.sec == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } printf(" TX last update: %s\n" " begin seq=%d, end seq=%d%s\n" " pkt loss=%s, dup=%s%s\n" " (msec) min avg max dev\n" " jitter : %s\n" " toh : %s\n", last_update, xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq, "", loss, dup, "", jitter, toh ); /* VoIP Metrics */ puts(" VoIP Metrics"); PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl); PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl); PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl); PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor); PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor); PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq); PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq); switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) { case PJMEDIA_RTCP_XR_PLC_DIS: sprintf(plc, "DISABLED"); break; case PJMEDIA_RTCP_XR_PLC_ENH: sprintf(plc, "ENHANCED"); break; case PJMEDIA_RTCP_XR_PLC_STD: sprintf(plc, "STANDARD"); break; case PJMEDIA_RTCP_XR_PLC_UNK: default: sprintf(plc, "UNKNOWN"); break; } switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) { case PJMEDIA_RTCP_XR_JB_FIXED: sprintf(jba, "FIXED"); break; case PJMEDIA_RTCP_XR_JB_ADAPTIVE: sprintf(jba, "ADAPTIVE"); break; default: sprintf(jba, "UNKNOWN"); break; } sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F); if (xr_stat.rx.voip_mtc.update.sec == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } printf(" RX last update: %s\n" " packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" " burst : density=%d (%.2f%%), duration=%d%s\n" " gap : density=%d (%.2f%%), duration=%d%s\n" " delay : round trip=%d%s, end system=%d%s\n" " level : signal=%s%s, noise=%s%s, RERL=%s%s\n" " quality : R factor=%s, ext R factor=%s\n" " MOS LQ=%s, MOS CQ=%s\n" " config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" " JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n", last_update, /* pakcets */ xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256, xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256, /* burst */ xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256, xr_stat.rx.voip_mtc.burst_dur, "ms", /* gap */ xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256, xr_stat.rx.voip_mtc.gap_dur, "ms", /* delay */ xr_stat.rx.voip_mtc.rnd_trip_delay, "ms", xr_stat.rx.voip_mtc.end_sys_delay, "ms", /* level */ signal_lvl, "dB", noise_lvl, "dB", rerl, "", /* quality */ r_factor, ext_r_factor, mos_lq, mos_cq, /* config */ plc, jba, jbr, xr_stat.rx.voip_mtc.gmin, /* JB delay */ xr_stat.rx.voip_mtc.jb_nom, "ms", xr_stat.rx.voip_mtc.jb_max, "ms", xr_stat.rx.voip_mtc.jb_abs_max, "ms" ); PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl); PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl); PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl); PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor); PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor); PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq); PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq); switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) { case PJMEDIA_RTCP_XR_PLC_DIS: sprintf(plc, "DISABLED"); break; case PJMEDIA_RTCP_XR_PLC_ENH: sprintf(plc, "ENHANCED"); break; case PJMEDIA_RTCP_XR_PLC_STD: sprintf(plc, "STANDARD"); break; case PJMEDIA_RTCP_XR_PLC_UNK: default: sprintf(plc, "unknown"); break; } switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) { case PJMEDIA_RTCP_XR_JB_FIXED: sprintf(jba, "FIXED"); break; case PJMEDIA_RTCP_XR_JB_ADAPTIVE: sprintf(jba, "ADAPTIVE"); break; default: sprintf(jba, "unknown"); break; } sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F); if (xr_stat.tx.voip_mtc.update.sec == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } printf(" TX last update: %s\n" " packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" " burst : density=%d (%.2f%%), duration=%d%s\n" " gap : density=%d (%.2f%%), duration=%d%s\n" " delay : round trip=%d%s, end system=%d%s\n" " level : signal=%s%s, noise=%s%s, RERL=%s%s\n" " quality : R factor=%s, ext R factor=%s\n" " MOS LQ=%s, MOS CQ=%s\n" " config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" " JB delay : cur=%d%s, max=%d%s, abs max=%d%s\n", last_update, /* pakcets */ xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256, xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256, /* burst */ xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256, xr_stat.tx.voip_mtc.burst_dur, "ms", /* gap */ xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256, xr_stat.tx.voip_mtc.gap_dur, "ms", /* delay */ xr_stat.tx.voip_mtc.rnd_trip_delay, "ms", xr_stat.tx.voip_mtc.end_sys_delay, "ms", /* level */ signal_lvl, "dB", noise_lvl, "dB", rerl, "", /* quality */ r_factor, ext_r_factor, mos_lq, mos_cq, /* config */ plc, jba, jbr, xr_stat.tx.voip_mtc.gmin, /* JB delay */ xr_stat.tx.voip_mtc.jb_nom, "ms", xr_stat.tx.voip_mtc.jb_max, "ms", xr_stat.tx.voip_mtc.jb_abs_max, "ms" ); /* RTT delay (by receiver side) */ printf(" (msec) min avg max last dev\n"); printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f %7.3f%s\n", xr_stat.rtt.min / 1000.0, xr_stat.rtt.mean / 1000.0, xr_stat.rtt.max / 1000.0, xr_stat.rtt.last / 1000.0, pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0, "" ); } while (0); #endif /* PJMEDIA_HAS_RTCP_XR */ }
/* * main() */ int main(int argc, char *argv[]) { pj_caching_pool cp; pjmedia_endpt *med_endpt; pj_pool_t *pool; pjmedia_port *rec_file_port = NULL, *play_file_port = NULL; pjmedia_master_port *master_port = NULL; pjmedia_snd_port *snd_port = NULL; pjmedia_stream *stream = NULL; pjmedia_port *stream_port; char tmp[10]; pj_status_t status; #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) /* SRTP variables */ pj_bool_t use_srtp = PJ_FALSE; char tmp_tx_key[64]; char tmp_rx_key[64]; pj_str_t srtp_tx_key = {NULL, 0}; pj_str_t srtp_rx_key = {NULL, 0}; pj_str_t srtp_crypto_suite = {NULL, 0}; int tmp_key_len; #endif /* Default values */ const pjmedia_codec_info *codec_info; pjmedia_codec_param codec_param; pjmedia_dir dir = PJMEDIA_DIR_DECODING; pj_sockaddr_in remote_addr; pj_uint16_t local_port = 4000; char *codec_id = NULL; char *rec_file = NULL; char *play_file = NULL; enum { OPT_CODEC = 'c', OPT_LOCAL_PORT = 'p', OPT_REMOTE = 'r', OPT_PLAY_FILE = 'w', OPT_RECORD_FILE = 'R', OPT_SEND_RECV = 'b', OPT_SEND_ONLY = 's', OPT_RECV_ONLY = 'i', #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) OPT_USE_SRTP = 'S', #endif OPT_SRTP_TX_KEY = 'x', OPT_SRTP_RX_KEY = 'y', OPT_HELP = 'h', }; struct pj_getopt_option long_options[] = { { "codec", 1, 0, OPT_CODEC }, { "local-port", 1, 0, OPT_LOCAL_PORT }, { "remote", 1, 0, OPT_REMOTE }, { "play-file", 1, 0, OPT_PLAY_FILE }, { "record-file", 1, 0, OPT_RECORD_FILE }, { "send-recv", 0, 0, OPT_SEND_RECV }, { "send-only", 0, 0, OPT_SEND_ONLY }, { "recv-only", 0, 0, OPT_RECV_ONLY }, #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) { "use-srtp", 2, 0, OPT_USE_SRTP }, { "srtp-tx-key", 1, 0, OPT_SRTP_TX_KEY }, { "srtp-rx-key", 1, 0, OPT_SRTP_RX_KEY }, #endif { "help", 0, 0, OPT_HELP }, { NULL, 0, 0, 0 }, }; int c; int option_index; pj_bzero(&remote_addr, sizeof(remote_addr)); /* init PJLIB : */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Parse arguments */ pj_optind = 0; while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1) { switch (c) { case OPT_CODEC: codec_id = pj_optarg; break; case OPT_LOCAL_PORT: local_port = (pj_uint16_t) atoi(pj_optarg); if (local_port < 1) { printf("Error: invalid local port %s\n", pj_optarg); return 1; } break; case OPT_REMOTE: { pj_str_t ip = pj_str(strtok(pj_optarg, ":")); pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":")); status = pj_sockaddr_in_init(&remote_addr, &ip, port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Invalid remote address", status); return 1; } } break; case OPT_PLAY_FILE: play_file = pj_optarg; break; case OPT_RECORD_FILE: rec_file = pj_optarg; break; case OPT_SEND_RECV: dir = PJMEDIA_DIR_ENCODING_DECODING; break; case OPT_SEND_ONLY: dir = PJMEDIA_DIR_ENCODING; break; case OPT_RECV_ONLY: dir = PJMEDIA_DIR_DECODING; break; #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) case OPT_USE_SRTP: use_srtp = PJ_TRUE; if (pj_optarg) { pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg)); } else { srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80"); } break; case OPT_SRTP_TX_KEY: tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg, (int)strlen(pj_optarg)); pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2); break; case OPT_SRTP_RX_KEY: tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg, (int)strlen(pj_optarg)); pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2); break; #endif case OPT_HELP: usage(); return 1; default: printf("Invalid options %s\n", argv[pj_optind]); return 1; } } /* Verify arguments. */ if (dir & PJMEDIA_DIR_ENCODING) { if (remote_addr.sin_addr.s_addr == 0) { printf("Error: remote address must be set\n"); return 1; } } if (play_file != NULL && dir != PJMEDIA_DIR_ENCODING) { printf("Direction is set to --send-only because of --play-file\n"); dir = PJMEDIA_DIR_ENCODING; } #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) /* SRTP validation */ if (use_srtp) { if (!srtp_tx_key.slen || !srtp_rx_key.slen) { printf("Error: Key for each SRTP stream direction must be set\n"); return 1; } } #endif /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Create memory pool for application purpose */ pool = pj_pool_create( &cp.factory, /* pool factory */ "app", /* pool name. */ 4000, /* init size */ 4000, /* increment size */ NULL /* callback on error */ ); /* Register all supported codecs */ status = init_codecs(med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Find which codec to use. */ if (codec_id) { unsigned count = 1; pj_str_t str_codec_id = pj_str(codec_id); pjmedia_codec_mgr *codec_mgr = pjmedia_endpt_get_codec_mgr(med_endpt); status = pjmedia_codec_mgr_find_codecs_by_id( codec_mgr, &str_codec_id, &count, &codec_info, NULL); if (status != PJ_SUCCESS) { printf("Error: unable to find codec %s\n", codec_id); return 1; } } else { /* Default to pcmu */ pjmedia_codec_mgr_get_codec_info( pjmedia_endpt_get_codec_mgr(med_endpt), 0, &codec_info); } /* Create stream based on program arguments */ status = create_stream(pool, med_endpt, codec_info, dir, local_port, &remote_addr, #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) use_srtp, &srtp_crypto_suite, &srtp_tx_key, &srtp_rx_key, #endif &stream); if (status != PJ_SUCCESS) goto on_exit; /* Get codec default param for info */ status = pjmedia_codec_mgr_get_default_param( pjmedia_endpt_get_codec_mgr(med_endpt), codec_info, &codec_param); /* Should be ok, as create_stream() above succeeded */ pj_assert(status == PJ_SUCCESS); /* Get the port interface of the stream */ status = pjmedia_stream_get_port( stream, &stream_port); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); if (play_file) { unsigned wav_ptime; wav_ptime = PJMEDIA_PIA_PTIME(&stream_port->info); status = pjmedia_wav_player_port_create(pool, play_file, wav_ptime, 0, -1, &play_file_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to use file", status); goto on_exit; } status = pjmedia_master_port_create(pool, play_file_port, stream_port, 0, &master_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create master port", status); goto on_exit; } status = pjmedia_master_port_start(master_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error starting master port", status); goto on_exit; } printf("Playing from WAV file %s..\n", play_file); } else if (rec_file) { status = pjmedia_wav_writer_port_create(pool, rec_file, PJMEDIA_PIA_SRATE(&stream_port->info), PJMEDIA_PIA_CCNT(&stream_port->info), PJMEDIA_PIA_SPF(&stream_port->info), PJMEDIA_PIA_BITS(&stream_port->info), 0, 0, &rec_file_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to use file", status); goto on_exit; } status = pjmedia_master_port_create(pool, stream_port, rec_file_port, 0, &master_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create master port", status); goto on_exit; } status = pjmedia_master_port_start(master_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error starting master port", status); goto on_exit; } printf("Recording to WAV file %s..\n", rec_file); } else { /* Create sound device port. */ if (dir == PJMEDIA_DIR_ENCODING_DECODING) status = pjmedia_snd_port_create(pool, -1, -1, PJMEDIA_PIA_SRATE(&stream_port->info), PJMEDIA_PIA_CCNT(&stream_port->info), PJMEDIA_PIA_SPF(&stream_port->info), PJMEDIA_PIA_BITS(&stream_port->info), 0, &snd_port); else if (dir == PJMEDIA_DIR_ENCODING) status = pjmedia_snd_port_create_rec(pool, -1, PJMEDIA_PIA_SRATE(&stream_port->info), PJMEDIA_PIA_CCNT(&stream_port->info), PJMEDIA_PIA_SPF(&stream_port->info), PJMEDIA_PIA_BITS(&stream_port->info), 0, &snd_port); else status = pjmedia_snd_port_create_player(pool, -1, PJMEDIA_PIA_SRATE(&stream_port->info), PJMEDIA_PIA_CCNT(&stream_port->info), PJMEDIA_PIA_SPF(&stream_port->info), PJMEDIA_PIA_BITS(&stream_port->info), 0, &snd_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create sound port", status); goto on_exit; } /* Connect sound port to stream */ status = pjmedia_snd_port_connect( snd_port, stream_port ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); } /* Start streaming */ pjmedia_stream_start(stream); /* Done */ if (dir == PJMEDIA_DIR_DECODING) printf("Stream is active, dir is recv-only, local port is %d\n", local_port); else if (dir == PJMEDIA_DIR_ENCODING) printf("Stream is active, dir is send-only, sending to %s:%d\n", pj_inet_ntoa(remote_addr.sin_addr), pj_ntohs(remote_addr.sin_port)); else printf("Stream is active, send/recv, local port is %d, " "sending to %s:%d\n", local_port, pj_inet_ntoa(remote_addr.sin_addr), pj_ntohs(remote_addr.sin_port)); for (;;) { puts(""); puts("Commands:"); puts(" s Display media statistics"); puts(" q Quit"); puts(""); printf("Command: "); fflush(stdout); if (fgets(tmp, sizeof(tmp), stdin) == NULL) { puts("EOF while reading stdin, will quit now.."); break; } if (tmp[0] == 's') print_stream_stat(stream, &codec_param); else if (tmp[0] == 'q') break; } /* Start deinitialization: */ on_exit: /* Destroy sound device */ if (snd_port) { pjmedia_snd_port_destroy( snd_port ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); } /* If there is master port, then we just need to destroy master port * (it will recursively destroy upstream and downstream ports, which * in this case are file_port and stream_port). */ if (master_port) { pjmedia_master_port_destroy(master_port, PJ_TRUE); play_file_port = NULL; stream = NULL; } /* Destroy stream */ if (stream) { pjmedia_transport *tp; tp = pjmedia_stream_get_transport(stream); pjmedia_stream_destroy(stream); pjmedia_transport_close(tp); } /* Destroy file ports */ if (play_file_port) pjmedia_port_destroy( play_file_port ); if (rec_file_port) pjmedia_port_destroy( rec_file_port ); /* Release application pool */ pj_pool_release( pool ); /* Destroy media endpoint. */ pjmedia_endpt_destroy( med_endpt ); /* Destroy pool factory */ pj_caching_pool_destroy( &cp ); /* Shutdown PJLIB */ pj_shutdown(); return (status == PJ_SUCCESS) ? 0 : 1; }
int main(int argc, char *argv[]) { pj_caching_pool cp; pjmedia_endpt *med_endpt; pj_pool_t *pool; pjmedia_port *file_port = NULL; pjmedia_port *stereo_port = NULL; pjmedia_snd_port *snd_port = NULL; int dev_id = -1; char tmp[10]; pj_status_t status; char *wav_file = NULL; unsigned mode = 0; unsigned rec_ch_cnt = 1; unsigned snd_ch_cnt = 2; enum { OPT_MODE = 'm', OPT_REC_CHANNEL = 'C', OPT_SND_CHANNEL = 'c', }; struct pj_getopt_option long_options[] = { { "mode", 1, 0, OPT_MODE }, { "rec-ch-cnt", 1, 0, OPT_REC_CHANNEL }, { "snd-ch-cnt", 1, 0, OPT_SND_CHANNEL }, { NULL, 0, 0, 0 }, }; int c; int option_index; /* Must init PJLIB first: */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Parse arguments */ pj_optind = 0; while((c=pj_getopt_long(argc,argv, "m:C:c:", long_options, &option_index))!=-1) { switch (c) { case OPT_MODE: if (mode) { app_perror(THIS_FILE, "Cannot record and play at once!", PJ_EINVAL); return 1; } mode = atoi(pj_optarg); break; case OPT_REC_CHANNEL: rec_ch_cnt = atoi(pj_optarg); break; case OPT_SND_CHANNEL: snd_ch_cnt = atoi(pj_optarg); break; default: printf("Invalid options %s\n", argv[pj_optind]); puts(desc); return 1; } } wav_file = argv[pj_optind]; /* Verify arguments. */ if (!wav_file) { app_perror(THIS_FILE, "WAV file not specified!", PJ_EINVAL); puts(desc); return 1; } if (!snd_ch_cnt || !rec_ch_cnt || rec_ch_cnt > 6) { app_perror(THIS_FILE, "Invalid or too many channel count!", PJ_EINVAL); puts(desc); return 1; } if (mode != MODE_RECORD && mode != MODE_PLAY) { app_perror(THIS_FILE, "Invalid operation mode!", PJ_EINVAL); puts(desc); return 1; } /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Create memory pool for our file player */ pool = pj_pool_create( &cp.factory, /* pool factory */ "app", /* pool name. */ 4000, /* init size */ 4000, /* increment size */ NULL /* callback on error */ ); if (mode == MODE_PLAY) { /* Create WAVE file player port. */ status = pjmedia_wav_player_port_create( pool, wav_file, PTIME, 0, 0, &file_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to open file", status); return 1; } /* Create sound player port. */ status = pjmedia_snd_port_create_player( pool, /* pool */ dev_id, /* device id. */ PJMEDIA_PIA_SRATE(&file_port->info),/* clock rate. */ snd_ch_cnt, /* # of channels. */ snd_ch_cnt * PTIME * /* samples per frame. */ PJMEDIA_PIA_SRATE(&file_port->info) / 1000, PJMEDIA_PIA_BITS(&file_port->info),/* bits per sample. */ 0, /* options */ 15, 5, &snd_port /* returned port */ ); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to open sound device", status); return 1; } if (snd_ch_cnt != PJMEDIA_PIA_CCNT(&file_port->info)) { status = pjmedia_stereo_port_create( pool, file_port, snd_ch_cnt, 0, &stereo_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create stereo port", status); return 1; } status = pjmedia_snd_port_connect(snd_port, stereo_port); } else { status = pjmedia_snd_port_connect(snd_port, file_port); } if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to connect sound port", status); return 1; } } else { /* Create WAVE file writer port. */ status = pjmedia_wav_writer_port_create(pool, wav_file, REC_CLOCK_RATE, rec_ch_cnt, rec_ch_cnt * PTIME * REC_CLOCK_RATE / 1000, NBITS, 0, 0, &file_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to open file", status); return 1; } /* Create sound player port. */ status = pjmedia_snd_port_create_rec( pool, /* pool */ dev_id, /* device id. */ REC_CLOCK_RATE, /* clock rate. */ snd_ch_cnt, /* # of channels. */ snd_ch_cnt * PTIME * REC_CLOCK_RATE / 1000, /* samples per frame. */ NBITS, /* bits per sample. */ 0, /* options */ 15, 5, &snd_port /* returned port */ ); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to open sound device", status); return 1; } if (rec_ch_cnt != snd_ch_cnt) { status = pjmedia_stereo_port_create( pool, file_port, snd_ch_cnt, 0, &stereo_port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create stereo port", status); return 1; } status = pjmedia_snd_port_connect(snd_port, stereo_port); } else { status = pjmedia_snd_port_connect(snd_port, file_port); } if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to connect sound port", status); return 1; } } /* Dump memory usage */ dump_pool_usage(THIS_FILE, &cp); /* * File should be playing and looping now, using sound device's thread. */ /* Sleep to allow log messages to flush */ pj_thread_sleep(100); printf("Mode = %s\n", (mode == MODE_PLAY? "playing" : "recording") ); printf("File port channel count = %d\n", PJMEDIA_PIA_CCNT(&file_port->info)); printf("Sound port channel count = %d\n", PJMEDIA_PIA_CCNT(&pjmedia_snd_port_get_port(snd_port)->info)); puts(""); puts("Press <ENTER> to stop and quit"); if (fgets(tmp, sizeof(tmp), stdin) == NULL) { puts("EOF while reading stdin, will quit now.."); } /* Start deinitialization: */ /* Destroy sound device */ status = pjmedia_snd_port_destroy( snd_port ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Destroy stereo port and file_port. * Stereo port will destroy all downstream ports (e.g. the file port) */ status = pjmedia_port_destroy( stereo_port? stereo_port : file_port); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Release application pool */ pj_pool_release( pool ); /* Destroy media endpoint. */ pjmedia_endpt_destroy( med_endpt ); /* Destroy pool factory */ pj_caching_pool_destroy( &cp ); /* Shutdown PJLIB */ pj_shutdown(); /* Done. */ return 0; }
/* This is the RX "tick". * This function is called periodically every "tick" milliseconds, and * it will determine whether to call get_frame() from the RX stream. */ static void rx_tick(const pj_time_val *t) { struct stream *strm = g_app.rx; pjmedia_port *port = g_app.rx->port; long pkt_interval; pkt_interval = PJMEDIA_PIA_SPF(&port->info) * 1000 / PJMEDIA_PIA_SRATE(&port->info) * g_app.cfg.rx_snd_burst; if (PJ_TIME_VAL_GTE(*t, strm->state.rx.next_schedule)) { unsigned i; for (i=0; i<g_app.cfg.rx_snd_burst; ++i) { struct log_entry entry; pjmedia_rtcp_stat stat; pjmedia_jb_state jstate; pj_bool_t has_frame; char msg[120]; unsigned last_empty; pjmedia_stream_get_stat(g_app.rx->strm, &stat); pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate); last_empty = jstate.empty; /* Pre GET event */ pj_bzero(&entry, sizeof(entry)); entry.event = EVENT_GET_PRE; entry.wall_clock = *t; entry.stat = &stat; entry.jb_state = &jstate; write_log(&entry, PJ_TRUE); /* GET */ run_one_frame(g_app.rx->port, g_app.rx_wav, &has_frame); /* Post GET event */ pjmedia_stream_get_stat(g_app.rx->strm, &stat); pjmedia_stream_get_stat_jbuf(g_app.rx->strm, &jstate); pj_bzero(&entry, sizeof(entry)); entry.event = EVENT_GET_POST; entry.wall_clock = *t; entry.stat = &stat; entry.jb_state = &jstate; msg[0] = '\0'; entry.log = msg; if (jstate.empty > last_empty) strcat(msg, "** JBUF was empty **"); if (!has_frame) strcat(msg, "** NULL frame was returned **"); write_log(&entry, PJ_TRUE); } strm->state.rx.next_schedule.msec += pkt_interval; pj_time_val_normalize(&strm->state.rx.next_schedule); } }
/* * Callback when SDP negotiation has completed. * We are interested with this callback because we want to start media * as soon as SDP negotiation is completed. */ static void call_on_media_update( pjsip_inv_session *inv, pj_status_t status) { pjmedia_stream_info stream_info; const pjmedia_sdp_session *local_sdp; const pjmedia_sdp_session *remote_sdp; pjmedia_port *media_port; if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "SDP negotiation has failed", status); /* Here we should disconnect call if we're not in the middle * of initializing an UAS dialog and if this is not a re-INVITE. */ return; } /* Get local and remote SDP. * We need both SDPs to create a media session. */ status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp); status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp); /* Create stream info based on the media audio SDP. */ status = pjmedia_stream_info_from_sdp(&stream_info, inv->dlg->pool, g_med_endpt, local_sdp, remote_sdp, 0); if (status != PJ_SUCCESS) { app_perror(THIS_FILE,"Unable to create audio stream info",status); return; } /* If required, we can also change some settings in the stream info, * (such as jitter buffer settings, codec settings, etc) before we * create the stream. */ /* Create new audio media stream, passing the stream info, and also the * media socket that we created earlier. */ status = pjmedia_stream_create(g_med_endpt, inv->dlg->pool, &stream_info, g_med_transport[0], NULL, &g_med_stream); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create audio stream", status); return; } /* Start the audio stream */ status = pjmedia_stream_start(g_med_stream); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to start audio stream", status); return; } /* Get the media port interface of the audio stream. * Media port interface is basicly a struct containing get_frame() and * put_frame() function. With this media port interface, we can attach * the port interface to conference bridge, or directly to a sound * player/recorder device. */ pjmedia_stream_get_port(g_med_stream, &media_port); /* Create sound port */ pjmedia_snd_port_create(inv->pool, PJMEDIA_AUD_DEFAULT_CAPTURE_DEV, PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV, PJMEDIA_PIA_SRATE(&media_port->info),/* clock rate */ PJMEDIA_PIA_CCNT(&media_port->info),/* channel count */ PJMEDIA_PIA_SPF(&media_port->info), /* samples per frame*/ PJMEDIA_PIA_BITS(&media_port->info),/* bits per sample */ 0, &g_snd_port); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create sound port", status); PJ_LOG(3,(THIS_FILE, "%d %d %d %d", PJMEDIA_PIA_SRATE(&media_port->info),/* clock rate */ PJMEDIA_PIA_CCNT(&media_port->info),/* channel count */ PJMEDIA_PIA_SPF(&media_port->info), /* samples per frame*/ PJMEDIA_PIA_BITS(&media_port->info) /* bits per sample */ )); return; } status = pjmedia_snd_port_connect(g_snd_port, media_port); /* Get the media port interface of the second stream in the session, * which is video stream. With this media port interface, we can attach * the port directly to a renderer/capture video device. */ #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) if (local_sdp->media_count > 1) { pjmedia_vid_stream_info vstream_info; pjmedia_vid_port_param vport_param; pjmedia_vid_port_param_default(&vport_param); /* Create stream info based on the media video SDP. */ status = pjmedia_vid_stream_info_from_sdp(&vstream_info, inv->dlg->pool, g_med_endpt, local_sdp, remote_sdp, 1); if (status != PJ_SUCCESS) { app_perror(THIS_FILE,"Unable to create video stream info",status); return; } /* If required, we can also change some settings in the stream info, * (such as jitter buffer settings, codec settings, etc) before we * create the video stream. */ /* Create new video media stream, passing the stream info, and also the * media socket that we created earlier. */ status = pjmedia_vid_stream_create(g_med_endpt, NULL, &vstream_info, g_med_transport[1], NULL, &g_med_vstream); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to create video stream", status); return; } /* Start the video stream */ status = pjmedia_vid_stream_start(g_med_vstream); if (status != PJ_SUCCESS) { app_perror( THIS_FILE, "Unable to start video stream", status); return; } if (vstream_info.dir & PJMEDIA_DIR_DECODING) { status = pjmedia_vid_dev_default_param( inv->pool, PJMEDIA_VID_DEFAULT_RENDER_DEV, &vport_param.vidparam); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to get default param of video " "renderer device", status); return; } /* Get video stream port for decoding direction */ pjmedia_vid_stream_get_port(g_med_vstream, PJMEDIA_DIR_DECODING, &media_port); /* Set format */ pjmedia_format_copy(&vport_param.vidparam.fmt, &media_port->info.fmt); vport_param.vidparam.dir = PJMEDIA_DIR_RENDER; vport_param.active = PJ_TRUE; /* Create renderer */ status = pjmedia_vid_port_create(inv->pool, &vport_param, &g_vid_renderer); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create video renderer device", status); return; } /* Connect renderer to media_port */ status = pjmedia_vid_port_connect(g_vid_renderer, media_port, PJ_FALSE); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to connect renderer to stream", status); return; } } /* Create capturer */ if (vstream_info.dir & PJMEDIA_DIR_ENCODING) { status = pjmedia_vid_dev_default_param( inv->pool, PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vport_param.vidparam); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to get default param of video " "capture device", status); return; } /* Get video stream port for decoding direction */ pjmedia_vid_stream_get_port(g_med_vstream, PJMEDIA_DIR_ENCODING, &media_port); /* Get capturer format from stream info */ pjmedia_format_copy(&vport_param.vidparam.fmt, &media_port->info.fmt); vport_param.vidparam.dir = PJMEDIA_DIR_CAPTURE; vport_param.active = PJ_TRUE; /* Create capturer */ status = pjmedia_vid_port_create(inv->pool, &vport_param, &g_vid_capturer); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create video capture device", status); return; } /* Connect capturer to media_port */ status = pjmedia_vid_port_connect(g_vid_capturer, media_port, PJ_FALSE); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to connect capturer to stream", status); return; } } /* Start streaming */ if (g_vid_renderer) { status = pjmedia_vid_port_start(g_vid_renderer); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to start video renderer", status); return; } } if (g_vid_capturer) { status = pjmedia_vid_port_start(g_vid_capturer); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to start video capturer", status); return; } } } #endif /* PJMEDIA_HAS_VIDEO */ /* Done with media. */ }
/**************************************************************************** * sound latency test */ static int calculate_latency(pj_pool_t *pool, pjmedia_port *wav, unsigned *lat_sum, unsigned *lat_cnt, unsigned *lat_min, unsigned *lat_max) { pjmedia_frame frm; short *buf; unsigned i, clock_rate, samples_per_frame; pj_size_t read, len; unsigned start_pos; pj_bool_t first; pj_status_t status; *lat_sum = 0; *lat_cnt = 0; *lat_min = 10000; *lat_max = 0; samples_per_frame = PJMEDIA_PIA_SPF(&wav->info); clock_rate = PJMEDIA_PIA_SRATE(&wav->info); frm.buf = pj_pool_alloc(pool, samples_per_frame * 2); frm.size = samples_per_frame * 2; len = pjmedia_wav_player_get_len(wav); buf = pj_pool_alloc(pool, len + samples_per_frame); /* Read the whole file */ read = 0; while (read < len/2) { status = pjmedia_port_get_frame(wav, &frm); if (status != PJ_SUCCESS) break; pjmedia_copy_samples(buf+read, (short*)frm.buf, samples_per_frame); read += samples_per_frame; } if (read < 2 * clock_rate) { systest_perror("The WAV file is too short", PJ_SUCCESS); return -1; } /* Zero the first 500ms to remove loud click noises * (keypad press, etc.) */ pjmedia_zero_samples(buf, clock_rate / 2); /* Loop to calculate latency */ start_pos = 0; first = PJ_TRUE; while (start_pos < len/2 - clock_rate) { int max_signal = 0; unsigned max_signal_pos = start_pos; unsigned max_echo_pos = 0; unsigned pos; unsigned lat; /* Get the largest signal in the next 0.7s */ for (i=start_pos; i<start_pos + clock_rate * 700 / 1000; ++i) { if (abs(buf[i]) > max_signal) { max_signal = abs(buf[i]); max_signal_pos = i; } } /* Advance 10ms from max_signal_pos */ pos = max_signal_pos + 10 * clock_rate / 1000; /* Get the largest signal in the next 800ms */ max_signal = 0; max_echo_pos = pos; for (i=pos; i<pos+clock_rate * 8 / 10; ++i) { if (abs(buf[i]) > max_signal) { max_signal = abs(buf[i]); max_echo_pos = i; } } lat = (max_echo_pos - max_signal_pos) * 1000 / clock_rate; #if 0 PJ_LOG(4,(THIS_FILE, "Signal at %dms, echo at %d ms, latency %d ms", max_signal_pos * 1000 / clock_rate, max_echo_pos * 1000 / clock_rate, lat)); #endif *lat_sum += lat; (*lat_cnt)++; if (lat < *lat_min) *lat_min = lat; if (lat > *lat_max) *lat_max = lat; /* Advance next loop */ if (first) { start_pos = max_signal_pos + clock_rate * 9 / 10; first = PJ_FALSE; } else { start_pos += clock_rate; } } return 0; }
static int aviplay(pj_pool_t *pool, const char *fname) { pjmedia_vid_port *renderer=NULL; pjmedia_vid_port_param param; const pjmedia_video_format_info *vfi; pjmedia_video_format_detail *vfd; pjmedia_snd_port *snd_port = NULL; pj_status_t status; int rc = 0; pjmedia_avi_streams *avi_streams; pjmedia_avi_stream *vid_stream, *aud_stream; pjmedia_port *vid_port = NULL, *aud_port = NULL; pjmedia_vid_codec *codec=NULL; avi_port_t avi_port; pj_bzero(&avi_port, sizeof(avi_port)); status = pjmedia_avi_player_create_streams(pool, fname, 0, &avi_streams); if (status != PJ_SUCCESS) { PJ_PERROR(2,("", status, " Error playing %s", fname)); rc = 210; goto on_return; } vid_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams, 0, PJMEDIA_TYPE_VIDEO); vid_port = pjmedia_avi_stream_get_port(vid_stream); if (vid_port) { pjmedia_vid_port_param_default(¶m); status = pjmedia_vid_dev_default_param(pool, PJMEDIA_VID_DEFAULT_RENDER_DEV, ¶m.vidparam); if (status != PJ_SUCCESS) { rc = 220; goto on_return; } /* Create renderer, set it to active */ param.active = PJ_TRUE; param.vidparam.dir = PJMEDIA_DIR_RENDER; vfd = pjmedia_format_get_video_format_detail(&vid_port->info.fmt, PJ_TRUE); pjmedia_format_init_video(¶m.vidparam.fmt, vid_port->info.fmt.id, vfd->size.w, vfd->size.h, vfd->fps.num, vfd->fps.denum); vfi = pjmedia_get_video_format_info( pjmedia_video_format_mgr_instance(), vid_port->info.fmt.id); /* Check whether the frame is encoded */ if (!vfi || vfi->bpp == 0) { /* Yes, prepare codec */ pj_str_t codec_id_st; unsigned info_cnt = 1, i, k; const pjmedia_vid_codec_info *codec_info; pj_str_t port_name = {"codec", 5}; pj_uint8_t *enc_buf = NULL; pj_size_t enc_buf_size = 0; pjmedia_vid_dev_info rdr_info; pjmedia_port codec_port; codec_port_data_t codec_port_data; pjmedia_vid_codec_param codec_param; struct codec_fmt *codecp = NULL; /* Lookup codec */ for (i = 0; i < sizeof(codec_fmts)/sizeof(codec_fmts[0]); i++) { if (vid_port->info.fmt.id == codec_fmts[i].pjmedia_id) { codecp = &codec_fmts[i]; break; } } if (!codecp) { rc = 242; goto on_return; } pj_cstr(&codec_id_st, codecp->codec_id); status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, &codec_id_st, &info_cnt, &codec_info, NULL); if (status != PJ_SUCCESS) { rc = 245; goto on_return; } status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info, &codec_param); if (status != PJ_SUCCESS) { rc = 246; goto on_return; } pjmedia_format_copy(&codec_param.enc_fmt, ¶m.vidparam.fmt); pjmedia_vid_dev_get_info(param.vidparam.rend_id, &rdr_info); for (i=0; i<codec_info->dec_fmt_id_cnt; ++i) { for (k=0; k<rdr_info.fmt_cnt; ++k) { if (codec_info->dec_fmt_id[i]==(int)rdr_info.fmt[k].id) { param.vidparam.fmt.id = codec_info->dec_fmt_id[i]; i = codec_info->dec_fmt_id_cnt; break; } } } /* Open codec */ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info, &codec); if (status != PJ_SUCCESS) { rc = 250; goto on_return; } status = pjmedia_vid_codec_init(codec, pool); if (status != PJ_SUCCESS) { rc = 251; goto on_return; } pjmedia_format_copy(&codec_param.dec_fmt, ¶m.vidparam.fmt); codec_param.dir = PJMEDIA_DIR_DECODING; codec_param.packing = PJMEDIA_VID_PACKING_WHOLE; status = pjmedia_vid_codec_open(codec, &codec_param); if (status != PJ_SUCCESS) { rc = 252; goto on_return; } /* Alloc encoding buffer */ enc_buf_size = codec_param.dec_fmt.det.vid.size.w * codec_param.dec_fmt.det.vid.size.h * 4 + 16; /*< padding, just in case */ enc_buf = pj_pool_alloc(pool,enc_buf_size); /* Init codec port */ pj_bzero(&codec_port, sizeof(codec_port)); status = pjmedia_port_info_init2(&codec_port.info, &port_name, 0x1234, PJMEDIA_DIR_ENCODING, &codec_param.dec_fmt); if (status != PJ_SUCCESS) { rc = 260; goto on_return; } pj_bzero(&codec_port_data, sizeof(codec_port_data)); codec_port_data.codec = codec; codec_port_data.src_port = vid_port; codec_port_data.enc_buf = enc_buf; codec_port_data.enc_buf_size = enc_buf_size; codec_port.get_frame = &codec_get_frame; codec_port.port_data.pdata = &codec_port_data; /* Check whether we need to convert the decoded frame */ if (codecp->need_conversion) { pjmedia_conversion_param conv_param; pjmedia_format_copy(&conv_param.src, ¶m.vidparam.fmt); pjmedia_format_copy(&conv_param.dst, ¶m.vidparam.fmt); conv_param.dst.id = codecp->dst_fmt; param.vidparam.fmt.id = conv_param.dst.id; status = pjmedia_converter_create(NULL, pool, &conv_param, &codec_port_data.conv); if (status != PJ_SUCCESS) { rc = 270; goto on_return; } } status = pjmedia_vid_port_create(pool, ¶m, &renderer); if (status != PJ_SUCCESS) { rc = 230; goto on_return; } status = pjmedia_vid_port_connect(renderer, &codec_port, PJ_FALSE); } else { status = pjmedia_vid_port_create(pool, ¶m, &renderer); if (status != PJ_SUCCESS) { rc = 230; goto on_return; } /* Connect avi port to renderer */ status = pjmedia_vid_port_connect(renderer, vid_port, PJ_FALSE); } if (status != PJ_SUCCESS) { rc = 240; goto on_return; } } aud_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams, 0, PJMEDIA_TYPE_AUDIO); aud_port = pjmedia_avi_stream_get_port(aud_stream); if (aud_port) { /* Create sound player port. */ status = pjmedia_snd_port_create_player( pool, /* pool */ -1, /* use default dev. */ PJMEDIA_PIA_SRATE(&aud_port->info),/* clock rate. */ PJMEDIA_PIA_CCNT(&aud_port->info), /* # of channels. */ PJMEDIA_PIA_SPF(&aud_port->info), /* samples per frame. */ PJMEDIA_PIA_BITS(&aud_port->info), /* bits per sample. */ 0, /* options */ &snd_port /* returned port */ ); if (status != PJ_SUCCESS) { rc = 310; goto on_return; } /* Connect file port to the sound player. * Stream playing will commence immediately. */ status = pjmedia_snd_port_connect(snd_port, aud_port); if (status != PJ_SUCCESS) { rc = 330; goto on_return; } } if (vid_port) { pjmedia_vid_dev_cb cb; pj_bzero(&cb, sizeof(cb)); avi_port.snd_port = snd_port; avi_port.vid_port = renderer; avi_port.is_running = PJ_TRUE; pjmedia_vid_port_set_cb(renderer, &cb, &avi_port); /* subscribe events */ pjmedia_event_subscribe(NULL, &avi_event_cb, &avi_port, renderer); if (snd_port) { /* Synchronize video rendering and audio playback */ pjmedia_vid_port_set_clock_src( renderer, pjmedia_snd_port_get_clock_src( snd_port, PJMEDIA_DIR_PLAYBACK)); } /* Start video streaming.. */ status = pjmedia_vid_port_start(renderer); if (status != PJ_SUCCESS) { rc = 270; goto on_return; } } while (!avi_port.is_quitting) { pj_thread_sleep(100); } on_return: if (snd_port) { pjmedia_snd_port_disconnect(snd_port); /* Without this sleep, Windows/DirectSound will repeteadly * play the last frame during destroy. */ pj_thread_sleep(100); pjmedia_snd_port_destroy(snd_port); } if (renderer) { pjmedia_event_unsubscribe(NULL, &avi_event_cb, &avi_port, renderer); pjmedia_vid_port_destroy(renderer); } if (aud_port) pjmedia_port_destroy(aud_port); if (vid_port) pjmedia_port_destroy(vid_port); if (codec) { pjmedia_vid_codec_close(codec); pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec); } return rc; }
static pj_status_t test_init(void) { struct stream_cfg strm_cfg; pj_status_t status; /* Must init PJLIB first: */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&g_app.cp, &pj_pool_factory_default_policy, 0); /* Pool */ g_app.pool = pj_pool_create(&g_app.cp.factory, "g_app", 512, 512, NULL); /* Log file */ if (g_app.cfg.log_file) { status = pj_file_open(g_app.pool, g_app.cfg.log_file, PJ_O_WRONLY, &g_app.log_fd); if (status != PJ_SUCCESS) { jbsim_perror("Error writing output file", status); goto on_error; } pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_COLOR | PJ_LOG_HAS_LEVEL_TEXT); pj_log_set_log_func(&log_cb); } /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&g_app.cp.factory, NULL, 0, &g_app.endpt); if (status != PJ_SUCCESS) { jbsim_perror("Error creating media endpoint", status); goto on_error; } /* Register codecs */ pjmedia_codec_register_audio_codecs(g_app.endpt, NULL); /* Create the loop transport */ status = pjmedia_transport_loop_create(g_app.endpt, &g_app.loop); if (status != PJ_SUCCESS) { jbsim_perror("Error creating loop transport", status); goto on_error; } /* Create transmitter stream */ pj_bzero(&strm_cfg, sizeof(strm_cfg)); strm_cfg.name = "tx"; strm_cfg.dir = PJMEDIA_DIR_ENCODING; strm_cfg.codec = g_app.cfg.codec; strm_cfg.ptime = g_app.cfg.tx_ptime; strm_cfg.dtx = g_app.cfg.tx_dtx; strm_cfg.plc = PJ_TRUE; status = stream_init(&strm_cfg, &g_app.tx); if (status != PJ_SUCCESS) goto on_error; /* Create transmitter WAV */ status = pjmedia_wav_player_port_create(g_app.pool, g_app.cfg.tx_wav_in, g_app.cfg.tx_ptime, 0, 0, &g_app.tx_wav); if (status != PJ_SUCCESS) { jbsim_perror("Error reading input WAV file", status); goto on_error; } /* Make sure stream and WAV parameters match */ if (PJMEDIA_PIA_SRATE(&g_app.tx_wav->info) != PJMEDIA_PIA_SRATE(&g_app.tx->port->info) || PJMEDIA_PIA_CCNT(&g_app.tx_wav->info) != PJMEDIA_PIA_CCNT(&g_app.tx->port->info)) { jbsim_perror("Error: Input WAV file has different clock rate " "or number of channels than the codec", PJ_SUCCESS); goto on_error; } /* Create receiver */ pj_bzero(&strm_cfg, sizeof(strm_cfg)); strm_cfg.name = "rx"; strm_cfg.dir = PJMEDIA_DIR_DECODING; strm_cfg.codec = g_app.cfg.codec; strm_cfg.ptime = g_app.cfg.rx_ptime; strm_cfg.dtx = PJ_TRUE; strm_cfg.plc = g_app.cfg.rx_plc; status = stream_init(&strm_cfg, &g_app.rx); if (status != PJ_SUCCESS) goto on_error; /* Create receiver WAV */ status = pjmedia_wav_writer_port_create(g_app.pool, g_app.cfg.rx_wav_out, PJMEDIA_PIA_SRATE(&g_app.rx->port->info), PJMEDIA_PIA_CCNT(&g_app.rx->port->info), PJMEDIA_PIA_SPF(&g_app.rx->port->info), PJMEDIA_PIA_BITS(&g_app.rx->port->info), 0, 0, &g_app.rx_wav); if (status != PJ_SUCCESS) { jbsim_perror("Error creating output WAV file", status); goto on_error; } /* Frame buffer */ g_app.framebuf = (pj_int16_t*) pj_pool_alloc(g_app.pool, MAX(PJMEDIA_PIA_SPF(&g_app.rx->port->info), PJMEDIA_PIA_SPF(&g_app.tx->port->info)) * sizeof(pj_int16_t)); /* Set the receiver in the loop transport */ pjmedia_transport_loop_disable_rx(g_app.loop, g_app.tx->strm, PJ_TRUE); /* Done */ return PJ_SUCCESS; on_error: test_destroy(); return status; }