eliminate support for multiple lws threads as part of fixing valgrind errors

Signed-off-by: Dave Horton <daveh@beachdognet.com>
This commit is contained in:
Dave Horton
2023-12-26 10:57:15 -05:00
parent a2324972eb
commit 420e51eac7
140 changed files with 19851 additions and 0 deletions

8
mod_aws_lex/LICENSE Normal file
View File

@@ -0,0 +1,8 @@
Copyright 2023, Drachtio Communications Services, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

10
mod_aws_lex/Makefile.am Normal file
View File

@@ -0,0 +1,10 @@
include $(top_srcdir)/build/modmake.rulesam
MODNAME=mod_aws_lex
mod_LTLIBRARIES = mod_aws_lex.la
mod_aws_lex_la_SOURCES = mod_aws_lex.c aws_lex_glue.cpp parser.cpp
mod_aws_lex_la_CFLAGS = $(AM_CFLAGS)
mod_aws_lex_la_CXXFLAGS = $(AM_CXXFLAGS) -std=c++11 -I${switch_srcdir}/libs/aws-sdk-cpp/aws-cpp-sdk-core/include -I${switch_srcdir}/libs/aws-sdk-cpp/aws-cpp-sdk-lexv2-runtime/include -I${switch_srcdir}/libs/aws-sdk-cpp/build/.deps/install/include
mod_aws_lex_la_LIBADD = $(switch_builddir)/libfreeswitch.la
mod_aws_lex_la_LDFLAGS = -avoid-version -module -no-undefined -L${switch_srcdir}/libs/aws-sdk-cpp/build/.deps/install/lib -L${switch_srcdir}/libs/aws-sdk-cpp/build/aws-cpp-sdk-core -L${switch_srcdir}/libs/aws-sdk-cpp/build/aws-cpp-sdk-lexv2-runtime -laws-cpp-sdk-lexv2-runtime -laws-cpp-sdk-core -laws-c-event-stream -laws-checksums -laws-c-common -lpthread -lcurl -lcrypto -lssl -lz

63
mod_aws_lex/README.md Normal file
View File

@@ -0,0 +1,63 @@
# mod_aws_lex
A Freeswitch module that connects to [AWS Lex](https://docs.aws.amazon.com/lex/) using the streaming API.
Once a Freeswitch channel is connected to a Lex bot, media is streamed to Lex, which returns information describing the "intent" that was detected, along with transcriptions and audio prompts and text to play to the caller. The handling of returned audio by the module is two-fold:
1. If an audio clip was returned, it is *not* immediately played to the caller, but instead is written to a temporary file on the Freeswitch server.
2. Next, a Freeswitch custom event is sent to the application containing the details of the dialogflow response as well as the path to the audio file.
This allows the application whether to decide to play the returned audio clip (via the mod_dptools 'play' command), or to use a text-to-speech service to generate audio using the returned prompt text.
## API
### Commands
The freeswitch module exposes the following API commands:
```
aws_lex_start <uuid> botId aliasId region locale [welcome-intent]
```
Attaches media bug to channel and performs streaming recognize request.
- `uuid` - freeswitch channel uuid
- `bot` - name of Lex bot
- `alias` - alias of Lex bot
- `region` - AWS region name (e.g 'us-east-1')
- `locale` - AWS language to use for speech recognition (e.g. 'en-US')
- `welcome-intent` - name of intent to trigger initially
```
aws_lex_dtmf <uuid> dtmf-entry
```
Notify Lex of a dtmf entry
```
aws_lex_play_done <uuid>
```
Notify Lex that an audio prompt has completed playing. The application needs to call this if barge-in is enabled.
```
aws_lex_stop <uuid>
```
Stop dialogflow on the channel.
### Channel variables
* `ACCESS_KEY_ID` - AWS access key id to use to authenticate; if not provided an environment variable of the same name is used if provided
* `SECRET_ACCESS_KEY` - AWS secret access key to use to authenticate; if not provided an environment variable of the same name is used if provided
* `LEX_WELCOME_MESSAGE` - text for a welcome message to play at audio start
* `x-amz-lex:start-silence-threshold-ms` - no-input timeout in milliseconds (Lex defaults to 4000 if not provided)
### Events
* `lex::intent` - an intent has been detected.
* `lex::transcription` - a transcription has been returned
* `lex::text_response` - a text response has been returned; the telephony application can play this using text-to-speech if desired.
* `lex::audio_provided` - an audio response (.mp3 format) has been returned; the telephony application can play this file if TTS is not being used
* `lex::text_response` - a text response was provided.
* `lex::playback_interruption` - the caller has spoken during prompt playback; the telephony application should kill the current audio prompt
* `lex::error` - dialogflow has returned an error
## Usage
When using [drachtio-fsrmf](https://www.npmjs.com/package/drachtio-fsmrf), you can access this API command via the api method on the 'endpoint' object.
```js
ep.api('aws_lex_start', `${ep.uuid} BookTrip Gamma us-east-1`);
```
# Example application
See [drachtio-lex-gateway](https://github.com/drachtio/drachtio-lex-phone-gateway).

View File

@@ -0,0 +1,829 @@
#include <cstdlib>
#include <switch.h>
#include <switch_json.h>
#include <string.h>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <fstream>
#include <string>
#include <sstream>
#include <map>
#include <float.h>
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/core/utils/logging/DefaultLogSystem.h>
#include <aws/core/utils/logging/AWSLogging.h>
#include <aws/lexv2-runtime/LexRuntimeV2Client.h>
#include <aws/lexv2-runtime/model/StartConversationRequest.h>
#include "mod_aws_lex.h"
#include "parser.h"
using namespace Aws;
using namespace Aws::Utils;
using namespace Aws::Auth;
using namespace Aws::LexRuntimeV2;
using namespace Aws::LexRuntimeV2::Model;
const char ALLOC_TAG[] = "drachtio";
static uint64_t playCount = 0;
static std::multimap<std::string, std::string> audioFiles;
static bool hasDefaultCredentials = false;
static bool awsLoggingEnabled = false;
static const char *endpointOverride = std::getenv("AWS_LEX_ENDPOINT_OVERRIDE");
static std::vector<Aws::String> locales{"en_AU", "en_GB", "en_US", "fr_CA", "fr_FR", "es_ES", "es_US", "it_IT"};
static switch_status_t hanguphook(switch_core_session_t *session) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_channel_state_t state = switch_channel_get_state(channel);
if (state == CS_HANGUP || state == CS_ROUTING) {
char * sessionId = switch_core_session_get_uuid(session);
auto range = audioFiles.equal_range(sessionId);
for (auto it = range.first; it != range.second; it++) {
std::string filename = it->second;
std::remove(filename.c_str());
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG,
"aws_lex_session_cleanup: removed audio file %s\n", filename.c_str());
}
audioFiles.erase(sessionId);
switch_core_event_hook_remove_state_change(session, hanguphook);
}
return SWITCH_STATUS_SUCCESS;
}
static bool parseMetadata(Aws::Map<Aws::String, Slot>& slots, Aws::Map<Aws::String, Aws::String>& attributes, char* metadata) {
cJSON* json = cJSON_Parse(metadata);
if (json) {
int numItems = cJSON_GetArraySize(json);
for (int i = 0; i < numItems; i++) {
cJSON* item = cJSON_GetArrayItem(json, i);
if (0 == strcmp("slots", item->string)) {
// pre-fill slots
if (cJSON_Object == item->type) {
int numSlots = cJSON_GetArraySize(item);
for (int j = 0; j < numSlots; j++) {
Slot slot;
Value value;
cJSON* jSlot = cJSON_GetArrayItem(item, j);
switch (jSlot->type) {
case cJSON_False:
value.SetInterpretedValue("false");
slot.SetValue(value);
slots[jSlot->string] = slot;
break;
case cJSON_True:
value.SetInterpretedValue("true");
slot.SetValue(value);
slots[jSlot->string] = slot;
break;
case cJSON_Number:
{
double d = jSlot->valuedouble;
char scratch[16];
if ((fabs(((double)jSlot->valueint) - d) <= DBL_EPSILON) && (d <= INT_MAX) && (d >= INT_MIN)) {
sprintf(scratch, "%d", jSlot->valueint);
}
else {
sprintf(scratch, "%f", jSlot->valuedouble);
}
value.SetInterpretedValue(scratch);
slot.SetValue(value);
slots[jSlot->string] = slot;
}
break;
case cJSON_String:
value.SetInterpretedValue(jSlot->valuestring);
slot.SetValue(value);
slots[jSlot->string] = slot;
break;
default:
break;
}
}
}
}
else if (0 == strcmp("context", item->string) && cJSON_Object == item->type) {
char buf[4096];
// special case: json string passed as x-amz-lex:channels:context to bot
if (!cJSON_PrintPreallocated(item, buf, 4096, 0)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "parse metadata fails due to excessive length\n");
}
else {
attributes["x-amz-lex:channels:context-format"] = "json";
attributes["x-amz-lex:channels:context"] = buf;
}
}
else {
// attributes
switch (item->type) {
case cJSON_False:
attributes[item->string] = false;
break;
case cJSON_True:
attributes[item->string] = true;
break;
case cJSON_Number:
{
double d = item->valuedouble;
if ((fabs(((double)item->valueint) - d) <= DBL_EPSILON) && (d <= INT_MAX) && (d >= INT_MIN)) {
attributes[item->string] = item->valueint;
}
else {
attributes[item->string] = d;
}
}
break;
case cJSON_String:
attributes[item->string] = item->valuestring;
break;
default:
break;
}
}
}
int count = slots.size() + attributes.size();
cJSON_Delete(json);
return count > 0;
}
return false;
}
class GStreamer {
public:
GStreamer(const char *sessionId,
char* bot,
char* alias,
char* region,
char *locale,
char *intentName,
char *metadata,
const char* awsAccessKeyId,
const char* awsSecretAccessKey,
responseHandler_t responseHandler,
errorHandler_t errorHandler) :
m_bot(bot), m_alias(alias), m_region(region), m_sessionId(sessionId), m_finished(false), m_finishing(false), m_packets(0),
m_pStream(nullptr), m_bPlayDone(false), m_bDiscardAudio(false)
{
Aws::String key(awsAccessKeyId);
Aws::String secret(awsSecretAccessKey);
Aws::String awsLocale(locale);
Aws::Client::ClientConfiguration config;
config.region = region;
if (endpointOverride) config.endpointOverride = endpointOverride;
char keySnippet[20];
strncpy(keySnippet, awsAccessKeyId, 4);
for (int i = 4; i < 20; i++) keySnippet[i] = 'x';
keySnippet[19] = '\0';
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p ACCESS_KEY_ID %s\n", this, keySnippet);
if (*awsAccessKeyId && *awsSecretAccessKey) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "using AWS creds %s %s\n", awsAccessKeyId, awsSecretAccessKey);
m_client = Aws::MakeUnique<LexRuntimeV2Client>(ALLOC_TAG, AWSCredentials(awsAccessKeyId, awsSecretAccessKey), config);
}
else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "No AWS credentials so using default credentials\n");
m_client = Aws::MakeUnique<LexRuntimeV2Client>(ALLOC_TAG, config);
}
m_handler.SetHeartbeatEventCallback([this](const HeartbeatEvent&)
{
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p recv heartbeat\n", this);
});
m_handler.SetPlaybackInterruptionEventCallback([this, responseHandler](const PlaybackInterruptionEvent& ev)
{
switch_core_session_t* psession = switch_core_session_locate(m_sessionId.c_str());
if (psession) {
cJSON* json = lex2Json(ev);
char* data = cJSON_PrintUnformatted(json);
responseHandler(psession, AWS_LEX_EVENT_PLAYBACK_INTERRUPTION, const_cast<char *>(data));
free(data);
cJSON_Delete(json);
switch_core_session_rwunlock(psession);
}
});
m_handler.SetTranscriptEventCallback([this, responseHandler](const TranscriptEvent& ev)
{
switch_core_session_t* psession = switch_core_session_locate(m_sessionId.c_str());
if (psession) {
cJSON* json = lex2Json(ev);
char* data = cJSON_PrintUnformatted(json);
responseHandler(psession, AWS_LEX_EVENT_TRANSCRIPTION, const_cast<char *>(data));
free(data);
cJSON_Delete(json);
switch_core_session_rwunlock(psession);
}
});
m_handler.SetTextResponseEventCallback([this, responseHandler](const TextResponseEvent& ev){
switch_core_session_t* psession = switch_core_session_locate(m_sessionId.c_str());
if (psession) {
cJSON* json = lex2Json(ev);
char* data = cJSON_PrintUnformatted(json);
responseHandler(psession, AWS_LEX_EVENT_TEXT_RESPONSE, data);
free(data);
cJSON_Delete(json);
switch_core_session_rwunlock(psession);
}
});
m_handler.SetAudioResponseEventCallback([this, responseHandler](const AudioResponseEvent& ev){
if (m_bDiscardAudio) return;
const Aws::Utils::ByteBuffer& audio = ev.GetAudioChunk();
uint32_t bytes = audio.GetLength();
auto contentType = ev.GetContentType();
auto eventId = ev.GetEventId();
switch_core_session_t* psession = switch_core_session_locate(m_sessionId.c_str());
if (psession) {
if (!m_f.is_open()) {
if (0 == bytes) return;
m_ostrCurrentPath.str("");
m_ostrCurrentPath << SWITCH_GLOBAL_dirs.temp_dir << SWITCH_PATH_SEPARATOR << m_sessionId << "_" << ++playCount << ".mp3";
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(psession), SWITCH_LOG_DEBUG, "GStreamer %p: writing new audio file %s\n", this, m_ostrCurrentPath.str().c_str());
m_f.open(m_ostrCurrentPath.str(), std::ofstream::binary);
m_f.write((const char*) audio.GetUnderlyingData(), bytes);
// add the file to the list of files played for this session, we'll delete when session closes
audioFiles.insert(std::pair<std::string, std::string>(m_sessionId, m_ostrCurrentPath.str().c_str()));
}
else if (0 == bytes) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(psession), SWITCH_LOG_DEBUG, "GStreamer %p: closing audio file %s\n", this, m_ostrCurrentPath.str().c_str());
m_f.flush();
m_f.close();
std::ostringstream s;
s << "{\"path\": \"" << m_ostrCurrentPath.str() << "\"}";
responseHandler(psession, AWS_LEX_EVENT_AUDIO_PROVIDED, const_cast<char *>(s.str().c_str()));
}
else {
m_f.write((const char*) audio.GetUnderlyingData(), bytes);
}
switch_core_session_rwunlock(psession);
}
});
m_handler.SetIntentResultEventCallback([this, responseHandler](const IntentResultEvent& ev)
{
switch_core_session_t* psession = switch_core_session_locate(m_sessionId.c_str());
if (psession) {
cJSON* json = lex2Json(ev);
char* data = cJSON_PrintUnformatted(json);
responseHandler(psession, AWS_LEX_EVENT_INTENT, data);
free(data);
cJSON_Delete(json);
switch_core_session_rwunlock(psession);
}
});
m_handler.SetOnErrorCallback([this, errorHandler](const Aws::Client::AWSError<LexRuntimeV2Errors>& err)
{
switch_core_session_t* psession = switch_core_session_locate(m_sessionId.c_str());
if (psession) {
cJSON* json = lex2Json(err);
char* data = cJSON_PrintUnformatted(json);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "GStreamer %p stream got error: %s\n", this, data);
errorHandler(psession, data);
free(data);
cJSON_Delete(json);
switch_core_session_rwunlock(psession);
}
});
if (locales.end() == std::find(locales.begin(), locales.end(), awsLocale)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p invalid locale %s provided, defaulting to en-US\n", this, locale);
awsLocale = "en_US";
}
m_request.SetBotId(bot);
m_request.SetBotAliasId(alias);
m_request.SetSessionId(sessionId);
m_request.SetEventStreamHandler(m_handler);
m_request.SetLocaleId(awsLocale);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p sessionId %s, botId %s, alias %s, region %s, locale %s \n", this, sessionId, bot, alias, region, awsLocale.c_str());
auto OnStreamReady = [this, metadata, intentName](StartConversationRequestEventStream& stream)
{
switch_core_session_t* psession = switch_core_session_locate(m_sessionId.c_str());
if (psession) {
switch_channel_t* channel = switch_core_session_get_channel(psession);
Aws::Map<Aws::String, Aws::String> sessionAttributes;
m_pStream = &stream;
// check channel vars for lex session attributes
bool bargein = false;
const char* var;
if (switch_channel_var_true(channel, "LEX_USE_TTS")) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "GStreamer %p using tts so audio packets will be discarded\n", this);
m_bDiscardAudio = true;
}
if (var = switch_channel_get_variable(channel, "x-amz-lex:audio:start-timeout-ms")) {
sessionAttributes.insert({"x-amz-lex:audio:start-timeout-ms:*:*", var});
}
Aws::Map<Aws::String, Slot> slots;
if (metadata) parseMetadata(slots, sessionAttributes, metadata);
SessionState sessionState;
sessionState.SetSessionAttributes(sessionAttributes);
ConfigurationEvent configurationEvent;
configurationEvent.SetResponseContentType("audio/mpeg");
Intent intent;
if (intentName && strlen(intentName) > 0) {
DialogAction dialogAction;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p setting initial intent to '%s'\n", this, intentName);
intent.SetName(intentName);
for (auto const& pair : slots) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p setting slot %s\n", this, pair.first.c_str());
intent.AddSlots(pair.first, pair.second);
}
sessionState.SetIntent(intent);
dialogAction.SetType(DialogActionType::Delegate);
sessionState.SetDialogAction(dialogAction);
}
else if (var = switch_channel_get_variable(channel, "LEX_WELCOME_MESSAGE")) {
Message message;
DialogAction dialogAction;
dialogAction.SetType(DialogActionType::ElicitIntent);
sessionState.SetDialogAction(dialogAction);
message.SetContent(var);
message.SetContentType(MessageContentType::PlainText);
configurationEvent.AddWelcomeMessages(message);
// erase the channel var, so it is not reused in future intent
switch_channel_set_variable(channel, "LEX_WELCOME_MESSAGE", nullptr);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p setting welcome message: %s\n", this, var);
}
configurationEvent.SetSessionState(sessionState);
stream.WriteConfigurationEvent(configurationEvent);
stream.flush();
PlaybackCompletionEvent playbackCompletionEvent;
stream.WritePlaybackCompletionEvent(playbackCompletionEvent);
stream.flush();
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p got stream ready\n", this);
switch_core_session_rwunlock(psession);
}
};
auto OnResponseCallback = [&](const LexRuntimeV2Client* pClient,
const StartConversationRequest& request,
const StartConversationOutcome& outcome,
const std::shared_ptr<const Aws::Client::AsyncCallerContext>&)
{
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p stream got final response\n", this);
if (!outcome.IsSuccess()) {
const LexRuntimeV2Error& err = outcome.GetError();
auto message = err.GetMessage();
auto exception = err.GetExceptionName();
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p stream got error response %s : %s\n", this, message.c_str(), exception.c_str());
}
std::lock_guard<std::mutex> lk(m_mutex);
m_finished = true;
m_cond.notify_one();
};
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p starting conversation\n", this);
m_client->StartConversationAsync(m_request, OnStreamReady, OnResponseCallback, nullptr/*context*/);
}
~GStreamer() {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer::~GStreamer wrote %d packets %p\n", m_packets, this);
}
void dtmf(char* dtmf) {
if (m_finishing || m_finished) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer::dtmf not writing because we are finished, %p\n", this);
return;
}
DTMFInputEvent dtmfInputEvent;
dtmfInputEvent.SetInputCharacter(dtmf);
m_pStream->WriteDTMFInputEvent(dtmfInputEvent);
m_pStream->flush();
}
void notify_play_done() {
std::lock_guard<std::mutex> lk(m_mutex);
m_bPlayDone = true;
m_cond.notify_one();
}
bool write(void* data, uint32_t datalen) {
if (m_finishing || m_finished) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer::write not writing because we are finished, %p\n", this);
return false;
}
//m_fOutgoingAudio.write((const char*) data, datalen);
Aws::Utils::ByteBuffer audio((const unsigned char *) data, datalen);
AudioInputEvent audioInputEvent;
audioInputEvent.SetAudioChunk(audio);
audioInputEvent.SetContentType("audio/lpcm; sample-rate=8000; sample-size-bits=16; channel-count=1; is-big-endian=false");
m_pStream->WriteAudioInputEvent(audioInputEvent);
m_pStream->flush();
return true;
}
void finish() {
if (m_finishing) return;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer::finish %p\n", this);
std::lock_guard<std::mutex> lk(m_mutex);
m_finishing = true;
m_cond.notify_one();
}
void processData() {
bool shutdownInitiated = false;
while (true) {
std::unique_lock<std::mutex> lk(m_mutex);
m_cond.wait(lk, [&, this] {
return m_bPlayDone || m_finished || (m_finishing && !shutdownInitiated);
});
// we have data to process or have been told we're done
if (m_finished) return;
if (m_finishing) {
shutdownInitiated = true;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer::writing disconnect event %p\n", this);
if (m_pStream) {
m_pStream->WriteAudioInputEvent({}); // per the spec, we have to send an empty event (i.e. without a payload) at the end.
DisconnectionEvent disconnectionEvent;
m_pStream->WriteDisconnectionEvent(disconnectionEvent);
m_pStream->flush();
m_pStream->Close();
m_pStream = nullptr;
//m_fOutgoingAudio.flush();
//m_fOutgoingAudio.close();
}
}
else {
if (m_bPlayDone) {
m_bPlayDone = false;
PlaybackCompletionEvent playbackCompletionEvent;
m_pStream->WritePlaybackCompletionEvent(playbackCompletionEvent);
m_pStream->flush();
}
}
}
}
private:
std::string m_sessionId;
std::string m_bot;
std::string m_alias;
std::string m_region;
Aws::UniquePtr<LexRuntimeV2Client> m_client;
StartConversationRequestEventStream* m_pStream;
StartConversationRequest m_request;
StartConversationHandler m_handler;
bool m_finishing;
bool m_finished;
uint32_t m_packets;
std::mutex m_mutex;
std::condition_variable m_cond;
std::ofstream m_f;
std::ostringstream m_ostrCurrentPath;
//std::ofstream m_fOutgoingAudio;
bool m_bPlayDone;
bool m_bDiscardAudio;
};
static void *SWITCH_THREAD_FUNC lex_thread(switch_thread_t *thread, void *obj) {
struct cap_cb *cb = (struct cap_cb *) obj;
bool ok = true;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "lex_thread: starting cb %p\n", (void *) cb);
GStreamer* pStreamer = new GStreamer(cb->sessionId, cb->bot, cb->alias, cb->region, cb->locale,
cb->intent, cb->metadata, cb->awsAccessKeyId, cb->awsSecretAccessKey,
cb->responseHandler, cb->errorHandler);
if (!pStreamer) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "lex_thread: Error allocating streamer\n");
return nullptr;
}
cb->streamer = pStreamer;
pStreamer->processData();
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "lex_thread: stopping cb %p\n", (void *) cb);
delete pStreamer;
cb->streamer = nullptr;
return NULL;
}
static void killcb(struct cap_cb* cb) {
if (cb) {
if (cb->streamer) {
GStreamer* p = (GStreamer *) cb->streamer;
delete p;
cb->streamer = NULL;
}
if (cb->resampler) {
speex_resampler_destroy(cb->resampler);
cb->resampler = NULL;
}
}
}
extern "C" {
switch_status_t aws_lex_init() {
const char* accessKeyId = std::getenv("AWS_ACCESS_KEY_ID");
const char* secretAccessKey= std::getenv("AWS_SECRET_ACCESS_KEY");
const char* awsTrace = std::getenv("AWS_TRACE");
if (NULL == accessKeyId && NULL == secretAccessKey) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE,
"\"AWS_ACCESS_KEY_ID\" and/or \"AWS_SECRET_ACCESS_KEY\" env var not set; authentication will expect channel variables of same names to be set\n");
}
else {
hasDefaultCredentials = true;
}
Aws::SDKOptions options;
if (awsTrace && 0 == strcmp("1", awsTrace)) {
awsLoggingEnabled = true;
options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace;
Aws::Utils::Logging::InitializeAWSLogging(
Aws::MakeShared<Aws::Utils::Logging::DefaultLogSystem>(
ALLOC_TAG, Aws::Utils::Logging::LogLevel::Trace, "aws_sdk_"));
}
Aws::InitAPI(options);
return SWITCH_STATUS_SUCCESS;
}
switch_status_t aws_lex_cleanup() {
Aws::SDKOptions options;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_aws_lex: shutting down API");
if (awsLoggingEnabled) {
options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace;
Aws::Utils::Logging::ShutdownAWSLogging();
}
Aws::ShutdownAPI(options);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_aws_lex: shutdown API complete");
return SWITCH_STATUS_SUCCESS;
}
// start lex on a channel
switch_status_t aws_lex_session_init(
switch_core_session_t *session,
responseHandler_t responseHandler,
errorHandler_t errorHandler,
uint32_t samples_per_second,
char* bot,
char* alias,
char* region,
char* locale,
char* intent,
char* metadata,
struct cap_cb **ppUserData
) {
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_channel_t *channel = switch_core_session_get_channel(session);
int err;
switch_threadattr_t *thd_attr = NULL;
switch_memory_pool_t *pool = switch_core_session_get_pool(session);
struct cap_cb* cb = (struct cap_cb *) switch_core_session_alloc(session, sizeof(*cb));
memset(cb, sizeof(cb), 0);
const char* awsAccessKeyId = switch_channel_get_variable(channel, "AWS_ACCESS_KEY_ID");
const char* awsSecretAccessKey = switch_channel_get_variable(channel, "AWS_SECRET_ACCESS_KEY");
if (!hasDefaultCredentials && (!awsAccessKeyId || !awsSecretAccessKey)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR,
"missing credentials: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be suuplied either as an env variable or channel variable\n");
status = SWITCH_STATUS_FALSE;
goto done;
}
strncpy(cb->sessionId, switch_core_session_get_uuid(session), 256);
if (awsAccessKeyId && awsSecretAccessKey) {
strncpy(cb->awsAccessKeyId, awsAccessKeyId, 128);
strncpy(cb->awsSecretAccessKey, awsSecretAccessKey, 128);
}
else {
strncpy(cb->awsAccessKeyId, std::getenv("AWS_ACCESS_KEY_ID"), 128);
strncpy(cb->awsSecretAccessKey, std::getenv("AWS_SECRET_ACCESS_KEY"), 128);
}
cb->responseHandler = responseHandler;
cb->errorHandler = errorHandler;
if (switch_mutex_init(&cb->mutex, SWITCH_MUTEX_NESTED, pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error initializing mutex\n");
status = SWITCH_STATUS_FALSE;
goto done;
}
strncpy(cb->bot, bot, MAX_BOTNAME);
strncpy(cb->alias, alias, MAX_BOTNAME);
strncpy(cb->locale, locale, MAX_LOCALE);
strncpy(cb->region, region, MAX_REGION);
if (intent) strncpy(cb->intent, intent, MAX_INTENT);
if (metadata) strncpy(cb->metadata, metadata, MAX_METADATA);
cb->resampler = speex_resampler_init(1, 8000, /*16000*/ 8000, SWITCH_RESAMPLE_QUALITY, &err);
if (0 != err) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "%s: Error initializing resampler: %s.\n",
switch_channel_get_name(channel), speex_resampler_strerror(err));
status = SWITCH_STATUS_FALSE;
goto done;
}
// hangup hook to clear temp audio files
switch_core_event_hook_add_state_change(session, hanguphook);
// create a thread to service the http/2 connection to lex
switch_threadattr_create(&thd_attr, pool);
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
switch_thread_create(&cb->thread, thd_attr, lex_thread, cb, pool);
*ppUserData = cb;
done:
return status;
}
switch_status_t aws_lex_session_dtmf(switch_core_session_t *session, char* dtmf) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
if (bug) {
struct cap_cb *cb = (struct cap_cb *) switch_core_media_bug_get_user_data(bug);
switch_status_t st;
// close connection and get final responses
switch_mutex_lock(cb->mutex);
GStreamer* streamer = (GStreamer *) cb->streamer;
if (streamer) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "aws_lex_session_dtmf: sending dtmf %s\n", dtmf);
streamer->dtmf(dtmf);
}
switch_mutex_unlock(cb->mutex);
return SWITCH_STATUS_SUCCESS;
}
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s Bug is not attached.\n", switch_channel_get_name(channel));
return SWITCH_STATUS_FALSE;
}
switch_status_t aws_lex_session_play_done(switch_core_session_t *session) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
if (bug) {
struct cap_cb *cb = (struct cap_cb *) switch_core_media_bug_get_user_data(bug);
switch_status_t st;
// close connection and get final responses
switch_mutex_lock(cb->mutex);
GStreamer* streamer = (GStreamer *) cb->streamer;
if (streamer) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "aws_lex_session_play_done: sending play done\n");
streamer->notify_play_done();
}
switch_mutex_unlock(cb->mutex);
return SWITCH_STATUS_SUCCESS;
}
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s Bug is not attached.\n", switch_channel_get_name(channel));
return SWITCH_STATUS_FALSE;
}
switch_status_t aws_lex_session_stop(switch_core_session_t *session, int channelIsClosing) {
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
if (bug) {
struct cap_cb *cb = (struct cap_cb *) switch_core_media_bug_get_user_data(bug);
switch_status_t st;
// close connection and get final responses
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "aws_lex_session_cleanup: acquiring lock\n");
switch_mutex_lock(cb->mutex);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "aws_lex_session_cleanup: acquired lock\n");
GStreamer* streamer = (GStreamer *) cb->streamer;
if (streamer) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "aws_lex_session_cleanup: sending writesDone..\n");
streamer->finish();
}
if (cb->thread) {
switch_status_t retval;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "aws_lex_session_cleanup: waiting for read thread to complete\n");
switch_thread_join(&retval, cb->thread);
cb->thread = NULL;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "aws_lex_session_cleanup: read thread completed\n");
}
killcb(cb);
switch_channel_set_private(channel, MY_BUG_NAME, NULL);
if (!channelIsClosing) switch_core_media_bug_remove(session, &bug);
switch_mutex_unlock(cb->mutex);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "aws_lex_session_cleanup: Closed aws session\n");
return SWITCH_STATUS_SUCCESS;
}
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s Bug is not attached.\n", switch_channel_get_name(channel));
return SWITCH_STATUS_FALSE;
}
switch_bool_t aws_lex_frame(switch_media_bug_t *bug, void* user_data) {
switch_core_session_t *session = switch_core_media_bug_get_session(bug);
uint8_t data[SWITCH_RECOMMENDED_BUFFER_SIZE];
switch_frame_t frame = {};
struct cap_cb *cb = (struct cap_cb *) user_data;
frame.data = data;
frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE;
if (switch_mutex_trylock(cb->mutex) == SWITCH_STATUS_SUCCESS) {
GStreamer* streamer = (GStreamer *) cb->streamer;
if (streamer) {
while (switch_core_media_bug_read(bug, &frame, SWITCH_TRUE) == SWITCH_STATUS_SUCCESS && !switch_test_flag((&frame), SFF_CNG)) {
if (frame.datalen) {
spx_int16_t out[SWITCH_RECOMMENDED_BUFFER_SIZE];
spx_uint32_t out_len = SWITCH_RECOMMENDED_BUFFER_SIZE;
spx_uint32_t in_len = frame.samples;
size_t written;
speex_resampler_process_interleaved_int(cb->resampler, (const spx_int16_t *) frame.data, (spx_uint32_t *) &in_len, &out[0], &out_len);
streamer->write( &out[0], sizeof(spx_int16_t) * out_len);
}
}
}
else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG,
"aws_lex_frame: not sending audio because aws channel has been closed\n");
}
switch_mutex_unlock(cb->mutex);
}
else {
//switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG,
// "aws_lex_frame: not sending audio since failed to get lock on mutex\n");
}
return SWITCH_TRUE;
}
void destroyChannelUserData(struct cap_cb* cb) {
killcb(cb);
}
}

View File

@@ -0,0 +1,14 @@
#ifndef __AWS_GLUE_H__
#define __AWS_GLUE_H__
switch_status_t aws_lex_init();
switch_status_t aws_lex_cleanup();
switch_status_t aws_lex_session_init(switch_core_session_t *session, responseHandler_t responseHandler, errorHandler_t errorHandler,
uint32_t samples_per_second, char* bot, char* alias, char* region, char* locale, char *intent, char* metadata, struct cap_cb **cb);
switch_status_t aws_lex_session_stop(switch_core_session_t *session, int channelIsClosing);
switch_status_t aws_lex_session_dtmf(switch_core_session_t *session, char* dtmf);
switch_status_t aws_lex_session_play_done(switch_core_session_t *session);
switch_bool_t aws_lex_frame(switch_media_bug_t *bug, void* user_data);
void destroyChannelUserData(struct cap_cb* cb);
#endif

375
mod_aws_lex/mod_aws_lex.c Normal file
View File

@@ -0,0 +1,375 @@
/*
*
* mod_lex.c -- Freeswitch module for running a aws lex conversation
*
*/
#include "mod_aws_lex.h"
#include "aws_lex_glue.h"
/* Prototypes */
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_aws_lex_shutdown);
SWITCH_MODULE_LOAD_FUNCTION(mod_aws_lex_load);
SWITCH_MODULE_DEFINITION(mod_aws_lex, mod_aws_lex_load, mod_aws_lex_shutdown, NULL);
static switch_status_t do_stop(switch_core_session_t *session);
static void responseHandler(switch_core_session_t* session, const char * type, char * json) {
switch_event_t *event;
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "json payload for type %s: %s.\n", type, json);
switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, type);
switch_channel_event_set_data(channel, event);
switch_event_add_body(event, "%s", json);
switch_event_fire(&event);
}
static void errorHandler(switch_core_session_t* session, const char * json) {
switch_event_t *event;
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, AWS_LEX_EVENT_ERROR);
switch_channel_event_set_data(channel, event);
switch_event_add_body(event, "%s", json);
switch_event_fire(&event);
do_stop(session);
}
static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{
switch_core_session_t *session = switch_core_media_bug_get_session(bug);
switch (type) {
case SWITCH_ABC_TYPE_INIT:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Got SWITCH_ABC_TYPE_INIT.\n");
break;
case SWITCH_ABC_TYPE_CLOSE:
{
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Got SWITCH_ABC_TYPE_CLOSE.\n");
aws_lex_session_stop(session, 1);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Finished SWITCH_ABC_TYPE_CLOSE.\n");
}
break;
case SWITCH_ABC_TYPE_READ:
return aws_lex_frame(bug, user_data);
break;
case SWITCH_ABC_TYPE_WRITE:
default:
break;
}
return SWITCH_TRUE;
}
static switch_status_t start_capture(switch_core_session_t *session, switch_media_bug_flag_t flags,
char* bot, char*alias, char* region, char* locale, char* intent, char* metadata)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug;
switch_codec_implementation_t read_impl = { 0 };
struct cap_cb *cb = NULL;
switch_status_t status = SWITCH_STATUS_SUCCESS;
if (switch_channel_get_private(channel, MY_BUG_NAME)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "a lex is already running on this channel, we will stop it.\n");
do_stop(session);
}
if (switch_channel_pre_answer(channel) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "channel must have at least early media to run lex.\n");
status = SWITCH_STATUS_FALSE;
goto done;
}
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "starting lex with bot %s, alias %s, region %s, locale %s, intent %s, metadata: %s\n",
bot, alias, region, locale, intent ? intent : "(none)", metadata ? metadata : "(none)");
switch_core_session_get_read_impl(session, &read_impl);
if (SWITCH_STATUS_FALSE == aws_lex_session_init(session, responseHandler, errorHandler,
read_impl.samples_per_second, bot, alias, region, locale, intent, metadata, &cb)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error initializing aws lex session.\n");
status = SWITCH_STATUS_FALSE;
goto done;
}
if ((status = switch_core_media_bug_add(session, "lex", NULL, capture_callback, (void *) cb, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error adding bug.\n");
status = SWITCH_STATUS_FALSE;
goto done;
}
switch_channel_set_private(channel, MY_BUG_NAME, bug);
done:
if (status == SWITCH_STATUS_FALSE) {
if (cb) destroyChannelUserData(cb);
}
return status;
}
static switch_status_t do_stop(switch_core_session_t *session)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_media_bug_t *bug = switch_channel_get_private(channel, MY_BUG_NAME);
if (bug) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Received user command command to stop lex.\n");
status = aws_lex_session_stop(session, 0);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "stopped lex.\n");
}
return status;
}
#define LEX_API_START_SYNTAX "<uuid> bot alias region locale [intent] [json-metadata]"
SWITCH_STANDARD_API(aws_lex_api_start_function)
{
char *mycmd = NULL, *argv[10] = { 0 };
int argc = 0;
switch_status_t status = SWITCH_STATUS_FALSE;
switch_media_bug_flag_t flags = SMBF_READ_STREAM | SMBF_READ_STREAM | SMBF_READ_PING;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "command %s\n", cmd);
if (!zstr(cmd) && (mycmd = strdup(cmd))) {
argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
}
if (zstr(cmd) || argc < 5) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error with command %s %s %s.\n", cmd, argv[0], argv[1]);
stream->write_function(stream, "-USAGE: %s\n", LEX_API_START_SYNTAX);
goto done;
} else {
switch_core_session_t *lsession = NULL;
if ((lsession = switch_core_session_locate(argv[0]))) {
char *bot = argv[1];
char *alias = argv[2];
char *region = argv[3];
char *locale = argv[4];
char *intent = NULL;
char *metadata = NULL;
if (argc > 5) {
if ('{' == *argv[5]) {
metadata = argv[5];
}
else {
intent = argv[5];
if (argc > 6) metadata = argv[6];
}
}
status = start_capture(lsession, flags, bot, alias, region, locale, intent, metadata);
switch_core_session_rwunlock(lsession);
}
}
if (status == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "+OK Success\n");
} else {
stream->write_function(stream, "-ERR Operation Failed\n");
}
done:
switch_safe_free(mycmd);
return SWITCH_STATUS_SUCCESS;
}
#define LEX_API_DTMF_SYNTAX "<uuid> dtmf"
SWITCH_STANDARD_API(aws_lex_api_dtmf_function)
{
char *mycmd = NULL, *argv[10] = { 0 };
int argc = 0;
switch_status_t status = SWITCH_STATUS_FALSE;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "command %s\n", cmd);
if (!zstr(cmd) && (mycmd = strdup(cmd))) {
argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
}
if (zstr(cmd) || argc < 2) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error with command %s %s %s.\n", cmd, argv[0], argv[1]);
stream->write_function(stream, "-USAGE: %s\n", LEX_API_DTMF_SYNTAX);
goto done;
} else {
switch_core_session_t *lsession = NULL;
if ((lsession = switch_core_session_locate(argv[0]))) {
char *dtmf = argv[1];
status = aws_lex_session_dtmf(lsession, dtmf);
switch_core_session_rwunlock(lsession);
}
}
if (status == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "+OK Success\n");
} else {
stream->write_function(stream, "-ERR Operation Failed\n");
}
done:
switch_safe_free(mycmd);
return SWITCH_STATUS_SUCCESS;
}
#define LEX_API_PLAY_DONE_SYNTAX "<uuid>"
SWITCH_STANDARD_API(aws_lex_api_play_done_function)
{
char *mycmd = NULL, *argv[10] = { 0 };
int argc = 0;
switch_status_t status = SWITCH_STATUS_FALSE;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "command %s\n", cmd);
if (!zstr(cmd) && (mycmd = strdup(cmd))) {
argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
}
if (zstr(cmd) || argc < 1) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error with command %s %s.\n", cmd, argv[0]);
stream->write_function(stream, "-USAGE: %s\n", LEX_API_DTMF_SYNTAX);
goto done;
} else {
switch_core_session_t *lsession = NULL;
if ((lsession = switch_core_session_locate(argv[0]))) {
status = aws_lex_session_play_done(lsession);
switch_core_session_rwunlock(lsession);
}
}
if (status == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "+OK Success\n");
} else {
stream->write_function(stream, "-ERR Operation Failed\n");
}
done:
switch_safe_free(mycmd);
return SWITCH_STATUS_SUCCESS;
}
#define LEX_API_STOP_SYNTAX "<uuid>"
SWITCH_STANDARD_API(aws_lex_api_stop_function)
{
char *mycmd = NULL, *argv[10] = { 0 };
int argc = 0;
switch_status_t status = SWITCH_STATUS_FALSE;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "command %s\n", cmd);
if (!zstr(cmd) && (mycmd = strdup(cmd))) {
argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
}
if (zstr(cmd) || argc != 1) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error with command %s.\n", cmd);
stream->write_function(stream, "-USAGE: %s\n", LEX_API_STOP_SYNTAX);
goto done;
} else {
switch_core_session_t *lsession = NULL;
if ((lsession = switch_core_session_locate(argv[0]))) {
status = do_stop(lsession);
switch_core_session_rwunlock(lsession);
}
}
if (status == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "+OK Success\n");
} else {
stream->write_function(stream, "-ERR Operation Failed\n");
}
done:
switch_safe_free(mycmd);
return SWITCH_STATUS_SUCCESS;
}
/* Macro expands to: switch_status_t mod_lex_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */
SWITCH_MODULE_LOAD_FUNCTION(mod_aws_lex_load)
{
switch_api_interface_t *api_interface;
/* create/register custom event message types */
if (switch_event_reserve_subclass(AWS_LEX_EVENT_INTENT) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", AWS_LEX_EVENT_INTENT);
return SWITCH_STATUS_TERM;
}
if (switch_event_reserve_subclass(AWS_LEX_EVENT_TRANSCRIPTION) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", AWS_LEX_EVENT_TRANSCRIPTION);
return SWITCH_STATUS_TERM;
}
if (switch_event_reserve_subclass(AWS_LEX_EVENT_TEXT_RESPONSE) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", AWS_LEX_EVENT_TEXT_RESPONSE);
return SWITCH_STATUS_TERM;
}
if (switch_event_reserve_subclass(AWS_LEX_EVENT_PLAYBACK_INTERRUPTION) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", AWS_LEX_EVENT_PLAYBACK_INTERRUPTION);
return SWITCH_STATUS_TERM;
}
if (switch_event_reserve_subclass(AWS_LEX_EVENT_AUDIO_PROVIDED) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", AWS_LEX_EVENT_AUDIO_PROVIDED);
return SWITCH_STATUS_TERM;
}
if (switch_event_reserve_subclass(AWS_LEX_EVENT_ERROR) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", AWS_LEX_EVENT_ERROR);
return SWITCH_STATUS_TERM;
}
/* connect my internal structure to the blank pointer passed to me */
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_aws_lex API loading..\n");
if (SWITCH_STATUS_FALSE == aws_lex_init()) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed initializing mod_aws_lex interface\n");
}
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_aws_lex API successfully loaded\n");
SWITCH_ADD_API(api_interface, "aws_lex_start", "Start an aws lex conversation", aws_lex_api_start_function, LEX_API_START_SYNTAX);
SWITCH_ADD_API(api_interface, "aws_lex_dtmf", "Send a dtmf entry to lex", aws_lex_api_dtmf_function, LEX_API_DTMF_SYNTAX);
SWITCH_ADD_API(api_interface, "aws_lex_play_done", "Notify lex that a play completed", aws_lex_api_play_done_function, LEX_API_PLAY_DONE_SYNTAX);
SWITCH_ADD_API(api_interface, "aws_lex_stop", "Terminate a aws lex", aws_lex_api_stop_function, LEX_API_STOP_SYNTAX);
switch_console_set_complete("add aws_lex_stop");
switch_console_set_complete("add aws_lex_play_done");
switch_console_set_complete("add aws_lex_dtmf dtmf-entry");
switch_console_set_complete("add aws_lex_start project lang");
switch_console_set_complete("add aws_lex_start bot alias region locale");
/* indicate that the module should continue to be loaded */
return SWITCH_STATUS_SUCCESS;
}
/*
Called when the system shuts down
Macro expands to: switch_status_t mod_lex_shutdown() */
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_aws_lex_shutdown)
{
aws_lex_cleanup();
switch_event_free_subclass(AWS_LEX_EVENT_INTENT);
switch_event_free_subclass(AWS_LEX_EVENT_TRANSCRIPTION);
switch_event_free_subclass(AWS_LEX_EVENT_TEXT_RESPONSE);
switch_event_free_subclass(AWS_LEX_EVENT_PLAYBACK_INTERRUPTION);
switch_event_free_subclass(AWS_LEX_EVENT_AUDIO_PROVIDED);
switch_event_free_subclass(AWS_LEX_EVENT_ERROR);
return SWITCH_STATUS_SUCCESS;
}

46
mod_aws_lex/mod_aws_lex.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef __MOD_LEX_H__
#define __MOD_LEX_H__
#include <switch.h>
#include <speex/speex_resampler.h>
#include <unistd.h>
#define MY_BUG_NAME "__aws_lex_bug__"
#define AWS_LEX_EVENT_INTENT "lex::intent"
#define AWS_LEX_EVENT_TRANSCRIPTION "lex::transcription"
#define AWS_LEX_EVENT_TEXT_RESPONSE "lex::text_response"
#define AWS_LEX_EVENT_AUDIO_PROVIDED "lex::audio_provided"
#define AWS_LEX_EVENT_PLAYBACK_INTERRUPTION "lex::playback_interruption"
#define AWS_LEX_EVENT_ERROR "lex::error"
#define MAX_LANG (12)
#define MAX_BOTNAME (128)
#define MAX_REGION (16)
#define MAX_LOCALE (7)
#define MAX_INTENT (52)
#define MAX_METADATA (1024)
/* per-channel data */
typedef void (*responseHandler_t)(switch_core_session_t* session, const char * type, char* json);
typedef void (*errorHandler_t)(switch_core_session_t* session, const char * reason);
struct cap_cb {
switch_mutex_t *mutex;
char sessionId[256];
char awsAccessKeyId[128];
char awsSecretAccessKey[128];
SpeexResamplerState *resampler;
void* streamer;
responseHandler_t responseHandler;
errorHandler_t errorHandler;
switch_thread_t* thread;
char bot[MAX_BOTNAME];
char alias[MAX_BOTNAME];
char region[MAX_REGION];
char locale[MAX_LOCALE];
char intent[MAX_INTENT];
char metadata[MAX_METADATA];
};
#endif

195
mod_aws_lex/parser.cpp Normal file
View File

@@ -0,0 +1,195 @@
#include "parser.h"
#include <switch.h>
cJSON* lex2Json(const TranscriptEvent& ev) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "transcript", cJSON_CreateString(ev.GetTranscript().c_str()));
cJSON_AddItemToObject(json, "eventId", cJSON_CreateString(ev.GetEventId().c_str()));
return json;
}
cJSON* lex2Json(const TextResponseEvent& ev) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "eventId", cJSON_CreateString(ev.GetEventId().c_str()));
cJSON* jMessages = cJSON_CreateArray();
cJSON_AddItemToObject(json, "messages", jMessages);
for (auto msg : ev.GetMessages()) {
cJSON_AddItemToArray(jMessages, lex2Json(msg));
}
return json;
}
cJSON* lex2Json(const Message& msg) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "msg", cJSON_CreateString(msg.GetContent().c_str()));
cJSON_AddItemToObject(json, "type",
cJSON_CreateString(MessageContentTypeMapper::GetNameForMessageContentType(msg.GetContentType()).c_str()));
return json;
}
cJSON* lex2Json(const IntentResultEvent& ev) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "eventId", cJSON_CreateString(ev.GetEventId().c_str()));
cJSON_AddItemToObject(json, "sessionId", cJSON_CreateString(ev.GetSessionId().c_str()));
cJSON_AddItemToObject(json, "sessionState", lex2Json(ev.GetSessionState()));
cJSON_AddItemToObject(json, "requestAttributes", lex2Json(ev.GetRequestAttributes()));
cJSON* jInterpretations = cJSON_CreateArray();
cJSON_AddItemToObject(json, "interpretations", jInterpretations);
for (auto interp : ev.GetInterpretations()) {
cJSON * jInterp = cJSON_CreateObject();
cJSON_AddItemToArray(jInterpretations, jInterp);
cJSON_AddItemToObject(jInterp, "confidence", cJSON_CreateNumber(interp.GetNluConfidence().GetScore()));
cJSON_AddItemToObject(jInterp, "sentiment", lex2Json(interp.GetSentimentResponse()));
cJSON_AddItemToObject(jInterp, "intent", lex2Json(interp.GetIntent()));
}
return json;
}
cJSON* lex2Json(const PlaybackInterruptionEvent& ev) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "reason",
cJSON_CreateString(PlaybackInterruptionReasonMapper::GetNameForPlaybackInterruptionReason(ev.GetEventReason()).c_str()));
cJSON_AddItemToObject(json, "causedBy", cJSON_CreateString(ev.GetCausedByEventId().c_str()));
cJSON_AddItemToObject(json, "eventId", cJSON_CreateString(ev.GetEventId().c_str()));
return json;
}
cJSON* lex2Json(const Aws::Map<Aws::String, Aws::String>& attr) {
cJSON * json = cJSON_CreateObject();
for (auto it: attr) {
cJSON_AddItemToObject(json, it.first.c_str(), cJSON_CreateString(it.second.c_str()));
}
return json;
}
cJSON* lex2Json(const Aws::Map<Aws::String, Slot>& slots) {
cJSON * json = cJSON_CreateObject();
for (auto it: slots) {
cJSON_AddItemToObject(json, it.first.c_str(), lex2Json(it.second));
}
return json;
}
cJSON* lex2Json(const SessionState& state) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "dialogAction", lex2Json(state.GetDialogAction()));
cJSON_AddItemToObject(json, "intent", lex2Json(state.GetIntent()));
cJSON* jContexts = cJSON_CreateArray();
cJSON_AddItemToObject(json, "activeContexts", jContexts);
for (auto context : state.GetActiveContexts()) {
cJSON_AddItemToArray(jContexts, lex2Json(context));
}
cJSON_AddItemToObject(json, "attributes", lex2Json(state.GetSessionAttributes()));
return json;
}
cJSON* lex2Json(const SentimentResponse& sentiment) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "type",
cJSON_CreateString(SentimentTypeMapper::GetNameForSentimentType(sentiment.GetSentiment()).c_str()));
cJSON_AddItemToObject(json, "score", lex2Json(sentiment.GetSentimentScore()));
return json;
}
cJSON* lex2Json(const Intent& intent) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "name", cJSON_CreateString(intent.GetName().c_str()));
cJSON_AddItemToObject(json, "slots", lex2Json(intent.GetSlots()));
cJSON_AddItemToObject(json, "intentState", cJSON_CreateString(IntentStateMapper::GetNameForIntentState(intent.GetState()).c_str()));
cJSON_AddItemToObject(json, "confirmationState", cJSON_CreateString(ConfirmationStateMapper::GetNameForConfirmationState(intent.GetConfirmationState()).c_str()));
return json;
}
cJSON* lex2Json(const DialogAction& dialogAction) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "type",
cJSON_CreateString(DialogActionTypeMapper::GetNameForDialogActionType(dialogAction.GetType()).c_str()));
cJSON_AddItemToObject(json, "slotToElicit", cJSON_CreateString(dialogAction.GetSlotToElicit().c_str()));
return json;
}
cJSON* lex2Json(const ActiveContext& context) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "name", cJSON_CreateString(context.GetName().c_str()));
cJSON_AddItemToObject(json, "ttl", lex2Json(context.GetTimeToLive()));
cJSON_AddItemToObject(json, "attributes", lex2Json(context.GetContextAttributes()));
return json;
}
cJSON* lex2Json(const SentimentScore& score) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "positive", cJSON_CreateNumber(score.GetPositive()));
cJSON_AddItemToObject(json, "negative", cJSON_CreateNumber(score.GetNegative()));
cJSON_AddItemToObject(json, "neutral", cJSON_CreateNumber(score.GetNeutral()));
cJSON_AddItemToObject(json, "mixed", cJSON_CreateNumber(score.GetMixed()));
return json;
}
cJSON* lex2Json(const ActiveContextTimeToLive& ttl) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "seconds", cJSON_CreateNumber(ttl.GetTimeToLiveInSeconds()));
cJSON_AddItemToObject(json, "turns", cJSON_CreateNumber(ttl.GetTurnsToLive()));
return json;
}
cJSON* lex2Json(const Slot& slot) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "value", lex2Json(slot.GetValue()));
return json;
}
cJSON* lex2Json(const Value& value) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "originalValue", cJSON_CreateString(value.GetOriginalValue().c_str()));
cJSON_AddItemToObject(json, "interpretedValue", cJSON_CreateString(value.GetInterpretedValue().c_str()));
cJSON* jResolved = cJSON_CreateArray();
cJSON_AddItemToObject(json, "resolvedValues", jResolved);
for (auto res : value.GetResolvedValues()) {
cJSON_AddItemToArray(jResolved, cJSON_CreateString(res.c_str()));
}
return json;
}
cJSON* lex2Json(const Aws::Client::AWSError<LexRuntimeV2Errors>& err) {
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "message", cJSON_CreateString(err.GetMessage().c_str()));
return json;
}

29
mod_aws_lex/parser.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef __PARSER_H__
#define __PARSER_H__
#include <switch_json.h>
#include <aws/lexv2-runtime/LexRuntimeV2Client.h>
#include <aws/lexv2-runtime/model/StartConversationRequest.h>
using namespace Aws::LexRuntimeV2;
using namespace Aws::LexRuntimeV2::Model;
cJSON* lex2Json(const TranscriptEvent& ev);
cJSON* lex2Json(const TextResponseEvent& ev);
cJSON* lex2Json(const Message& msg);
cJSON* lex2Json(const IntentResultEvent& ev);
cJSON* lex2Json(const PlaybackInterruptionEvent& ev);
cJSON* lex2Json(const Aws::Map<Aws::String, Aws::String>& attr);
cJSON* lex2Json(const Aws::Map<Aws::String, Slot>& slots);
cJSON* lex2Json(const SessionState& state) ;
cJSON* lex2Json(const SentimentResponse& sentiment) ;
cJSON* lex2Json(const Intent& intent) ;
cJSON* lex2Json(const DialogAction& dialogAction);
cJSON* lex2Json(const ActiveContext& context);
cJSON* lex2Json(const SentimentScore& score);
cJSON* lex2Json(const ActiveContextTimeToLive& ttl);
cJSON* lex2Json(const Slot& slot);
cJSON* lex2Json(const Value& value) ;
cJSON* lex2Json(const Aws::Client::AWSError<LexRuntimeV2Errors>& err);
#endif