Hello,
Thanks for a great product.
I want to use PJSIP's C API to record the incoming audio to a file on a machine with _no_ hardware sound device. I’ve read the FAQ and the PJMEDIA documentation but am still unclear about the architecture of bridges, media ports and sound devices.
My target machines, both Windows 10 and Linux, have no hardware audio devices installed, so I call pjsua_set_null_snd_dev()
after initializing and starting the libraries. Example code below.
Later after I make a SIP call and it is answered I attempt to attach a WAV file writer to the DEFAULT_CAPTURE_DEV, but the pjmedia_aud_dev_default_param(…) call returns PJMEDIA_AUD_INVALID_DEV.
Runing the code on a machine (either Win or Linux) _with_ a hardware sound device succeeds and things progress as I expect. Even though
pjsua_set_null_snd_dev() is invoked. Absolutely no changes to the code.
Is there a resource I can consult so to understand this better?
James
/** Code **/
#include <pjlib.h>
#include <pjlib-util.h>
#include <pjnath.h>
#include <pjsip.h>
#include <pjsip_ua.h>
#include <pjsip_simple.h>
#include <pjsua-lib/pjsua.h>
#include <pjmedia.h>
#include <pjmedia-codec.h>
#include <pj/log.h>
#include <pj/os.h>
int main(int, char **)
{
// Create pjsua first!
pj_status_t status = pjsua_create();
if (status != PJ_SUCCESS)
{
fprintf(stderr,"pjsua_create error\n");
return -1;
}
// Init pjsua
pjsua_config cfg;
pjsua_logging_config log_cfg;
pjsua_config_default(&cfg);
pjsua_logging_config_default(&log_cfg);
log_cfg.console_level = 4;
status = pjsua_init(&cfg, &log_cfg, NULL);
if (status != PJ_SUCCESS)
{
fprintf(stderr,"pjsua_init error\n");
return -1;
}
// Proactively list known audio devices so we are sure there are NONE
pjmedia_aud_dev_info info[64];
unsigned info_count = 64;
pjsua_enum_aud_devs(info, &info_count);
fprintf(stderr,"Listing known sound devices, total of [%u]\n", info_count);
for (unsigned i = 0; i<info_count; ++i)
{
fprintf(stderr,"Name [%s]", info[i].name);
}
// Add transport
pjsua_transport_config tcfg;
pjsua_transport_id trans_id;
pjsua_transport_config_default(&tcfg);
tcfg.port = 5060;
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &tcfg, &trans_id);
if (status != PJ_SUCCESS)
{
fprintf(stderr, "pjsua_transport_create error\n");
return -1;
}
// Initialization is done, now start pjsua
status = pjsua_start();
if (status != PJ_SUCCESS)
{
fprintf(stderr, "pjsua_start error\n");
return -1;
}
// Set NULL sound
status = pjsua_set_null_snd_dev();
if (status != PJ_SUCCESS)
{
fprintf(stderr, "pjsua_set_null_snd_dev error");
return -1;
}
// Register to a SIP server by creating SIP account, I happen to use Asterisk
pjsua_acc_id acc_id;
fprintf(stderr, "Setting up SIP server registration\n");
{
pjsua_acc_config cfg;
pjsua_acc_config_default(&cfg);
cfg.id = pj_str("sip:6001@10.0.0.21");
cfg.reg_uri = cfg.id; // same as ID
cfg.cred_count = 1;
cfg.cred_info[0].realm = pj_str("*");
cfg.cred_info[0].scheme = pj_str("digest");
cfg.cred_info[0].username = pj_str("6001");
cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
cfg.cred_info[0].data = "">
status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
if (status != PJ_SUCCESS)
{
fprintf(stderr, "pjsua_acc_add error\n");
return -1;
}
}
fprintf(stderr, "Waiting for SIP server registration to complete....\n");
Sleep(2000); // sleep 2 seconds
// Call extension 9 on my Asterisk server at 10.0.0.21:5060
pj_str_t sip_target(pj_str("sip:9@10.0.0.21"));
fprintf(stderr, "Making call to [%s]\n", sip_target.ptr);
pjsua_call_id call_id;
status = pjsua_call_make_call(acc_id, &sip_target, 0, NULL, NULL, &call_id);
if (status != PJ_SUCCESS)
{
fprintf(stderr, "pjsua_call_make_call error\n");
return -1;
}
pj_pool_t * pool = nullptr;
pjmedia_port * wav = nullptr;
pjmedia_aud_stream *strm = nullptr;
pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav-audio", 1000, 1000, NULL);
if (nullptr == pool)
{
fprintf(stderr,"Pool creation failed\n");
return -1;
}
// 8kHz, single channel 16bit MS WAV format file
status = pjmedia_wav_writer_port_create(pool, "test.wav", 8000, 1, 320, 16, PJMEDIA_FILE_WRITE_PCM, 0, &wav);
if (status != PJ_SUCCESS)
{
fprintf(stderr, "Error creating WAV file\n");
return -1;
}
pjmedia_aud_param param;
//////////////////////////////////////////////////////
// FAILURE HERE : This is the function call which returns PJMEDIA_AUD_INVALID_DEV
//////////////////////////////////////////////////////
status = pjmedia_aud_dev_default_param(PJMEDIA_AUD_DEFAULT_CAPTURE_DEV, ¶m);
if (status != PJ_SUCCESS)
{
fprintf(stderr, "pjmedia_aud_dev_default_param()");
return -1;
}
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, &test_rec_cb, &test_play_cb, wav, &strm);
if (status != PJ_SUCCESS)
{
fprintf(stderr, "Error opening the sound stream");
return -1;
}
status = pjmedia_aud_stream_start(strm);
if (status != PJ_SUCCESS)
{
fprintf(stderr, "Error starting the sound device");
return -1;
}
// Spend some time allowing the called party to pick up and recording to proceed
Sleep(10000); // sleep 10 seconds
// Clean up code omitted
return 0;
}