add playAudioFile() and caller menu skeleton
- playAudioFile() makes it possible to play a WAV file to either the caller or the mumble channel - menu skeleton has DTMF handling and is ready for adding '*n' functions (like *5 = mute).
This commit is contained in:
parent
e0ae088c63
commit
82015dc14c
@ -107,6 +107,9 @@ namespace sip {
|
||||
|
||||
virtual void onDtmfDigit(pj::OnDtmfDigitParam &prm) override;
|
||||
|
||||
virtual void playAudioFile(std::string file);
|
||||
virtual void playAudioFile(std::string file, bool in_chan);
|
||||
|
||||
private:
|
||||
sip::PjsuaCommunicator &communicator;
|
||||
pj::Account &account;
|
||||
@ -145,14 +148,24 @@ namespace sip {
|
||||
communicator.logger.notice(msgText);
|
||||
communicator.onStateChange(msgText);
|
||||
|
||||
pj_thread_sleep(500); // sleep a moment to allow connection to stabilize
|
||||
this->playAudioFile(communicator.file_welcome);
|
||||
|
||||
communicator.got_dtmf = "";
|
||||
|
||||
/*
|
||||
* if no pin is set, go ahead and turn off mute/deaf
|
||||
* otherwise, wait for pin to be entered
|
||||
*/
|
||||
if ( communicator.pin.length() == 0 ) {
|
||||
if ( communicator.caller_pin.length() == 0 ) {
|
||||
// No PIN set... enter DTMF root menu and turn off mute/deaf
|
||||
communicator.dtmf_mode = DTMF_MODE_ROOT;
|
||||
communicator.onMuteDeafChange(0);
|
||||
} else {
|
||||
// PIN set... enter DTMF unauth menu and play PIN prompt message
|
||||
communicator.dtmf_mode = DTMF_MODE_UNAUTH;
|
||||
pj_thread_sleep(500); // pause briefly after announcement
|
||||
this->playAudioFile(communicator.file_prompt_pin);
|
||||
}
|
||||
|
||||
} else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
|
||||
@ -191,32 +204,184 @@ namespace sip {
|
||||
}
|
||||
}
|
||||
|
||||
void _Call::playAudioFile(std::string file) {
|
||||
this->playAudioFile(file, false); // default is NOT to echo to mumble
|
||||
}
|
||||
|
||||
/* TODO:
|
||||
* - local deafen before playing and undeafen after?
|
||||
*/
|
||||
void _Call::playAudioFile(std::string file, bool in_chan) {
|
||||
communicator.logger.notice("Entered playAudioFile(%s)", file.c_str());
|
||||
pj::AudioMediaPlayer player;
|
||||
pj::MediaFormatAudio mfa;
|
||||
pj::AudioMediaPlayerInfo pinfo;
|
||||
int wavsize;
|
||||
int sleeptime;
|
||||
|
||||
if ( ! pj_file_exists(file.c_str()) ) {
|
||||
communicator.logger.warn("File not found (%s)", file.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: use some library to get the actual length in millisec
|
||||
*
|
||||
* This just gets the file size and divides by a constant to
|
||||
* estimate the length of the WAVE file in milliseconds.
|
||||
* This depends on the encoding bitrate, etc.
|
||||
*/
|
||||
|
||||
auto ci = getInfo();
|
||||
if (ci.media.size() != 1) {
|
||||
throw sip::Exception("ci.media.size is not 1");
|
||||
}
|
||||
|
||||
if (ci.media[0].status == PJSUA_CALL_MEDIA_ACTIVE) {
|
||||
auto *aud_med = static_cast<pj::AudioMedia *>(getMedia(0));
|
||||
|
||||
try {
|
||||
player.createPlayer(file, PJMEDIA_FILE_NO_LOOP);
|
||||
pinfo = player.getInfo();
|
||||
sleeptime = pinfo.sizeBytes / (pinfo.payloadBitsPerSample * 3);
|
||||
|
||||
/*
|
||||
communicator.logger.notice("DEBUG: wavsize=%d pbps=%d bytes=%d samples=%d",
|
||||
wavsize, pinfo.payloadBitsPerSample, pinfo.sizeBytes, pinfo.sizeSamples);
|
||||
communicator.logger.notice("WAVE length in ms: %d", sleeptime);
|
||||
*/
|
||||
|
||||
if ( in_chan ) { // choose the target sound output
|
||||
player.startTransmit(*communicator.media);
|
||||
} else {
|
||||
player.startTransmit(*aud_med);
|
||||
}
|
||||
|
||||
pj_thread_sleep(sleeptime);
|
||||
|
||||
if ( in_chan ) { // choose the target sound output
|
||||
player.stopTransmit(*communicator.media);
|
||||
} else {
|
||||
player.stopTransmit(*aud_med);
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
communicator.logger.notice("Error playing file %s", file.c_str());
|
||||
}
|
||||
} else {
|
||||
communicator.logger.notice("Call not active - can't play file %s", file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void _Call::onDtmfDigit(pj::OnDtmfDigitParam &prm) {
|
||||
//communicator.logger.notice("DTMF digit '%s' (call %d).",
|
||||
// prm.digit.c_str(), getId());
|
||||
pj::CallOpParam param;
|
||||
|
||||
if ( communicator.pin.length() > 0 ) {
|
||||
if ( prm.digit == "#" ) {
|
||||
//communicator.logger.notice("DTMF got string command %s",
|
||||
// communicator.got_dtmf.c_str());
|
||||
if ( communicator.got_dtmf == communicator.pin ) {
|
||||
communicator.logger.notice("Caller entered correct PIN");
|
||||
communicator.onMuteDeafChange(0);
|
||||
} else {
|
||||
communicator.logger.notice("Caller entered wrong PIN");
|
||||
param.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
|
||||
this->hangup(param);
|
||||
|
||||
/*
|
||||
* DTMF CALLER MENU
|
||||
*/
|
||||
|
||||
switch ( communicator.dtmf_mode ) {
|
||||
case DTMF_MODE_UNAUTH:
|
||||
/*
|
||||
* IF UNAUTH, the only thing we allow is to authorize.
|
||||
*/
|
||||
switch ( prm.digit[0] ) {
|
||||
case '#':
|
||||
/*
|
||||
* When user presses '#', test PIN entry
|
||||
*/
|
||||
if ( communicator.caller_pin.length() > 0 ) {
|
||||
if ( communicator.got_dtmf == communicator.caller_pin ) {
|
||||
communicator.logger.notice("Caller entered correct PIN");
|
||||
communicator.dtmf_mode = DTMF_MODE_ROOT;
|
||||
this->playAudioFile(communicator.file_entering_channel);
|
||||
communicator.onMuteDeafChange(0);
|
||||
this->playAudioFile(communicator.file_announce_new_caller, true);
|
||||
} else {
|
||||
communicator.logger.notice("Caller entered wrong PIN");
|
||||
this->playAudioFile(communicator.file_invalid_pin);
|
||||
if ( communicator.pin_fails++ >= MAX_PIN_FAILS ) {
|
||||
param.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
|
||||
pj_thread_sleep(500); // pause before next announcement
|
||||
this->playAudioFile(communicator.file_goodbye);
|
||||
pj_thread_sleep(500); // pause before next announcement
|
||||
this->hangup(param);
|
||||
}
|
||||
this->playAudioFile(communicator.file_prompt_pin);
|
||||
}
|
||||
communicator.got_dtmf = "";
|
||||
}
|
||||
break;
|
||||
case '*':
|
||||
/*
|
||||
* Allow user to reset PIN entry by pressing '*'
|
||||
*/
|
||||
communicator.got_dtmf = "";
|
||||
this->playAudioFile(communicator.file_prompt_pin);
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* In all other cases, add input digit to stack
|
||||
*/
|
||||
communicator.got_dtmf = communicator.got_dtmf + prm.digit;
|
||||
if ( communicator.got_dtmf.size() > MAX_CALLER_PIN_LEN ) {
|
||||
// just drop 'em if too long
|
||||
param.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
|
||||
this->playAudioFile(communicator.file_goodbye);
|
||||
pj_thread_sleep(500); // pause before next announcement
|
||||
this->hangup(param);
|
||||
}
|
||||
}
|
||||
communicator.got_dtmf = "";
|
||||
} else {
|
||||
// communicator.logger.notice("DTMF append %s to %s",
|
||||
// prm.digit.c_str(), communicator.got_dtmf.c_str());
|
||||
communicator.got_dtmf = communicator.got_dtmf + prm.digit;
|
||||
}
|
||||
} else {
|
||||
communicator.logger.notice("DTMF ignoring %s", prm.digit.c_str());
|
||||
break;
|
||||
case DTMF_MODE_ROOT:
|
||||
/*
|
||||
* User already authenticated; no data entry pending
|
||||
*/
|
||||
switch ( prm.digit[0] ) {
|
||||
case '*':
|
||||
/*
|
||||
* Switch user to 'star' menu
|
||||
*/
|
||||
communicator.dtmf_mode = DTMF_MODE_STAR;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* Default is to ignore all digits in root
|
||||
*/
|
||||
communicator.logger.notice("Ignore DTMF digit '%s' in ROOT state", prm.digit.c_str());
|
||||
}
|
||||
break;
|
||||
case DTMF_MODE_STAR:
|
||||
/*
|
||||
* User already entered '*'; time to perform action
|
||||
*/
|
||||
switch ( prm.digit[0] ) {
|
||||
/*
|
||||
case '5':
|
||||
// Mute line
|
||||
communicator.onMuteChange(1);
|
||||
this->playAudioFile(communicator.file_mute_on);
|
||||
break;
|
||||
case '6':
|
||||
// Un-mute line
|
||||
this->playAudioFile(communicator.file_mute_off);
|
||||
communicator.onMuteChange(0);
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
communicator.logger.notice("Unsupported DTMF digit '%s' in state STAR", prm.digit.c_str());
|
||||
}
|
||||
/*
|
||||
* In any case, switch back to root after one digit
|
||||
*/
|
||||
communicator.dtmf_mode = DTMF_MODE_ROOT;
|
||||
break;
|
||||
default:
|
||||
communicator.logger.notice("Unexpected DTMF '%s' in unknown state '%d'", prm.digit.c_str(),
|
||||
communicator.dtmf_mode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _Account::onRegState(pj::OnRegStateParam &prm) {
|
||||
|
@ -18,10 +18,14 @@
|
||||
#include <climits>
|
||||
#include <bits/unique_ptr.h>
|
||||
|
||||
enum dtmf_modes_t {DTMF_MODE_UNAUTH, DTMF_MODE_ROOT, DTMF_MODE_STAR};
|
||||
|
||||
namespace sip {
|
||||
|
||||
constexpr int DEFAULT_PORT = 5060;
|
||||
constexpr int SAMPLING_RATE = 48000;
|
||||
constexpr int MAX_CALLER_PIN_LEN = 64;
|
||||
constexpr int MAX_PIN_FAILS = 2;
|
||||
|
||||
class Exception : public std::runtime_error {
|
||||
public:
|
||||
@ -76,8 +80,22 @@ namespace sip {
|
||||
int16_t *samples,
|
||||
unsigned int length);
|
||||
|
||||
std::string pin;
|
||||
// config params we get from config.ini
|
||||
std::string caller_pin;
|
||||
std::string file_welcome;
|
||||
std::string file_prompt_pin;
|
||||
std::string file_entering_channel;
|
||||
std::string file_announce_new_caller;
|
||||
std::string file_invalid_pin;
|
||||
std::string file_goodbye;
|
||||
std::string file_mute_on;
|
||||
std::string file_mute_off;
|
||||
std::string file_menu;
|
||||
|
||||
// TODO: move these to private?
|
||||
std::string got_dtmf;
|
||||
dtmf_modes_t dtmf_mode = DTMF_MODE_ROOT;
|
||||
int pin_fails = 0;
|
||||
|
||||
std::function<void(int16_t *, int)> onIncomingPcmSamples;
|
||||
|
||||
@ -85,6 +103,8 @@ namespace sip {
|
||||
|
||||
std::function<void(int)> onMuteDeafChange;
|
||||
|
||||
std::function<void(int)> onMuteChange;
|
||||
|
||||
pj_status_t mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame);
|
||||
|
||||
pj_status_t mediaPortPutFrame(pjmedia_port *port, pjmedia_frame *frame);
|
||||
|
@ -28,11 +28,28 @@ channelNameExpression =
|
||||
# in the same group
|
||||
autodeaf = 0
|
||||
|
||||
# Bitrate of Opus encoder in B/s
|
||||
# Adjust it if you need to meet the specific bandwidth requirements of Murmur server
|
||||
opusEncoderBitrate = 16000
|
||||
|
||||
[app]
|
||||
|
||||
# Caller PIN needed to authenticate the phone call itself. The caller presses
|
||||
# the PIN, followed by the hash '#' key. On success, the caller is
|
||||
# unmuted/undeafened. On failure, the SIP call is hung up.
|
||||
pin = 4321
|
||||
|
||||
# Bitrate of Opus encoder in B/s
|
||||
# Adjust it if you need to meet the specific bandwidth requirements of Murmur server
|
||||
opusEncoderBitrate = 16000
|
||||
[files]
|
||||
# These files are used for the caller and mumble channel audio clips.
|
||||
# The paths below assume that you are running ./mumsi in the build/ dir.
|
||||
welcome = ../media/welcome.wav
|
||||
prompt_pin = ../media/prompt-pin.wav
|
||||
entering_channel = ../media/entering-channel.wav
|
||||
announce_new_caller = ../media/announce-new-caller.wav
|
||||
invalid_pin = ../media/invalid-pin.wav
|
||||
goodbye = ../media/goodbye.wav
|
||||
mute_on = ../media/mute-on.wav
|
||||
mute_off = ../media/mute-off.wav
|
||||
menu = ../media/menu.wav
|
||||
|
||||
|
||||
|
49
main.cpp
49
main.cpp
@ -98,9 +98,54 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
/* default to <no pin> */
|
||||
try {
|
||||
pjsuaCommunicator.pin = conf.getString("mumble.pin");
|
||||
pjsuaCommunicator.caller_pin = conf.getString("app.caller_pin");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.pin = "";
|
||||
pjsuaCommunicator.caller_pin = "";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_welcome = conf.getString("file.welcome");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_welcome = "welcome.wav";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_prompt_pin = conf.getString("file.prompt_pin");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_prompt_pin = "prompt-pin.wav";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_entering_channel = conf.getString("file.entering_channel");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_entering_channel = "entering-channel.wav";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_announce_new_caller = conf.getString("file.announce_new_caller");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_announce_new_caller = "announce-new-caller.wav";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_invalid_pin = conf.getString("file.invalid_pin");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_invalid_pin = "invalid-pin.wav";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_goodbye = conf.getString("file.goodbye");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_goodbye = "goodbye.wav";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_mute_on = conf.getString("file.mute_on");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_mute_on = "mute-on.wav";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_mute_off = conf.getString("file.mute_off");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_mute_off = "mute-off.wav";
|
||||
}
|
||||
|
||||
try { pjsuaCommunicator.file_menu = conf.getString("file.menu");
|
||||
} catch (...) {
|
||||
pjsuaCommunicator.file_menu = "menu.wav";
|
||||
}
|
||||
|
||||
mumbleCommunicator.connect(mumbleConf);
|
||||
|
24
media/Makefile
Normal file
24
media/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
# Makefile
|
||||
#
|
||||
# This file generates the WAVE files from text files on macOS
|
||||
#
|
||||
|
||||
WAVES := $(subst .msg,.wav,$(wildcard *.msg)) blow.wav
|
||||
|
||||
VOICE := --voice=Samantha
|
||||
RATE := --rate=15
|
||||
QUALITY := --quality=127
|
||||
|
||||
|
||||
all: $(WAVES)
|
||||
|
||||
%.wav: %.aiff
|
||||
afconvert "$<" -d LEI16 "$@"
|
||||
|
||||
%.aiff: %.msg
|
||||
say $(VOICE) $(RATE) -o $@ < $<
|
||||
|
||||
#announce-new-caller.wav: /System/Library/Sounds/Blow.aiff
|
||||
# afconvert "$<" -d LEI16 "$@"
|
||||
|
||||
|
1
media/announce-new-caller.msg
Normal file
1
media/announce-new-caller.msg
Normal file
@ -0,0 +1 @@
|
||||
Caller joined
|
BIN
media/announce-new-caller.wav
Normal file
BIN
media/announce-new-caller.wav
Normal file
Binary file not shown.
BIN
media/blow.wav
Normal file
BIN
media/blow.wav
Normal file
Binary file not shown.
1
media/entering-channel.msg
Normal file
1
media/entering-channel.msg
Normal file
@ -0,0 +1 @@
|
||||
entering channel
|
BIN
media/entering-channel.wav
Normal file
BIN
media/entering-channel.wav
Normal file
Binary file not shown.
1
media/goodbye.msg
Normal file
1
media/goodbye.msg
Normal file
@ -0,0 +1 @@
|
||||
Goodbye
|
BIN
media/goodbye.wav
Normal file
BIN
media/goodbye.wav
Normal file
Binary file not shown.
1
media/invalid-pin.msg
Normal file
1
media/invalid-pin.msg
Normal file
@ -0,0 +1 @@
|
||||
Invalid pin
|
BIN
media/invalid-pin.wav
Normal file
BIN
media/invalid-pin.wav
Normal file
Binary file not shown.
3
media/menu.msg
Normal file
3
media/menu.msg
Normal file
@ -0,0 +1,3 @@
|
||||
Press star one for status.
|
||||
Press star five to mute.
|
||||
Press star six to un-mute.
|
BIN
media/menu.wav
Normal file
BIN
media/menu.wav
Normal file
Binary file not shown.
1
media/mute-off.msg
Normal file
1
media/mute-off.msg
Normal file
@ -0,0 +1 @@
|
||||
mute off
|
BIN
media/mute-off.wav
Normal file
BIN
media/mute-off.wav
Normal file
Binary file not shown.
1
media/mute-on.msg
Normal file
1
media/mute-on.msg
Normal file
@ -0,0 +1 @@
|
||||
mute on
|
BIN
media/mute-on.wav
Normal file
BIN
media/mute-on.wav
Normal file
Binary file not shown.
1
media/prompt-pin.msg
Normal file
1
media/prompt-pin.msg
Normal file
@ -0,0 +1 @@
|
||||
Please enter pin
|
BIN
media/prompt-pin.wav
Normal file
BIN
media/prompt-pin.wav
Normal file
Binary file not shown.
1
media/welcome.msg
Normal file
1
media/welcome.msg
Normal file
@ -0,0 +1 @@
|
||||
Welcome to the conference bridge
|
BIN
media/welcome.wav
Normal file
BIN
media/welcome.wav
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user