D-Modem/d-modem.c

440 lines
12 KiB
C

/*
* Copyright (C) 2021 Aon plc
*
* 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 <fcntl.h>
#include <unistd.h>
#include <stdbool.h>
#include <time.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
// test
#include <pjsua-lib/pjsua.h>
#include <pjsua-lib/pjsua_internal.h>
#ifdef HAS_PULSE
#include <pulse/simple.h>
#endif
#define PCM_FILE "dmodem.%s.s16le.9600hz.pcm"
#undef PCM_FILE
//#define HAS_PULSE
#ifdef HAS_PULSE
pa_simple *pa_s1 = NULL;
pa_simple *pa_s2 = NULL;
const pa_sample_spec pa_ss = {
.format = PA_SAMPLE_S16LE,
.rate = 9600,
.channels = 1
};
#endif
#ifdef PCM_FILE
int wave_recv = -1;
int wave_transmit = -1;
#endif
#define SIGNATURE PJMEDIA_SIG_CLASS_PORT_AUD('D','M')
#define DMODEM_DIAL_MODE 0
#define DMODEM_ANSWER_MODE 1
uint8_t mode = DMODEM_DIAL_MODE;
uint8_t ringing = 0;
uint8_t answered = 0;
int ppid;
pjsua_call_id incoming;
struct dmodem {
pjmedia_port base;
pj_timestamp timestamp;
pj_sock_t sock;
};
static struct dmodem port;
static bool destroying = false;
static pj_pool_t *pool;
void stop_pa();
static void error_exit(const char *title, pj_status_t status) {
pjsua_perror(__FILE__, title, status);
if (!destroying) {
destroying = true;
pjsua_destroy();
stop_pa();
if (answered)
kill(ppid, SIGINT);
exit(1);
}
}
void start_pa() {
#ifdef PCM_FILE
char _wavbuf[50];
sprintf(_wavbuf, PCM_FILE, "recv");
wave_recv = open(_wavbuf, O_WRONLY | O_CREAT, 00644);
sprintf(_wavbuf, PCM_FILE, "transmit");
wave_recv = open(_wavbuf, O_WRONLY | O_CREAT, 00644);
if (wave_recv <= 0 || wave_transmit <= 0)
error_exit("open wave files", 1);
#endif
#ifdef HAS_PULSE
pa_s1 = pa_simple_new(NULL, // Use the default server.
"dmodem", // Our application's name.
PA_STREAM_PLAYBACK,
NULL, // Use the default device.
"recv", // Description of our stream.
&pa_ss, // Our sample format.
NULL, // Use default channel map
NULL, // Use default buffering attributes.
NULL // Ignore error code.
);
pa_s2 = pa_simple_new(NULL, // Use the default server.
"dmodem", // Our application's name.
PA_STREAM_PLAYBACK,
NULL, // Use the default device.
"transmit", // Description of our stream.
&pa_ss, // Our sample format.
NULL, // Use default channel map
NULL, // Use default buffering attributes.
NULL // Ignore error code.
);
if (!pa_s1 || !pa_s2)
error_exit("pulseaudio", 1);
#endif
}
void stop_pa() {
#ifdef HAS_PULSE
if(pa_s1)
pa_simple_free(pa_s1);
if(pa_s2)
pa_simple_free(pa_s2);
pa_s1 = 0;
pa_s1 = 0;
#endif
#ifdef PCM_FILE
if (wave_recv >= 0)
close(wave_recv);
if (wave_transmit >= 0)
close(wave_transmit);
wave_recv = -1;
wave_transmit = -1;
#endif
}
static pj_status_t dmodem_put_frame(pjmedia_port *this_port, pjmedia_frame *frame) {
struct dmodem *sm = (struct dmodem *)this_port;
int len;
if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {
#ifdef HAS_PULSE
pa_simple_write(pa_s1, frame->buf, frame->size, NULL);
#endif
#ifdef PCM_FILE
write(wave_recv, frame->buf, frame->size);
#endif
if ((len=write(sm->sock, frame->buf, frame->size)) != frame->size) {
error_exit("error writing frame",0);
}
}
return PJ_SUCCESS;
}
static pj_status_t dmodem_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) {
struct dmodem *sm = (struct dmodem *)this_port;
frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); // MAX? what is
int len;
if ((len=read(sm->sock, frame->buf, frame->size)) != frame->size) {
error_exit("error reading frame",0);
}
frame->timestamp.u64 = sm->timestamp.u64;
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
sm->timestamp.u64 += PJMEDIA_PIA_SPF(&this_port->info);
#ifdef HAS_PULSE
pa_simple_write(pa_s2, frame->buf, frame->size, NULL);
#endif
#ifdef PCM_FILE
write(wave_transmit, frame->buf, frame->size);
#endif
return PJ_SUCCESS;
}
static pj_status_t dmodem_on_destroy(pjmedia_port *this_port) {
printf("destroy\n");
if (answered)
kill(ppid, SIGINT);
exit(-1);
}
/* Callback called by the library when call's state has changed */
static void on_call_state(pjsua_call_id call_id, pjsip_event *e) {
pjsua_call_info ci;
PJ_UNUSED_ARG(e);
pjsua_call_get_info(call_id, &ci);
PJ_LOG(3,(__FILE__, "Call %d state=%.*s", call_id,
(int)ci.state_text.slen,
ci.state_text.ptr));
if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
close(port.sock);
if (!destroying) {
destroying = true;
pjsua_destroy();
stop_pa();
if (answered)
kill(ppid, SIGINT);
exit(0);
}
}
}
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) {
pjsua_call_info ci;
pjsua_call_get_info(call_id, &ci);
char* tmp = malloc(pj_strlen(&ci.remote_contact)+1);
strcpy(tmp, ci.remote_contact.ptr);
tmp[pj_strlen(&ci.remote_contact)] = '\0';
if (answered) {
PJ_LOG(2,(__FILE__, "Incoming call rejected from: %s", tmp));
pjsua_call_hangup(call_id, 0u, NULL, NULL);
return;
}
PJ_LOG(2,(__FILE__, "Incoming call from: %s", tmp));
free(tmp);
incoming = call_id;
ringing = 1;
}
/* Callback called by the library when call's media state has changed */
static void on_call_media_state(pjsua_call_id call_id) {
pjsua_call_info ci;
pjsua_conf_port_id port_id;
static int done=0;
pjsua_call_get_info(call_id, &ci);
// printf("media_status %d media_cnt %d ci.conf_slot %d aud.conf_slot %d\n",ci.media_status,ci.media_cnt,ci.conf_slot,ci.media[0].stream.aud.conf_slot);
if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
if (!done) {
pjsua_conf_add_port(pool, &port.base, &port_id);
pjsua_conf_connect(ci.conf_slot, port_id);
pjsua_conf_connect(port_id, ci.conf_slot);
done = 1;
}
} else {
done = 0;
}
}
int main(int argc, char *argv[]) {
pjsua_acc_id acc_id;
pj_status_t status;
if (argc != 5) {
return -1;
}
ppid = atoi(argv[3]);
char* _modemid_tmp = argv[4];
for (size_t i = strlen(_modemid_tmp) - 1 ; ; i--) {
if (i < 0 || _modemid_tmp[i] < '0' || _modemid_tmp[i] > '9') {
_modemid_tmp += i+1;
break;
}
}
int sip_port = atoi(_modemid_tmp);
sip_port = sip_port >= 0 ? sip_port : 0;
sip_port += 5060;
sip_port = sip_port <= 65535 ? sip_port : 65535;
if(!strncmp(argv[1], "rr", 2)) {
mode = DMODEM_ANSWER_MODE;
}
signal(SIGPIPE,SIG_IGN);
char *dialstr = argv[1];
int has_sip_user = 1;
const char *_sip_user = getenv("SIP_LOGIN");
char *sip_user = NULL;
if (!_sip_user) {
has_sip_user = 0;
_sip_user = "placeholder:placeholder@placeholder";
}
sip_user = malloc(strlen(_sip_user) + 1);
strcpy(sip_user, _sip_user);
char *sip_domain = strchr(sip_user,'@');
if (!sip_domain) {
return -1;
}
*sip_domain++ = '\0';
char *sip_pass = strchr(sip_user,':');
if (!sip_pass) {
return -1;
}
*sip_pass++ = '\0';
status = pjsua_create();
if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);
if (!has_sip_user)
PJ_LOG(2,(__FILE__, "SIP_LOGIN is empty, no registration will be attempted"));
/* Init pjsua */
{
pjsua_config cfg;
pjsua_logging_config log_cfg;
pjsua_media_config med_cfg;
pjsua_config_default(&cfg);
cfg.cb.on_call_media_state = &on_call_media_state;
cfg.cb.on_call_state = &on_call_state;
if(mode == DMODEM_ANSWER_MODE) {
cfg.cb.on_incoming_call = &on_incoming_call;
}
pjsua_logging_config_default(&log_cfg);
// log_cfg.console_level = 4;
log_cfg.console_level = 2;
pjsua_media_config_default(&med_cfg);
med_cfg.no_vad = true;
med_cfg.ec_tail_len = 0;
med_cfg.jb_max = -1;
med_cfg.jb_init = -1;
med_cfg.audio_frame_ptime = 10;
med_cfg.quality = 10;
med_cfg.enable_ice = PJ_FALSE;
med_cfg.enable_turn = PJ_FALSE;
status = pjsua_init(&cfg, &log_cfg, &med_cfg);
if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);
}
pjsua_set_ec(0,0); // maybe?
pjsua_set_null_snd_dev();
/* g711 only */
pjsua_codec_info codecs[32];
unsigned count = sizeof(codecs)/sizeof(*codecs);
pjsua_enum_codecs(codecs,&count);
for (int i=0; i<count; i++) {
int pri = 0;
if (pj_strcmp2(&codecs[i].codec_id,"PCMA/8000/1") == 0) {
pri = 1;
} else if (pj_strcmp2(&codecs[i].codec_id,"PCMU/8000/1") == 0) {
pri = 2;
}
pjsua_codec_set_priority(&codecs[i].codec_id, pri);
// printf("codec: %s %d\n",pj_strbuf(&codecs[i].codec_id),pri);
}
pjsua_transport_id transport_id;
/* Add UDP transport. */
{
pjsua_transport_config cfg;
pjsua_transport_config_default(&cfg);
if (mode)
cfg.port = sip_port;
if (getenv("PJSIP_IPV6"))
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP6, &cfg, &transport_id);
else
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, &transport_id);
if (status != PJ_SUCCESS) error_exit("Error creating transport", status);
}
char buf[384];
//printf("Initializing pool\n");
pj_caching_pool cp;
pj_caching_pool_init(&cp, NULL, 1024*1024);
pool = pj_pool_create(&cp.factory, "pool1", 4000, 4000, NULL);
pj_str_t name = pj_str("dmodem");
memset(&port,0,sizeof(port));
port.sock = atoi(argv[2]); // inherited from parent
pjmedia_port_info_init(&port.base.info, &name, SIGNATURE, 9600, 1, 16, 192);
port.base.put_frame = dmodem_put_frame;
port.base.get_frame = dmodem_get_frame;
port.base.on_destroy = dmodem_on_destroy;
memset(buf,0,sizeof(buf));
write(port.sock, buf, sizeof(buf));
/* Initialization is done, now start pjsua */
status = pjsua_start();
if (status != PJ_SUCCESS) error_exit("Error starting pjsua", status);
if (has_sip_user)
{
pjsua_acc_config cfg;
pjsua_acc_config_default(&cfg);
snprintf(buf,sizeof(buf),"sip:%s@%s",sip_user,sip_domain);
pj_strdup2(pool,&cfg.id,buf);
snprintf(buf,sizeof(buf),"sip:%s",sip_domain);
pj_strdup2(pool,&cfg.reg_uri,buf);
cfg.register_on_acc_add = mode ? true : false;
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(sip_user);
cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
cfg.cred_info[0].data = pj_str(sip_pass);
status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
if (status != PJ_SUCCESS) error_exit("Error adding account", status);
}
else
{
status == pjsua_acc_add_local(transport_id, 1, &acc_id);
if (status != PJ_SUCCESS) error_exit("Error adding local account", status);
}
pjsua_get_var()->tpdata[transport_id].has_bound_addr = PJ_TRUE;
if (getenv("PJSIP_IPV6"))
pjsua_get_var()->acc[acc_id].cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
start_pa();
if(mode == DMODEM_DIAL_MODE) {
if (has_sip_user)
snprintf(buf,sizeof(buf),"sip:%s@%s",dialstr,sip_domain);
else
snprintf(buf,sizeof(buf),"sip:%s",dialstr);
PJ_LOG(2,(__FILE__, "calling %s\n",buf));
pj_str_t uri = pj_str(buf);
pjsua_call_id callid;
status = pjsua_call_make_call(acc_id, &uri, 0, NULL, NULL, &callid);
if (status != PJ_SUCCESS) error_exit("Error making call", status);
}
struct timespec ts = {1, 0};
while(1) {
if(mode == DMODEM_ANSWER_MODE) {
if(ringing) {
status = pjsua_call_answer(incoming, 200, NULL, NULL);
if (status != PJ_SUCCESS) error_exit("Error answering call", status);
ringing = 0;
answered = 1;
}
}
nanosleep(&ts,NULL);
}
return 0;
}