blob: 48bcad4e37c1c3dd7664cc43623568ac1cfef296 [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia-audiodev/audiodev.h>
#include <pjmedia-audiodev/audiotest.h>
#include <pjmedia.h>
#include <pjlib.h>
#include <pjlib-util.h>
#define THIS_FILE "auddemo.c"
#define MAX_DEVICES 64
#define WAV_FILE "auddemo.wav"
static unsigned dev_count;
static unsigned playback_lat = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
static unsigned capture_lat = PJMEDIA_SND_DEFAULT_REC_LATENCY;
static void app_perror(const char *title, pj_status_t status)
{
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror(status, errmsg, sizeof(errmsg));
printf( "%s: %s (err=%d)\n",
title, errmsg, status);
}
static void list_devices(void)
{
unsigned i;
pj_status_t status;
dev_count = pjmedia_aud_dev_count();
if (dev_count == 0) {
PJ_LOG(3,(THIS_FILE, "No devices found"));
return;
}
PJ_LOG(3,(THIS_FILE, "Found %d devices:", dev_count));
for (i=0; i<dev_count; ++i) {
pjmedia_aud_dev_info info;
status = pjmedia_aud_dev_get_info(i, &info);
if (status != PJ_SUCCESS)
continue;
PJ_LOG(3,(THIS_FILE," %2d: %s [%s] (%d/%d)",
i, info.driver, info.name, info.input_count, info.output_count));
}
}
static const char *decode_caps(unsigned caps)
{
static char text[200];
unsigned i;
text[0] = '\0';
for (i=0; i<31; ++i) {
if ((1 << i) & caps) {
const char *capname;
capname = pjmedia_aud_dev_cap_name((pjmedia_aud_dev_cap)(1 << i),
NULL);
strcat(text, capname);
strcat(text, " ");
}
}
return text;
}
static void show_dev_info(unsigned index)
{
#define H "%-20s"
pjmedia_aud_dev_info info;
char formats[200];
pj_status_t status;
if (index >= dev_count) {
PJ_LOG(1,(THIS_FILE, "Error: invalid index %u", index));
return;
}
status = pjmedia_aud_dev_get_info(index, &info);
if (status != PJ_SUCCESS) {
app_perror("pjmedia_aud_dev_get_info() error", status);
return;
}
PJ_LOG(3, (THIS_FILE, "Device at index %u:", index));
PJ_LOG(3, (THIS_FILE, "-------------------------"));
PJ_LOG(3, (THIS_FILE, H": %u (0x%x)", "ID", index, index));
PJ_LOG(3, (THIS_FILE, H": %s", "Name", info.name));
PJ_LOG(3, (THIS_FILE, H": %s", "Driver", info.driver));
PJ_LOG(3, (THIS_FILE, H": %u", "Input channels", info.input_count));
PJ_LOG(3, (THIS_FILE, H": %u", "Output channels", info.output_count));
PJ_LOG(3, (THIS_FILE, H": %s", "Capabilities", decode_caps(info.caps)));
formats[0] = '\0';
if (info.caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
unsigned i;
for (i=0; i<info.ext_fmt_cnt; ++i) {
char bitrate[32];
switch (info.ext_fmt[i].id) {
case PJMEDIA_FORMAT_L16:
strcat(formats, "L16/");
break;
case PJMEDIA_FORMAT_PCMA:
strcat(formats, "PCMA/");
break;
case PJMEDIA_FORMAT_PCMU:
strcat(formats, "PCMU/");
break;
case PJMEDIA_FORMAT_AMR:
strcat(formats, "AMR/");
break;
case PJMEDIA_FORMAT_G729:
strcat(formats, "G729/");
break;
case PJMEDIA_FORMAT_ILBC:
strcat(formats, "ILBC/");
break;
default:
strcat(formats, "unknown/");
break;
}
sprintf(bitrate, "%u", info.ext_fmt[i].det.aud.avg_bps);
strcat(formats, bitrate);
strcat(formats, " ");
}
}
PJ_LOG(3, (THIS_FILE, H": %s", "Extended formats", formats));
#undef H
}
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));
}
}
}
static pj_status_t wav_rec_cb(void *user_data, pjmedia_frame *frame)
{
return pjmedia_port_put_frame((pjmedia_port*)user_data, frame);
}
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);
}
static pj_status_t wav_play_cb(void *user_data, pjmedia_frame *frame)
{
return pjmedia_port_get_frame((pjmedia_port*)user_data, frame);
}
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 = 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, 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);
}
static void print_menu(void)
{
puts("");
puts("Audio demo menu:");
puts("-------------------------------");
puts(" l List devices");
puts(" R Refresh devices");
puts(" i ID Show device info for device ID");
puts(" t RID PID CR PTIM [CH] Perform test on the device:");
puts(" RID: record device ID (-1 for no)");
puts(" PID: playback device ID (-1 for no)");
puts(" CR: clock rate");
puts(" PTIM: ptime in ms");
puts(" CH: # of channels");
puts(" r RID [FILE] Record capture device RID to WAV file");
puts(" p PID [FILE] Playback WAV file to device ID PID");
puts(" d [RLAT PLAT] Get/set sound device latencies (in ms):");
puts(" Specify no param to get current latencies setting");
puts(" RLAT: record latency (-1 for default)");
puts(" PLAT: playback latency (-1 for default)");
puts(" v Toggle log verbosity");
puts(" q Quit");
puts("");
printf("Enter selection: ");
fflush(stdout);
}
int main()
{
pj_caching_pool cp;
pj_bool_t done = PJ_FALSE;
pj_status_t status;
/* Init pjlib */
status = pj_init();
PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
/* Must create a pool factory before we can allocate any memory. */
pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
status = pjmedia_aud_subsys_init(&cp.factory);
if (status != PJ_SUCCESS) {
app_perror("pjmedia_aud_subsys_init()", status);
pj_caching_pool_destroy(&cp);
pj_shutdown();
return 1;
}
list_devices();
while (!done) {
char line[80];
print_menu();
if (fgets(line, sizeof(line), stdin)==NULL)
break;
switch (line[0]) {
case 'l':
list_devices();
break;
case 'R':
pjmedia_aud_dev_refresh();
puts("Audio device list refreshed.");
break;
case 'i':
{
unsigned dev_index;
if (sscanf(line+2, "%u", &dev_index) != 1) {
puts("error: device ID required");
break;
}
show_dev_info(dev_index);
}
break;
case 't':
{
pjmedia_dir dir;
int rec_id, play_id;
unsigned clock_rate, ptime, chnum;
int cnt;
cnt = sscanf(line+2, "%d %d %u %u %u", &rec_id, &play_id,
&clock_rate, &ptime, &chnum);
if (cnt < 4) {
puts("error: not enough parameters");
break;
}
if (clock_rate < 8000 || clock_rate > 128000) {
puts("error: invalid clock rate");
break;
}
if (ptime < 10 || ptime > 500) {
puts("error: invalid ptime");
break;
}
if (cnt==5) {
if (chnum < 1 || chnum > 4) {
puts("error: invalid number of channels");
break;
}
} else {
chnum = 1;
}
if (rec_id >= 0 && rec_id < (int)dev_count) {
if (play_id >= 0 && play_id < (int)dev_count)
dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
else
dir = PJMEDIA_DIR_CAPTURE;
} else if (play_id >= 0 && play_id < (int)dev_count) {
dir = PJMEDIA_DIR_PLAYBACK;
} else {
puts("error: at least one valid device index required");
break;
}
test_device(dir, rec_id, play_id, clock_rate, ptime, chnum);
}
break;
case 'r':
/* record */
{
int index;
char filename[80];
int count;
count = sscanf(line+2, "%d %s", &index, filename);
if (count==1)
record(index, NULL);
else if (count==2)
record(index, filename);
else
puts("error: invalid command syntax");
}
break;
case 'p':
/* playback */
{
int index;
char filename[80];
int count;
count = sscanf(line+2, "%d %s", &index, filename);
if (count==1)
play_file(index, NULL);
else if (count==2)
play_file(index, filename);
else
puts("error: invalid command syntax");
}
break;
case 'd':
/* latencies */
{
int rec_lat, play_lat;
if (sscanf(line+2, "%d %d", &rec_lat, &play_lat) == 2) {
capture_lat = (unsigned)
(rec_lat>=0? rec_lat:PJMEDIA_SND_DEFAULT_REC_LATENCY);
playback_lat = (unsigned)
(play_lat >= 0? play_lat : PJMEDIA_SND_DEFAULT_PLAY_LATENCY);
printf("Recording latency=%ums, playback latency=%ums",
capture_lat, playback_lat);
} else {
printf("Current latencies: record=%ums, playback=%ums",
capture_lat, playback_lat);
}
puts("");
}
break;
case 'v':
if (pj_log_get_level() <= 3) {
pj_log_set_level(5);
puts("Logging set to detail");
} else {
pj_log_set_level(3);
puts("Logging set to quiet");
}
break;
case 'q':
done = PJ_TRUE;
break;
}
}
pj_caching_pool_destroy(&cp);
pj_shutdown();
return 0;
}