Example #1
0
/*
 * Create sound player port.
 */
PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool,
						    int dev_id,
						    unsigned clock_rate,
						    unsigned channel_count,
						    unsigned samples_per_frame,
						    unsigned bits_per_sample,
						    unsigned options,
						    pjmedia_snd_port **p_port)
{
    pjmedia_snd_port_param param;
    pj_status_t status;

    pjmedia_snd_port_param_default(&param);

    /* Normalize dev_id */
    if (dev_id < 0)
	dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;

    status = pjmedia_aud_dev_default_param(dev_id, &param.base);
    if (status != PJ_SUCCESS)
	return status;

    param.base.dir = PJMEDIA_DIR_PLAYBACK;
    param.base.play_id = dev_id;
    param.base.clock_rate = clock_rate;
    param.base.channel_count = channel_count;
    param.base.samples_per_frame = samples_per_frame;
    param.base.bits_per_sample = bits_per_sample;
    param.options = options;
    param.ec_options = 0;

    return pjmedia_snd_port_create2(pool, &param, p_port);
}
Example #2
0
/*
 * Create sound recorder AEC.
 */
PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool,
						 int dev_id,
						 unsigned clock_rate,
						 unsigned channel_count,
						 unsigned samples_per_frame,
						 unsigned bits_per_sample,
						 unsigned options,
						 pjmedia_snd_port **p_port)
{
    pjmedia_snd_port_param param;
    pj_status_t status;

    pjmedia_snd_port_param_default(&param);

    status = pjmedia_aud_dev_default_param(dev_id, &param.base);
    if (status != PJ_SUCCESS)
	return status;

    param.base.dir = PJMEDIA_DIR_CAPTURE;
    param.base.rec_id = dev_id;
    param.base.clock_rate = clock_rate;
    param.base.channel_count = channel_count;
    param.base.samples_per_frame = samples_per_frame;
    param.base.bits_per_sample = bits_per_sample;
    param.options = options;
    param.ec_options = 0;

    return pjmedia_snd_port_create2(pool, &param, p_port);
}
Example #3
0
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, &param);
    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(&param, &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);
}
Example #4
0
/*
 * Create sound player port.
 */
PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool,
						    int dev_id,
						    unsigned clock_rate,
						    unsigned channel_count,
						    unsigned samples_per_frame,
						    unsigned bits_per_sample,
						    unsigned options,
						    pjmedia_snd_port **p_port)
{
    pjmedia_snd_port_param param;
    pj_status_t status;

    PJ_UNUSED_ARG(options);

    status = pjmedia_aud_dev_default_param(dev_id, &param.base);
    if (status != PJ_SUCCESS)
	return status;

    param.base.dir = PJMEDIA_DIR_PLAYBACK;
    param.base.play_id = dev_id;
    param.base.clock_rate = clock_rate;
    param.base.channel_count = channel_count;
    param.base.samples_per_frame = samples_per_frame;
    param.base.bits_per_sample = bits_per_sample;
    param.options = options;

    return pjmedia_snd_port_create2(pool, &param, p_port);
}
Example #5
0
static void play_file(unsigned play_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_player_port_create(pool, filename, 20, 0, 0, &wav);
    if (status != PJ_SUCCESS) {
	app_perror("Error opening WAV file", status);
	goto on_return;
    }

    status = pjmedia_aud_dev_default_param(play_index, &param);
    if (status != PJ_SUCCESS) {
	app_perror("pjmedia_aud_dev_default_param()", status);
	goto on_return;
    }

    param.dir = PJMEDIA_DIR_PLAYBACK;
    param.clock_rate = wav->info.clock_rate;
    param.samples_per_frame = wav->info.samples_per_frame;
    param.channel_count = wav->info.channel_count;
    param.bits_per_sample = wav->info.bits_per_sample;

    status = pjmedia_aud_stream_create(&param, NULL, &wav_play_cb, 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, "Playback 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);
}
Example #6
0
/* Start sound */
static pj_status_t snd_start(unsigned flag)
{
    pj_status_t status;

    if (strm != NULL) {
    	app_perror("snd already open", PJ_EINVALIDOP);
    	return PJ_EINVALIDOP;
    }

    pjmedia_aud_dev_default_param(0, &param);
    param.channel_count = CHANNEL_COUNT;
    param.clock_rate = CLOCK_RATE;
    param.samples_per_frame = SAMPLES_PER_FRAME;
    param.dir = (pjmedia_dir) flag;
    param.ext_fmt.id = PJMEDIA_FORMAT_AMR;
    param.ext_fmt.bitrate = 12200;
    param.output_route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;

    status = pjmedia_aud_stream_create(&param, &rec_cb, &play_cb, NULL, &strm);
    if (status != PJ_SUCCESS) {
    	app_perror("snd open", status);
    	return status;
    }

    rec_cnt = play_cnt = 0;
    pj_gettimeofday(&t_start);

    pjmedia_delay_buf_reset(delaybuf);

    status = pjmedia_aud_stream_start(strm);
    if (status != PJ_SUCCESS) {
    	app_perror("snd start", status);
    	pjmedia_aud_stream_destroy(strm);
    	strm = NULL;
    	return status;
    }

    return PJ_SUCCESS;
}
Example #7
0
static pj_status_t open_stream( pjmedia_dir dir,
			        int rec_id,
				int play_id,
				unsigned clock_rate,
				unsigned channel_count,
				unsigned samples_per_frame,
				unsigned bits_per_sample,
				pjmedia_snd_rec_cb rec_cb,
				pjmedia_snd_play_cb play_cb,
				void *user_data,
				pjmedia_snd_stream **p_snd_strm)
{
    pj_pool_t *pool;
    pjmedia_snd_stream *snd_strm;
    pjmedia_aud_param param;
    pj_status_t status;

    /* Initialize parameters */
    if (dir & PJMEDIA_DIR_CAPTURE) {
	status = pjmedia_aud_dev_default_param(rec_id, &param);
    } else {
	status = pjmedia_aud_dev_default_param(play_id, &param);
    }
    if (status != PJ_SUCCESS)
	return status;

    param.dir = dir;
    param.rec_id = rec_id;
    param.play_id = play_id;
    param.clock_rate = clock_rate;
    param.channel_count = channel_count;
    param.samples_per_frame = samples_per_frame;
    param.bits_per_sample = bits_per_sample;

    /* Latencies setting */
    if ((dir & PJMEDIA_DIR_CAPTURE) && g_sys.user_rec_latency) {
	param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY;
	param.input_latency_ms = g_sys.user_rec_latency;
    }
    if ((dir & PJMEDIA_DIR_PLAYBACK) && g_sys.user_play_latency) {
	param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
	param.output_latency_ms = g_sys.user_play_latency;
    }

    /* Create sound wrapper */
    pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(),
			  "legacy-snd", 512, 512, NULL);
    snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream);
    snd_strm->pool = pool;
    snd_strm->user_rec_cb = rec_cb;
    snd_strm->user_play_cb = play_cb;
    snd_strm->user_user_data = user_data;

    /* Create the stream */
    status = pjmedia_aud_stream_create(&param, &snd_rec_cb, 
				       &snd_play_cb, snd_strm,
				       &snd_strm->aud_strm);
    if (status != PJ_SUCCESS) {
	pj_pool_release(pool);
	return status;
    }

    *p_snd_strm = snd_strm;
    return PJ_SUCCESS;
}
Example #8
0
static void pcap2wav(const pj_str_t *codec,
		     const pj_str_t *wav_filename,
		     pjmedia_aud_dev_index dev_id,
		     const pj_str_t *srtp_crypto,
		     const pj_str_t *srtp_key)
{
    const pj_str_t WAV = {".wav", 4};
    struct pkt
    {
	pj_uint8_t	 buffer[320];
	pjmedia_rtp_hdr	*rtp;
	pj_uint8_t	*payload;
	unsigned	 payload_len;
    } pkt0;
    pjmedia_codec_mgr *cmgr;
    const pjmedia_codec_info *ci;
    pjmedia_codec_param param;
    unsigned samples_per_frame;
    pj_status_t status;

    /* Initialize all codecs */
    T( pjmedia_codec_register_audio_codecs(app.mept, NULL) );

    /* Create SRTP transport is needed */
#if PJMEDIA_HAS_SRTP
    if (srtp_crypto->slen) {
	pjmedia_srtp_crypto crypto;

	pj_bzero(&crypto, sizeof(crypto));
	crypto.key = *srtp_key;
	crypto.name = *srtp_crypto;
	T( pjmedia_transport_srtp_create(app.mept, NULL, NULL, &app.srtp) );
	T( pjmedia_transport_srtp_start(app.srtp, &crypto, &crypto) );
    }
#else
    PJ_UNUSED_ARG(srtp_crypto);
    PJ_UNUSED_ARG(srtp_key);
#endif

    /* Read first packet */
    read_rtp(pkt0.buffer, sizeof(pkt0.buffer), &pkt0.rtp, 
	     &pkt0.payload, &pkt0.payload_len, PJ_FALSE);

    cmgr = pjmedia_endpt_get_codec_mgr(app.mept);

    /* Get codec info and param for the specified payload type */
    app.pt = pkt0.rtp->pt;
    if (app.pt >=0 && app.pt < 96) {
	T( pjmedia_codec_mgr_get_codec_info(cmgr, pkt0.rtp->pt, &ci) );
    } else {
	unsigned cnt = 2;
	const pjmedia_codec_info *info[2];
	T( pjmedia_codec_mgr_find_codecs_by_id(cmgr, codec, &cnt, 
					       info, NULL) );
	if (cnt != 1)
	    err_exit("Codec ID must be specified and unique!", 0);

	ci = info[0];
    }
    T( pjmedia_codec_mgr_get_default_param(cmgr, ci, &param) );

    /* Alloc and init codec */
    T( pjmedia_codec_mgr_alloc_codec(cmgr, ci, &app.codec) );
    T( pjmedia_codec_init(app.codec, app.pool) );
    T( pjmedia_codec_open(app.codec, &param) );

    /* Init audio device or WAV file */
    samples_per_frame = ci->clock_rate * param.info.frm_ptime / 1000;
    if (pj_strcmp2(wav_filename, "-") == 0) {
	pjmedia_aud_param aud_param;

	/* Open audio device */
	T( pjmedia_aud_dev_default_param(dev_id, &aud_param) );
	aud_param.dir = PJMEDIA_DIR_PLAYBACK;
	aud_param.channel_count = ci->channel_cnt;
	aud_param.clock_rate = ci->clock_rate;
	aud_param.samples_per_frame = samples_per_frame;
	T( pjmedia_aud_stream_create(&aud_param, NULL, &play_cb, 
				     NULL, &app.aud_strm) );
	T( pjmedia_aud_stream_start(app.aud_strm) );
    } else if (pj_stristr(wav_filename, &WAV)) {
	/* Open WAV file */
	T( pjmedia_wav_writer_port_create(app.pool, wav_filename->ptr,
					  ci->clock_rate, ci->channel_cnt,
					  samples_per_frame,
					  param.info.pcm_bits_per_sample, 0, 0,
					  &app.wav) );
    } else {
	err_exit("invalid output file", PJ_EINVAL);
    }

    /* Loop reading PCAP and writing WAV file */
    for (;;) {
	struct pkt pkt1;
	pj_timestamp ts;
	pjmedia_frame frames[16], pcm_frame;
	short pcm[320];
	unsigned i, frame_cnt;
	long samples_cnt, ts_gap;

	pj_assert(sizeof(pcm) >= samples_per_frame);

	/* Parse first packet */
	ts.u64 = 0;
	frame_cnt = PJ_ARRAY_SIZE(frames);
	T( pjmedia_codec_parse(app.codec, pkt0.payload, pkt0.payload_len, 
				&ts, &frame_cnt, frames) );

	/* Decode and write to WAV file */
	samples_cnt = 0;
	for (i=0; i<frame_cnt; ++i) {
	    pjmedia_frame pcm_frame;

	    pcm_frame.buf = pcm;
	    pcm_frame.size = samples_per_frame * 2;

	    T( pjmedia_codec_decode(app.codec, &frames[i], 
				    (unsigned)pcm_frame.size, &pcm_frame) );
	    if (app.wav) {
		T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
	    }
	    if (app.aud_strm) {
		T( wait_play(&pcm_frame) );
	    }
	    samples_cnt += samples_per_frame;
	}

	/* Read next packet */
	read_rtp(pkt1.buffer, sizeof(pkt1.buffer), &pkt1.rtp,
		 &pkt1.payload, &pkt1.payload_len, PJ_TRUE);

	/* Fill in the gap (if any) between pkt0 and pkt1 */
	ts_gap = pj_ntohl(pkt1.rtp->ts) - pj_ntohl(pkt0.rtp->ts) -
		 samples_cnt;
	while (ts_gap >= (long)samples_per_frame) {

	    pcm_frame.buf = pcm;
	    pcm_frame.size = samples_per_frame * 2;

	    if (app.codec->op->recover) {
		T( pjmedia_codec_recover(app.codec, (unsigned)pcm_frame.size, 
					 &pcm_frame) );
	    } else {
		pj_bzero(pcm_frame.buf, pcm_frame.size);
	    }

	    if (app.wav) {
		T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
	    }
	    if (app.aud_strm) {
		T( wait_play(&pcm_frame) );
	    }
	    ts_gap -= samples_per_frame;
	}
	
	/* Next */
	pkt0 = pkt1;
	pkt0.rtp = (pjmedia_rtp_hdr*)pkt0.buffer;
	pkt0.payload = pkt0.buffer + (pkt1.payload - pkt1.buffer);
    }
}
Example #9
0
/****************************************************************************
 * test: audio system test
 */
static void systest_audio_test(void)
{
    enum {
	GOOD_MAX_INTERVAL = 5,
    };
    const pjmedia_dir dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
    pjmedia_aud_param param;
    pjmedia_aud_test_results result;
    pj_size_t textbufpos;
    enum gui_key key;
    unsigned problem_count = 0;
    const char *problems[16];
    char drifttext[120];
    test_item_t *ti;
    const char *title = "Audio Device Test";
    pj_status_t status;

    ti = systest_alloc_test_item(title);
    if (!ti)
	return;

    key = gui_msgbox(title,
		     "This will run an automated test for about "
		     "ten seconds or so, and display some "
		     "statistics about your sound device. "
		     "Please don't do anything until the test completes. "
		     "Press OK to start, or CANCEL to skip this test.",
		     WITH_OKCANCEL);
    if (key != KEY_OK) {
	ti->skipped = PJ_TRUE;
	return;
    }

    PJ_LOG(3,(THIS_FILE, "Running %s", title));

    /* Disable sound device in pjsua first */
    pjsua_set_no_snd_dev();

    /* Setup parameters */
    status = pjmedia_aud_dev_default_param(systest.play_id, &param);
    if (status != PJ_SUCCESS) {
	systest_perror("Sorry we had error in pjmedia_aud_dev_default_param()", status);
	pjsua_set_snd_dev(systest.rec_id, systest.play_id);
	ti->success = PJ_FALSE;
	pj_strerror(status, ti->reason, sizeof(ti->reason));
	ti->reason[sizeof(ti->reason)-1] = '\0';
	return;
    }

    param.dir = dir;
    param.rec_id = systest.rec_id;
    param.play_id = systest.play_id;
    param.clock_rate = systest.media_cfg.snd_clock_rate;
    param.channel_count = systest.media_cfg.channel_count;
    param.samples_per_frame = param.clock_rate * param.channel_count *
			      systest.media_cfg.audio_frame_ptime / 1000;

    /* Latency settings */
    param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
		    PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
    param.input_latency_ms = systest.media_cfg.snd_rec_latency;
    param.output_latency_ms = systest.media_cfg.snd_play_latency;

    /* Run the test */
    status = pjmedia_aud_test(&param, &result);
    if (status != PJ_SUCCESS) {
	systest_perror("Sorry we encountered error with the test", status);
	pjsua_set_snd_dev(systest.rec_id, systest.play_id);
	ti->success = PJ_FALSE;
	pj_strerror(status, ti->reason, sizeof(ti->reason));
	ti->reason[sizeof(ti->reason)-1] = '\0';
	return;
    }

    /* Restore pjsua sound device */
    pjsua_set_snd_dev(systest.rec_id, systest.play_id);

    /* Analyze the result! */
    strcpy(textbuf, "Here are the audio statistics:\r\n");
    textbufpos = strlen(textbuf);

    if (result.rec.frame_cnt==0) {
	problems[problem_count++] =
	    "No audio frames were captured from the microphone. "
	    "This means the audio device is not working properly.";
    } else {
	pj_ansi_snprintf(textbuf+textbufpos,
			 sizeof(textbuf)-textbufpos,
			 "Rec : interval (min/max/avg/dev)=\r\n"
			 "         %u/%u/%u/%u (ms)\r\n"
			 "      max burst=%u\r\n",
			 result.rec.min_interval,
			 result.rec.max_interval,
			 result.rec.avg_interval,
			 result.rec.dev_interval,
			 result.rec.max_burst);
	textbufpos = strlen(textbuf);

	if (result.rec.max_burst > GOOD_MAX_INTERVAL) {
	    problems[problem_count++] =
		"Recording max burst is quite high";
	}
    }

    if (result.play.frame_cnt==0) {
	problems[problem_count++] =
	    "No audio frames were played to the speaker. "
	    "This means the audio device is not working properly.";
    } else {
	pj_ansi_snprintf(textbuf+textbufpos,
			 sizeof(textbuf)-textbufpos,
			 "Play: interval (min/max/avg/dev)=\r\n"
			 "         %u/%u/%u/%u (ms)\r\n"
			 "      burst=%u\r\n",
			 result.play.min_interval,
			 result.play.max_interval,
			 result.play.avg_interval,
			 result.play.dev_interval,
			 result.play.max_burst);
	textbufpos = strlen(textbuf);

	if (result.play.max_burst > GOOD_MAX_INTERVAL) {
	    problems[problem_count++] =
		"Playback max burst is quite high";
	}
    }

    if (result.rec_drift_per_sec) {
	const char *which = result.rec_drift_per_sec>=0 ? "faster" : "slower";
	unsigned drift = result.rec_drift_per_sec>=0 ?
			    result.rec_drift_per_sec :
			    -result.rec_drift_per_sec;

	pj_ansi_snprintf(drifttext, sizeof(drifttext),
			"Clock drifts detected. Capture "
			"is %d samples/sec %s "
			"than the playback device",
			drift, which);
	problems[problem_count++] = drifttext;
    }

    if (problem_count == 0) {
	pj_ansi_snprintf(textbuf+textbufpos,
			 sizeof(textbuf)-textbufpos,
			 "\r\nThe sound device seems to be okay!");
	textbufpos = strlen(textbuf);

	key = gui_msgbox("Audio Device Test", textbuf, WITH_OK);
    } else {
	unsigned i;

	pj_ansi_snprintf(textbuf+textbufpos,
			 sizeof(textbuf)-textbufpos,
			 "There could be %d problem(s) with the "
			 "sound device:\r\n",
			 problem_count);
	textbufpos = strlen(textbuf);

	for (i=0; i<problem_count; ++i) {
	    pj_ansi_snprintf(textbuf+textbufpos,
			     sizeof(textbuf)-textbufpos,
			     " %d: %s\r\n", i+1, problems[i]);
	    textbufpos = strlen(textbuf);
	}

	key = gui_msgbox(title, textbuf, WITH_OK);
    }

    ti->success = PJ_TRUE;
    pj_ansi_strncpy(ti->reason, textbuf, sizeof(ti->reason));
    ti->reason[sizeof(ti->reason)-1] = '\0';
}
Example #10
0
static void test_device(pjmedia_dir dir, unsigned rec_id, unsigned play_id, 
			unsigned clock_rate, unsigned ptime, 
			unsigned chnum)
{
    pjmedia_aud_param param;
    pjmedia_aud_test_results result;
    pj_status_t status;

    if (dir & PJMEDIA_DIR_CAPTURE) {
	status = pjmedia_aud_dev_default_param(rec_id, &param);
    } else {
	status = pjmedia_aud_dev_default_param(play_id, &param);
    }

    if (status != PJ_SUCCESS) {
	app_perror("pjmedia_aud_dev_default_param()", status);
	return;
    }

    param.dir = dir;
    param.rec_id = rec_id;
    param.play_id = play_id;
    param.clock_rate = clock_rate;
    param.channel_count = chnum;
    param.samples_per_frame = clock_rate * chnum * ptime / 1000;

    /* Latency settings */
    param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | 
		    PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
    param.input_latency_ms = capture_lat;
    param.output_latency_ms = playback_lat;

    PJ_LOG(3,(THIS_FILE, "Performing test.."));

    status = pjmedia_aud_test(&param, &result);
    if (status != PJ_SUCCESS) {
	app_perror("Test has completed with error", status);
	return;
    }

    PJ_LOG(3,(THIS_FILE, "Done. Result:"));

    if (dir & PJMEDIA_DIR_CAPTURE) {
	if (result.rec.frame_cnt==0) {
	    PJ_LOG(1,(THIS_FILE, "Error: no frames captured!"));
	} else {
	    PJ_LOG(3,(THIS_FILE, "  %-20s: interval (min/max/avg/dev)=%u/%u/%u/%u, burst=%u",
		      "Recording result",
		      result.rec.min_interval,
		      result.rec.max_interval,
		      result.rec.avg_interval,
		      result.rec.dev_interval,
		      result.rec.max_burst));
	}
    }

    if (dir & PJMEDIA_DIR_PLAYBACK) {
	if (result.play.frame_cnt==0) {
	    PJ_LOG(1,(THIS_FILE, "Error: no playback!"));
	} else {
	    PJ_LOG(3,(THIS_FILE, "  %-20s: interval (min/max/avg/dev)=%u/%u/%u/%u, burst=%u",
		      "Playback result",
		      result.play.min_interval,
		      result.play.max_interval,
		      result.play.avg_interval,
		      result.play.dev_interval,
		      result.play.max_burst));
	}
    }

    if (dir==PJMEDIA_DIR_CAPTURE_PLAYBACK) {
	if (result.rec_drift_per_sec == 0) {
	    PJ_LOG(3,(THIS_FILE, " No clock drift detected"));
	} else {
	    const char *which = result.rec_drift_per_sec>=0 ? "faster" : "slower";
	    unsigned drift = result.rec_drift_per_sec>=0 ? 
				result.rec_drift_per_sec :
				-result.rec_drift_per_sec;

	    PJ_LOG(3,(THIS_FILE, " Clock drifts detected. Capture device "
				 "is running %d samples per second %s "
				 "than the playback device",
				 drift, which));
	}
    }
}